TLAHuynh Van Gioi Em

Yumi Kitchen

Operations and profit system for a real food business selling in store and on GrabFood and ShopeeFood, replacing the owner's spreadsheets: recipe-driven inventory, per-channel sales, shift reconciliation and profit reporting.

Role
Solo full-stack developer
Period
Apr 2026 – May 2026
Stack
Next.js 16React 19TypeScriptPostgreSQL (raw SQL)ExcelJSTailwind CSSPlaywrightHeroku

At a glance

  • 6weeks to build
  • 3sales channels in one profit report
  • 96end-to-end tests, including a full shop scenario
  • Built it in six weeks for a real shop that ran on spreadsheets, and carried its past figures over from Excel so the profit history stayed whole from day one.
  • Turned how the business actually works into rules: every sale deducts ingredients by recipe and records its cost at that moment, so the owner sees true profit per dish and per channel.
  • Modelled the delivery platforms' real fees, including 8% VAT on GrabFood commission and ad fees, so the profit shown matches what lands in the bank account.
  • Gave the owner daily control: cash and bank reconciliation per shift, stocktakes with an undo window, capital tracking, and Excel import and export for every kind of record.
  • Tested the business math, not just the screens: an end-to-end scenario opens a shop from zero and checks stock, cost, revenue and margin after every step.

The problem

A small fried-food stall sells at the counter, on GrabFood and on ShopeeFood. Each channel takes a different commission, the apps add VAT and ad fees, and ingredient prices change with every delivery. The owner knew how much money came in, but not how much of it was profit, or which dish was losing money.

Yumi Kitchen is a small ERP built for that owner: menu, recipes, stock, orders on every channel, cash shifts and expenses, turned into an accurate profit-and-loss report. I built it alone in six weeks, and the owner moved over from Excel with their past figures and ran the stall on it until they stopped selling.

Yumi Kitchen P&L page for October with profit for the period, break-even progress, and a breakdown from revenue to net revenue, contribution and overhead
P&L: from gross revenue to profit, with break-even progress. Demo data.

Scope

  • A menu with a recipe for every dish, down to the gram.
  • Ingredients, suppliers and purchases; stock updates itself with every order.
  • Orders from the counter and both apps, plus a customer ordering page with Telegram alerts.
  • Cash shifts, expenses, and Excel import and export for everything.

About 30K lines of TypeScript, 48 API endpoints and 20 PostgreSQL tables.

Architecture

One Next.js 16 app: pages render on the server and the business logic lives in route handlers under /api. No ORM: raw SQL over pg, with a small migration runner I wrote. It runs on Heroku.

Next.js 16 (App Router)
├── /menu, /checkout, /order     customer ordering
├── /admin/*                     owner's back office
└── /api/*  route handlers  ──►  raw SQL (pg)  ──►  PostgreSQL
                                  costing · stock · P&L rules
Yumi Kitchen admin overview with today's orders, revenue, completion rate, stock alerts and the list of recent orders by channel
Overview: today's orders, revenue and stock alerts, with each order's channel and net amount. Demo data.

Key technical decisions

Recipes drive stock, and cost is frozen at the moment of sale

Each order deducts its ingredients by recipe and records what they cost at that moment. So last week's cost of goods stays right after a supplier raises prices, and cancelling an order puts back exactly what it took.

Integer math for rates and margins

Rates are stored as integer basis points (5500 = 55.00%), and all rounding happens in one helper, so no floating-point error ends up in the profit figure.

Real platform rules, not a simplified model

The P&L follows how each app actually pays out: its own commission, 8% VAT on GrabFood's commission, and ad fees. If an app charged a different amount, the owner can enter the real figure on the order.

Yumi Kitchen menu page listing dishes with sale price, recipe cost and margin percentage colour-coded by threshold
Menu: each dish's cost and margin, recomputed when an ingredient's price changes. Demo data.

Built for an owner who lives in spreadsheets

Closing a shift compares the cash the system expects with the cash counted and records the difference. Excel templates let the owner bring existing data in without retyping it.

Yumi Kitchen ingredient inventory with total stock value, items below threshold and a table of stock, threshold, latest purchase price and value
Inventory: stock levels, alert thresholds and value at the latest purchase price. Demo data.

Tests that check the money, not the status code

Playwright runs a whole store lifecycle (buy stock, sell on every channel, cancel orders, close a shift) and checks stock, cost, revenue and margin after each step. A test passes only when the numbers are right.

What the real shop taught me

  • Each app charges differently. Only GrabFood adds VAT on its fees, so only Grab orders get it.
  • Discounts come in two shapes, a fixed amount or a percentage, and orders accept both.
  • Dates follow the shop's day. While dates were in UTC, recurring expenses were saved one day early. Everything now runs on Vietnam time, and a migration fixed the old rows without touching the ones the owner had edited.
  • The owner thinks in dishes, not tables, so dishes can be duplicated and recipe lines reordered by drag and drop.

What I would change next

Move the admin token from localStorage to an httpOnly cookie, refuse to start when the session secret is missing, and move the in-memory rate limiter to PostgreSQL before running more than one instance.

← All projects