The problem
TTMI’s internal ERP is used across 50 stores, ~500 employees and 4 brands. Finance staff enter daily sales figures in a grid that behaves like a spreadsheet: they paste blocks, drag-select and move around with the keyboard. I built the frontend for this area and worked with the backend on the API contract.
Four things could go wrong.
Totals had to be exact. JavaScript numbers are floating point, so they cannot represent many amounts exactly and they lose precision on very large sums.
An empty cell had to mean “not entered yet”. If the UI treated it as zero, dashboards would show revenue figures that nobody had typed in.
Several people could edit the same period. A browser tab opened an hour ago must not silently overwrite newer data.
Exports to Excel had their own issues. One formatting rule applied to whole columns made whole-number amounts show a dangling decimal separator and risked turning identifiers such as staff codes into numbers. A company-wide monthly export also needed per-employee detail, and one request per employee was slow.
What I did
Integer money, summed with BigInt
Amounts travel and live in the client as integer strings, and totals are summed with BigInt. Thousands separators exist only on screen. This keeps totals exact well beyond the range where floating point breaks. The trade-off is conversion code at every boundary: input, paste, display and payload.
Blank is its own state
“Not entered” stays distinct from zero through display, paste, the request payload and what the API stores. Dashboards show entered days against missing days, so a gap never looks like a slow day. Every aggregate needed a separate path for blanks, and the product owners had to agree on what an empty cell means before I wrote any arithmetic.
Revision checks with a visible conflict
Each save sends the revision it was based on, and the API rejects stale writes. On the client, the save shows a before-and-after total for confirmation, blocks double submits, and keeps the local draft if the save fails or conflicts. Users occasionally have to reload and re-apply their changes. I chose that over last-write-wins, where one person’s figures vanish without anyone noticing.
One number format helper for every export
I wrote a shared export helper that picks the cell format from the actual value, whole or fractional, and applies it only to measurable fields so identifiers stay as text. Every export module now routes through it. It is less declarative than configuring each column, and it only works if every exporter uses it, which I enforced by moving the existing exports onto it.
Batched exports that are all or nothing
For the company-wide export, I replaced per-employee requests with compact batch requests fetched in small parallel waves. If any batch fails, the export stops and shows an error. A single failing batch blocks the whole file. I accepted that because a missing file is safer than a payroll sheet with zeros where data failed to load.
Result
The finance screens and exports are in production and used by the finance team. Totals are exact, blanks are never counted as zero, and two people editing the same period get a visible conflict.
I verified exported workbooks by reading them back programmatically and comparing them with the on-screen grid, with Vitest covering the money and export helpers. One formatting rule now governs every export, and the company-wide export needs a handful of batch round trips. I did not measure its timing, so I make no speed claim.
What I’d do differently
I would settle the meaning of an empty cell with finance on day one, before any grid code existed, because changing it later touched every aggregate. I would also time the batched export before and after, so its improvement could be stated with a number.