A multi-tenant ERP for three cutting-tool manufacturers Zoho could not hold.
Three cutting-tool manufacturers in Mumbai outgrew Zoho. We built CFX, a custom multi-tenant ERP on AWS with a dedicated Android sales app and Gemini AI integration.

Paper counts replaced by one sticker per bag. Exact stock by rack and by weight, low-stock alerts on WhatsApp twice a day, and purchase orders raised from the same screen. Desktop and handset, one codebase, signal optional.

Lucky Traders already took their orders through software. We built that part first: a customer portal that moved 573 trade accounts off WhatsApp and phone calls and onto one structured order flow. The godown behind it was another story. Stock was counted on paper. Ask how many bags of M12x24 were standing, and the honest answer was a walk to the rack.
The Stock app closes that gap. It is a desktop and Android application, built on Tauri, that signs in with the same accounts and passes the same permission guard as the back office. It keeps no database of its own. It is a window into the one the business already runs on.
What does a fastener godown actually hold? Not "stock". Bags. Bags of hex bolts, self-drilling screws, anchor fasteners and washers, in GP and SS 304, standing on numbered racks. Most weigh 50 kg. Some weigh 48.5 or 49. The odd one weighs 19.
That last detail is where paper fails, and it fails quietly. Four bags is not 200 kg when one of them is a 19 kg bag. A count on a sheet is a snapshot, yet the godown is a moving picture: it knows how many bags were there the morning somebody wrote it, and nothing about which bag left at noon, which rack it came from, or whether the figure a customer is being quoted over the phone is still true.
The founder wanted three things. An exact answer to "how much is on the shelf, and where". Fewer steps between a lorry arriving and its stock being usable. And a reorder that did not depend on somebody noticing an empty rack.
The constraints came from the godown itself. Signal is patchy among packed racks. The office machine has Tally in front of it most of the day. And the people doing the work are carrying bags, not sitting at desks.
The decision the whole system turns on: a bag is a record, not a share of a total. Five bags of M12x24 across three racks are five records, each with its own code and its own printed sticker. Stock is a count of the bags still standing, so it is exact by construction rather than by arithmetic that can drift.
Everything else follows from that. Scanning a bag out needs no quantity, because the sticker is the bag. Scanning the same sticker twice does nothing, because the database will not dispatch a bag that has already left. Two identical bags on the same rack still scan out separately, because they carry different codes.
Receiving a delivery is one screen that never leaves. Type thirty, and thirty boxes open for what each bag weighs, defaulted to the item's pack size. "Set all to" fills them in one go, because most bags off a lorry are the same and the exceptions are the point. Stickers print at the end on 2 inch square thermal labels, carrying the item name, the weight and the rack, with the rack set largest: the person sticking them is matching the rack and nothing else.
Every movement carries two dates: when the stock physically moved, and when somebody typed it in. A lorry unloaded on Saturday and booked on Monday reports as Saturday.
Open the screen and scan. No quantity, no confirm button, repeats ignored. On Windows the app reads a USB barcode gun, and it keeps listening while it sits minimised behind Tally, so dispatching a bag never means switching windows. On Android it scans with the camera.
The app is punctilious about the difference between blank and zero. A bag nobody has weighed is shown as not weighed, never as zero and never as an assumed 50 kg. Totals read "12 bags, 587.5 kg, 4 not weighed". A screen that quietly guessed would be confidently wrong, which is worse than being visibly incomplete.
Any item can carry a low-stock threshold. Twice a day, at 07:00 and 15:00, whatever has newly fallen below it goes out by email and WhatsApp, grouped by vendor. The Reorder screen starts from that same list, every line ticked with a proposed quantity. Untick what does not belong, raise the order, and the PDF goes to the vendor by email or WhatsApp.
An item already on order is not proposed again. That one rule is how the same bolt stops being ordered twice, from two suppliers, on two afternoons.
Each vendor carries a lead time. An order still open past it is flagged on screen and chased in a daily email until every line is booked in. Receiving stock against an order moves the order with it, from sent to part received to received, and a fully delivered line is struck through rather than dropped, so the order still reads true against the vendor's delivery note.
A count sheet still goes round the godown, but now the system prints it: every item in rack order, the system figure beside a blank column. It comes back as an upload, validated in full before a single figure is written. A blank means "not counted", so an auditor who never reached a rack cannot empty it by accident.
The owner is not always beside either machine. A single web screen, behind the same login, answers the one question that matters on a phone call: search the item, filter by rack, read the figure. It cannot book a single bag in or out, by design.
A lorry pulls in with thirty bags of hex bolts. The picker opens Stock In and finds the item even with a misspelt search, because every search runs through a forgiving matcher. Thirty bags, set all to 50, correct the two that came in lighter, choose the rack, print. Thirty stickers come off the printer while the bags are still on the floor.
If the godown has no signal at that moment, nothing changes. The sticker codes are minted on the device, the delivery waits in a queue, and the status line under the header says how many entries are waiting to send. When the connection returns, they go.
An order is packed. Each bag is scanned on its way out, and the count drops by exactly the bags that left, from exactly the racks they left.
At three in the afternoon the low-stock message arrives on WhatsApp, already sorted by supplier. The purchase orders go out from the same list.
That evening a customer calls the owner, who is nowhere near the godown. Four bags of M12x24, rack M6, each with its weight. The answer takes about as long as the question.
The godown stopped being counted on paper and started being counted by sticker. The questions that used to need a walk (how much, where, since when) now have an answer on a screen, and the answer is exact because every bag is accounted for individually.
Entry happens where the work happens. On a handset at the rack, with or without signal, and on the office machine without anyone leaving Tally.
Reordering moved from memory to a list. What is low arrives on WhatsApp twice a day, sorted by supplier, and late orders chase themselves.
And the business now runs on one database. A product in the customer portal, a bag on a rack and a line on a purchase order are records in the same PostgreSQL, under the same accounts and the same permissions.
The app is built on Tauri 2: a React 19 and TypeScript front end inside a Rust shell. One codebase ships a signed Windows installer and an Android APK. It talks to the ERP, a PHP 8.5 application on FrankenPHP over PostgreSQL 18, through a bearer-token API. The device token lives in Windows Credential Manager, not in browser storage, and can be revoked per device.
Tauri earned its place here for three reasons specific to this app. It had to run on Android handsets as well as Windows desktops, from one codebase. It needed native reach on Windows: Raw Input, so the barcode gun is heard while the window sits behind Tally, and a serial port for guns configured that way. And it needed signed self-updates, so a fix reaches every desktop without anyone visiting the godown. Rust handles the parts a browser cannot reach. The screens stay ordinary web code.
Offline is the default, not a mode. Every write is queued on the device first, even with a live connection, and flushed every twenty seconds and on reconnect. Each queued entry carries an identifier minted on the device, so a request that committed and then timed out is recognised on retry instead of being applied twice. The full item list is held locally and refreshed every five minutes, which is why search is instant.
Releases are published to the firm's own object storage, verified on readback, and served through short-lived signed links. The desktop updater refuses any build not signed with the release key.
The app went live on 15-Aug-2026. In its first five weeks, changes landed on eighteen separate days, each shaped by what the godown reported back.
A ledger kept bag by bag is the foundation for the questions a founder actually asks of stock. Which items are turning. Which have been sitting. Which vendors deliver late, and by how much. The data to answer each of them exactly now exists, because nothing in it was estimated. Every bag counts.
Three cutting-tool manufacturers in Mumbai outgrew Zoho. We built CFX, a custom multi-tenant ERP on AWS with a dedicated Android sales app and Gemini AI integration.
AK Suppliers & Distributors ran a WooCommerce storefront beside a custom ERP for three years. In 2026 we retired WooCommerce, rebuilt the storefront as a custom Next.js application on the ERP's own PostgreSQL, added a live PayU checkout, and wired Tally in as the single source of truth for products, pricing, and invoices.
A highly custom ERP with AI capabilities to remove manual workflows from the operation. Modules for customer management, HR, online portal automation, and custom quotations.
Engagements begin with a conversation. Not a pitch. A direct discussion about your operations and the visibility you are missing.