Chapter 19: POS Client Application
The Point of Sale Terminal
The POS Client is the primary interface for retail associates. It must be fast, reliable, and work offline when network connectivity is lost. This chapter provides complete specifications for building a production-grade POS terminal.
Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| Framework | .NET MAUI or Blazor Hybrid | Cross-platform, native performance |
| Local Database | SQLite | Embedded, zero-config, reliable |
| State Management | Fluxor or custom MVVM | Predictable state changes |
| Hardware API | Platform Invoke (P/Invoke) | Direct hardware access |
| Sync Engine | Custom HTTP + SignalR | Real-time + batch sync |
Architecture Overview
┌─────────────────────────────────────────────────────────────────────┐
│ POS CLIENT APPLICATION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Views │ │ ViewModels │ │ Services │ │ Hardware │ │
│ │ (XAML/ │◄─┤ (State + │◄─┤ (Business │◄─┤ Drivers │ │
│ │ Blazor) │ │ Commands) │ │ Logic) │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ └────────────────┴────────────────┴────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Local SQLite │ │
│ │ Database │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Sync Engine │ │
│ │ (Online/Queue) │ │
│ └────────┬────────┘ │
└──────────────────────────────────┬──────────────────────────────────┘
│
┌────────▼────────┐
│ Central API │
│ (When Online) │
└─────────────────┘
Screen Specifications
Screen 1: Login Screen
Purpose: Authenticate retail associates with fast PIN entry.
Route: /login
╔════════════════════════════════════════════════════════════════════╗
║ ║
║ ┌──────────────────────────┐ ║
║ │ │ ║
║ │ STORE LOGO │ ║
║ │ [128x128] │ ║
║ │ │ ║
║ └──────────────────────────┘ ║
║ ║
║ NEXUS CLOTHING ║
║ Greenbrier Mall (GM) ║
║ ║
║ ┌──────────────────────────┐ ║
║ │ │ ║
║ │ Enter Employee PIN │ ║
║ │ │ ║
║ │ ● ● ● ○ ○ ○ │ ║
║ │ │ ║
║ └──────────────────────────┘ ║
║ ║
║ ┌─────┬─────┬─────┐ ║
║ │ 1 │ 2 │ 3 │ ║
║ ├─────┼─────┼─────┤ ║
║ │ 4 │ 5 │ 6 │ ║
║ ├─────┼─────┼─────┤ ║
║ │ 7 │ 8 │ 9 │ ║
║ ├─────┼─────┼─────┤ ║
║ │ CLR │ 0 │ ENT │ ║
║ └─────┴─────┴─────┘ ║
║ ║
║ [Manager Override] ║
║ ║
║ ───────────────────────────────────────────────────────────── ║
║ Status: ● Online | Last Sync: 2 min ago | v1.2.0 ║
╚════════════════════════════════════════════════════════════════════╝
Components:
| Component | Specification |
|---|---|
| Logo | 128x128px, tenant-specific |
| Store Name | 24px, Bold, Primary color |
| Location | 14px, Secondary text |
| PIN Display | 6 circles, filled = entered |
| Numpad | 80x80px buttons, touch-friendly |
| Clear (CLR) | Resets PIN entry |
| Enter (ENT) | Submits PIN for validation |
| Manager Override | Opens manager auth dialog |
| Status Bar | Connection, sync, version |
Behavior:
- PIN validated locally first (hash comparison)
- Failed attempts: 3 max before lockout
- Lockout duration: 5 minutes (configurable)
- Auto-login timeout: 30 seconds of inactivity returns to login
Screen 2: Main Sale Screen
Purpose: Primary transaction interface for ringing up sales.
Route: /sale
╔════════════════════════════════════════════════════════════════════╗
║ NEXUS CLOTHING - GM Sarah M. 12/29/2024 2:45 PM ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌─────────────────────────────────────────────────────────────┐ ║
║ │ [Scan Item or Enter SKU...] [SEARCH] │ ║
║ └─────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────┐ ┌───────────────────────┐ ║
║ │ CART (3) │ │ TOTALS │ ║
║ ├──────────────────────────────────┤ ├───────────────────────┤ ║
║ │ │ │ │ ║
║ │ 1. Galaxy V-Neck Tee $29 │ │ Subtotal: $104.00 │ ║
║ │ Size: M | Color: Navy │ │ │ ║
║ │ Qty: 2 [-] [+] $58 │ │ Discount: -$10.00 │ ║
║ │ [DEL] │ │ │ ║
║ │ ─────────────────────────────────│ │ Tax (6%): $5.64 │ ║
║ │ 2. Slim Fit Chinos $46 │ │ │ ║
║ │ Size: 32 | Color: Khaki │ │ ───────────────────── │ ║
║ │ Qty: 1 [-] [+] $46 │ │ │ ║
║ │ [DEL] │ │ TOTAL: $99.64 │ ║
║ │ ─────────────────────────────────│ │ │ ║
║ │ │ │ │ ║
║ │ │ └───────────────────────┘ ║
║ │ │ ║
║ │ │ ┌───────────────────────┐ ║
║ │ │ │ [DISCOUNT] [HOLD] │ ║
║ │ │ │ │ ║
║ │ │ │ [CUSTOMER] [VOID] │ ║
║ │ │ │ │ ║
║ └──────────────────────────────────┘ │ ┌───────────────────┐ │ ║
║ │ │ │ │ ║
║ ┌──────────────────────────────────┐ │ │ PAY │ │ ║
║ │ Customer: John Smith │ │ │ $99.64 │ │ ║
║ │ Loyalty: Gold (2,450 pts) │ │ │ │ │ ║
║ │ [Remove Customer] │ │ └───────────────────┘ │ ║
║ └──────────────────────────────────┘ └───────────────────────┘ ║
║ ║
╠════════════════════════════════════════════════════════════════════╣
║ [F1 Help] [F2 Lookup] [F3 Returns] [F4 Reports] ● Online Rcpt#42 ║
╚════════════════════════════════════════════════════════════════════╝
Layout Regions:
| Region | Width | Content |
|---|---|---|
| Header | 100% | Store, associate, date/time |
| Search Bar | 100% | SKU/barcode entry with search |
| Cart Panel | 60% | Line items with quantity controls |
| Totals Panel | 40% | Running totals, discounts, tax |
| Action Buttons | 40% | Discount, Hold, Customer, Void |
| Pay Button | 40% | Large, prominent payment trigger |
| Customer Info | 60% | Attached customer details |
| Footer | 100% | Function keys, status, receipt # |
Cart Item Layout:
┌─────────────────────────────────────────────────────────────┐
│ 1. Galaxy V-Neck Tee $29 │
│ Size: M | Color: Navy │
│ Qty: 2 [-] [+] $58 │
│ [DEL] │
└─────────────────────────────────────────────────────────────┘
Keyboard Shortcuts:
| Key | Action |
|---|---|
| F1 | Help overlay |
| F2 | Product lookup |
| F3 | Returns mode |
| F4 | Quick reports |
| F5 | Price check |
| F8 | Suspend sale |
| F9 | Recall sale |
| F12 | Manager functions |
| Enter | Add scanned item |
| Esc | Cancel current action |
Screen 3: Customer Lookup
Purpose: Find or create customer records for loyalty tracking.
Route: /customer-lookup (Modal overlay)
╔════════════════════════════════════════════════════════════════════╗
║ CUSTOMER LOOKUP [X] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ [Search by name, phone, email, or loyalty #...] │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ RESULTS (3 found) │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ │ ║
║ │ ○ John Smith │ ║
║ │ Phone: (555) 123-4567 │ ║
║ │ Email: john.smith@email.com │ ║
║ │ Loyalty: Gold (2,450 pts) | Last Visit: 12/15/2024 │ ║
║ │ │ ║
║ │ ─────────────────────────────────────────────────────────── │ ║
║ │ │ ║
║ │ ○ Johnny Smith Jr. │ ║
║ │ Phone: (555) 234-5678 │ ║
║ │ Email: johnny.jr@email.com │ ║
║ │ Loyalty: Silver (890 pts) | Last Visit: 11/20/2024 │ ║
║ │ │ ║
║ │ ─────────────────────────────────────────────────────────── │ ║
║ │ │ ║
║ │ ○ Jonathan Smithson │ ║
║ │ Phone: (555) 345-6789 │ ║
║ │ Email: j.smithson@work.com │ ║
║ │ Loyalty: None | Last Visit: 10/05/2024 │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [NEW CUSTOMER] [SELECT] [CANCEL] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
New Customer Form:
╔════════════════════════════════════════════════════════════════════╗
║ NEW CUSTOMER [X] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ First Name * Last Name * ║
║ ┌──────────────────┐ ┌──────────────────────────────────────────┐ ║
║ │ John │ │ Smith │ ║
║ └──────────────────┘ └──────────────────────────────────────────┘ ║
║ ║
║ Phone * Email ║
║ ┌──────────────────────────┐ ┌────────────────────────────────┐ ║
║ │ (555) 123-4567 │ │ john.smith@email.com │ ║
║ └──────────────────────────┘ └────────────────────────────────┘ ║
║ ║
║ Address Line 1 ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 123 Main Street │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ City State ZIP ║
║ ┌────────────────────┐ ┌─────────┐ ┌───────────────────────────┐║
║ │ Virginia Beach │ │ VA ▼ │ │ 23451 │║
║ └────────────────────┘ └─────────┘ └───────────────────────────┘║
║ ║
║ [ ] Enroll in Loyalty Program ║
║ [ ] Subscribe to email marketing ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [SAVE] [CANCEL] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
Screen 4: Returns Processing
Purpose: Process merchandise returns and exchanges.
Route: /returns
╔════════════════════════════════════════════════════════════════════╗
║ RETURNS PROCESSING [Exit Return]║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ STEP 1: FIND ORIGINAL TRANSACTION ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Receipt #: [________________] OR [Lookup by Customer] │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ ORIGINAL TRANSACTION #12345 12/20/2024 │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ Customer: John Smith │ ║
║ │ Payment: Visa ****4242 │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ │ ║
║ │ [x] 1. Galaxy V-Neck Tee (M, Navy) $29.00 │ ║
║ │ Reason: [Wrong Size ▼] │ ║
║ │ Condition: [Good - Resellable ▼] │ ║
║ │ │ ║
║ │ [ ] 2. Slim Fit Chinos (32, Khaki) $46.00 │ ║
║ │ │ ║
║ │ [ ] 3. Leather Belt (M) $35.00 │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌────────────────────────┐ ┌───────────────────────────────────┐║
║ │ RETURN SUMMARY │ │ REFUND TO │║
║ ├────────────────────────┤ ├───────────────────────────────────┤║
║ │ Items: 1 │ │ ○ Original Payment (Visa ****42) │║
║ │ Subtotal: $29.00 │ │ ○ Store Credit │║
║ │ Tax Refund: $1.74 │ │ ○ Cash │║
║ │ ────────────────── │ │ ○ Exchange (Add to New Sale) │║
║ │ TOTAL: $30.74 │ │ │║
║ └────────────────────────┘ └───────────────────────────────────┘║
║ ║
║ Manager Approval Required: [ ] Over $100 [ ] No Receipt ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [SCAN RETURN ITEMS] [PROCESS RETURN] [CANCEL] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
Return Reasons (Configurable):
- Wrong Size
- Wrong Color
- Defective
- Changed Mind
- Gift Return
- Price Adjustment
- Other
Return Conditions:
- Good - Resellable
- Damaged - Cannot Resell
- Missing Tags - Markdown
Screen 5: Inventory Lookup
Purpose: Check stock levels across all locations.
Route: /inventory (Modal overlay)
╔════════════════════════════════════════════════════════════════════╗
║ INVENTORY LOOKUP [X] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ [Search by SKU, name, or scan barcode...] [SEARCH]│ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Galaxy V-Neck Tee $29.00 │ ║
║ │ SKU: NXJ1078-NAV-M │ ║
║ │ ────────────────────────────────────────────────────────────│ ║
║ │ │ ║
║ │ VARIANTS: │ ║
║ │ ┌────────────┬─────┬─────┬─────┬─────┬─────┬───────┐ │ ║
║ │ │ Size/Color │ HQ │ GM │ HM │ LM │ NM │ TOTAL │ │ ║
║ │ ├────────────┼─────┼─────┼─────┼─────┼─────┼───────┤ │ ║
║ │ │ S / Navy │ 12 │ 3 │ 2 │ 4 │ 1 │ 22 │ │ ║
║ │ │ M / Navy │ 15 │ 5*│ 3 │ 2 │ 0 │ 25 │ │ ║
║ │ │ L / Navy │ 8 │ 4 │ 1 │ 3 │ 2 │ 18 │ │ ║
║ │ │ XL / Navy │ 4 │ 2 │ 0 │ 1 │ 1 │ 8 │ │ ║
║ │ │ S / Black │ 10 │ 2 │ 3 │ 2 │ 2 │ 19 │ │ ║
║ │ │ M / Black │ 18 │ 6 │ 4 │ 5 │ 3 │ 36 │ │ ║
║ │ └────────────┴─────┴─────┴─────┴─────┴─────┴───────┘ │ ║
║ │ │ ║
║ │ * Current Location (GM) │ ║
║ │ │ ║
║ │ Last Updated: 12/29/2024 2:30 PM │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [REQUEST TRANSFER] [PRICE CHECK] [CLOSE] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
Screen 6: End of Day
Purpose: Close register, balance cash, generate reports.
Route: /end-of-day
╔════════════════════════════════════════════════════════════════════╗
║ END OF DAY - Close Register [Cancel] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ Register: REGISTER-01 (GM) Date: 12/29/2024 ║
║ Cashier: Sarah Miller Shift: 9:00 AM - 5:30 PM ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ SALES SUMMARY │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ │ ║
║ │ Total Transactions: 47 │ ║
║ │ Gross Sales: $3,245.67 │ ║
║ │ Returns: -$125.00 │ ║
║ │ Discounts: -$89.50 │ ║
║ │ ────────────────────────────────────── │ ║
║ │ Net Sales: $3,031.17 │ ║
║ │ Tax Collected: $181.87 │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ CASH COUNT │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ │ ║
║ │ Starting Cash: $200.00 │ ║
║ │ Cash Sales: $845.50 │ ║
║ │ Cash Returns: -$45.00 │ ║
║ │ ────────────────────────────────────── │ ║
║ │ Expected Cash: $1,000.50 │ ║
║ │ │ ║
║ │ Counted Cash: [_______________] <-- Enter amount │ ║
║ │ │ ║
║ │ Variance: $___.__ (Calculates automatically) │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ PAYMENT BREAKDOWN │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ Cash: $845.50 (28 trans) │ ║
║ │ Credit Card: $1,856.32 (15 trans) │ ║
║ │ Debit Card: $254.35 (3 trans) │ ║
║ │ Store Credit: $75.00 (1 trans) │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [PRINT REPORT] [RECOUNT] [CLOSE REGISTER] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
Payment Screen
Purpose: Process various payment methods.
╔════════════════════════════════════════════════════════════════════╗
║ PAYMENT [X] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ AMOUNT DUE: $99.64 ║
║ ║
║ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ║
║ │ │ │ │ │ │ ║
║ │ [CREDIT] │ │ [DEBIT] │ │ [CASH] │ ║
║ │ CARD │ │ CARD │ │ │ ║
║ │ │ │ │ │ │ ║
║ └─────────────────┘ └─────────────────┘ └─────────────────┘ ║
║ ║
║ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ║
║ │ │ │ │ │ │ ║
║ │ [GIFT] │ │ [STORE] │ │ [SPLIT] │ ║
║ │ CARD │ │ CREDIT │ │ PAYMENT │ ║
║ │ │ │ │ │ │ ║
║ └─────────────────┘ └─────────────────┘ └─────────────────┘ ║
║ ║
║ ═══════════════════════════════════════════════════════════════ ║
║ ║
║ CASH QUICK AMOUNTS: ║
║ ║
║ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ║
║ │ $20 │ │ $50 │ │ $100 │ │ $120 │ │ EXACT │ │ OTHER │ ║
║ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ ║
║ ║
║ Amount Tendered: $________ ║
║ Change Due: $________ ║
║ ║
║ ┌────────────────────────────────────────────────────────────────╢
║ │ ║
║ │ [PROCESS] [CANCEL] ║
║ │ ║
║ └────────────────────────────────────────────────────────────────╢
╚════════════════════════════════════════════════════════════════════╝
State Management
Application State Model
public class PosState
{
// Authentication
public AuthState Auth { get; set; }
// Current Transaction
public TransactionState Transaction { get; set; }
// Cart Items
public List<CartItem> Cart { get; set; }
// Customer
public CustomerState Customer { get; set; }
// Register
public RegisterState Register { get; set; }
// Sync Status
public SyncState Sync { get; set; }
// UI State
public UiState Ui { get; set; }
}
public class TransactionState
{
public string TransactionId { get; set; }
public TransactionType Type { get; set; } // Sale, Return, Exchange
public TransactionStatus Status { get; set; }
public decimal Subtotal { get; set; }
public decimal DiscountTotal { get; set; }
public decimal TaxTotal { get; set; }
public decimal GrandTotal { get; set; }
public List<PaymentEntry> Payments { get; set; }
public decimal BalanceDue { get; set; }
}
State Actions
| Action | Description |
|---|---|
AddToCart | Add item with quantity |
UpdateQuantity | Change line item quantity |
RemoveFromCart | Delete line item |
ApplyDiscount | Add transaction/line discount |
AttachCustomer | Link customer to sale |
ProcessPayment | Record payment entry |
VoidTransaction | Cancel entire transaction |
SuspendSale | Park sale for later |
RecallSale | Resume suspended sale |
Sync Service Design
Sync Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ SYNC ENGINE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ OUTBOUND │ │ INBOUND │ │ CONFLICT │ │
│ │ QUEUE │────▶│ HANDLER │────▶│ RESOLVER │ │
│ │ (SQLite) │ │ (API Sync) │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LOCAL SQLITE DATABASE │ │
│ │ - Transactions (pending sync) │ │
│ │ - Products (cached catalog) │ │
│ │ - Customers (cached records) │ │
│ │ - Inventory (last known levels) │ │
│ │ - Sync metadata (timestamps, versions) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Sync Priorities
| Priority | Data Type | Frequency | Direction |
|---|---|---|---|
| 1 (Critical) | Transactions | Immediate | Outbound |
| 2 (High) | Inventory Changes | 5 min | Both |
| 3 (Medium) | Customers | 15 min | Both |
| 4 (Low) | Products | 1 hour | Inbound |
| 5 (Batch) | Reports | Daily | Outbound |
Offline Queue Schema
CREATE TABLE sync_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
entity_type TEXT NOT NULL, -- 'transaction', 'customer', etc.
entity_id TEXT NOT NULL,
action TEXT NOT NULL, -- 'create', 'update', 'delete'
payload TEXT NOT NULL, -- JSON serialized data
priority INTEGER DEFAULT 5,
retry_count INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
last_attempt TEXT,
status TEXT DEFAULT 'pending' -- 'pending', 'syncing', 'failed', 'synced'
);
CREATE INDEX idx_sync_queue_status ON sync_queue(status, priority);
Hardware Integration
Receipt Printer
public interface IReceiptPrinter
{
Task<bool> PrintReceiptAsync(Receipt receipt);
Task<bool> OpenCashDrawerAsync();
Task<bool> CutPaperAsync();
Task<PrinterStatus> GetStatusAsync();
}
public class EpsonTM88Printer : IReceiptPrinter
{
private readonly string _portName;
public async Task<bool> PrintReceiptAsync(Receipt receipt)
{
var commands = new List<byte>();
// Initialize printer
commands.AddRange(new byte[] { 0x1B, 0x40 }); // ESC @
// Center align
commands.AddRange(new byte[] { 0x1B, 0x61, 0x01 }); // ESC a 1
// Store header (double width/height)
commands.AddRange(new byte[] { 0x1D, 0x21, 0x11 }); // GS ! 0x11
commands.AddRange(Encoding.ASCII.GetBytes(receipt.StoreName + "\n"));
// Reset text size
commands.AddRange(new byte[] { 0x1D, 0x21, 0x00 });
// ... additional formatting
// Cut paper
commands.AddRange(new byte[] { 0x1D, 0x56, 0x00 }); // GS V 0
return await SendToPortAsync(commands.ToArray());
}
}
Barcode Scanner
public interface IBarcodeScanner
{
event EventHandler<BarcodeScannedEventArgs> BarcodeScanned;
Task StartListeningAsync();
Task StopListeningAsync();
}
public class HoneywellScanner : IBarcodeScanner
{
public event EventHandler<BarcodeScannedEventArgs> BarcodeScanned;
private SerialPort _port;
public async Task StartListeningAsync()
{
_port = new SerialPort("COM3", 9600);
_port.DataReceived += OnDataReceived;
_port.Open();
}
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
var barcode = _port.ReadLine().Trim();
BarcodeScanned?.Invoke(this, new BarcodeScannedEventArgs(barcode));
}
}
Cash Drawer
public interface ICashDrawer
{
Task<bool> OpenAsync();
Task<bool> IsOpenAsync();
}
public class ApgCashDrawer : ICashDrawer
{
private readonly IReceiptPrinter _printer;
public async Task<bool> OpenAsync()
{
// Most cash drawers open via printer kick command
return await _printer.OpenCashDrawerAsync();
}
}
Local Database Schema
-- Products (cached from central)
CREATE TABLE products (
id TEXT PRIMARY KEY,
sku TEXT NOT NULL UNIQUE,
barcode TEXT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
cost REAL,
category_id TEXT,
tax_rate REAL DEFAULT 0,
is_active INTEGER DEFAULT 1,
last_synced TEXT NOT NULL
);
-- Inventory (cached levels)
CREATE TABLE inventory (
product_id TEXT NOT NULL,
location_code TEXT NOT NULL,
quantity INTEGER NOT NULL,
last_synced TEXT NOT NULL,
PRIMARY KEY (product_id, location_code)
);
-- Customers (cached)
CREATE TABLE customers (
id TEXT PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
phone TEXT,
email TEXT,
loyalty_tier TEXT,
loyalty_points INTEGER DEFAULT 0,
last_synced TEXT NOT NULL
);
-- Transactions (local first, then synced)
CREATE TABLE transactions (
id TEXT PRIMARY KEY,
transaction_number INTEGER NOT NULL,
type TEXT NOT NULL,
status TEXT NOT NULL,
customer_id TEXT,
associate_id TEXT NOT NULL,
register_id TEXT NOT NULL,
subtotal REAL NOT NULL,
discount_total REAL DEFAULT 0,
tax_total REAL NOT NULL,
grand_total REAL NOT NULL,
created_at TEXT NOT NULL,
completed_at TEXT,
synced_at TEXT,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- Transaction Line Items
CREATE TABLE transaction_items (
id TEXT PRIMARY KEY,
transaction_id TEXT NOT NULL,
product_id TEXT NOT NULL,
sku TEXT NOT NULL,
name TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price REAL NOT NULL,
discount REAL DEFAULT 0,
tax_amount REAL NOT NULL,
line_total REAL NOT NULL,
FOREIGN KEY (transaction_id) REFERENCES transactions(id)
);
-- Payments
CREATE TABLE payments (
id TEXT PRIMARY KEY,
transaction_id TEXT NOT NULL,
method TEXT NOT NULL,
amount REAL NOT NULL,
reference TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (transaction_id) REFERENCES transactions(id)
);
Performance Requirements
| Metric | Target | Measurement |
|---|---|---|
| App Launch | < 3 seconds | Cold start to login screen |
| Item Scan | < 100ms | Barcode to cart display |
| Product Search | < 200ms | Keystroke to results |
| Payment Process | < 2 seconds | Button tap to receipt |
| Offline Switch | Instant | Seamless transition |
| Sync Latency | < 5 seconds | Transaction to central |
Security Considerations
| Concern | Mitigation |
|---|---|
| PIN Storage | Hashed with bcrypt, salted |
| Local DB | SQLCipher encryption |
| API Tokens | Secure storage (Keychain/DPAPI) |
| PCI Compliance | No card data stored locally |
| Session Timeout | Auto-logout after inactivity |
| Audit Trail | All actions logged with timestamp |
Role-Based Interface (Learned from Lightspeed)
Competitive Insight: Lightspeed separates Cashier and Manager views to reduce cognitive load. Cashiers see only what they need; managers have full access.
Cashier Mode vs Manager Mode
┌─────────────────────────────────────────────────────────────────────────┐
│ ROLE-BASED UI SPLIT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CASHIER MODE MANAGER MODE │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ • Sales Screen │ │ • Full Dashboard │ │
│ │ • Customer Lookup │ │ • All Cashier Features │ │
│ │ • Basic Returns │ │ • Inventory Adjustments │ │
│ │ • Receipt Reprint │ │ • Employee Management │ │
│ │ • Price Check │ │ • Reports & Analytics │ │
│ │ │ │ • System Settings │ │
│ │ [Simplified Navigation] │ │ • Void/Override Powers │ │
│ │ [Large Touch Targets] │ │ • Cash Drawer Access │ │
│ └─────────────────────────┘ │ • End of Day Close │ │
│ └─────────────────────────┘ │
│ │
│ [Switch to Manager Mode] ─────────────────────────> [PIN Required] │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Mode Switch Behavior
public class RoleModeService
{
private UserRole _currentRole = UserRole.Cashier;
public async Task<bool> SwitchToManagerMode(string managerPin)
{
var isValid = await _authService.ValidateManagerPin(managerPin);
if (isValid)
{
_currentRole = UserRole.Manager;
_auditLog.Log("MODE_SWITCH", "Switched to Manager mode");
return true;
}
return false;
}
public void SwitchToCashierMode()
{
_currentRole = UserRole.Cashier;
_auditLog.Log("MODE_SWITCH", "Switched to Cashier mode");
}
public bool CanAccess(string feature) => _rolePermissions[_currentRole].Contains(feature);
}
Actions Requiring Manager Override
| Action | Cashier Can Do | Manager PIN Required |
|---|---|---|
| Ring sale | ✅ | - |
| Apply discount > 20% | ❌ | ✅ |
| Void transaction | ❌ | ✅ |
| Return without receipt | ❌ | ✅ |
| Open cash drawer (no sale) | ❌ | ✅ |
| Price override | ❌ | ✅ |
| View reports | ❌ | ✅ |
| End of day close | ❌ | ✅ |
View Preferences (Learned from Lightspeed)
Competitive Insight: Lightspeed offers Grid/List toggle and Dark Mode. Users have different preferences - some like images, some prefer text density.
Product Display Options
┌─────────────────────────────────────────────────────────────────────────┐
│ Products [Grid ▣] [List ≡] [🌙 Dark Mode] │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ GRID VIEW (Default) LIST VIEW │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌─────────────────────────────────┐│
│ │ [IMG] │ │ [IMG] │ │ [IMG] │ │ 👕 Galaxy Tee - Navy - $29 [+] ││
│ │ Tee │ │ Pants │ │ Jacket│ │ 👖 Slim Chinos - Khaki - $46[+] ││
│ │ $29 │ │ $46 │ │ $89 │ │ 🧥 Bomber Jacket - $89 [+] ││
│ │ [+] │ │ [+] │ │ [+] │ │ 👔 Oxford Shirt - $55 [+] ││
│ └───────┘ └───────┘ └───────┘ └─────────────────────────────────┘│
│ │
│ Best for: Visual products Best for: High SKU count │
│ Use when: Fashion, gifts Use when: Hardware, parts │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Dark Mode Support
┌─────────────────────────────────────────────────────────────────────────┐
│ THEME MODES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ LIGHT MODE (Default) DARK MODE │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Background: #FFFFFF │ │ Background: #1a1a2e │ │
│ │ Text: #1f2937 │ │ Text: #f3f4f6 │ │
│ │ Primary: #4f46e5 │ │ Primary: #818cf8 │ │
│ │ Cards: #f9fafb │ │ Cards: #16213e │ │
│ │ Borders: #e5e7eb │ │ Borders: #374151 │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ Benefits: Benefits: │
│ • Standard retail look • Reduces eye strain (long shifts) │
│ • Better in bright stores • Better for low-light stores │
│ • Matches receipts • Saves battery (OLED screens) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
User Preferences Storage
public class UserPreferences
{
public string EmployeeId { get; set; }
public ViewMode ProductViewMode { get; set; } = ViewMode.Grid;
public ThemeMode Theme { get; set; } = ThemeMode.Light;
public int GridColumnsCount { get; set; } = 4;
public bool ShowProductImages { get; set; } = true;
public bool ShowQuickAccessBar { get; set; } = true;
public List<string> FavoriteProducts { get; set; } = new();
public string DefaultCategory { get; set; }
}
public enum ViewMode { Grid, List }
public enum ThemeMode { Light, Dark, System }
Quick Access Layout Editor (Learned from Lightspeed)
Competitive Insight: Lightspeed lets managers arrange product buttons visually. Frequently sold items get prime positions.
Layout Editor Screen
╔════════════════════════════════════════════════════════════════════╗
║ QUICK ACCESS LAYOUT EDITOR [Save] [Cancel] ║
╠════════════════════════════════════════════════════════════════════╣
║ ║
║ QUICK ACCESS BAR (Drag to arrange) ║
║ ┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ ║
║ │ Galaxy │ Slim │ Bomber │ Gift │ Bag │ [Empty] │ ║
║ │ Tee │ Chinos │ Jacket │ Card │ Small │ [+] │ ║
║ │ $29 │ $46 │ $89 │ $25+ │ $3 │ │ ║
║ └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ ║
║ ║
║ AVAILABLE PRODUCTS (Drag to Quick Access bar above) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Search: [_______________] Category: [All ▼] │ ║
║ ├──────────────────────────────────────────────────────────────┤ ║
║ │ │ ║
║ │ ⬚ Oxford Shirt ⬚ Crew Neck ⬚ Cargo Pants │ ║
║ │ ⬚ Polo Classic ⬚ Denim Jacket ⬚ Belt Leather │ ║
║ │ ⬚ Hoodie Basic ⬚ V-Neck Tee ⬚ Socks 3-Pack │ ║
║ │ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ Layout applies to: ○ This register only ● All registers at GM ║
║ ║
╚════════════════════════════════════════════════════════════════════╝
Quick Access Data Model
public class QuickAccessLayout
{
public string Id { get; set; }
public string TenantId { get; set; }
public string LocationId { get; set; } // null = all locations
public string RegisterId { get; set; } // null = all registers
public List<QuickAccessSlot> Slots { get; set; } = new();
public DateTime UpdatedAt { get; set; }
public string UpdatedBy { get; set; }
}
public class QuickAccessSlot
{
public int Position { get; set; } // 0-11 (2 rows of 6)
public string ItemId { get; set; }
public string CustomLabel { get; set; } // Override display name
public string CustomColor { get; set; } // Button color (#hex)
}
Quick Access Display
┌─────────────────────────────────────────────────────────────────────────┐
│ QUICK ACCESS [Edit] │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ #6366f1 │ │ #10b981 │ │ #f59e0b │ │ #ef4444 │ │ #8b5cf6 │ │ #ec4899│ │
│ │ Galaxy │ │ Slim │ │ Bomber │ │ Gift │ │ Bag │ │ Hoodie │ │
│ │ Tee │ │ Chinos │ │ Jacket │ │ Card │ │ Small │ │ │ │
│ │ $29 │ │ $46 │ │ $89 │ │ $25+ │ │ $3 │ │ $55 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘ │
│ │
│ One-tap access to best sellers. Manager can customize via [Edit]. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Summary
The POS Client Application is designed for:
- Speed: Sub-second response times for all common operations
- Reliability: Full offline capability with automatic sync
- Usability: Touch-friendly, keyboard shortcuts, minimal training
- Security: PIN auth, encrypted storage, audit logging
- Integration: Hardware support for printers, scanners, drawers
- Role-Based UI: Simplified Cashier mode, full Manager mode (Lightspeed insight)
- Personalization: Grid/List toggle, Dark mode, Quick Access layout (Lightspeed insight)
Competitive Feature Matrix
| Feature | Our POS | Retail Pro | Lightspeed |
|---|---|---|---|
| Offline Mode | ✅ Full | ✅ Full | ❌ None |
| Role-Based UI | ✅ Yes | ✅ Yes | ✅ Yes |
| Dark Mode | ✅ Yes | ❌ No | ✅ Yes |
| Grid/List Toggle | ✅ Yes | ❌ No | ✅ Yes |
| Quick Access Editor | ✅ Yes | ✅ Yes | ✅ Yes |
| Keyboard Shortcuts | ✅ F1-F12 | ✅ Full | ⚠️ Limited |
| PIN Login | ✅ Yes | ✅ Yes | ✅ Yes |
| Manager Override | ✅ Yes | ✅ Yes | ✅ Yes |
Implementation complete. Ready for engineer review.