tallyprime-sdk 0.2.0

Rust SDK for TallyPrime XML/HTTP integration with typed APIs for masters, vouchers, and financial reports.
# `tallyprime-sdk`

Rust SDK for integrating with TallyPrime over XML/HTTP.

This crate provides a blocking client for:

- reading masters such as ledgers, groups, stock items, and currencies
- reading vouchers and day-book style voucher ranges
- reading built-in reports such as trial balance, balance sheet, and profit and loss
- creating masters such as ledgers, groups, and stock items

The SDK is built around Tally's XML interface and uses typed Rust models for both inputs and outputs.

## Status

This project is working against a live Tally instance and has integration tests for:

- group creation
- ledger creation
- stock item creation
- voucher fetching
- purchase voucher creation using the lower-level XML builder

The high-level client is stable for read flows and master creation. Advanced voucher import flows exist, but some of them are still exposed through lower-level XML builder utilities rather than a dedicated top-level client method.

## Requirements

- Rust 1.75+ recommended
- TallyPrime running with XML over HTTP enabled
- Default endpoint: `http://localhost:9000`

If you use a specific company, set it explicitly with `TALLY_COMPANY` or `TallyConfig.current_company`. If you do not, the SDK will try to discover the active company loaded in Tally.

## Installation

Add the crate from crates.io:

```toml
[dependencies]
tallyprime-sdk = "0.1"
```

If you want to reference it from Git:

```toml
[dependencies]
tallyprime-sdk = { git = "https://github.com/labs-infinitum/tallyprime-sdk" }
```

## Quick Start

```rust
use tallyprime_sdk::config::TallyConfig;
use tallyprime_sdk::TallyClient;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = TallyClient::new(TallyConfig {
        host: "localhost".into(),
        port: 9000,
        current_company: Some("My Company Pvt. Ltd.".into()),
        ..Default::default()
    })?;

    client.test_connection()?;

    let company = client.active_company_name()?;
    println!("Active company: {:?}", company);

    let ledgers = client.get_ledgers()?;
    println!("Found {} ledgers", ledgers.len());

    let vouchers = client.get_vouchers_in_range("20250401", "20250731")?;
    println!("Found {} vouchers", vouchers.len());

    Ok(())
}
```

## Configuration

The client is configured through [`TallyConfig`](src/config.rs):

```rust
#[derive(Debug, Clone)]
pub struct TallyConfig {
    pub host: String,
    pub port: u16,
    pub timeout_secs: u64,
    pub retry_attempts: u32,
    pub current_company: Option<String>,
    pub tally_net_account: Option<String>,
    pub tally_net_password: Option<String>,
}
```

Defaults:

- `host = "localhost"`
- `port = 9000`
- `timeout_secs = 30`
- `retry_attempts = 3`

## Public API

Main entry point: [`TallyClient`](src/client/mod.rs)

Connection and session:

- `TallyClient::new`
- `TallyClient::test_connection`
- `TallyClient::active_company_name`

Master reads:

- `get_ledgers() -> Vec<LedgerSummary>`
- `get_groups() -> Vec<GroupSummary>`
- `get_stock_items() -> Vec<StockItemSummary>`
- `get_currencies() -> Vec<CurrencySummary>`

Master writes:

- `create_ledger(&Ledger) -> ImportResult`
- `create_group(&Group) -> ImportResult`
- `create_stock_item(&StockItem) -> ImportResult`

Debug variants are also available and print the raw XML request/response:

- `create_ledger_debug`
- `create_group_debug`
- `create_stock_item_debug`

Voucher reads:

- `get_vouchers(from, to) -> Vec<Voucher>`
- `get_vouchers_in_range(from, to) -> Vec<Voucher>`

Reports:

- `get_trial_balance(from, to, explode_flag) -> Vec<TrialBalanceEntry>`
- `get_balance_sheet(from, to, explode_flag) -> Vec<BalanceSheetEntry>`
- `get_profit_and_loss(from, to, explode_flag) -> Vec<ProfitAndLossEntry>`
- `get_gst_computation(from, to) -> GstComputationReport`
- `get_gstr1(from, to) -> Gstr1Report` (voucher-derived; Tally has no builtin HTTP `GSTR-1` report ID)

## Typed Results

Collection reads return typed summaries rather than tuples:

- [`LedgerSummary`]src/models/collection.rs
- [`GroupSummary`]src/models/collection.rs
- [`StockItemSummary`]src/models/collection.rs
- [`CurrencySummary`]src/models/collection.rs

Create/import calls return [`ImportResult`](src/models/import.rs), which includes:

- `created`
- `altered`
- `deleted`
- `combined`
- `ignored`
- `errors`
- `cancelled`
- `exceptions`
- `last_voucher_id`
- `last_master_id`
- `line_errors`

Example:

```rust
use tallyprime_sdk::config::TallyConfig;
use tallyprime_sdk::{Group, TallyClient};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = TallyClient::new(TallyConfig::default())?;

    let result = client.create_group(&Group {
        name: "SDK Demo Group".into(),
        parent: None,
        group_type: None,
        alias: None,
        basic_group_is_calculable: None,
        is_addable: None,
        is_subledger: None,
        addl_alloc_type: None,
        as_original: None,
        affects_gross_profit: None,
        hsn_applicable_from: None,
        hsn_code: None,
        hsn_description: None,
        hsn_classification_name: None,
        hsn_source_of_details: None,
        gst_applicable_from: None,
        gst_taxability: None,
        gst_source_of_details: None,
        gst_classification_name: None,
        gst_rate_duty_head: None,
        gst_rate_valuation_type: None,
        gst_rate: None,
        gst_state_name: None,
    })?;

    println!(
        "created={}, altered={}, exceptions={}",
        result.created, result.altered, result.exceptions
    );

    Ok(())
}
```

## Examples

The crate includes runnable examples under [`examples/`](examples):

- [`fetch_all_accounts.rs`]examples/fetch_all_accounts.rs
- [`fetch_all_groups.rs`]examples/fetch_all_groups.rs
- [`fetch_all_currencies.rs`]examples/fetch_all_currencies.rs
- [`fetch_day_book.rs`]examples/fetch_day_book.rs
- [`fetch_trial_balance.rs`]examples/fetch_trial_balance.rs
- [`fetch_balance_sheet.rs`]examples/fetch_balance_sheet.rs
- [`fetch_profit_and_loss.rs`]examples/fetch_profit_and_loss.rs
- [`fetch_gstr1.rs`]examples/fetch_gstr1.rs
- [`create_ledger.rs`]examples/create_ledger.rs
- [`create_ledger_entry.rs`]examples/create_ledger_entry.rs

Run them with:

```bash
git clone https://github.com/labs-infinitum/tallyprime-sdk
cd tallyprime-sdk

cargo run --example fetch_all_accounts
cargo run --example fetch_all_groups
cargo run --example fetch_all_currencies
cargo run --example fetch_day_book -- --fy 2025-2026
cargo run --example fetch_day_book -- --from 20250401 --to 20250731 --verbose
cargo run --example fetch_trial_balance -- --fy 2025-2026
cargo run --example fetch_balance_sheet -- --fy 2025-2026
cargo run --example fetch_profit_and_loss -- --fy 2025-2026
cargo run --example fetch_gstr1 -- --fy 2025-2026
cargo run --example create_ledger
cargo run --example create_ledger -- --name "Acme Traders" --parent "Sundry Debtors"
cargo run --example create_ledger_entry -- --party "Acme Traders" --amount 1000 --date 20260701
```

Supported example flags:

- `--fy YYYY-YYYY`
- `--from YYYYMMDD`
- `--to YYYYMMDD`
- `--verbose` for day book voucher ledger-line output
- `--flat` for report exports without `EXPLODEFLAG`
- `--name`, `--parent`, `--opening-balance`, and `--debug` for ledger creation
- `--party`, `--account`, `--amount`, `--date`, `--bill-ref`, `--voucher-number`, `--voucher-type`, `--narration`, and `--debug` for ledger entries

Environment variables used by the examples:

- `TALLY_HOST`
- `TALLY_PORT`
- `TALLY_COMPANY`

## Date Handling

The SDK expects date inputs in `YYYYMMDD` format for most public methods.

Example:

- `20250401`
- `20260331`

For voucher reads, `get_vouchers_in_range` applies an exact client-side range filter after parsing the XML response. This exists because Tally's voucher/day book exports are not always consistent about honoring date filters across environments.

## Lower-Level XML Access

For advanced or not-yet-wrapped flows, the crate also exposes the lower-level XML pieces:

- [`xml_builder`]src/xml_builder/mod.rs
- [`client::post_xml`]src/client/http.rs
- [`client::parse_simple_response`]src/client/parse.rs

That is how the current purchase item-invoice integration test is implemented.

## Development

Useful commands:

```bash
cargo fmt
cargo check --examples
cargo test --lib
cargo package
```

Unit tests under `src/` run with `cargo test` / `cargo test --lib` and do not need Tally.

Integration tests under `tests/` require a reachable TallyPrime instance and are marked `#[ignore]`:

```bash
cargo test -- --ignored
```

Set `TALLY_HOST`, `TALLY_PORT`, and optionally `TALLY_COMPANY` when running ignored tests. If no company is active and `TALLY_COMPANY` is not set, some flows will skip.

## Releases

Preferred path: run the **Create Release** workflow from the Actions tab.

1. Configure [Trusted Publishing]https://crates.io/docs/trusted-publishing on crates.io for this repo, with workflow `release.yml`.
2. Choose a bump type:
   - `bugfix``x.y.z``x.y.(z+1)`
   - `minor``x.y.z``x.(y+1).0`
   - `major``x.y.z``(x+1).0.0`
3. Create Release updates `Cargo.toml` / `Cargo.lock`, commits to `main`, and tags `vX.Y.Z`.
4. It then dispatches the **Release** workflow, which publishes that tag to crates.io via OIDC (no `CARGO_REGISTRY_TOKEN` secret).

You can still publish by pushing a tag yourself:

```bash
git tag v0.1.0 && git push origin v0.1.0
```

The **Release** workflow also supports manual `workflow_dispatch` for publish-only / dry-run retries.

## Limitations

- The client is blocking today and uses `reqwest::blocking`.
- Voucher creation is not yet exposed as a single high-level `create_voucher(...)` client API.
- Some advanced Tally/TDL workflows still require the lower-level XML builder layer.

## License

This repository is licensed under the Apache License 2.0.

See [LICENSE](LICENSE) and [NOTICE](NOTICE).