Skip to content
Contact

Case studies / Tigg Accounting · Jun – Sep 2025

The account decides the currency

Multi-currency accounting breaks when the currency can be changed in the wrong place. I built the rules that tie currency to the account, and a quick-approve for bank reconciliation that is one click only when it's safe.

FinanceFrontendRulesMerged to production · Jul – Sep 2025In use (first-party report)

Role

Interaction design (directly in code) and frontend

Timeline

Jun – Sep 2025

Team

CEO, senior frontend engineer (review), backend engineers, QA

Platform

Accounting web

Not mine

Backend: suggestion data, posting, bank feeds

Measured outcome

None measured

the key decision

Currency follows the account, and leaving NPR empties the rate, so a rate of 1 can't slip into a foreign payment.

how deep do you want to go?

Finance rules
Diagram from the case study. Product screenshots are being added.
That's the brief. Switch to Story for the full argument, or Deep for states, iterations and implementation.
Chapter 01

A number that passes every check and is still wrong

In the summer of 2025 Tigg Accounting needed to handle more than one currency properly. Most Nepali businesses keep their books only in rupees, but some pay foreign suppliers, bill clients in dollars, or hold a foreign-currency bank account. For them, every payment, receipt and bank line carries two facts: a currency, and a rate to NPR.

Walking through the money forms, I found the failure that worried me most. A form opened in NPR, where the exchange rate is 1 and the field is disabled. If the user then switched the currency to USD, the 1 stayed. Saving worked, because 1 is a present and positive number. The ledger then recorded a USD 1,250.00 payment as NPR 1,250.00 instead of roughly NPR 1,66,750.

1 · Form opens CurrencyRate to NPR NPR 1 Rate is 1 and disabled.Correct for NPR. 2 · User picks USD CurrencyRate to NPR USD 1 The 1 from NPR is kept.Nothing signals a problem. 3 · Save Rule: rate is required ✓ Rule: rate > 0 ✓ Saved Valid, and wrong. What a USD 1,250.00 payment is worth in the books (NPR) Recorded at rate 1 NPR 1,250.00 Intended at rate 133.40 NPR 1,66,750.00 083,3751,66,750
Reconstruction, fictional dataA default that is true for NPR survives into a foreign context and passes both validation rules. Amounts are fictional.

An empty field gets noticed; a wrong number that looks right does not. That idea shaped the rest of the work: a default is a claim, and the form should only make claims that are true in the current context.

Chapter 02

Context and how I worked

I was the only UI/UX designer at Tigg, and at that point I was moving from my internship into a frontend role. The trigger was the product's move to real multi-currency support. I didn't find a Figma file for these surfaces because I designed them directly in code. I changed the live forms, tested each rule against the forms that used it, and the senior frontend engineer reviewed every change before it was merged.

My research was a surface audit. I went through every place in the accounting app where an amount or a currency control appears and noted what it showed and what it let people do:

  • the dashboard bank cards, the bank overview and its charts;
  • bank statements, the reconciliation report and the matching drawers;
  • customer and supplier payments, quick receipt, quick payment, cash transfer;
  • the sales documents (quotation, sales order, invoice, credit note);
  • the journal voucher used for forex adjustments.

Some context applied everywhere. NPR is the base currency, so every rate is written "to NPR" and NPR is listed first. A revaluation gain or loss is booked in NPR. Many businesses run more than one branch, and a branch changes which documents apply.

Chapter 03

Three rules for the forms

The audit came down to three rules. Each one answers a question the forms had left open.

1. An amount always carries its currency

Dashboard balances, charts, statements and the reconciliation drawers now show the currency code or symbol next to the number, with the minus sign in the right place. The code is smaller and lighter than the amount: it gives context without adding clutter.

2. The account decides the currency

A payment from a USD bank account can only move USD. When someone picks a foreign-currency account in a payment, receipt or transfer, the form sets the currency to match and locks the selector. The rate stays editable, because the rate is still the user's decision.

My first version locked the currency for every account, NPR accounts included. Two days later I limited the lock to foreign accounts. Looking back, locking an NPR account added friction without preventing any real mistake. The risky case was always a foreign account posted in the wrong currency.

3. Leaving NPR clears the rate

Choosing NPR sets the rate to 1 and disables the field. Leaving NPR for a foreign currency empties the field and makes it required, so saving is blocked with "Exchange Rate is required" until someone types a rate. The forex-adjustment journal always opens in NPR with the currency fixed, because revaluation is recorded in the base currency.

L · Locked by account (account currency ≠ NPR): selector disabled, rate stays editable S0 · Hiddencurrency setting offpayload: NPR, rate 1 S1 · NPRrate = 1, field disabledselector free S2 · Foreign, rate emptyrate = "", field requiredsave blocked: rate required S3 · Foreign, rate setrate > 0: valid≤ 0: must be greater than 0 setting on, form opensNPR sorted first select foreignrate := "" select NPRrate := 1 select NPR · rate := 1 type a rate clear rate other foreign: rate kept
Reconstruction, fictional dataThe currency pair as a statechart. Every path from NPR into a foreign currency goes through the empty, required rate. The lock is an overlay that applies whenever the chosen account is not in NPR.
Product screenshot, test company dataWhat an accountant sees after choosing a USD account.
Chapter 04

One click, only when one click is enough

The same principle carried over to bank reconciliation. Before this work, every unmatched bank line needed "Find Match" or a full drawer to create a receipt or payment, even when the answer was obvious: a monthly fee, or a regular customer paying again. The backend could already suggest an account for a line, but the table didn't use the suggestions.

I designed and built a suggested account and an approve tick on every pending row, in the Bank Statement tab and in the bank overview's list of recent unmatched transactions. The first suggestion is filled in. If it's wrong, the user can search for any account, or clear the field. A cleared row stays empty, because clearing a suggestion is a decision and the table shouldn't quietly fill it again.

The hard question was when the tick is allowed to post straight away. A one-click post can't guess an exchange rate or a branch. So the tick checks three things in order: whether the organisation uses billing locations (branches), whether the line is in a foreign currency, and whether the bank account is. If any answer is yes, nothing is posted. The full drawer opens with the chosen account already filled in, and it asks only for what's missing.

✓ pressedaccount chosen Billing locationsenabled? Line currencyforeign? Bank currencyforeign? Post directlyright record type no no no Open the full drawerpre-filled with the chosen account (or the first suggestion); nothing is posted yes yes yes Order of checks: organisation → line → bank. An unset currency counts as NPR.
DiagramThe guard behind the tick: three yes/no questions that can be read and checked in review. An unset currency counts as NPR.

I also removed the match percentage from the suggestion label. The score behind it came on different scales in different responses, so any number I showed would look more precise than the data really was. Now the label shows only the account name.

Product screenshot, test company dataThe status column holds the whole decision: find a match, choose an account, approve, or open the full form.
Chapter 05

What shipped

The work ran from late June to early September 2025 in three stages, each merged to production after review:

  1. Jun – Jul 2025Currency visible and bound to the accountCurrency on every money surface, the account lock, and the rate that clears when leaving NPR. Merged to production in July.
  2. Jul 2025Currency in the reconciliation drawerThe add-payment drawer follows the bank's currency, and it still works when multi-currency is switched off.
  3. Aug – Sep 2025Suggestions and quick approveSuggested accounts, search, clearing, the guard, and links from a reconciled line to its records. Merged to production in early September.

Quick approve is used in the product today. Measured outcome: none. I have no figures for error reduction, reconciliation time or how often suggestions are accepted, so I don't claim any.

A small sign that the rule held up: in March 2026 another engineer built a new document form and reused the same "clear the rate when leaving NPR" behaviour.

If I were measuring this, I'd track four things: how often the tick is used, how often people change or clear the suggestion, how often the guard sends a line to the full form and for which reason, and how many one-click posts are reversed within a week. That last number is the real safety signal.

Chapter 06

What I learned

A default is a claim. "1" is only true for NPR. A pre-filled number is more dangerous than an empty field because it passes validation. I learned this twice: two weeks after fixing the forms, I briefly let the reconciliation drawer fill an empty rate with 1 for convenience, and replaced it with an empty, required field the same day.

Constrain only what can go wrong. Locking NPR accounts protected nobody. A constraint should match a real risk, not just look careful.

Speed should depend on context. One click promises that nothing else is needed. When a branch or a rate is involved, the honest response is the full form, already filled in, rather than a guess or an error.

Deep dive 07

The rule sheet and the row states

The currency pair, as a table

This is the sheet I used to check each form by hand. The codebase had no automated tests, so every rule had to be simple enough to check this way during review.

BeforeActionAccount currencyCurrency afterRate afterSelector
(new form)form opensnoneNPR (listed first)1, disabledfree
NPRchoose USDnoneUSDempty, requiredfree
USDchoose NPRnoneNPR1, disabledfree
anychoose accountUSDUSDempty until typedlocked
anychoose accountNPRNPR1, disabledfree
noneopen reconciliation drawerbank in USDUSDthe line's rate, or emptylocked
noneforex adjustmentnoneNPR1, disabledfixed

With multi-currency switched off for the organisation, the currency controls don't appear at all, and documents are saved in NPR at 1.

A pending bank line

StateWhat the row showsWhat the tick does
Suggestedthe first suggestion, filled inposts, or opens the drawer if the guard says so
Chosenanother suggestion, or any searched accountsame as above
Clearedan empty selector that stays emptynothing until an account is chosen
No suggestion"Select account"; the list loads when openednothing until an account is chosen
Reconciled"Reconciled" with up to two linked records and "+N more"none; the links open the records

When a line posts directly, the kind of record follows from the line and the account. Money in from a customer becomes a customer payment and money out to a supplier becomes a supplier payment. Anything else becomes a quick receipt or a quick payment. A failure shows the server's message, or "Transaction Failed", and leaves the row as it was.

23 Jul · prototypeRaw score Rent Expense (0.87)▾ Rent Expense (0.87)Office Rent (0.61) Suggestions only; if there are none,disabled: "No suggestions available". When all suggestions are wrong,there is no way out but the drawer. 14–15 AugPercentage + searchable list Rent Expense (87%)▾ Rent Expense (87%)Office Rent (61%)Advertising ExpenseAudit Fees Any account can be searched (20 at atime, 300 ms debounce). The first optionis auto-selected — suggested or not. 18 Aug · currentName only Rent Expense▾ Rent ExpenseOffice RentAdvertising ExpenseAudit Fees Only a real suggestion is auto-selected.Fixed 300 px width keeps the tick inplace whatever the account name. The score's scale was inconsistent (0–1 in some responses, 0–100 in others), so any number shown implied a precision the data did not have.
Reconstruction, fictional dataThree versions of the suggestion label, July to August 2025. Removing the percentage gave up an explanation in exchange for honesty. Showing the reason in words is a possible next step. Scores and account names are illustrative.
Product screenshot, test company dataThe fallback path: the account chosen in the row carries over, and only the missing facts are asked for.
Deep dive 08

Iterations and notes on sources

How the lock found its place

WhenVersionWhat happened
Early July 2025Lock for every accountBuilt, then limited two days later
Early July 2025Work out the currency by watching the account field afterwardsDidn't work reliably; I reverted it the next day and passed the currency along with the user's choice instead
Same weekLock inside the shared currency fieldReplaced within hours: the field couldn't know why the currency was foreign
Same weekEach form decides and tells the fieldKept. A review round also simplified the shared field, removing options I had added to it
A few days laterFix for blank formsNew, empty forms showed the selector locked by mistake. Fixed with an NPR default

Where these facts come from

No ticket, design file or chat record exists for this 2025 work. Dates and behaviour come from the product and its version history, which I've read again for this write-up. Where I give a reason for a change, it's my reading of the change looking back, not a note I wrote at the time. The screens above are placeholders for screenshots from a test company. The diagrams are redrawn, and all amounts are fictional.

Deep chapters (states, iterations, implementation) are hidden in Story mode. Switch to Deep at the top to read them.