Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions THIRD_PARTY_LICENSES_RUST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,10 @@ parking_lot_core 0.9.12
License: MIT OR Apache-2.0
Source: https://github.com/Amanieu/parking_lot

pdf-writer 0.15.0
License: MIT OR Apache-2.0
Source: https://github.com/typst/pdf-writer

percent-encoding 2.3.2
License: MIT OR Apache-2.0
Source: https://github.com/servo/rust-url/
Expand Down Expand Up @@ -947,6 +951,10 @@ rusty-fork 0.3.1
License: MIT OR Apache-2.0
Source: https://github.com/altsysrq/rusty-fork

ryu 1.0.23
License: Apache-2.0 OR BSL-1.0
Source: https://github.com/dtolnay/ryu

same-file 1.0.6
License: Unlicense OR MIT
Source: https://github.com/BurntSushi/same-file
Expand Down Expand Up @@ -10280,6 +10288,7 @@ Apache License 2.0
- objc2-exception-helper 0.1.1
- objc2-quartz-core 0.3.2
- objc2-web-kit 0.3.2
- pdf-writer 0.15.0
- pin-project-lite 0.2.17
- proc-macro2 1.0.107
- quote 1.0.47
Expand All @@ -10288,6 +10297,7 @@ Apache License 2.0
- rand_xorshift 0.4.0
- raw-window-handle 0.6.2
- rustc-hash 2.1.3
- ryu 1.0.23
- semver 1.0.28
- serde-untagged 0.1.9
- serde 1.0.229
Expand Down
2 changes: 1 addition & 1 deletion docs/tally/compatibility/compatibility-matrix.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"bridge_commit_sha": "be1c20cc3fd66fa1ece196505c69f26e555e4b8e",
"compatibility_surface_sha256": "431d9bdd19da89efafb9397f01a55b368840077f05a3fc11ae16833645d0d5e6",
"compatibility_surface_sha256": "24d812389629d56f5750b2b021089c1fcf3d1a330b0bd440683aa54219b60303",
"claims": [
{
"claim_id": "erp9-6-6-3-windows-education-xml-one-company",
Expand Down
12 changes: 6 additions & 6 deletions docs/tally/compatibility/compatibility-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@
},
{
"path": "src-tauri/Cargo.lock",
"sha256": "15d064b88c4d2cb68bcbc8a727c55702f2ad9b894363be8f8f945f79c9536357"
"sha256": "7aac9a6af2fdab6e117af7d3aa26ba04434701fe02d3010010fa3f3793236212"
},
{
"path": "src-tauri/Cargo.toml",
"sha256": "5c809e9170457a41ab18de4188c0af9ff06cc741e0facd293ae26f0949de2351"
"sha256": "3bb11f1d204fa55443e80686a1def19210b8756ace49585cb308b27e649b52a1"
},
{
"path": "src-tauri/crates/bridge-tally-core/Cargo.toml",
Expand Down Expand Up @@ -219,7 +219,7 @@
},
{
"path": "src-tauri/src/commands.rs",
"sha256": "3fd0e744e22a4078694e7c80b538a293bf4921776393d160c1d209078f03812c"
"sha256": "116c0ab468896bff2c98db3aaada794d5378ef8e32934bcad737c27fb592a5e7"
},
{
"path": "src-tauri/src/db/encrypted.rs",
Expand Down Expand Up @@ -359,7 +359,7 @@
},
{
"path": "src/OutstandingsScreen.tsx",
"sha256": "be442a765fbf8f3440abb6c4673e5baefec8f628743c314693f9d5ebed276dab"
"sha256": "df11bdbbbfb4f62b7738cd30ea69a2ce5b15eb8a5b5567358e67d6d04569bb90"
},
{
"path": "src/TallyReadinessFlow.tsx",
Expand All @@ -379,7 +379,7 @@
},
{
"path": "src/styles.css",
"sha256": "33bd1f956b6b1ece2924fa8741fec9cb46a4b3b912c421b435b587c2e6293d6d"
"sha256": "ff23d59914a48f3f3621499a8121fc63ee93f7b18ad4ae787d7f1d6e04b81281"
},
{
"path": "src/tally-company-selection.ts",
Expand Down Expand Up @@ -434,5 +434,5 @@
"sha256": "a27f294ee15e407b69fdfc73609e8708ac0509b6e6a8872daef5451fde61a8db"
}
],
"manifest_sha256": "431d9bdd19da89efafb9397f01a55b368840077f05a3fc11ae16833645d0d5e6"
"manifest_sha256": "24d812389629d56f5750b2b021089c1fcf3d1a330b0bd440683aa54219b60303"
}
20 changes: 20 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v4", "serde"] }
x509-parser = "0.18"
zeroize = "1"
pdf-writer = "0.15.0"

[dev-dependencies]
tally-protocol-simulator = { path = "crates/tally-protocol-simulator" }
zip = { version = "8.6.0", default-features = false, features = ["deflate"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"
Expand Down
69 changes: 63 additions & 6 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::db::tally_mirror::{
};
use crate::gst::{GstDraftRequest, GstReturnDraft};
use crate::reports::party_statement::{build_party_statement, PartyStatementError};
use crate::reports::party_statement_pdf::render_party_statement_pdf;
use crate::reports::party_statement_xlsx::render_party_statement_xlsx;
use crate::sync::coordinator::{SnapshotCoordinator, SnapshotJobStatus};
use crate::sync::reconciliation::ExternalReferenceCatalog;
Expand Down Expand Up @@ -2786,9 +2787,21 @@ pub async fn save_report_download(
app: tauri::AppHandle,
file_name: String,
contents: String,
) -> Result<String, String> {
save_report_download_bytes(&app, &file_name, contents.as_bytes())
}

/// Shared byte-oriented implementation for every local report export.
///
/// The public command keeps its text-only IPC contract for CSV exports; binary
/// formats use this same checked path after their renderer has produced bytes.
fn save_report_download_bytes(
app: &tauri::AppHandle,
file_name: &str,
contents: &[u8],
) -> Result<String, String> {
use tauri::Manager as _;
let file_name = portable_export_file_name(&file_name)?;
let file_name = portable_export_file_name(file_name)?;

// Tauri's own path resolver, so this needs no extra crate and no
// capability grant.
Expand All @@ -2797,7 +2810,7 @@ pub async fn save_report_download(
.download_dir()
.or_else(|_| app.path().home_dir())
.map_err(|_| "Bridge could not locate a folder to save into.".to_string())?;
let path = write_unique_download(&downloads, &file_name, contents.as_bytes())
let path = write_unique_download(&downloads, &file_name, contents)
.map_err(|error| format!("Bridge could not write the export: {error}"))?;
Ok(path.to_string_lossy().into_owned())
}
Expand Down Expand Up @@ -2886,6 +2899,9 @@ pub struct ExportPartyStatementRequest {
pub company: String,
pub as_of_yyyymmdd: String,
pub party: String,
/// XLSX remains the default for callers that predate the PDF option.
#[serde(default)]
pub format: PartyStatementFormat,
/// The `open_bills`/`unallocated_by_party` rows the frontend already
/// holds from `fetch_tally_outstandings`. This command reads no Tally
/// endpoint of its own -- `OutstandingsLoadResult::Complete` already
Expand All @@ -2894,7 +2910,15 @@ pub struct ExportPartyStatementRequest {
pub unallocated_by_party: Vec<UnallocatedParty>,
}

/// Builds one party's aged-bills statement as an `.xlsx` workbook and writes
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PartyStatementFormat {
#[default]
Xlsx,
Pdf,
}

/// Builds one party's aged-bills statement in the requested format and writes
/// it to the user's Downloads folder.
///
/// Mirrors `save_report_download` exactly: Tauri's own path resolver, the
Expand Down Expand Up @@ -2930,8 +2954,18 @@ pub async fn export_party_statement(
}
})?;

let bytes = render_party_statement_xlsx(&statement)
.map_err(|error| format!("Bridge could not build the statement: {error}"))?;
let (bytes, extension) = match request.format {
PartyStatementFormat::Xlsx => (
render_party_statement_xlsx(&statement)
.map_err(|error| format!("Bridge could not build the statement: {error}"))?,
"xlsx",
),
PartyStatementFormat::Pdf => (
render_party_statement_pdf(&statement)
.map_err(|error| format!("Bridge could not build the statement: {error}"))?,
Comment thread
lamemustafa marked this conversation as resolved.
"pdf",
),
};

let mut slug = statement_filename_slug(&statement.party);
slug.truncate(150);
Expand All @@ -2942,7 +2976,7 @@ pub async fn export_party_statement(
.download_dir()
.or_else(|_| app.path().home_dir())
.map_err(|_| "Bridge could not locate a folder to save into.".to_string())?;
let path = write_unique_statement_file(&downloads, &stem, "xlsx", &bytes)?;
let path = write_unique_statement_file(&downloads, &stem, extension, &bytes)?;
Ok(path.to_string_lossy().into_owned())
}

Expand Down Expand Up @@ -3053,6 +3087,29 @@ mod statement_export_tests {
}
}

#[cfg(test)]
mod party_statement_export_tests {
use super::*;

#[test]
fn statement_export_format_defaults_to_xlsx_and_accepts_pdf() {
let base = serde_json::json!({
"company": "Synthetic Books Pvt Ltd",
"as_of_yyyymmdd": "20260808",
"party": "Synthetic Party",
"open_bills": [],
"unallocated_by_party": [],
});
let defaulted: ExportPartyStatementRequest = serde_json::from_value(base.clone()).unwrap();
assert!(matches!(defaulted.format, PartyStatementFormat::Xlsx));

let mut pdf = base;
pdf["format"] = serde_json::Value::String("pdf".to_string());
let pdf: ExportPartyStatementRequest = serde_json::from_value(pdf).unwrap();
assert!(matches!(pdf.format, PartyStatementFormat::Pdf));
}
}

#[derive(Debug, Deserialize)]
pub struct BaseCurrencyRequest {
pub config: TallyConfig,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/reports/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
//! `fetch_tally_outstandings` -- no module here issues a Tally request.

pub mod party_statement;
pub mod party_statement_pdf;
pub mod party_statement_xlsx;
Loading