BLISSORA Retail POS
A modern point-of-sale, inventory, returns, expense, and reporting system
for cosmetics and beauty-product retailers.
- Overview
- Platform Support
- Features
- Role-Based Access
- Technology Stack
- Architecture
- Getting Started
- Environment Variables
- Demo Accounts
- Available Scripts
- Database Design
- Core Business Workflows
- Application Routes
- Receipt and Thermal-Printer Setup
- Deployment
- Project Structure
- Quality Checks
- Security Notes
- Troubleshooting
- Roadmap
- Contributing
POS Cosmetics is a production-oriented retail management application built for small and growing cosmetics shops. It combines daily cashier operations and back-office administration in one desktop-first web application.
The system supports product and barcode management, stock tracking, sales, partial returns, expenses, user accounts, shop settings, receipts, and filterable management reports. Historical sale and return records keep product and price snapshots so later product changes do not alter past transactions.
Important
POS Cosmetics is designed for fixed desktop and laptop workstations used inside a cosmetics shop.
The current version is optimized for cashier and administrative workflows on larger screens and is not fully responsive for mobile phones or tablets.
For the intended experience, use a modern desktop browser. Mobile responsiveness is outside the scope of this desktop release.
- Today's sales value and completed-sale count
- Active-product and low-stock summaries
- Today's operating expenses
- Recent transactions and returns
- A 30-day sales-versus-returns chart
- Role-aware dashboard data for Admin and Cashier users
- Search products by name or barcode
- USB barcode-scanner input flow
- Add products directly from the stock browser
- Change quantities and apply item-level discounts
- Apply a cart-level discount
- Accept cash and card payments
- Calculate the paid amount and balance
- Add optional transaction notes
- Complete the sale and reduce stock in one database transaction
- Reprint recently completed receipts
- Create and edit products
- Unique barcode validation
- Automatic barcode and internal-code generation
- Buying price, selling price, category, and stock-threshold management
- Active and inactive product states
- Low-stock detection using each product's configured limit
- Manual stock-in and stock-out adjustments
- Stock-movement history with before-and-after quantities
- Safe product deletion: products with transaction history are deactivated instead of removing their historical references
- Find the original sale by invoice number
- Process full or partial item returns
- Prevent returns above the remaining returnable quantity
- Calculate refund amounts from historical sale values
- Restore stock and record the stock movement transactionally
- Keep a searchable return history
- Create, edit, filter, and delete expense records
- Organize expenses by category
- Track who created each expense
- Filter records by date range
- Display totals for the selected period
- Daily sales
- Weekly sales
- Monthly sales
- Gross product margin based on historical buying-price snapshots
- Best-selling products
- Current stock
- Expenses
- Sales by cashier
- Date-range and optional cashier filtering
- PDF and Excel export
- Separate Admin and Cashier accounts
- Secure password hashing with bcrypt
- Activate or deactivate user access
- Configure shop name, address, phone number, and receipt text
- Configure one to three receipt copies
- Sri Lankan Rupee formatting (
LKR/Rs.)
- Browser-printable receipt layout
- Direct ESC/POS thermal printing through QZ Tray
- Optional receipt logo and invoice barcode
- Configurable printer name, encoding, paper feed, and automatic cut
- Browser-print fallback when direct thermal printing is unavailable
| Module | Admin | Cashier |
|---|---|---|
| Dashboard | ✅ | ✅ |
| Sales / POS | ✅ | ✅ |
| Returns | ✅ | ✅ |
| Products | ✅ | — |
| Inventory | ✅ | — |
| Reports and exports | ✅ | — |
| Expenses | ✅ | — |
| Users | ✅ | — |
| Settings | ✅ | — |
Access is enforced in navigation, request middleware, protected pages, and sensitive API routes.
| Area | Technology |
|---|---|
| Framework | Next.js 16.2.4 App Router |
| UI runtime | React 19.2.4 |
| Language | TypeScript 5 with strict type checking |
| Styling | Tailwind CSS 4 |
| UI components | Radix UI primitives and reusable shadcn-style components |
| Icons | Lucide React |
| Forms | React Hook Form |
| Validation | Zod |
| Database | PostgreSQL |
| Hosted database | Neon-compatible Prisma adapter |
| ORM | Prisma 7.7 |
| Authentication | Signed JWT session cookie and bcrypt password hashing |
| Charts | Recharts |
| PDF export | jsPDF and jspdf-autotable |
| Excel export | SheetJS (xlsx) |
| Receipt barcode | JsBarcode |
| Thermal printing | QZ Tray and raw ESC/POS commands |
| Date utilities | date-fns |
flowchart TB
UI["Next.js App Router UI"]
Entry["Server Actions and API Routes"]
Services["Services and Zod Validation"]
Prisma["Prisma ORM"]
Database[("PostgreSQL / Neon")]
QZ["QZ Tray"]
Printer["ESC/POS Thermal Printer"]
UI --> Entry
Entry --> Services
Services --> Prisma
Prisma --> Database
UI --> QZ
QZ --> Printer
The UI uses Server Components for data-heavy pages and Client Components for interactive workflows such as checkout, forms, returns, and printing.
Server actions and route handlers authorize requests before calling the service layer. The service layer validates inputs and manages database transactions.
- Node.js
20.9.0or newer - npm
- A PostgreSQL database, such as Neon
- Git
git clone https://github.com/prashankulathunga/POS-Cosmatics.git
cd POS-CosmaticsCreate a .env file in the project root before installing dependencies.
DATABASE_URL="postgresql://USER:PASSWORD@HOST/DATABASE?sslmode=require"
AUTH_SECRET="replace-this-with-a-random-secret-of-at-least-32-characters"
APP_URL="http://localhost:3000"Generate a strong authentication secret with:
openssl rand -base64 48Copy the generated value into AUTH_SECRET.
npm installPrisma Client is generated automatically by the postinstall script.
npm run db:migratenpm run db:seednpm run devOpen http://localhost:3000.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string used by Prisma and the application | postgresql://... |
AUTH_SECRET |
Secret used to sign session tokens; must contain at least 32 characters | Generated random value |
APP_URL |
Exact public origin of the application | http://localhost:3000 |
| Variable | Default | Description |
|---|---|---|
QZ_PRIVATE_KEY |
— | Server-only RSA private key used to sign QZ Tray requests |
NEXT_PUBLIC_QZ_TRAY_SCRIPT_SRC |
QZ Tray 2.2.6 CDN URL | Browser script source for QZ Tray |
NEXT_PUBLIC_RECEIPT_PRINTER_NAME |
POS-80 |
Preferred installed printer name |
NEXT_PUBLIC_RECEIPT_ENABLE_LOGO |
false |
Prints the configured logo when set to true |
NEXT_PUBLIC_RECEIPT_ENABLE_BARCODE |
false |
Prints the invoice barcode when set to true |
NEXT_PUBLIC_RECEIPT_FORCE_RAW |
true |
Sends raw ESC/POS data through QZ Tray |
NEXT_PUBLIC_RECEIPT_ENCODING |
Cp437 |
Character encoding used by the printer |
NEXT_PUBLIC_RECEIPT_LOGO_PATH |
/Bls-rm.png |
Public path of the receipt logo |
Variables prefixed with NEXT_PUBLIC_ are included in the browser bundle. Never store secrets in them.
The seed script creates these development accounts:
| Role | Username | Password |
|---|---|---|
| Admin | admin |
admin123 |
| Cashier | cashier |
cashier123 |
Warning
These credentials are for local development and demonstrations only. Change both passwords immediately if you seed a shared or production database.
The seed also creates starter product categories, expense categories, sample products, opening stock movements, settings, and example expenses.
| Command | Purpose |
|---|---|
npm run dev |
Start the Next.js development server |
npm run build |
Generate Prisma Client and create a production build |
npm run start |
Start the compiled production server |
npm run lint |
Run ESLint across the project |
npm run db:generate |
Generate Prisma Client |
npm run db:migrate |
Create or apply migrations in development |
npm run db:push |
Push the schema directly without creating a migration |
npm run db:seed |
Run the idempotent starter-data seed |
npm run db:studio |
Open Prisma Studio |
Use the following command to apply committed migrations in production:
npx prisma migrate deployReserve db:push for local prototyping because it does not create migration history.
erDiagram
User ||--o{ Sale : processes
User ||--o{ Return : processes
User ||--o{ Expense : creates
User ||--o{ StockMovement : records
Category o|--o{ Product : classifies
Product o|--o{ SaleItem : appears_in
Product o|--o{ ReturnItem : appears_in
Product ||--o{ StockMovement : has
Sale ||--|{ SaleItem : contains
Sale ||--o| Payment : has
Sale ||--o{ Return : receives
Return ||--|{ ReturnItem : contains
ExpenseCategory o|--o{ Expense : classifies
- Monetary fields use fixed-precision database decimals.
SaleItemstores product-name, barcode, buying-price, and selling-price snapshots.ReturnItemstores product, cost, and refund-value snapshots.- A sale, its payment, stock deduction, and stock-movement records are saved in one Prisma transaction.
- A return, its stock restoration, and stock-movement records are also saved transactionally.
- Products with historical references are deactivated rather than permanently deleted.
- Frequently filtered fields have database indexes for product, sales, inventory, expense, and return queries.
- The cashier scans a barcode or searches for a product.
- The application verifies that the product is active and in stock.
- The cashier updates quantities, discounts, payment method, and paid amount.
- Zod validates the checkout payload.
- The server recalculates prices from database values.
- Prisma saves the sale, sale items, payment, stock changes, and stock movements in one transaction.
- The receipt can be printed or reprinted from the saved sale.
- The cashier finds the original invoice.
- The application calculates the remaining returnable quantity for each item.
- The cashier selects quantities and enters an optional reason.
- Prisma saves the return and restores stock in one transaction.
- Historical snapshot values determine the refund amount.
- An Admin selects a product.
- A positive or negative adjustment and note are entered.
- The service prevents stock from falling below zero.
- The product quantity and audit-style movement record are saved together.
| Route | Access | Purpose |
|---|---|---|
/login |
Public | Sign in |
/dashboard |
Admin, Cashier | Sales and operations overview |
/pos |
Admin, Cashier | Checkout and recent receipts |
/returns |
Admin, Cashier | Process and review returns |
/products |
Admin | Product catalog |
/inventory |
Admin | Stock levels and movements |
/expenses |
Admin | Expense management |
/reports |
Admin | Report filters and exports |
/users |
Admin | Account management |
/settings |
Admin | Shop and receipt configuration |
All application API routes require an authenticated session. Report export is restricted to Admin users.
Browser printing works without QZ Tray. Direct raw ESC/POS printing requires additional local setup:
-
Install and run QZ Tray 2.2.x on the cashier computer.
-
Connect the thermal printer and confirm its operating-system printer name.
-
Set
NEXT_PUBLIC_RECEIPT_PRINTER_NAMEif the printer is not namedPOS-80. -
Create a QZ Tray certificate and its matching RSA private key.
-
Place the public certificate at:
public/digital-certificate.txt -
Store the matching private key in
QZ_PRIVATE_KEY. For a single-line environment value, represent PEM line breaks with literal\nsequences. -
Restart the application and allow QZ Tray's connection prompt.
The public certificate and private key must be a matching pair. Never commit the private key.
The current .gitignore also excludes the public certificate, so ensure your deployment process supplies it if direct printing is required in production.
-
Create a PostgreSQL project in Neon.
-
Add these variables to the Vercel project:
DATABASE_URL="your-neon-postgresql-connection-string" AUTH_SECRET="your-random-secret-with-at-least-32-characters" APP_URL="https://your-production-domain"
-
Apply the committed database migrations:
npx prisma migrate deploy
-
Use the standard build command:
npm run build
The build script already runs
prisma generatebeforenext build. -
Deploy the application.
-
Run the seed command only if you intentionally want the starter records:
npm run db:seed
If migrations are not handled by a separate release step, the Vercel Build Command can be:
npx prisma migrate deploy && npm run buildAfter changing AUTH_SECRET, existing session cookies become invalid and users must sign in again.
.
├── app/
│ ├── (auth)/ # Login layout and page
│ ├── (dashboard)/ # Protected application pages
│ └── api/ # Authenticated route handlers
├── components/
│ ├── dashboard/ # Charts and statistic cards
│ ├── forms/ # Product, user, expense, stock, and settings forms
│ ├── layout/ # Sidebar, top bar, and dashboard shell
│ ├── pos/ # Interactive POS terminal
│ ├── receipt/ # Browser-printable receipt
│ ├── returns/ # Return-processing interface
│ └── ui/ # Reusable UI primitives
├── lib/
│ ├── actions/ # Authorized server actions
│ ├── auth/ # Session and permission helpers
│ ├── db/ # Prisma Client singleton
│ ├── services/ # Business logic and database queries
│ ├── validations/ # Zod schemas and inferred input types
│ ├── constants.ts # Navigation, roles, currencies, and report types
│ ├── env.ts # Runtime environment validation
│ ├── print-escpos.ts # QZ Tray and ESC/POS integration
│ ├── types.ts # Shared application contracts
│ └── utils.ts # Formatting and identifier helpers
├── prisma/
│ ├── migrations/ # Versioned PostgreSQL migrations
│ ├── schema.prisma # Database schema
│ └── seed.ts # Starter data
├── public/ # Static assets and receipt logo
├── proxy.ts # Authentication and role-aware route protection
└── package.json # Dependencies and scripts
Run both checks before committing or deploying:
npm run lint
npm run buildThe production build performs strict TypeScript checking.
No automated unit or end-to-end test command is currently configured, so linting and a successful production build are the current baseline checks.
- Passwords are hashed with bcrypt using a cost factor of 12.
- Sessions use signed
HS256JWTs stored inHttpOnlycookies. - Session cookies use
SameSite=LaxandSecurein production. - Sessions expire after seven days.
- Protected paths are checked before page rendering.
- Sensitive server actions and API routes perform their own authorization.
- Inputs are validated with Zod before business logic runs.
AUTH_SECRET,DATABASE_URL, andQZ_PRIVATE_KEYmust never be committed.- Seeded passwords must be changed before real-world use.
- Production databases should have automated backups and restricted credentials.
If TypeScript reports implicit any errors on Prisma query results or cannot resolve generated model types, regenerate Prisma Client:
npm run db:generate
npm run buildAlso confirm that @prisma/client and prisma use compatible versions and that DATABASE_URL exists while generation runs.
Confirm the shared contracts are exported from lib/types.ts, because the @/lib/types alias resolves to that file:
rg "export type (ReportData|DashboardData)" lib/types.tsCheck that:
DATABASE_URLis not empty.AUTH_SECRETcontains at least 32 characters.APP_URLis a complete URL, includinghttp://orhttps://.
For local development:
npm run db:migrateFor production:
npx prisma migrate deploy- Confirm QZ Tray is open on the cashier computer.
- Confirm the configured printer name matches the operating-system printer name.
- Confirm
public/digital-certificate.txtis available. - Confirm
QZ_PRIVATE_KEYmatches that certificate. - Confirm the QZ Tray script is reachable.
- Use the browser-print fallback while diagnosing the direct-print connection.
- Automated unit, integration, and end-to-end tests
- Persisted held carts across sessions and devices
- Product image upload and optimization
- CSV product import and export
- Advanced audit and activity logs
- More detailed tax and discount configuration
- Supplier and purchase-order management
- Multi-branch inventory
- Offline-tolerant cashier workflow
- Additional dashboard and forecasting reports
- Optional responsive tablet and mobile interface
-
Create a focused feature branch.
-
Keep business logic in
lib/services. -
Validate external inputs with a schema in
lib/validations. -
Preserve transaction boundaries for sales, returns, and stock changes.
-
Run the quality checks:
npm run lint npm run build
-
Open a pull request with a clear description and verification notes.