POS App

Sellino POS – Flutter Counter App (Android & iOS)
Version: 1.0.0
Built with: Flutter (Dart)
Backend: Sellino POS API /api/v10/*

Developed By: BugBuild Labs

Introduction

The Sellino POS App is the counter in your pocket — a native Flutter app for Android and iOS that runs the same till as the web POS, against the same backend, counters and cash registers, through the API (/api/v10/*).

What the app does:

  • Sell from a fast terminal — search or scan barcodes, build the cart, take payment
  • Open and close cash registers on the branch's counters
  • Carry the same branch switcher as the web POS — stock, warehouse and register follow the active branch
  • Browse and process orders, with receipts as PDF or thermal print
  • Check product stock, and record adjustment, damage and transfers
  • Manage customers, suppliers and membership points at the counter
  • Run the whole back office — catalogue, purchase orders & returns, branches & warehouses, users & roles, a full accounting suite, loyalty and promotions
  • Read day-to-day reports and receive stock/transfer push alerts

Role-aware menu: the app's menu mirrors the web POS panel's sidebar one-to-one and every item is gated by the signed-in user's role permissions — a cashier sees a short till menu, the owner sees everything. See Roles & the Menu.

Who is it for: the cashier and the shop owner. Everything it writes lands in the same registers, journals and stock the web admin reads.

POS terminal
POS terminal

Requirements

  • Flutter SDK (stable channel) with Dart — flutter doctor should pass.
  • Android Studio (Android SDK 21+) and/or Xcode for iOS builds (macOS only).
  • A running Sellino POS backend reachable over HTTPS — set up first via Quick Start.
  • An approved staff login on that backend (cashier, branch manager or admin).
  • For push notifications: a Firebase project — see Common Setup.

Package & Source Code

The download's PosAppSourceCode folder holds the complete Flutter project. Open it in Android Studio or VS Code and run flutter pub get.

  • lib/core/ — environment config, API client, theme, shared widgets and controllers.
  • lib/features/ — one folder per screen area. The till: auth, branch, pos_register, pos_management (terminal & cart), orders, product_stock, stock_adjustment, stock_damage, stock_transfer, warehouse, contacts, membership, reports, notifications, settings. The back office: products / product (in-house catalogue), category_management, brands, attribute_management, tax_rates, units, purchase_order, purchase_stock, return_requests, replacements, users, roles, chart_of_accounts, bank_account, bank_deposit, mfs_account, fund_transfer, expense, investment, owner_withdraw, liability_settlement, journal, transactions, reconciliation, promotions (coupons, flash sales, deals), campaign, rewards, membership_settings, system (activity & login logs).
  • assets/branding/ — app icon sources used by the launcher-icon generator.

App Configuration

  1. Open the PosAppSourceCode folder in your IDE and run flutter pub get.
  2. Set the API base URL in lib/core/env/prod_env.dart (release builds) or lib/core/env/dev_env.dart (debug builds) — image URLs are derived from it automatically.
  3. Paste the App API key (kAppApiKey) from Admin → Settings → API Security in the same file. Wrong or missing key → 401 on every screen.
  4. Configure push notification keys if used — see Common Setup.
// lib/core/env/prod_env.dart   → used by RELEASE builds
class ProdEnv implements Env {
  static const String kBaseUrl      = 'https://yourdomain.com/api/v10';  // your API root
  static const String kAppApiKey    = 'PASTE-KEY-FROM-ADMIN-PANEL';      // Settings → API Security
}

// lib/core/env/dev_env.dart    → used by DEBUG / PROFILE builds (your local backend)
// imageBaseUrl is derived from kBaseUrl.

Tip: always use HTTPS — plain HTTP is blocked by default on Android/iOS. Built-in guard: a release build refuses to start if the key is still empty or the URL still points at a dev host (localhost, .test…), so a forgotten swap fails on your desk, not at the counter.

Never regenerate the App API key after release unless it leaked — installed apps stop working until users update. See Web → Security → App API Key.

App Name & Icon

  • Name: change the display name in android/app/src/main/AndroidManifest.xml (android:label) and iOS Info.plist (CFBundleDisplayName).
  • Package / bundle id: set your own unique id (Android applicationId in android/app/build.gradle; iOS bundle identifier in Xcode) before publishing.
  • Icon: replace the sources in assets/branding/ and regenerate launcher icons (flutter_launcher_icons).

Build, Signing & Release

Before either platform: confirm the release build points at your production backend — lib/core/env/prod_env.dart. The app's built-in guard throws on first launch if the key is empty or the URL is still a dev host, so a forgotten swap fails on your desk.

Android — release signing

1. Generate a keystore. Run this once and keep the file safe — Google Play ties your app to it forever; lose it and you cannot publish updates under the same listing.

keytool -genkey -v -keystore ~/sellino-pos-release.jks \
  -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-pos

# keytool ships with the JDK. On Windows it is:
#   "%JAVA_HOME%\bin\keytool" -genkey -v -keystore %USERPROFILE%\sellino-pos-release.jks ^
#     -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-pos

It asks for a keystore password, a key password and your name/organisation details. Note both passwords down — the next step needs them.

2. Create android/key.properties (same folder as android/app/), with exactly these four keys:

storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=sellino-pos
storeFile=/Users/you/sellino-pos-release.jks

# storeFile — absolute path, or a path relative to the android/ folder.
# Windows: use forward slashes, e.g. C:/Users/you/sellino-pos-release.jks

Never commit key.properties or the .jks file to source control, and never ship them to anyone. They are the identity of your published app.

3. The Gradle wiring is already done. android/app/build.gradle.kts reads that file and applies it — nothing to edit, shown here so you know what happens:

// Loads key.properties if the buyer created it
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

signingConfigs {
    create("release") {
        if (keystorePropertiesFile.exists()) {
            keyAlias      = keystoreProperties["keyAlias"] as String?
            keyPassword   = keystoreProperties["keyPassword"] as String?
            storeFile     = (keystoreProperties["storeFile"] as String?)?.let { file(it) }
            storePassword = keystoreProperties["storePassword"] as String?
        }
    }
}

buildTypes {
    release {
        // Your keystore when key.properties exists; debug signing otherwise,
        // so `flutter run --release` still works before you set one up.
        signingConfig = if (keystorePropertiesFile.exists())
            signingConfigs.getByName("release")
        else
            signingConfigs.getByName("debug")
    }
}

Play Store builds must not be debug-signed. If key.properties is missing the build silently falls back to the debug key — Google Play will reject that upload. Create the file before your first release.

4. Build.

flutter build appbundle --release   # .aab → Google Play
flutter build apk --release        # .apk → direct install / other stores

# Verify what signed it (should NOT say "Android Debug"):
keytool -printcert -jarfile build/app/outputs/bundle/release/app-release.aab

Also set your own applicationId in android/app/build.gradle.kts before publishing — the package ships as com.sellino.pos and every listing needs a unique id you own.

iOS — signing & distribution

iOS builds need macOS with Xcode and a paid Apple Developer Program membership (US$99/year — a third-party cost, not included).

  1. Bundle identifier. Open ios/Runner.xcworkspace in Xcode (the workspace, not the project). Select the Runner target → General → set your own reverse-domain Bundle Identifier (e.g. com.yourbrand.pos). It must match the App ID you register with Apple, and should mirror the Android applicationId.
  2. Signing team. Xcode → Settings → Accounts → add your Apple ID. Then on the Runner target → Signing & Capabilities → tick Automatically manage signing and pick your Team. Xcode then creates the development certificate and provisioning profile for you — this is the recommended route.
  3. Register the App ID at Identifiers using the same bundle id. If you use push, enable the Push Notifications capability here and generate the APNs key — see Common Setup.
  4. Create the app record in App Store ConnectMy Apps → + → pick the bundle id, set name, primary language and SKU.
  5. Version & build number. Set them in pubspec.yaml (version: 1.0.0+1 — the part after + is the build number). Every upload to App Store Connect needs a build number higher than the last.
  6. Archive and upload.
    flutter build ipa --release
    # then either open the generated .xcarchive in Xcode → Distribute App,
    # or upload build/ios/ipa/*.ipa with Transporter (free, Mac App Store)
    In Xcode the equivalent is Product → ArchiveDistribute AppApp Store Connect.
  7. TestFlight → review. The build appears in App Store Connect after processing (a few minutes to an hour). Test it via TestFlight, fill in the store listing, privacy details and screenshots, then submit for review.

Apple's review requires a reachable Privacy Policy URL and an in-app account deletion path. Both ship with the app — the sign-in screen links the policy (edited in Website → Pages) and Settings carries account deletion.

Store accounts (Google Play one-time US$25 / Apple Developer US$99 per year) are third-party services and are not included with this item.

For Developers — API Structure & Data Model

The app is a pure API client: it holds no business logic of its own and writes nothing the web panel cannot read. Everything goes through /api/v10/* on your Laravel backend.

Every request carries these headers

Accept:        application/json
X-App-Key:     <the App API key from Settings → API Security>   // rejected with 401 if wrong
Authorization: Bearer <jwt>                                     // authenticated calls only
X-Branch-Id:   <active branch id>                               // the multi-branch scope
Accept-Language: en | bn | ar | he                              // drives translated responses

X-Branch-Id is the heart of the app. Stock, counters, registers, orders and reports are all scoped to it server-side, so switching branch in the UI changes what every subsequent endpoint returns. It is restored from secure storage before the first request on launch.

Response envelope

Every endpoint answers in the same shape, so the app has one parser and one error path:

{ "status": true,  "message": "Request successfully executed", "data": { … } }
{ "status": false, "message": "Human readable error",          "data": {} }

// Paginated lists put the rows in data.items alongside:
//   current_page · last_page · per_page · total

Endpoint families

Routes live in routes/api/v10/ on the backend, one file per area. The app's back office uses the staff group (routes/api/v10/staff/, ~34 files) — each route behind auth:api, auth.staff and its own hasPermission:* gate:

auth pos cash-register counter orders products inventory warehouses branches categories brands attributes customers suppliers members accounts reports returns replacements coupon campaigns flash-sell users panel-features activity-logs archive

The same hasPermission:* names drive the app's drawer — see Roles & the Menu. A role missing order_read gets no Orders menu item and a 403 if it calls the endpoint directly.

Core data model

EntityRelationship
BranchThe top-level scope. Owns warehouses, counters and staff assignments; every stock row, register and order belongs to one.
Warehouse → RackStock lives per warehouse; racks locate it inside one. The cart's rack picker reads these.
Counter → Cash RegisterA counter is the physical till; a register is one shift on it (open → sell → close). One open register per counter, one per user.
Product → VariantProducts carry attributes; the sellable unit is the variant (own SKU, barcode, price, stock).
Stock → Cost layersOn-hand quantity per variant per warehouse, backed by FIFO cost layers that purchases create and sales consume.
Order → Items → PaymentsA sale, its lines, and one or more payments (cash / bank / MFS split), tied to the register it was rung on.
Journal → TransactionsEvery money movement posts a balanced journal of ledger lines against Chart of Accounts heads — sales, expenses, pickups, purchases, returns.
User → Role → PermissionsRoles bundle permissions; users may also carry per-user overrides. This is what gates both menu and API.

Auth & token lifecycle

  • Login returns a JWT plus its expiry; the app stores it with flutter_secure_storage (Keychain on iOS, EncryptedSharedPreferences on Android) — never in plain preferences.
  • An interceptor refreshes the token through POST /login/refresh before expiry, and retries once on a 401; concurrent calls share one refresh instead of racing.
  • Auth endpoints are rate limited server-side (throttle middleware) against brute force.

Extending it

  • Add a backend route under routes/api/v10/staff/ with its hasPermission:* gate, then a matching service + controller in the app's feature folder — the folder layout under lib/features/ mirrors these route files closely.
  • lib/core/network/api_endpoints.dart holds every path in one place; dio_client.dart owns headers, refresh and error mapping.
  • Full endpoint-by-endpoint reference: docs/api-v10-endpoints.md in the backend source.

Login & Branch

Sign in with the same staff account you use on the web — email/mobile and password, or OTP login instead. The token is kept in secure storage.

After login the app shows the active branch bar — the same rule as the web POS. If your account may work in more than one branch, the bar becomes a switcher; changing branch reloads products, warehouse and the register check, because all three belong to the branch.

No selling happens until a cash register is open on one of the branch's counters — that's the next section.

Sign in
Sign in (password or OTP)

Roles & the Menu

The app's drawer menu mirrors the web POS panel's sidebar one-to-one — same sections, same order, same permission gates. Every menu item checks the signed-in user's role permissions (fetched at login), so each role sees exactly the pages it may use:

  • A cashier role with only order & POS permissions gets a short menu — Orders, POS Management, maybe Customers.
  • A branch manager also sees stock, purchasing and reports.
  • The owner / admin sees the full back office — catalogue, accounting, users & roles, promotions, everything below.

Items a role lacks are simply not drawn — no greyed-out entries, no 403 screens. The backend's panel feature flags can additionally hide whole sections for an installation.

Permissions are managed in Users & Roles — in the app or the web admin; both edit the same roles.

Permission-gated drawermenu follows the role

POS Register

The POS Register tab mirrors the web's counter flow: pick a free counter of the active branch, open a register with an opening balance, sell against it, and close it at shift end with the counted cash. Openings, sales, expenses and pickups all post to the counter's own cash account, so the drawer reconciles in accounting.

  • Only counters of the active branch are offered — same rule as the web.
  • A user runs one open register at a time; a counter holds one open register.
  • Register history shows opening/closing balances and differences.
Welcome
Welcome / continue

Counters, Petty Cash & Pickup

The POS Management group holds the cash side of the till beyond the register itself:

  • Counters — the branch's physical tills. See each counter's state (free or holding an open register) and add new counters without leaving the app.
  • Cash Registers — every register past and present: opening balance, sales taken, closing count and the difference, with a detail view per register.
  • Petty Expenses — record small cash spends (tea, transport, supplies) straight out of the open register's drawer; each posts to its expense head and the counter's cash account.
  • Cash Pickup — move excess cash out of the drawer to a safe or bank account mid-shift, so the drawer count stays sane and the pickup is journalled.

All four post to the counter's own cash account — the drawer reconciles in accounting exactly as on the web.

Terminal, Cart & Checkout

The terminal lists the branch's sellable variants with price and live stock. Search by name/SKU or scan a barcode with the camera; tapping a product adds it to the cart. The cart supports quantity edit, line discount, bill discount, coupon and membership-point redemption — then checkout takes cash, bank, mobile money or a mix, calculates change, and completes the sale against the open register.

Terminal
Terminal — search & scan
Cart items
Cart — lines, rack & discount
Checkout summary
Coupon, points & summary
Payment
Payment — cash, bank or MFS

Receipts print to a thermal printer or share as PDF — the same receipt the web POS produces.

Orders

The Orders tab lists sales with status, source and totals. Open an order for its items, payments and receipt; reprint or share the receipt from the detail screen.

Orders
Orders — search & status filters

Product Stock

The Product Stock tab shows on-hand quantity per variant per warehouse of the active branch, with search and low-stock visibility — the same numbers the web's Product Stock page reads.

Product stock
Stock by warehouse
Cost layers
Cost layers (FIFO batches)

Adjustment · Damage · Transfer

  • Stock adjustment — correct counted stock up or down with a reason; posts the same journal as the web.
  • Damage — write off damaged units out of sellable stock.
  • Stock transfer — move stock between warehouses with the request/approve flow; transfer alerts arrive as push notifications.

Purchase Orders & Returns

  • Purchase Orders — raise a PO on a supplier, receive the stock into a warehouse (goods land as FIFO cost layers), and track the supplier's due; the same PO the web panel shows.
  • Customer Returns — take a sold item back over the counter with a refund; the unit returns to sellable stock or goes to damage.
  • Return & Refund — the customer-side return requests queue: review, approve or refuse.
  • Replacements — run the replacement flow step by step; every step posts its accounting effect, mirroring the web module.
  • Supplier Returns — send stock back to a supplier; reduces stock and the supplier's due with the matching journal.

Products & Catalogue

The full catalogue back office, straight from the drawer's Products section:

Category Attributes Attribute values Brands Tax rates Units In-house products Deleted products Barcodes

  • In-house products — the full product form on the phone: create and edit products with variants, pricing, media and stock settings.
  • Category / Brands / Attributes & values / Tax rates / Units — the lookup tables every product build needs, each a list with create & edit.
  • Deleted products — the recycle bin; restore a product that was removed by mistake.
  • Barcodes — pick products and print barcode labels.

Branches, Warehouses & Racks

The location tree the whole system hangs off: branches (each with its own counters, stock and registers), the warehouses inside each branch, and the racks inside each warehouse that the cart's rack picker reads. Create and manage all three from the app.

Stock, registers and the terminal always follow the active branch from the branch bar.

Branch → Warehouse → Rackthe location tree

Users & Roles

Staff management without opening the web admin:

  • Users — create staff accounts, set their role and branches, edit or deactivate them, and fine-tune per-user permission overrides.
  • Roles — create and edit roles as bundles of permissions (order read, stock write, report view…).

These are the same roles the web panel uses — and they are what decides which menu items and screens each user gets, here and on the web (see Roles & the Menu).

Users & Rolespermissions per role

Accounting

The complete accounting suite from the web panel, on the phone — everything reads and writes the same ledger:

Chart of Accounts Bank Accounts MFS Accounts Fund Transfers Expense Investment Owner Withdraw Liability Settlement Journals Bank Deposits Transactions Reconciliation

  • Chart of Accounts — browse the account tree and add heads.
  • Bank / MFS accounts & deposits — manage the money accounts and record deposits into them.
  • Fund transfers — move money between accounts (drawer → bank, bank → MFS…).
  • Expense / Investment / Owner withdraw / Liability settlement — record each with its own screen; every entry posts a balanced journal.
  • Journals — view every journal the system posts, and create or edit manual ones.
  • Transactions — the raw ledger lines, filterable by account and date.
  • Reconciliation — tick ledger lines off against bank statements, with statement history.

Loyalty & Rewards

  • Membership Settings — the earn & redeem rates the checkout uses.
  • Product Points — per-product point values for targeted rewards.
  • Members — enrol customers, see points balances and history (also reachable from Customers).
Loyalty & Rewardspoints earn & redeem

Promotions

  • Flash Sales — create timed sale windows with discounted products; edit and monitor them from the list.
  • Coupons — create coupon codes with rules, watch usage on the detail screen; the terminal's coupon field accepts them.
  • Campaigns — group products under a campaign with its own schedule and discounts.
Promotionsflash sales · coupons · campaigns

Customers, Suppliers & Membership

Create and search customers right from the counter, attach one to a sale, and manage membership — the points balance shows at checkout and can be redeemed against the bill, using the same rates configured in Loyalty & Rewards.

Suppliers live beside customers in the same drawer section — the contact list that Purchase Orders & Supplier Returns draw on, with dues visible per supplier.

Customerscreate · search · points

Reports & Notifications

The Reports hub carries the day-to-day numbers, and the drawer links each report directly — every link gated by that report's own permission:

Stock valuation Stock Low stock Purchase summary Supplier due Expiry Expense Profit & loss Income statement Trial balance Balance sheet Cash register Cash pickup

The bell collects push notifications (low stock, transfer approvals) delivered through Firebase once Common Setup is done.

Reports
Reports — inventory & accounting

Settings & Audit

  • Profile — name, avatar and password of the signed-in staff.
  • Language — English, Arabic and Bengali (RTL-aware), following the backend's language list.
  • Printer — pair a Bluetooth thermal printer for receipts.
  • Login Activity — who signed in, when and from where (admin audit).
  • Activity Logs — the change trail across the system, with a detail view per entry.
  • Media Library — browse the backend's media files the product forms attach from.
  • Archive — soft-deleted records by type, with restore.
  • Logout — clears the secure token.

The audit screens and archive are permission-gated like everything else — most roles never see them.

Changelog

Version 1.0.0
  • Initial release of the Sellino POS App (Flutter, Android & iOS).
  • POS terminal with product search, camera barcode scanning, cart, discounts, coupon and membership redemption.
  • Cash registers on branch counters — open, sell, close, with history.
  • Branch switcher matching the web POS; stock, warehouse and register follow the active branch.
  • Orders list and detail with receipt reprint/share (PDF, thermal print).
  • Product stock per warehouse; stock adjustment, damage and transfer flows.
  • Customers, suppliers and membership points at the counter.
  • Full back-office drawer mirroring the web POS sidebar, permission-gated per role: products & catalogue (categories, brands, attributes, tax rates, units, in-house products, deleted products, barcodes), purchase orders, customer/supplier returns, return requests & replacements, branches/warehouses/racks, users & roles, the accounting suite (chart of accounts, bank/MFS accounts, fund transfers, expense, investment, owner withdraw, liability settlement, journals, bank deposits, transactions, reconciliation), loyalty & rewards, and promotions (flash sales, coupons, campaigns).
  • Petty expenses and cash pickup against the open register; counter management.
  • Reports hub with per-report drawer links (stock, purchase, accounting and cash families) and Firebase push notifications (stock & transfer alerts).
  • Audit screens: login activity, activity logs, media library and archive with restore.
  • Secure token storage (flutter_secure_storage); multi-language EN/AR/BN with RTL.

FAQ

Q: Do the app and the web POS share data?
Yes — same backend, same counters, same registers, same stock. A sale in the app appears in the web admin instantly, and vice versa.

Q: Where do I set the server URL?
In lib/core/env/prod_env.dart (release builds) — set kBaseUrl to https://yourdomain.com/api/v10. Debug builds read dev_env.dart.

Q: Why can't I log in?
Confirm the staff account exists and is active on the backend, the API URL is correct and served over valid HTTPS, and the App API key matches Settings → API Security.

Q: Why is the counter list empty?
Either every counter of the active branch already has an open register, or your branch has no counters yet — create them in the web admin under POS Management → Counters.

Q: Why doesn't a user see a menu item that I see?
Their role lacks that page's permission — the drawer only draws items the signed-in role may use. Grant the permission under Users & Roles (in the app or the web admin) and have them re-open the app.

Q: Can I change the app name and icon?
Yes — see App Name & Icon, and set a unique package/bundle id before publishing.