style(src-tauri): cargo fmt

Pure mechanical rustfmt reformat (default config, no rustfmt.toml).
This commit is contained in:
soubarna
2026-10-05 17:19:13 +05:30
parent e1031b3ecc
commit b6cf46b883
27 changed files with 4430 additions and 1042 deletions
+186 -42
View File
@@ -150,7 +150,12 @@ pub fn remarks_marker(number: &str) -> String {
fn verified_totals(inv: &Invoice) -> Result<(TaxType, i64, Totals), String> {
let tax_type = parse_tax_type(&inv.tax_type)?;
let rate_bp = (inv.tax_rate * 100.0).round() as i64;
let totals = gst::compute_totals(&inv.items, gst::rupees_to_paise(inv.discount), tax_type, rate_bp);
let totals = gst::compute_totals(
&inv.items,
gst::rupees_to_paise(inv.discount),
tax_type,
rate_bp,
);
let stored = [
(totals.cgst, inv.cgst_amount, "CGST"),
(totals.sgst, inv.sgst_amount, "SGST"),
@@ -199,13 +204,21 @@ fn distinct_descriptions(items: &[InvoiceItem]) -> Vec<String> {
.enumerate()
.map(|(i, item)| {
let trimmed = item.description.trim();
let full = if trimmed.is_empty() { format!("Item {}", i + 1) } else { trimmed.to_string() };
let full = if trimmed.is_empty() {
format!("Item {}", i + 1)
} else {
trimmed.to_string()
};
let (head, tail) = first_line_and_rest(&full);
let mut candidate = full.clone();
let mut n = 2;
while used.contains(&candidate.to_lowercase()) {
let head = format!("{head} ({n})");
candidate = if tail.is_empty() { head } else { format!("{head}\n{tail}") };
candidate = if tail.is_empty() {
head
} else {
format!("{head}\n{tail}")
};
n += 1;
}
used.insert(candidate.to_lowercase());
@@ -271,7 +284,10 @@ fn item_rows(ctx: &InvoiceContext, apply_gst: bool) -> Result<Vec<Value>, String
));
}
let (head, _) = first_line_and_rest(&descriptions[i]);
row.insert("item_name".into(), json!(truncate_chars(head, ITEM_NAME_MAX)));
row.insert(
"item_name".into(),
json!(truncate_chars(head, ITEM_NAME_MAX)),
);
}
}
row.insert("description".into(), json!(descriptions[i]));
@@ -296,7 +312,9 @@ fn item_rows(ctx: &InvoiceContext, apply_gst: bool) -> Result<Vec<Value>, String
fn tax_row(account: &str, head: &str, rate: &str) -> Result<Value, String> {
if account.trim().is_empty() {
return Err(format!("The {head} account is not set in the ERPNext settings."));
return Err(format!(
"The {head} account is not set in the ERPNext settings."
));
}
Ok(json!({
"charge_type": "On Net Total",
@@ -306,10 +324,19 @@ fn tax_row(account: &str, head: &str, rate: &str) -> Result<Value, String> {
}))
}
fn tax_rows(cfg: &ErpnextConfig, tax_type: TaxType, rate_bp: i64, supplier_state: &str) -> Result<Vec<Value>, String> {
fn tax_rows(
cfg: &ErpnextConfig,
tax_type: TaxType,
rate_bp: i64,
supplier_state: &str,
) -> Result<Vec<Value>, String> {
match tax_type {
TaxType::None => Ok(Vec::new()),
TaxType::Igst => Ok(vec![tax_row(&cfg.igst_account, "IGST", &scaled_decimal(rate_bp, 2))?]),
TaxType::Igst => Ok(vec![tax_row(
&cfg.igst_account,
"IGST",
&scaled_decimal(rate_bp, 2),
)?]),
TaxType::CgstSgst => {
// rate_bp is in hundredths of a percent; half of it in thousandths of a percent is rate_bp * 5.
let half = scaled_decimal(rate_bp * 5, 3);
@@ -319,7 +346,10 @@ fn tax_rows(cfg: &ErpnextConfig, tax_type: TaxType, rate_bp: i64, supplier_state
} else {
&cfg.sgst_account
};
Ok(vec![tax_row(&cfg.cgst_account, "CGST", &half)?, tax_row(second_account, second, &half)?])
Ok(vec![
tax_row(&cfg.cgst_account, "CGST", &half)?,
tax_row(second_account, second, &half)?,
])
}
}
}
@@ -328,7 +358,10 @@ pub fn build_sales_invoice(ctx: &InvoiceContext) -> Result<BuiltRequest, String>
let inv = ctx.invoice;
let cfg = ctx.config;
if inv.status != "issued" {
return Err(format!("Invoice {} is {}; only issued invoices are sent to ERPNext.", inv.number, inv.status));
return Err(format!(
"Invoice {} is {}; only issued invoices are sent to ERPNext.",
inv.number, inv.status
));
}
if ctx.customer.trim().is_empty() {
return Err("The ERPNext customer is not resolved.".into());
@@ -346,7 +379,9 @@ pub fn build_sales_invoice(ctx: &InvoiceContext) -> Result<BuiltRequest, String>
}
NamingMode::Series => {
if cfg.naming_series.trim().is_empty() {
return Err("ERPNext series naming is selected but no naming series is set.".into());
return Err(
"ERPNext series naming is selected but no naming series is set.".into(),
);
}
body.insert("naming_series".into(), json!(cfg.naming_series.trim()));
}
@@ -396,14 +431,21 @@ pub fn build_sales_invoice(ctx: &InvoiceContext) -> Result<BuiltRequest, String>
body.insert("discount_amount".into(), money(totals.discount));
}
body.insert("items".into(), Value::Array(item_rows(ctx, gst_fields)?));
body.insert("taxes".into(), Value::Array(tax_rows(cfg, tax_type, rate_bp, &ctx.vendor.state_code)?));
body.insert(
"taxes".into(),
Value::Array(tax_rows(cfg, tax_type, rate_bp, &ctx.vendor.state_code)?),
);
if ctx.submit {
body.insert("docstatus".into(), json!(1));
}
let mirror = cfg.naming_mode == NamingMode::Mirror;
Ok(BuiltRequest {
path: if mirror { SALES_INVOICE_V2 } else { SALES_INVOICE_V1 },
path: if mirror {
SALES_INVOICE_V2
} else {
SALES_INVOICE_V1
},
body: Value::Object(body),
idempotent: mirror,
})
@@ -416,7 +458,11 @@ fn has_gstin(client: &Client) -> bool {
!g.is_empty() && !g.eq_ignore_ascii_case("NA")
}
pub fn build_customer(client: &Client, cfg: &ErpnextConfig, india_compliance: bool) -> Result<BuiltRequest, String> {
pub fn build_customer(
client: &Client,
cfg: &ErpnextConfig,
india_compliance: bool,
) -> Result<BuiltRequest, String> {
let name = client.name.trim();
if name.is_empty() {
return Err("The client has no name.".into());
@@ -427,19 +473,33 @@ pub fn build_customer(client: &Client, cfg: &ErpnextConfig, india_compliance: bo
set_if(&mut body, "customer_group", &cfg.customer_group);
set_if(&mut body, "territory", &cfg.territory);
if india_compliance {
body.insert("gst_category".into(), json!(gst_category_label(&client.gst_category)?));
let taxable_registration = matches!(client.gst_category.as_str(), "registered_regular" | "composition" | "sez");
body.insert(
"gst_category".into(),
json!(gst_category_label(&client.gst_category)?),
);
let taxable_registration = matches!(
client.gst_category.as_str(),
"registered_regular" | "composition" | "sez"
);
if taxable_registration && has_gstin(client) {
let gstin = client.gstin.trim().to_ascii_uppercase();
gst::validate_gstin(&gstin).map_err(|e| format!("Client GSTIN: {e}"))?;
body.insert("gstin".into(), json!(gstin));
}
}
Ok(BuiltRequest { path: CUSTOMER, body: Value::Object(body), idempotent: false })
Ok(BuiltRequest {
path: CUSTOMER,
body: Value::Object(body),
idempotent: false,
})
}
/// `customer` is the name ERPNext returned when the Customer was created (a duplicate becomes "X - 1").
pub fn build_address(client: &Client, customer: &str, india_compliance: bool) -> Result<BuiltRequest, String> {
pub fn build_address(
client: &Client,
customer: &str,
india_compliance: bool,
) -> Result<BuiltRequest, String> {
if customer.trim().is_empty() {
return Err("The ERPNext customer is not resolved.".into());
}
@@ -472,16 +532,31 @@ pub fn build_address(client: &Client, customer: &str, india_compliance: bool) ->
set_if(&mut body, "pincode", &client.pincode);
body.insert("country".into(), json!("India"));
if india_compliance {
body.insert("gst_category".into(), json!(gst_category_label(&client.gst_category)?));
if has_gstin(client) && matches!(client.gst_category.as_str(), "registered_regular" | "composition" | "sez") {
body.insert("gstin".into(), json!(client.gstin.trim().to_ascii_uppercase()));
body.insert(
"gst_category".into(),
json!(gst_category_label(&client.gst_category)?),
);
if has_gstin(client)
&& matches!(
client.gst_category.as_str(),
"registered_regular" | "composition" | "sez"
)
{
body.insert(
"gstin".into(),
json!(client.gstin.trim().to_ascii_uppercase()),
);
}
}
body.insert(
"links".into(),
json!([{ "link_doctype": "Customer", "link_name": customer.trim() }]),
);
Ok(BuiltRequest { path: ADDRESS, body: Value::Object(body), idempotent: false })
Ok(BuiltRequest {
path: ADDRESS,
body: Value::Object(body),
idempotent: false,
})
}
#[cfg(test)]
@@ -539,7 +614,13 @@ mod tests {
Vendor::from_snapshot(&inv.vendor_snapshot).unwrap()
}
fn build(inv: &Invoice, cfg: &ErpnextConfig, ic: bool, codes: &[Option<String>], submit: bool) -> Result<BuiltRequest, String> {
fn build(
inv: &Invoice,
cfg: &ErpnextConfig,
ic: bool,
codes: &[Option<String>],
submit: bool,
) -> Result<BuiltRequest, String> {
let v = vendor(inv);
build_sales_invoice(&InvoiceContext {
invoice: inv,
@@ -597,7 +678,10 @@ mod tests {
assert_eq!(scaled_decimal(1500, 3), "1.5");
assert_eq!(scaled_decimal(2625, 3), "2.625");
assert_eq!(money(731_005).to_string(), "7310.05");
assert_eq!(money(i64::from(u32::MAX) * 100 + 7).to_string(), "4294967295.07");
assert_eq!(
money(i64::from(u32::MAX) * 100 + 7).to_string(),
"4294967295.07"
);
}
#[test]
@@ -607,7 +691,10 @@ mod tests {
assert_eq!(place_of_supply_label("97").unwrap(), "97-Other Territory");
assert_eq!(place_of_supply_label("96").unwrap(), "96-Other Countries");
for (code, name) in gst::STATES {
assert_eq!(place_of_supply_label(code).unwrap(), format!("{code}-{name}"));
assert_eq!(
place_of_supply_label(code).unwrap(),
format!("{code}-{name}")
);
}
assert!(place_of_supply_label("28").is_err());
}
@@ -750,10 +837,16 @@ mod tests {
#[test]
fn plain_erpnext_registered_vendor_gets_tax_rows_but_no_india_compliance_fields() {
let mut conn = registered();
let inv = issue(&mut conn, json!({ "items": [{"description": "Design", "mode": "fixed", "amount": 100.0, "hsnSac": "998314"}] }));
let inv = issue(
&mut conn,
json!({ "items": [{"description": "Design", "mode": "fixed", "amount": 100.0, "hsnSac": "998314"}] }),
);
let req = build(&inv, &cfg(), false, &[None], false).unwrap();
assert_eq!(req.body["taxes"].as_array().unwrap().len(), 2);
assert!(req.body.get("place_of_supply").is_none() && req.body.get("is_reverse_charge").is_none());
assert!(
req.body.get("place_of_supply").is_none()
&& req.body.get("is_reverse_charge").is_none()
);
assert!(req.body["items"][0].get("gst_hsn_code").is_none());
}
@@ -795,8 +888,17 @@ mod tests {
let req = build(&inv, &cfg(), false, &codes, false).unwrap();
let rows = req.body["items"].as_array().unwrap();
assert!(rows[0].get("stock_uom").is_none());
assert_eq!((rows[0]["uom"].as_str(), rows[0]["conversion_factor"].as_i64()), (Some("Hour"), Some(1)));
assert_eq!((rows[1]["uom"].as_str(), rows[1]["stock_uom"].as_str()), (Some("Minute"), Some("Minute")));
assert_eq!(
(
rows[0]["uom"].as_str(),
rows[0]["conversion_factor"].as_i64()
),
(Some("Hour"), Some(1))
);
assert_eq!(
(rows[1]["uom"].as_str(), rows[1]["stock_uom"].as_str()),
(Some("Minute"), Some("Minute"))
);
}
#[test]
@@ -849,7 +951,10 @@ mod tests {
assert!(!req.idempotent);
assert!(req.body.get("name").is_none());
assert_eq!(req.body["naming_series"], "SINV-.YY.-");
assert_eq!(req.body["remarks"], format!("Voiced invoice {}\nNet 30", inv.number));
assert_eq!(
req.body["remarks"],
format!("Voiced invoice {}\nNet 30", inv.number)
);
c.naming_series.clear();
assert!(build(&inv, &c, true, &[None], false).is_err());
@@ -859,8 +964,15 @@ mod tests {
fn submit_adds_docstatus_and_drafts_do_not() {
let mut conn = registered();
let inv = issue(&mut conn, json!({}));
assert!(build(&inv, &cfg(), true, &[None], false).unwrap().body.get("docstatus").is_none());
assert_eq!(build(&inv, &cfg(), true, &[None], true).unwrap().body["docstatus"], 1);
assert!(build(&inv, &cfg(), true, &[None], false)
.unwrap()
.body
.get("docstatus")
.is_none());
assert_eq!(
build(&inv, &cfg(), true, &[None], true).unwrap().body["docstatus"],
1
);
}
#[test]
@@ -874,7 +986,9 @@ mod tests {
// A supplier in a union territory without a legislature charges UTGST, not SGST.
let mut ut = inv.clone();
ut.vendor_snapshot = ut.vendor_snapshot.replace("\"vendorStateCode\":\"27\"", "\"vendorStateCode\":\"04\"");
ut.vendor_snapshot = ut
.vendor_snapshot
.replace("\"vendorStateCode\":\"27\"", "\"vendorStateCode\":\"04\"");
let req = build(&ut, &cfg(), true, &[None], false).unwrap();
assert_eq!(req.body["taxes"][1]["account_head"], "Output UTGST - AC");
assert_eq!(req.body["taxes"][1]["description"], "UTGST @ 2.625%");
@@ -901,11 +1015,17 @@ mod tests {
let mut inv = issue(&mut conn, json!({}));
let mut c = cfg();
c.cgst_account.clear();
assert!(build(&inv, &c, true, &[None], false).unwrap_err().contains("CGST account"));
assert!(build(&inv, &c, true, &[None], false)
.unwrap_err()
.contains("CGST account"));
c.company.clear();
assert!(build(&inv, &c, true, &[None], false).unwrap_err().contains("company"));
assert!(build(&inv, &c, true, &[None], false)
.unwrap_err()
.contains("company"));
inv.status = "cancelled".into();
assert!(build(&inv, &cfg(), true, &[None], false).unwrap_err().contains("only issued"));
assert!(build(&inv, &cfg(), true, &[None], false)
.unwrap_err()
.contains("only issued"));
}
#[test]
@@ -913,9 +1033,17 @@ mod tests {
let mut conn = registered();
let mut inv = issue(&mut conn, json!({}));
inv.due_date = "2026-03-01".into();
assert!(build(&inv, &cfg(), true, &[None], false).unwrap().body.get("due_date").is_none());
assert!(build(&inv, &cfg(), true, &[None], false)
.unwrap()
.body
.get("due_date")
.is_none());
inv.due_date.clear();
assert!(build(&inv, &cfg(), true, &[None], false).unwrap().body.get("due_date").is_none());
assert!(build(&inv, &cfg(), true, &[None], false)
.unwrap()
.body
.get("due_date")
.is_none());
}
#[test]
@@ -960,11 +1088,17 @@ mod tests {
}
let mut unreg = client();
unreg.gst_category = "unregistered".into();
assert!(build_customer(&unreg, &cfg(), true).unwrap().body.get("gstin").is_none());
assert!(build_customer(&unreg, &cfg(), true)
.unwrap()
.body
.get("gstin")
.is_none());
let mut bad = client();
bad.gstin = "29AABCU9603R1Z0".into();
assert!(build_customer(&bad, &cfg(), true).unwrap_err().contains("GSTIN"));
assert!(build_customer(&bad, &cfg(), true)
.unwrap_err()
.contains("GSTIN"));
}
#[test]
@@ -989,7 +1123,10 @@ mod tests {
);
let mut jk = client();
jk.state_code = "01".into();
assert_eq!(build_address(&jk, "X", true).unwrap().body["state"], "Jammu and Kashmir");
assert_eq!(
build_address(&jk, "X", true).unwrap().body["state"],
"Jammu and Kashmir"
);
}
#[test]
@@ -997,7 +1134,10 @@ mod tests {
let mut c = client();
c.address_line1.clear();
c.address = "5 Park Street\nKolkata, West Bengal - 700016".into();
assert_eq!(build_address(&c, "X", false).unwrap().body["address_line1"], "5 Park Street");
assert_eq!(
build_address(&c, "X", false).unwrap().body["address_line1"],
"5 Park Street"
);
let mut no_city = client();
no_city.city.clear();
@@ -1005,7 +1145,11 @@ mod tests {
let mut no_state = client();
no_state.state_code.clear();
assert!(build_address(&no_state, "X", true).is_err());
assert!(build_address(&no_state, "X", false).unwrap().body.get("state").is_none());
assert!(build_address(&no_state, "X", false)
.unwrap()
.body
.get("state")
.is_none());
assert!(build_address(&client(), " ", true).is_err());
}
}