A 6-store family-owned jewellery chain in Coimbatore had been running on Tally Prime since 2009. Beautiful, conservative, audit-friendly — and increasingly the bottleneck. Inventory was a separate Excel per store. Hallmark BIS numbers were tracked in a third spreadsheet. Live gold rates were typed into an invoice template by a counter executive every morning at 10:15. The owner's son took over operations in March 2025 and gave us a brief: "build me a web ERP that does what Tally does, plus the four sheets, and ties to live gold rates. Do not break a single audit." We shipped in 90 days. This is the playbook.
The Answer in 60 Words
We extracted 16 years of Tally vouchers via the Tally ODBC + XML API, normalised them into a Postgres schema, ran a 4-week dual-write (every Tally entry also wrote to the web ERP), reconciled daily, and cutover on a Sunday at 11:14 pm. The web ERP runs on Next.js + Postgres + a Tally bridge written in Python.
Why This Matters Now (For Indian Jewellery SMBs)
Three forces hit jewellery SMBs in 2025. First, BIS hallmark mandatory traceability on 6-digit HUID makes Tally-only inventory an audit risk — every piece needs a separately searchable trail. Second, gold rates have become volatile enough that a 10:15 am rate is wrong by 11:30 am — cash-flow leaks were quietly opening. Third, customers under 35 expect a digital invoice with QR for the BIS number; they are checking it on the BIS Care app before they leave the store. The choice for SMBs is no longer "Tally vs ERP" — it is "Tally plus a custom layer" or "lose the next-gen customer."
We do not believe SMBs should rip out Tally. Tally is genuinely good at what it does (statutory accounting). The play is to build a web ERP that handles the parts Tally is bad at (multi-store inventory, hallmark traceability, live rates, customer portals) and bridge the two so the financial books still flow through Tally for the CA's audit.
The Client (Specific Details)
The Architecture (Tally-as-Source-of-Truth Pattern)
The Stack (And What We Picked Instead)
| Layer | Choice | Alternative | Why |
|---|---|---|---|
| Frontend | Next.js 14 + Tailwind | React + Vite | Server components made the 14,000-SKU search fast on counter terminals (mostly older Windows 10 boxes with 4 GB RAM). |
| Database | PostgreSQL 16 | MySQL | Audit trail was a hard requirement; Postgres' `pgaudit` extension and JSONB for hallmark history were the deciding factors. |
| Tally bridge | Python 3.11 + tdlserver XML | TallyConnect (paid) | The owner's CA was already familiar with Tally's XML API. The free path saved the licence fee and let us own the bridge. |
| Hosting | DigitalOcean Mumbai droplets | AWS Mumbai | Owner wanted single-cloud, single-vendor billing. DO's INR billing matched his preference for round-number monthly invoices. |
| Auth | Clerk | Auth.js | 78 staff with 5 role types. Clerk's organisation feature did 2 weeks of RBAC work for us. |
| Real-time | Pusher Channels | Supabase Realtime | Counter terminals are constantly subscribed to gold rate updates. Pusher's per-channel pricing fit better than usage-billed Supabase. |
The 90-Day Plan (Copy This)
The Schema That Mattered (Pieces + Movements)
The hardest design decision was how to model a piece of jewellery. A single piece moves states constantly — vault, display, customer-trial, hold-for-customer, sold, returned, melted. The CA needed a daily ledger that summed correctly. The ops head needed live "where is this piece" lookup. We landed on event sourcing for movements, with a materialised view for current-state lookup.
-- piece_movements is append-only
CREATE TABLE piece_movements (
id BIGSERIAL PRIMARY KEY,
piece_id BIGINT REFERENCES pieces(id),
from_location TEXT NOT NULL, -- 'vault.coimbatore.race-course'
to_location TEXT NOT NULL,
movement_type TEXT NOT NULL, -- 'transfer','sale','return','melt'
moved_by_user_id BIGINT NOT NULL,
voucher_id BIGINT, -- if part of a Tally voucher
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metadata JSONB
);
-- materialised view: current location per piece
CREATE MATERIALIZED VIEW piece_current_location AS
SELECT DISTINCT ON (piece_id)
piece_id, to_location AS current_location, occurred_at AS as_of
FROM piece_movements
ORDER BY piece_id, occurred_at DESC;
CREATE UNIQUE INDEX ON piece_current_location (piece_id);
-- Refresh after every movement (or batched, every 90s)
REFRESH MATERIALIZED VIEW CONCURRENTLY piece_current_location; The CA's audit team has since used piece_movements to reconstruct the location history of any piece on any date — something that was impossible with the Excel approach. They now ask for it instead of working around it.
The Cost Breakdown
Run cost (steady state): DigitalOcean Mumbai droplets, Postgres-managed (DO), Pusher Channels, Clerk Pro (78 users), gold-rate API allowances, daily backups to S3 + offsite Wasabi, Sentry + Better Stack monitoring.
The Outcome (Numbers That Mattered to the Owner)
The recovered margin came from auto-applied live rates. Pre-build, the counter executive's morning rate was used for the entire day, so afternoon rate spikes silently became margin loss to the firm. Post-build, the rate updates every 90 seconds, with a 12-minute customer-quote lock to keep the in-shop conversation honest.
The Pre-Cutover Checklist (We Refuse to Run Without This)
- Tally bridge runs in dual-write mode for ≥ 14 consecutive days with zero reconciliation drift > ₹100
- HUID-to-piece mapping verified for every active SKU (counted manually at one store as a spot check)
- Gold-rate failover tested — kill the primary source, confirm fallback to median of remaining 2 sources
- All voucher templates round-trip through bridge — ie the CA can see a "web sale" voucher in Tally and it looks identical to a manual one
- Day-end close report matches Tally's day book to the rupee, run twice on staging
- Per-store admin can pause web ERP and revert to Tally-only for that store via one click (we have used it twice in 6 months — once for a network outage, once for a hardware fault)
- Roll-back plan: Postgres point-in-time restore tested, Tally state preserved (we keep Tally as the audit-immutable source for 24 months post-cutover)
- Customer-facing invoice prints HUID + BIS QR — verified scannable on 5 different Android phones
- Counter staff trained on the piece-movement screen; spot-check passes before rollout
- Backup script for Tally companies tested; restore time ≤ 15 minutes
What We Deliberately Did Not Build
1. A customer-facing app. The owner wanted one. We pushed back hard. Customer-facing jewellery apps need photography pipelines, return-flow workflows, and a CRM that no SMB jeweller is operationally ready for. We shipped a customer-side WhatsApp invoice instead — every sale generates a PDF + WhatsApp message in 4 seconds. v2 candidate: a logged-in "my pieces" portal for customers.
2. AI-driven customer personalisation. A vendor pitched "AI predicts which customer will buy what" as a paid add-on. We declined for the client. With ~32 counter staff who know their regular customers by name, the human network beats any AI for the first 2 years of the build. Revisit when the chain crosses 12 stores.
3. Multi-currency support. The owner has cousins in Dubai and a vague export plan. We built USD/AED placeholders into the schema but did not ship the UI. Rule: do not ship a feature for a use case 12 months away. Re-evaluate in 2027.
Common Mistakes (Each One Hurts)
Symptom: "Tally voucher numbers collide." Cause: web-originated and Tally-originated vouchers share the same numbering series. Fix: namespace them at the prefix (SI/W/ vs SI/T/) so the CA can tell which side a voucher originated from at a glance.
Symptom: "Closing stock differs by ₹X every month." Cause: a manual stock-journal entry the CA runs after-hours that the bridge has not seen. Fix: have the bridge poll for "recently modified vouchers" not just "new vouchers", and batch-process anything with a modified date in the last 24 hours. Or, better, ban manual after-hours entries during the dual-write window.
Symptom: "Gold rate spikes mid-customer-quote and the customer is angry." Cause: customer was quoted at 10:15 am rate, paid at 12:30 pm at 12:30 pm rate. Fix: lock the quote for 12 minutes from issuance. After 12 minutes, re-quote with the live rate. This is industry-standard for reputable jewellers; we matched it in software.
Symptom: "BIS HUID search returns 'not found' for valid pieces." Cause: HUIDs entered with leading zero variants. Fix: normalise to 6-digit string with leading zeros, both on entry and on search.
Symptom: "Counter staff bypass the new system at peak hours." Cause: the counter UI is slower than the muscle memory of typing in Tally. Fix: ship a one-keystroke "quick sale" mode that drops to a barcode-scan-and-quantity-only view. We added this in week 16 post-launch — adoption climbed from 64% to 93% in 11 days.
A Detail That Saved Us On Day 67
A senior counter staff at the Erode-2 store called the project lead at 10:42 am: "the gold rate is showing one number — Mehrasons site is showing another." The bridge's median-of-3 logic had picked the wrong rate because Riddisiddhi Bullions had returned a stale value (cached from 8 minutes earlier). We added a "max staleness 90 seconds" filter on the rate sources, with auto-failover to the next source if the primary is stale. Three weeks later, IBJA's API went down for 22 minutes — the failover handled it without a human noticing.
Where Custom ERP Fits In Our Wider Work
We have shipped variants of this Tally-bridge pattern for:
Tally-as-source-of-truth is a pattern that respects the CA's audit habits. Every Indian SMB build we ship in finance-adjacent verticals starts here. Founder-essay version of why we make this trade-off lives at viveksinra.com.
A Common Question About Long-Term Maintenance
Tally is a moving target. Tally Prime 5.0 is on the roadmap, GST 2.0 ships April 1 2026 (we covered the 6 software changes in our March 2026 checklist), and the XML schema occasionally adds non-backwards-compatible fields. We staff 4 hours/month of bridge maintenance into the run contract. In 18 months of running similar bridges across 4 clients, we have spent ~8 hours total on Tally Prime upgrade compatibility. Cost is real but bounded.
FAQ
Why not just buy a jewellery-specific ERP?
We considered three (Synergics, Aaron, Logic). Their lowest-tier subscription for 6 stores came with 2-week onboarding and a roadmap controlled by the vendor. Custom build costs more upfront but the owner controls the roadmap, owns the data, and pays no per-user fee. Payback vs Synergics at our cost level: 4.7 years. The owner's framing: "I would rather own the software than rent it."
Can the bridge work with Tally on cloud?
Yes — Tally Cloud's HTTP XML API works the same. We've tested it. The polling latency is slightly higher (avg 3.4s vs 0.6s for on-prem) but the developer experience is otherwise identical.
What about the audit trail for the tax authorities?
Tally remains the immutable source for accounting. Every web-originated transaction lands in Tally with a "source: web-erp" tag in the narration. The CA can audit Tally as before. We have run two CA-led audits since cutover; both passed without queries.
How did you handle migration of the 4 Excel sheets?
For each sheet (display inventory, vault, hallmark issuance log, manual gold-rate book), we wrote a one-time importer that read the latest snapshot, normalised it into the relevant Postgres table, and asked the senior accountant to approve a sign-off PDF. Total: 6 days of effort across 4 sheets.
What was the team composition?
Three engineers (one senior on the bridge and reconciliation, one mid on the Next.js ERP, one junior on the gold-rate service and HUID tracker), one designer at 0.4 FTE, one QA at 0.5 FTE, and the project lead at 0.3 FTE. Plus 8 weeks of a Tally bridge specialist who had built similar bridges at 3 prior firms.
Did the CA push back on the migration?
Heavily, in week 1. By week 6 (after the dual-write demo), he was the project's strongest internal advocate. What turned him: showing him that Tally remained the audit source and the web ERP only added information, never removed it.
Can we add a customer-facing portal in v2?
Yes, and the schema is ready for it. The piece-movement event sourcing means a "track my piece" customer experience is straightforward. We are quoting v2 separately because the customer-facing layer needs different security review and a customer-support workflow that does not exist today.
What happens if Tally goes down?
The web ERP keeps running. The bridge queues outbound vouchers to a Postgres outbox table; when Tally comes back, they replay in order. We tested this for 4 hours of simulated downtime. Zero data loss.
Have a legacy Tally setup you want extended into a custom ERP?
We migrate Tally-only operations to custom web ERPs for Indian SMBs in retail, manufacturing, and trading verticals. Typical project: 12–18 weeks, fixed scope. The CA stays in the room from day one. The first call is free; we will tell you whether your business is ready or whether you should stay on Tally for another year.
Book an ERP Build Call
