{group.label}Group total
- {formatCompact(group.totals.receivable)}
+ {formatCompact(group.totals.receivable, "INR")}
0 ? "is-overdue" : undefined}>
- {formatCompact(group.totals.overdue)}
+ {formatCompact(group.totals.overdue, "INR")}
- {formatCompact(group.totals.unallocated)}
+ {formatCompact(group.totals.unallocated, "INR")}
{group.rows.map(renderRow)}
diff --git a/src/OutstandingsScreen.tsx b/src/OutstandingsScreen.tsx
index 9b493d0..0572387 100644
--- a/src/OutstandingsScreen.tsx
+++ b/src/OutstandingsScreen.tsx
@@ -3,7 +3,7 @@ import { Building2, ChevronRight, Download, RefreshCw } from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import { isNonRetryableOutstandingsBoundary, outstandingsAgeingAnchorLabel, outstandingsAgeingDisclosure, outstandingsPartialState, type OutstandingsAgeingAnchor } from "./outstandings-copy";
import { csvNumericCell, csvRow, csvTextCell, type CsvCell } from "./outstandings-csv";
-import { canStartOutstandingsRead } from "./outstandings-currency";
+import { canStartOutstandingsRead, outstandingsCurrencySymbol } from "./outstandings-currency";
import { groupOpenBillsByParty, type OpenBill, type PartyBillsState } from "./outstandings-bills";
import {
asOfBoundValueForAsOf,
@@ -89,6 +89,7 @@ type LoadResult =
reason_code: string;
requested_as_of_yyyymmdd?: string;
tally_as_of_yyyymmdd?: string;
+ foreign_currency_ledger_name?: string;
synced_at_unix_ms: number;
};
@@ -175,6 +176,7 @@ export function OutstandingsScreen({
result.reason_code,
result.requested_as_of_yyyymmdd,
result.tally_as_of_yyyymmdd,
+ result.foreign_currency_ledger_name,
)
: null;
const outstandingsUnavailable = result?.state === "partial" && isNonRetryableOutstandingsBoundary(result.reason_code);
@@ -1027,21 +1029,13 @@ function formatMoney(value: string, currencyAssertion: "INR") {
const tail = whole.slice(-3);
const head = whole.slice(0, -3).replace(/\B(?=(\d{2})+(?!\d))/g, ",");
const grouped = head ? `${head},${tail}` : tail;
- return `${negative ? "−" : ""}${currencySymbol(currencyAssertion)}${grouped}${fraction ? `.${fraction.padEnd(2, "0")}` : ""}`;
+ return `${negative ? "−" : ""}${outstandingsCurrencySymbol(currencyAssertion)}${grouped}${fraction ? `.${fraction.padEnd(2, "0")}` : ""}`;
}
function isInrCompleteResult(result: LoadResult | null): result is InrCompleteResult {
return result?.state === "complete" && result.currency_assertion === "INR";
}
-function currencySymbol(currencyAssertion: "INR") {
- return currencyAssertion === "INR" ? "₹" : unreachableCurrencyAssertion(currencyAssertion);
-}
-
-function unreachableCurrencyAssertion(currencyAssertion: never): never {
- throw new Error(`Unsupported outstandings currency assertion: ${currencyAssertion}`);
-}
-
function formatDate(value: string) {
// `new Date("2026-07-01")` parses as UTC midnight, so west of UTC it renders
// the PREVIOUS day -- and with a day-01 string, the previous MONTH. Report
diff --git a/src/outstandings-copy.ts b/src/outstandings-copy.ts
index 5689104..3334da3 100644
--- a/src/outstandings-copy.ts
+++ b/src/outstandings-copy.ts
@@ -10,6 +10,7 @@ export function outstandingsPartialReason(
value: string,
requestedAsOf?: string,
tallyAsOf?: string,
+ foreignCurrencyLedgerName?: string,
) {
if (value === "native_outstandings_as_of_refused") {
if (requestedAsOf && tallyAsOf) {
@@ -32,9 +33,17 @@ export function outstandingsPartialReason(
if (value === "company_base_currency_not_inr") {
return "this company's verified base currency is not INR";
}
+ if (value === "company_base_currency_undetermined") {
+ return "Tally defines multiple Currency masters, so Bridge cannot identify this company's base currency. Open the company and confirm INR manually; the single-company read can then name any foreign-currency ledger that blocks the report";
+ }
if (value === "company_outstandings_read_failed") {
return "this company read failed while the remaining companies continued";
}
+ if (value === "company_foreign_currency_ledger_balance") {
+ return foreignCurrencyLedgerName
+ ? `Tally reported a foreign-currency closing balance for ledger ${foreignCurrencyLedgerName}`
+ : "Tally reported a foreign-currency closing balance";
+ }
if (value === "tally_segment_latency_trending_restart_recommended") {
return "comparable segments kept slowing toward the safety deadline; Tally may need a restart before another sync";
}
@@ -82,6 +91,7 @@ export function outstandingsPartialState(
reasonCode: string,
requestedAsOf?: string,
tallyAsOf?: string,
+ foreignCurrencyLedgerName?: string,
): OutstandingsPartialState {
if (reasonCode === "native_outstandings_as_of_refused") {
return {
@@ -126,6 +136,14 @@ export function outstandingsPartialState(
tallyReadAttempted: true,
};
}
+ if (reasonCode === "company_foreign_currency_ledger_balance") {
+ return {
+ title: "Outstandings are not available for this company",
+ message: `${outstandingsPartialReason(reasonCode, requestedAsOf, tallyAsOf, foreignCurrencyLedgerName)}. Bridge withheld the totals rather than guessing a base-currency amount.`,
+ retryable: false,
+ tallyReadAttempted: true,
+ };
+ }
return {
title: "Partial result withheld",
message: `Bridge could not prove every requested segment complete (${outstandingsPartialReason(reasonCode)}). No totals were calculated.`,
diff --git a/src/outstandings-currency.ts b/src/outstandings-currency.ts
index bb505fa..715111b 100644
--- a/src/outstandings-currency.ts
+++ b/src/outstandings-currency.ts
@@ -6,3 +6,13 @@ export function canStartOutstandingsRead(
) {
return company?.guid === inrAssertedCompanyGuid;
}
+
+export type OutstandingsCurrencyAssertion = "INR";
+
+export function outstandingsCurrencySymbol(currencyAssertion: OutstandingsCurrencyAssertion) {
+ return currencyAssertion === "INR" ? "₹" : unreachableCurrencyAssertion(currencyAssertion);
+}
+
+function unreachableCurrencyAssertion(currencyAssertion: never): never {
+ throw new Error(`Unsupported outstandings currency assertion: ${currencyAssertion}`);
+}