first commit
This commit is contained in:
148
README.md
Normal file
148
README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Nearle POS
|
||||
|
||||
An enterprise Point of Sale terminal for supermarkets, grocery stores, pharmacies and retail shops. Built for **Flutter Desktop** (Windows/macOS/Linux) and **Android tablets**.
|
||||
|
||||
Brand colour `#662582` · Inter typeface · 16px corner radius · touch-first targets.
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
flutter run -d windows # or macos, linux, or a connected tablet
|
||||
flutter test # 60+ unit and widget tests
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
Requires Flutter 3.27 / Dart 3.6 or newer. See [Version compatibility](#version-compatibility) if `flutter analyze` complains about theme types.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
Clean architecture in three layers. Dependencies point inward only: `presentation → domain ← data`. The domain layer imports nothing from Flutter, which is what makes the pricing engine testable in isolation.
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart Entry point: orientation lock, store + audio warmup
|
||||
├── app/
|
||||
│ ├── app.dart MaterialApp.router, global shortcuts, text-scale lock
|
||||
│ └── providers.dart Dependency injection graph, cashier session, clock
|
||||
│
|
||||
├── core/
|
||||
│ ├── constants/ Store details, GST rates, loyalty ratios, asset paths
|
||||
│ ├── theme/ Colours, 4pt spacing scale, typography, ThemeData
|
||||
│ ├── router/ GoRouter routes and page transitions
|
||||
│ ├── services/ Barcode capture, scanner beeps, thermal receipt PDF
|
||||
│ ├── utils/ Formatters, validators, BuildContext extensions
|
||||
│ └── widgets/ GlassCard, PrimaryButton, NumericKeypad, StatusPill
|
||||
│
|
||||
├── domain/ Pure Dart — no Flutter imports
|
||||
│ ├── entities/ Product, Customer, Cart, SaleTransaction
|
||||
│ ├── repositories/ Abstract contracts
|
||||
│ └── usecases/ CheckoutSale
|
||||
│
|
||||
├── data/
|
||||
│ ├── datasources/ LocalStore (swappable), seed catalogue
|
||||
│ └── repositories/ Concrete implementations
|
||||
│
|
||||
└── presentation/ One folder per feature: screens / widgets / providers
|
||||
├── welcome/ customer/ pos/ payment/ receipt/
|
||||
```
|
||||
|
||||
### Why the domain layer is pure
|
||||
|
||||
`Cart` is an immutable value object that computes every figure on the bill as a getter — subtotal, per-slab GST, membership discount, loyalty redemption, round-off, points earned. No widget, repository or provider participates in the arithmetic. That means the money maths is covered by fast unit tests with no `pumpWidget` and no mocking, and swapping `LocalStore` for Hive or a REST backend changes nothing above the data layer.
|
||||
|
||||
---
|
||||
|
||||
## The five screens
|
||||
|
||||
| Screen | What it does |
|
||||
|---|---|
|
||||
| **Welcome** | Three entry paths: New Customer, Existing Customer, Skip (walk-in). Vector illustration painted in code, so it stays crisp at 4K. |
|
||||
| **Customer Registration** | Mobile + name required; email, gender, DOB optional. Saves and drops straight into billing. |
|
||||
| **Existing Customer** | Large numeric keypad, auto-searches on the 10th digit. Shows name, tier, points and lifetime spend, or offers Register / Walk-in when nothing matches. |
|
||||
| **POS Dashboard** | Three columns: navigation, product grid, always-visible bill. Category chips, live search, barcode billing. |
|
||||
| **Payment** | Cash, Card, UPI, Wallet, Gift Card and arbitrary splits. Live change calculation with denomination shortcuts. |
|
||||
| **Receipt** | Success summary beside a paper-style preview. Counts down and starts the next sale on its own. |
|
||||
|
||||
---
|
||||
|
||||
## Fast cashier workflow
|
||||
|
||||
**Barcode billing has no dialogs.** `BarcodeService` listens to `HardwareKeyboard` globally rather than depending on a text field holding focus. It distinguishes a scanner from a human by keystroke timing — characters arriving faster than `barcodeScanTimeout` (120ms apart) are treated as a scan, so the cashier can still type into the same search box by hand. On a hit the item is added or its quantity incremented, a beep plays, and a floating toast confirms it. Nothing ever needs dismissing between items.
|
||||
|
||||
**Other workflow details:**
|
||||
|
||||
- Tapping a product card bills it immediately; the card shows a live quantity badge.
|
||||
- Newest cart line renders first, mirroring what was just scanned.
|
||||
- Every mutation goes through `CartController`, so scanner input, taps and shortcuts share one code path — and one undo stack (F8).
|
||||
- Swipe a cart line to remove it.
|
||||
- `F2` focuses search, `Esc` releases it.
|
||||
- Bills can be parked and resumed.
|
||||
- Stock is checked before adding, not after — an over-scan beeps and refuses rather than failing at checkout.
|
||||
|
||||
---
|
||||
|
||||
## Pricing engine
|
||||
|
||||
Prices are GST-inclusive, per Indian retail convention, so tax is extracted rather than added.
|
||||
|
||||
- **Per-product GST slabs.** Vegetables at 0%, milk at 5%, biscuits at 18%, aerated drinks at 28%. The receipt breaks GST out per slab, split into CGST and SGST.
|
||||
- **Bill-level discounts are apportioned.** When a membership discount or manual discount reduces the bill, the GST charged is scaled by the same factor rather than left at the pre-discount figure.
|
||||
- **Membership tiers are automatic.** Bronze / Silver / Gold / Platinum derive from lifetime spend and carry 0 / 2 / 5 / 8 percent off, applied without cashier action.
|
||||
- **Loyalty.** One point per ₹10 spent; each point is worth ₹0.25 on redemption. Redemption is capped at both the balance held and the bill value, and re-clamps automatically if the bill shrinks after points were applied.
|
||||
- **Round-off** to the nearest rupee is shown as its own line.
|
||||
- All money passes through an `asMoney` extension that rounds to two decimals, so floating-point drift never reaches a total.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
flutter test
|
||||
```
|
||||
|
||||
- `test/unit/cart_test.dart` — the pricing engine: GST extraction, discount stacking, apportionment, loyalty caps, tier thresholds, round-off, and the guarantee that a payable never goes negative.
|
||||
- `test/unit/checkout_test.dart` — end-to-end sale completion against real repositories: stock decrement, invoice sequencing, split tenders, loyalty movement, and every rejection path. `LocalStore.reset()` re-seeds between tests so fixtures never leak.
|
||||
- `test/unit/validators_test.dart` — mobile/email/name validation and formatter output.
|
||||
- `test/widget/primary_button_test.dart` — button and status pill rendering, tap and busy states.
|
||||
|
||||
---
|
||||
|
||||
## Swapping the data layer
|
||||
|
||||
`LocalStore` is a process-local map that stands in for a database. To move to Hive, SQLite or an HTTP API, implement the three interfaces in `domain/repositories/` and rebind them in `app/providers.dart`:
|
||||
|
||||
```dart
|
||||
final productRepositoryProvider = Provider<ProductRepository>(
|
||||
(ref) => HiveProductRepository(ref.watch(hiveBoxProvider)),
|
||||
);
|
||||
```
|
||||
|
||||
Nothing in `domain/` or `presentation/` changes.
|
||||
|
||||
---
|
||||
|
||||
## Version compatibility
|
||||
|
||||
Two Flutter APIs used here moved recently:
|
||||
|
||||
- `ThemeData.cardTheme` / `dialogTheme` take `CardThemeData` / `DialogThemeData` on Flutter 3.29+. On older versions, drop the `Data` suffix in `lib/core/theme/app_theme.dart`.
|
||||
- `Color.withValues(alpha:)` requires Flutter 3.27. On older versions substitute `withOpacity()`.
|
||||
|
||||
---
|
||||
|
||||
## Status and known gaps
|
||||
|
||||
**This project has not been compiled.** It was written in an environment without a Dart SDK or network access, so `flutter pub get`, `flutter analyze` and `flutter test` have never run against it. Verification was static: the import graph resolves with no missing or orphaned files, and every symbol referenced in the tests exists in `lib/`. That cannot catch type errors, signature mismatches, or drift in the pinned package APIs. Expect to fix a handful of issues on first build — the theme types above are the most likely.
|
||||
|
||||
Deliberately out of scope, stubbed as clear extension points:
|
||||
|
||||
- **Cash drawer** — `ReceiptService.openCashDrawer()` logs the ESC/POS kick sequence but does not send it. Wire in your printer's serial passthrough.
|
||||
- **AI product search** — the search bar ranks by exact barcode, SKU, prefix then fuzzy match. No model is called.
|
||||
- **Bottom navigation modules** — Dashboard, Products, Inventory, Customers, Promos, Reports, Suppliers and Settings render with live badge counts but are not routed.
|
||||
- **Authentication** — `cashierSessionProvider` holds a hardcoded session.
|
||||
- **Sounds** — the three bundled WAVs are synthesised placeholders. Replace with your own; `SoundService` swallows playback failures so a missing file never blocks billing.
|
||||
Reference in New Issue
Block a user