tallyprime-sdk 0.2.0

Rust SDK for TallyPrime XML/HTTP integration with typed APIs for masters, vouchers, and financial reports.
use super::extract::{
    extract_currencies_from_xml, extract_groups_from_xml, extract_ledger_details_from_xml,
    extract_ledgers_from_xml, extract_stock_item_details_from_xml, extract_stock_items_from_xml,
};
use super::{parse, TallyClient};
use crate::errors::Result;
use crate::models::{
    CurrencySummary, Group, GroupSummary, ImportResult, Ledger, LedgerDetails, LedgerSummary,
    StockItem, StockItemDetails, StockItemSummary,
};
use crate::xml_builder::XmlBuilder;

impl TallyClient {
    /// Create a ledger master in Tally.
    pub fn create_ledger(&self, ledger: &Ledger) -> Result<ImportResult> {
        ledger.validate()?;
        let map = ledger.to_map();
        let xml = XmlBuilder::create_ledger_request(&map)?;
        self.execute_create_request(&xml)
    }

    /// Same as [`Self::create_ledger`], but prints the XML request/response.
    pub fn create_ledger_debug(&self, ledger: &Ledger) -> Result<ImportResult> {
        ledger.validate()?;
        let map = ledger.to_map();
        let xml = XmlBuilder::create_ledger_request(&map)?;
        self.execute_debug_create_request(&xml)
    }

    /// Create a group master in Tally.
    pub fn create_group(&self, group: &Group) -> Result<ImportResult> {
        group.validate()?;
        let map = group.to_map();
        let xml = XmlBuilder::create_group_request(&map)?;
        self.execute_create_request(&xml)
    }

    /// Same as [`Self::create_group`], but prints the XML request/response.
    pub fn create_group_debug(&self, group: &Group) -> Result<ImportResult> {
        group.validate()?;
        let map = group.to_map();
        let xml = XmlBuilder::create_group_request(&map)?;
        self.execute_debug_create_request(&xml)
    }

    /// Create a stock item master in Tally.
    pub fn create_stock_item(&self, item: &StockItem) -> Result<ImportResult> {
        item.validate()?;
        let map = item.to_map();
        let xml = XmlBuilder::create_stock_item_request(&map)?;
        self.execute_create_request(&xml)
    }

    /// Same as [`Self::create_stock_item`], but prints the XML request/response.
    pub fn create_stock_item_debug(&self, item: &StockItem) -> Result<ImportResult> {
        item.validate()?;
        let map = item.to_map();
        let xml = XmlBuilder::create_stock_item_request(&map)?;
        self.execute_debug_create_request(&xml)
    }

    /// List ledger names (and parents when available).
    pub fn get_ledgers(&self) -> Result<Vec<LedgerSummary>> {
        self.fetch_name_parent_collection("Ledger", extract_ledgers_from_xml)
    }

    /// List group names (and parents when available).
    pub fn get_groups(&self) -> Result<Vec<GroupSummary>> {
        self.fetch_name_parent_collection("Group", extract_groups_from_xml)
    }

    /// List stock item names (and parents when available).
    pub fn get_stock_items(&self) -> Result<Vec<StockItemSummary>> {
        self.fetch_name_parent_collection("Stock Item", extract_stock_items_from_xml)
    }

    /// List currencies configured in the company.
    pub fn get_currencies(&self) -> Result<Vec<CurrencySummary>> {
        let current_company = self.current_company_name()?;
        let xml = XmlBuilder::create_currency_export_request(current_company.as_deref())?;
        let resp = self.post_xml(&xml)?;
        Ok(extract_currencies_from_xml(&resp))
    }

    /// Export rich ledger master details (mailing, GSTIN, bank, opening balance, …).
    pub fn get_ledger_details(&self) -> Result<Vec<LedgerDetails>> {
        let current_company = self.current_company_name()?;
        let xml = XmlBuilder::create_ledger_export_request(current_company.as_deref())?;
        let resp = self.post_xml(&xml)?;
        Ok(extract_ledger_details_from_xml(&resp))
    }

    /// Export rich stock item details (units, HSN/GST, opening values, …).
    pub fn get_stock_item_details(&self) -> Result<Vec<StockItemDetails>> {
        let current_company = self.current_company_name()?;
        let xml = XmlBuilder::create_stock_item_export_request(current_company.as_deref())?;
        let resp = self.post_xml(&xml)?;
        Ok(extract_stock_item_details_from_xml(&resp))
    }

    /// Shared by masters + vouchers create helpers.
    pub(super) fn execute_create_request(&self, xml: &str) -> Result<ImportResult> {
        let resp = self.post_xml(xml)?;
        Ok(parse::parse_simple_response_public(&resp))
    }

    pub(super) fn execute_debug_create_request(&self, xml: &str) -> Result<ImportResult> {
        let prepared = self.prepare_request_xml(xml)?;
        println!(
            "\n================ XML Request ================\nPOST {}\nContent-Type: text/xml\n\n{}\n============================================\n",
            self.base_url, prepared
        );
        let resp = self.post_raw_xml(&prepared)?;
        println!(
            "\n================ Raw Response ================\n{}\n============================================\n",
            resp
        );
        Ok(parse::parse_simple_response_public(&resp))
    }

    fn fetch_name_parent_collection<T>(
        &self,
        collection_type: &str,
        extractor: fn(&str) -> Vec<T>,
    ) -> Result<Vec<T>> {
        let mut stat = serde_json::Map::new();
        stat.insert(
            "FETCHLIST".into(),
            serde_json::json!({ "FETCH": ["NAME", "PARENT"] }),
        );
        if let Some(company) = self.current_company_name()? {
            stat.insert(
                "SVCURRENTCOMPANY".into(),
                serde_json::Value::String(company),
            );
        }
        let xml = XmlBuilder::create_export_request("COLLECTION", collection_type, Some(&stat))?;
        let resp = self.post_xml(&xml)?;
        Ok(extractor(&resp))
    }
}