Bài toán
Một quán đồ chiên nhỏ bán tại quầy, trên GrabFood và ShopeeFood. Mỗi kênh thu một mức hoa hồng khác nhau, các nền tảng còn tính thêm VAT và phí quảng cáo, trong khi giá nguyên liệu thay đổi theo từng lần nhập hàng. Chủ quán biết doanh thu mỗi ngày, nhưng không biết lợi nhuận thực tế, cũng không biết món nào đang bán lỗ.
Yumi Kitchen là một ERP gọn nhẹ xây dựng riêng cho quán: thực đơn, công thức, tồn kho, đơn hàng trên mọi kênh, ca bán hàng và chi phí, tổng hợp thành một báo cáo lãi lỗ chính xác. Tôi phát triển một mình trong sáu tuần. Chủ quán đã chuyển hẳn từ Excel sang, cùng toàn bộ số liệu cũ, và vận hành quán trên hệ thống này cho đến khi ngừng kinh doanh.
Phạm vi
- Thực đơn, mỗi món có công thức chi tiết đến từng gram.
- Nguyên liệu, nhà cung cấp, phiếu nhập; tồn kho tự cập nhật theo từng đơn.
- Đơn hàng từ quầy và hai ứng dụng giao đồ ăn, cùng trang đặt món cho khách với thông báo đơn mới qua Telegram.
- Ca bán hàng và tiền mặt, chi phí, import/export Excel cho mọi loại dữ liệu.
Khoảng 30k dòng TypeScript, 48 API endpoint và 20 bảng PostgreSQL.
Kiến trúc
Một ứng dụng Next.js 16: trang render ở server, logic nghiệp vụ nằm trong các route handler dưới /api. Không dùng ORM mà viết SQL thuần qua pg, kèm một migration runner nhỏ do tôi tự viết. Ứng dụng chạy trên Heroku.
Next.js 16 (App Router)
├── /menu, /checkout, /order khách đặt món
├── /admin/* trang quản trị của chủ quán
└── /api/* route handlers ──► raw SQL (pg) ──► PostgreSQL
giá vốn · tồn kho · quy tắc P&L
Các quyết định kỹ thuật chính
Tồn kho trừ theo công thức, giá vốn chốt ngay lúc bán
Mỗi đơn hàng tự trừ nguyên liệu theo công thức và ghi nhận giá vốn tại đúng thời điểm bán. Nhờ vậy, giá vốn của tuần trước vẫn chính xác dù nhà cung cấp tăng giá sau đó, và khi hủy đơn, kho được hoàn lại đúng phần đã trừ.
Tỷ lệ và biên lợi nhuận tính bằng số nguyên
Các tỷ lệ được lưu dưới dạng basis point số nguyên (5500 = 55,00%), và mọi phép làm tròn đều đi qua một hàm duy nhất, nên sai số dấu phẩy động không lọt vào con số lợi nhuận.
Tính đúng theo cách các nền tảng thu phí
Báo cáo lãi lỗ bám sát cách từng nền tảng thanh toán cho quán: hoa hồng riêng của từng kênh, VAT 8% trên hoa hồng GrabFood và phí quảng cáo. Nếu nền tảng thu khác với mức hệ thống tính, chủ quán có thể nhập số liệu thực tế cho từng đơn.
Thiết kế cho người chủ quen làm việc với Excel
Khi chốt ca, hệ thống đối chiếu số tiền mặt theo sổ sách với số tiền kiểm đếm thực tế và ghi nhận phần chênh lệch. Các file Excel mẫu cho phép chủ quán đưa dữ liệu sẵn có vào hệ thống mà không cần nhập lại thủ công.
Test kiểm tra con số, không chỉ status code
Playwright chạy trọn một vòng vận hành của quán (nhập hàng, bán trên mọi kênh, hủy đơn, chốt ca) và kiểm tra tồn kho, giá vốn, doanh thu, biên lợi nhuận sau từng bước. Test chỉ pass khi các con số đều đúng.
Những điều học được từ quán thật
- Mỗi nền tảng thu phí theo cách riêng. Chỉ GrabFood tính VAT trên phí, nên chỉ đơn GrabFood được cộng VAT.
- Giảm giá có hai dạng, số tiền cố định hoặc phần trăm, và đơn hàng hỗ trợ cả hai.
- Ngày phải tính theo giờ của quán. Khi còn tính theo UTC, các khoản chi định kỳ bị ghi sớm một ngày. Hiện toàn bộ hệ thống chạy theo giờ Việt Nam, và một migration đã sửa dữ liệu cũ mà không ảnh hưởng đến những bản ghi chủ quán đã chỉnh sửa thủ công.
- Chủ quán nghĩ theo món ăn, không theo bảng dữ liệu, nên có thể nhân bản món và kéo thả để sắp xếp lại các dòng trong công thức.
Những gì tôi sẽ làm tiếp
Chuyển admin token từ localStorage sang cookie httpOnly, không cho ứng dụng khởi động khi thiếu session secret, và chuyển rate limiter từ bộ nhớ sang PostgreSQL trước khi mở rộng lên nhiều instance.