Skip to case study
Parth AkholkarLet's talk
00Craveto · FMCG distribution2026Live

Order-to-invoice automation

A production order-to-invoice system for a B2B FMCG distributor. WhatsApp orders are parsed by Claude, billed by deterministic code, approved by a human, and written into Tally through an outbound-only office agent.

Client
A B2B FMCG distributor in Ahmedabad
Role
Solo builder, independent consultant
Stack
Anthropic SDK · TypeScript · Tally XML · Railway
₹50L+worth of orders processed
30 → 1 minof staff time per order
382automated tests on billing, routing and Tally

Snapshot

Craveto is a production order-to-invoice system for a B2B FMCG distributor in Ahmedabad. A free-text order arrives through WhatsApp; Claude parses it to structured line items; a deterministic engine computes the bill; a human reviews it; an outbound-only Windows agent writes the approved sale order into Tally Prime and the system produces the customer PI.

Context

The client runs the way most small Indian FMCG distributors run: on WhatsApp and Tally, with pricing and scheme logic that lives in someone's head. The order-to-invoice step - reading a WhatsApp message, working out quantities and schemes, writing it into Tally, generating a proforma invoice - was entirely manual. The brief was to automate that step without asking the client to change how they take orders.

Problem

Two things made this harder than "point an LLM at some text":

  • The business logic is genuinely intricate: case-to-piece conversion, N+1 quantity schemes scaled to carton size, percentage-of-value sample schemes paid in a chosen product, per-client live pricing, CGST/SGST vs IGST with round-off. Get any of it wrong and someone gets billed incorrectly.
  • Tally Prime has no official API. Every other option was a manual re-entry step or a brittle screen-scrape.

A few days before go-live, a bulk Tally import crashed the client's office PC. That forced the real question this project is actually about: what do you let an LLM decide, and what do you never let it near.

Approach

The LLM does language, never arithmetic. Claude's only job is turning a free-text WhatsApp order into structured line items. Every number after that - case-to-piece conversion, scheme math, GST, round-off - runs through a pure, deterministic TypeScript engine with zero I/O. It's golden-tested against real invoices, not just unit-tested in the abstract.

Human approval is a hard gate, not UX polish. Nothing reaches Tally or a client without a person clicking approve on a generated proforma invoice first. The system's job is to remove the manual re-typing, not the human judgment.

Group routing fails closed. An unmapped WhatsApp group is refused outbound entirely. The failure mode of "wrong client sees another client's negotiated price" is worse than the system doing nothing, so the default is nothing.

The incident, fixed as a protocol. A manual backup run while the scratch company was open crashed Tally. Production data was not lost, but the failure changed the operating rules: the backup script now refuses to run while Tally is open, the first write goes to a scratch company, and the cloud can only reach the office through an agent that dials outward. The bug was closed at the process level, not hidden behind a retry.

Six packages, boundaries drawn on trust. A parser that only produces structure. A billing core with no I/O at all. A hand-written client for Tally's XML/HTTP gateway, reverse-engineered since no official API exists. A Fastify + SQLite backend holding the job queue, audit log, and approval dashboard. A Bun-compiled Windows agent - the only process allowed anywhere near the real Tally install - polling outbound-only. A WhatsApp integration scoped to a per-client group whitelist. Each package can only do the one thing its position in the pipeline requires.

What it does that manual entry can't

  • The same price, every time. Each customer's rates and schemes come from fixed rules, not from whoever is typing that day. A "23+1" is never missed or doubled.
  • Prices never reach the wrong customer. Orders from an unrecognised WhatsApp group are refused outright, so one client never sees another's negotiated rates.
  • A person still signs off. Nothing is written into Tally or sent to a customer until someone approves the generated invoice.
  • Orders are read the moment they arrive. Late-night and weekend orders are parsed and priced straight away, waiting for approval instead of piling up.
  • Every order leaves a trail. Each step, from the original message to the Tally voucher, is logged, so any invoice can be traced back to what the customer actually asked for.

Tech

  • Anthropic SDK (Claude) for order parsing
  • TypeScript billing engine inside a 382-test system suite, golden-tested against real invoices
  • Hand-written Tally Prime XML/HTTP gateway client (fast-xml-parser, pdfkit)
  • Fastify + SQLite for the job queue, audit log, and approval dashboard
  • Bun-compiled Windows agent for the Tally-adjacent machine
  • WhatsApp integration with a per-client group whitelist
  • pnpm monorepo, deployed on Railway

What I learned

The interesting design decisions in this project aren't about the LLM at all - they're about where to put the walls around it. An LLM that's good at reading messy human text is not a reason to let it anywhere near a number that ends up on an invoice. The system that survived contact with a real client's office PC is the one that draws that line early and doesn't move it under pressure to ship faster.