POS API - Cashier, Inventory & Reporting
A Go and MySQL REST API behind a food-and-beverage point-of-sale app: orders and payments, menu with variants and modifiers, recipe-based inventory with automatic stock deduction, expenses, and sales reporting with optional AI product analysis.
Overview
This is the backend of a point-of-sale system used by a cashier app and an owner app. It is written in Go with Fiber v2, uses GORM on MySQL, and authenticates users with JWT (HS256, 24-hour tokens) and bcrypt-hashed passwords. It exposes about 80 endpoints in 15 route groups, and a Flutter client consumes it. The code is organised in clear layers: routes, handlers, helpers, GORM models, and DTOs. Orders are the core. A cashier opens a bill for a table (dine-in or take-away) and can keep adding items to it. Every change to a bill runs in one database transaction: the invoice number is generated per day (prefix, date, running sequence) and re-checked for uniqueness, each product row is locked with SELECT ... FOR UPDATE, and finished-goods stock is checked when a product manages its own stock. If the same bill is edited again, the previous items are rolled back first (stock returned, old items removed) and rebuilt, so totals and stock never drift. The server then recalculates the bill: subtotal, promo (fixed or percentage, with minimum purchase and maximum discount), tax from the company profile, and rounding to the nearest 100 rupiah. Cost of goods (HPP) and clean profit are stored per item and per order at the moment of sale. Inventory is recipe-based. A menu item can have a recipe of raw materials in any unit, and the API converts between units using each unit's conversion factor (for example millilitres in the recipe against litres in the warehouse). Variants and modifiers (extra toppings and similar add-ons) are shared master records that can also consume raw materials. Raw stock is deducted with an atomic conditional UPDATE (stock = stock - x WHERE stock >= x), so two cashiers paying at the same moment cannot push stock below zero, and every deduction is written to a stock-movement ledger with the balance after and the cost at that time. Cancelling an order is owner-only and restores finished-goods stock, recipe ingredients, and modifier ingredients. Roles and visibility: users are either owner or cashier. Cost price and HPP are only returned to the owner. Order history can reveal financial figures to a cashier only with the owner's access PIN. Cancelling orders and opening the product analysis are owner-only. Reporting and insight: the statistics endpoints aggregate revenue, discounts, tax, HPP, and profit directly in SQL for any date range, and break results down by product, hour and day trends, payment method, dining option, and employee, with a product-sales export. An owner-only product analysis endpoint lists top sellers, slow movers, stock risks, and active promos. If an AI key is configured, it adds a narrative insight from the Gemini API through a small Client interface. The AI part is optional: any failure is swallowed and the raw statistics are always returned. Operations: every request is written to a daily log file with the user ID, and important actions (login, create, pay, cancel, and so on) are stored in a sys_logs audit table with username, role, and IP address. Server credentials are read from a config file whose database user, password, and name are AES-encrypted. Uploaded images are served as static files. On graceful shutdown the server can run a database backup depending on the time of day. Schema changes are managed with golang-migrate through a small CLI that is run manually and never on startup. The gallery shows the architecture, the order checkout flow, and the database in two ERDs (catalog and inventory, then sales, cash, and finance). The last image is a real handler from the codebase. The repository does not contain automated tests yet.
Impact
Gives the cashier and owner apps one backend for sales, stock, expenses, and reporting. Ingredient stock is deducted automatically from recipes at order time and every movement is traceable, while owner-only controls protect cost data and cancellations.
What I Learned
I designed and built the backend on my own. It taught me to keep money and stock consistent with transactions, row locks, and atomic updates; to model recipes with unit conversion; to separate owner and cashier visibility; and to add audit logging, safe migrations, and an optional AI feature that can never break the core API.
Key Features
- JWT authentication, bcrypt passwords, and owner/cashier role-based access
- Open bills per table with dine-in or take-away, invoice numbers, and add-items-later editing
- Transactional checkout: row locks, atomic stock deduction, promo, tax, and rounding in one transaction
- Menu with categories, variants, modifiers, and kitchen/label print flags
- Recipe-based inventory with unit conversion and automatic raw-material deduction
- Stock-movement ledger with balance and cost at the time of each change
- Cash and non-cash payment, owner-only cancellation with full stock restore
- Promo codes (fixed or percentage), expense tracking with categories and proof images
- Statistics by date range, product export, and optional Gemini-based product analysis
- Audit log table, daily access logs, encrypted config credentials, and golang-migrate migrations
Gallery
