# WBMS Flutter App — Deployment Guide

How to build, configure, sign, and ship the **WBMS** Flutter mobile app (field
meter-reading, cashiering, and company operations) that talks to the Laravel
backend at `billing.happyimart.com`.

> Scope: **company level only**. This guide covers deploying the app for the
> people inside one company — admins, cashiers, meter readers, installers. The
> SaaS / multi-company management layer and the `super_admin` role are out of
> scope and are handled by the web backend, not this app.

API contract: see [FLUTTER_API_DOCUMENTATION.md](FLUTTER_API_DOCUMENTATION.md).

---

## 1. Prerequisites

| Tool | Version |
|------|---------|
| Flutter SDK | 3.24+ (Dart 3.5+) |
| Android Studio / SDK | API 34, build-tools 34 |
| Xcode (iOS) | 15+ on macOS |
| Java JDK | 17 |
| CocoaPods (iOS) | latest |

```bash
flutter --version
flutter doctor          # all checks green before continuing
```

---

## 2. Environment configuration (flavors)

The app ships in three environments, each pointing at a different API base URL and
(optionally) bundling a default tenant slug.

| Flavor | API base URL | Default tenant |
|--------|--------------|----------------|
| `dev` | `http://10.0.2.2:8000/api/v1` | dev seed |
| `staging` | `https://staging.billing.happyimart.com/api/v1` | chosen at runtime |
| `prod` | `https://billing.happyimart.com/api/v1` | chosen at runtime |

Pass config at **compile time** with `--dart-define` (no secrets in source):

```bash
flutter run \
  --flavor dev \
  --dart-define=API_BASE_URL=http://10.0.2.2:8000/api/v1 \
  --dart-define=DEFAULT_TENANT=barangay-ws
```

Read it in Dart:

```dart
class Env {
  static const apiBaseUrl = String.fromEnvironment('API_BASE_URL',
      defaultValue: 'https://billing.happyimart.com/api/v1');
  static const defaultTenant = String.fromEnvironment('DEFAULT_TENANT');
}
```

Keep per-flavor `--dart-define-from-file` JSON files **out of git** (add to
`.gitignore`) and provide a committed `config.example.json`.

---

## 3. Android build & signing

### 3.1 Create a keystore (once, keep it safe & backed up)

```bash
keytool -genkey -v -keystore ~/wbms-upload.jks \
  -keyalg RSA -keysize 2048 -validity 10000 -alias wbms
```

### 3.2 `android/key.properties` (git-ignored)

```properties
storePassword=********
keyPassword=********
keyAlias=wbms
storeFile=/absolute/path/wbms-upload.jks
```

### 3.3 Wire signing into `android/app/build.gradle`

```gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
        }
    }
}
```

### 3.4 Build artifacts

```bash
# App Bundle for Play Store
flutter build appbundle --release --flavor prod \
  --dart-define=API_BASE_URL=https://billing.happyimart.com/api/v1

# APK for direct/sideload distribution to field staff
flutter build apk --release --flavor prod \
  --dart-define=API_BASE_URL=https://billing.happyimart.com/api/v1
```

Output: `build/app/outputs/bundle/prodRelease/app-prod-release.aab`.

### 3.5 Permissions (`AndroidManifest.xml`)

Only request what the role workflows need:
`INTERNET`, `ACCESS_FINE_LOCATION` (geo-tag readings), `CAMERA` (meter photos).

---

## 4. iOS build & signing

```bash
cd ios && pod install && cd ..
flutter build ipa --release --flavor prod \
  --dart-define=API_BASE_URL=https://billing.happyimart.com/api/v1
```

- Set up signing in Xcode → **Signing & Capabilities** (team + provisioning profile).
- Bump `CFBundleShortVersionString` / build number to match `pubspec.yaml`.
- Upload `build/ios/ipa/*.ipa` via **Transporter** or `xcrun altool`.

---

## 5. Store deployment

### 5.1 Google Play
1. Play Console → create app → internal testing track.
2. Upload the `.aab`, complete the data-safety & content rating forms.
3. Promote internal → closed (field-staff testers) → production.

### 5.2 Apple App Store
1. App Store Connect → new app, fill metadata + privacy nutrition labels.
2. Upload build via Transporter → submit for review via TestFlight first.

### 5.3 Enterprise / direct distribution (recommended for internal staff)
Most users are company staff, not the public. Options:
- **Android**: host the signed APK on the backend behind a login, or use Play
  **internal app sharing**.
- **iOS**: TestFlight (up to 10k testers) or Apple Business Manager for managed
  distribution.

---

## 6. Company-level roles in the app

The app authenticates each user and reads `user.role` from the login response
(see API doc §3). Deployment-relevant behavior:

| Role | Default landing screen | Notes |
|------|------------------------|-------|
| `company_admin` | Dashboard | Full company access; can approve readings, view all data |
| `cashier` | Payments / collections | Records payments, issues receipts |
| `meter_reader` | Assigned route list | **Offline-first**; queues readings, syncs when online |
| `installer` | Work orders | Updates install/disconnection job status |
| `staff` | Read-only dashboard | Configurable, view-only |
| `customer` | My account | Self-service billing/readings view |

There is **no** `super_admin` screen in the app — tenant/company management lives
on the web backend.

**Rollout per role**: pilot with a single barangay/company, onboard meter readers
first (field validation of offline sync), then cashiers, then open admin access.

---

## 7. CI/CD (GitHub Actions example)

```yaml
name: build-android
on:
  push:
    tags: ['v*']
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.24.x' }
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test
      - name: Restore keystore & key.properties from secrets
        run: |
          echo "$KEYSTORE_B64" | base64 -d > android/wbms-upload.jks
          printf '%s' "$KEY_PROPERTIES" > android/key.properties
        env:
          KEYSTORE_B64: ${{ secrets.KEYSTORE_B64 }}
          KEY_PROPERTIES: ${{ secrets.KEY_PROPERTIES }}
      - run: |
          flutter build appbundle --release --flavor prod \
            --dart-define=API_BASE_URL=${{ secrets.API_BASE_URL }}
      - uses: actions/upload-artifact@v4
        with: { name: app-bundle, path: build/app/outputs/bundle/prodRelease/*.aab }
```

Store the keystore (base64), `key.properties`, and API URLs as **encrypted CI
secrets** — never commit them.

---

## 8. Versioning & release checklist

`pubspec.yaml` → `version: 1.4.0+24` (semver `+` build number; bump build every upload).

Before each release:
- [ ] `flutter analyze` and `flutter test` pass
- [ ] Pointed at the correct `API_BASE_URL` for the target environment
- [ ] App version & build number bumped
- [ ] Certificate pinning / HTTPS verified against prod
- [ ] Offline reading sync tested on a flaky connection
- [ ] Role gating verified: each role sees only its screens
- [ ] Login → `/auth/me` → logout round-trip works
- [ ] Crash reporting (Sentry/Firebase Crashlytics) enabled in release
- [ ] Backed up the keystore & passwords in the company password vault

---

## 9. Backend coordination

The app depends on these backend pieces (Laravel) — **all implemented and verified**:
- Token auth endpoints `/api/v1/auth/login|me|logout` — implemented with Laravel
  Sanctum in `App\Http\Controllers\Api\AuthController`; routes in
  [routes/api.php](routes/api.php) under the `v1` prefix. See API doc §2.
- Role gating via the `api.role` middleware
  (`App\Http\Middleware\EnsureApiRole`), aliased in
  [bootstrap/app.php](bootstrap/app.php), using `App\Models\Role` constants.
- Personal access tokens stored in the `personal_access_tokens` table (Sanctum
  migration applied).

With per-user tokens the company is derived from the authenticated user, so the
legacy `X-Tenant` header is **optional** on token-authenticated routes (it is
still required by the older `MeterReadingApiController` api_key endpoints).

Coordinate API base URLs and CORS allowed origins with the backend team before
each environment goes live.

---

**Version**: 1.1 · **Last updated**: 2026-06-20 · Companion to
[FLUTTER_API_DOCUMENTATION.md](FLUTTER_API_DOCUMENTATION.md)
</content>
