A full-stack restaurant point-of-sale system:
- Backend: C# ASP.NET Core 9 Web API, Dapper, SQL Server, JWT auth, Serilog, Swagger
- Frontend: React + TypeScript + Vite, React Query, React Router
ResturantPOS/
RestaurantPOS.sln
database/
schema.sql -- run first on a fresh database, creates everything
seed.sql -- run second, sample categories/menu/tables/payment methods/permissions
add_*.sql -- historical migrations for upgrading a database created before
these were folded into schema.sql/seed.sql (see note below)
src/
RestaurantPOS.Domain -- entities
RestaurantPOS.Application -- DTOs, repository interfaces, billing calculator, screen keys
RestaurantPOS.Infrastructure -- Dapper repositories, JWT service
RestaurantPOS.Api -- controllers, Program.cs, appsettings.json, screen-based authorization
frontend/
src/
api/ -- axios client
types/ -- shared TS interfaces
state/ -- auth context, restaurant/currency context
components/-- shared UI + layout
pages/ -- Tables, Order Taking, Kitchen Display, Billing, Shift, Reports, Menu Setup,
Tables & Sections, Users, Permissions, Audit Log, receipt/KOT print views
Note on
database/add_*.sqlfiles: if you're setting up a brand-new database, you only needschema.sqlandseed.sql— everything from these incremental scripts has already been folded into them. Theadd_*.sqlscripts are only needed if you have an existing database created before a given feature was added and want to upgrade it in place instead of starting fresh.
- .NET 9 SDK
- SQL Server (Express/Developer/LocalDB all work) reachable at
localhost - Node.js 18+ and npm
sqlcmd -S localhost -i database\schema.sql
sqlcmd -S localhost -i database\seed.sqlThe seed script inserts a placeholder password hash for the admin user that
will not verify. Generate a real BCrypt hash before logging in — easiest way:
cd src\RestaurantPOS.Api
dotnet runThen, with the API running, open Swagger (check the console output for the actual port —
typically https://localhost:62640/swagger) and hash a password once via a scratch endpoint, or
simply update the row directly:
UPDATE Users SET PasswordHash = '<bcrypt hash>' WHERE Username = 'admin';You can generate a BCrypt hash from PowerShell with a one-off C# script, or add a
temporary /api/auth/bootstrap endpoint that calls BCrypt.Net.BCrypt.HashPassword("Admin@123")
and prints it, then remove it once you have the hash. Once you can log in as admin
(Administrator role), you can create all further staff accounts from the Users screen —
no more direct SQL needed after this first bootstrap.
cd src\RestaurantPOS.Api
dotnet restore
dotnet build
dotnet run- Update
appsettings.json→ConnectionStrings:RestaurantPOSif your SQL Server instance isn't the defaultlocalhosttrusted-connection setup. - Set a real JWT signing secret via user-secrets — do not put it in
appsettings.json, since that file is committed to source control:dotnet user-secrets set "Jwt:Key" "<a long random string, 32+ chars>"
appsettings.jsonships with an obvious placeholder (CHANGE_THIS_TO_A_LONG_RANDOM_SECRET...) precisely so a real secret never accidentally gets committed. Anyone else running this (a teammate, a different machine) needs to set their own. - Swagger UI is enabled in the Development environment at
/swagger. - CORS is configured for
http://localhost:5173(the Vite dev server) — updateCors:AllowedOriginsif you serve the frontend elsewhere.
cd frontend
npm install
npm run devVisit http://localhost:5173. The API base URL is hardcoded to
https://localhost:62640/api in frontend/src/api/client.ts — update it if your API
runs on a different port (check the dotnet run console output, or launchSettings.json).
- JWT login, role claims (Administrator/Manager/Cashier/Waiter/Kitchen Staff)
- Dynamic, admin-configurable role permissions — Billing, Shift, Reports, Menu Setup, and
Tables & Sections access can be granted per role from the Permissions screen, enforced on
both the frontend (nav/routes) and the backend (real
403s, not just hidden UI). Administrator always has full access, hardcoded; Users management, Permissions, and Audit Log are always Administrator-only and are not part of the configurable grid, to prevent privilege escalation. - User management — create/disable/reset-password for staff accounts from the Users screen.
- Dining tables with live status (Available/Occupied/Reserved/Cleaning) — a table only becomes Occupied once an order actually has an item in it (not merely from opening it), and frees back up if emptied out again before anything is sent to the kitchen.
- Admin-managed floors/sections and tables — add a new section or table without touching SQL.
- Categories + menu items with variants and modifier groups, including required/single-choice option groups (e.g. a required "Spice Level" on curries, absent on breads).
- Custom order items (ticket-only, never touch the menu).
- Order taking → send to kitchen → Kitchen Display → status flow.
- Seat assignment + split-by-seat billing — assign items to a seat number, then bill each seat separately with its own receipt; the table stays occupied until every seat is settled.
- Billing: server-computed tax, including optional compound tax components (e.g. India's CGST + SGST shown as separate lines instead of one flat rate — purely additive, existing flat-rate categories are unaffected), discount with reason, split payments across multiple methods, and customer search/attach (with quick-create for a new customer).
- Bill voiding (Administrator/Manager only) — reopens the affected order/items and excludes the bill from all sales reporting, with a full audit trail.
- Printed receipts and kitchen order tickets, via the browser's print dialog — works with printers registered as a normal Windows printer; this is not raw ESC/POS network printing.
- Shift open/close with correct cash reconciliation (expected cash = opening float + cash sales actually taken during the shift), a live in-shift sales/payment-method breakdown, and full shift history with per-shift drill-down.
- Reports: date-range and shift filtering, gross sales, tax collected, payment-method breakdown, top items (scoped to actually-billed orders, not abandoned/unpaid ones).
- Audit log of sensitive actions — discounts, voids, user/permission changes, shift open/close — Administrator only.
- Restaurant settings (name, address, GST/registration number, currency symbol) — no currency symbol is hardcoded anywhere in the UI or on receipts.
- Menu setup screen (categories, items, tax categories and their optional compound components, modifier/option groups).
- Real ESC/POS or network printer protocol integration — printing goes through the OS print dialog (works with most registered thermal receipt printers), not a raw printer socket.
- Real-time push (SignalR) — the frontend polls instead.
- Inventory, multi-branch, offline mode, reservation integration.
- Refresh token rotation — a
RefreshTokenstable exists in the schema but isn't wired up. - Split-by-seat apportions a flat (currency-amount) order-level discount across seats by each seat's subtotal share; a percent discount applies the same to every seat regardless of split.
See PRODUCTION_READINESS.md for details, but in short:
- No automated tests or CI pipeline — every change so far has been verified by hand.
- No backup/disaster-recovery process for the database.
- Uses a self-signed dev HTTPS certificate — needs a real TLS cert (or a reverse proxy) for a real network.
- Single-restaurant by design —
RestaurantIdis hardcoded to1in a few places. - No payment gateway integration — payments are recorded manually, not processed through a card/gateway API.