# Baskit — Project Build Rundown

**Generated:** 2026-07-28  
**Repository:** `baskit_app` (Flutter customer app + thin catalogue API)  
**Sibling:** `baskit_ops` (CodeIgniter 4 admin console + transactional API on PostgreSQL)

---

## 1. What Baskit Is

Baskit is a South African grocery comparison and shopping app. Shoppers browse a live price catalogue across **Pick n Pay**, **Checkers Sixty60**, and **Woolworths**, build a basket, optionally run **Smart Split** (cheapest retailer per line), check out via **PayFast** (sandbox), and track orders. Post-delivery **returns** (photo + reason) flow into the Ops console for review.

Phase 1 shipped as a **browsing MVP** backed by Dokploy MySQL. Phase 2 added design-aligned UI, local auth/checkout stubs, Group Buy, picker demo, analytics, sponsored placements, and Ops order sync.

---

## 2. Architecture at a Glance

```mermaid
flowchart TB
    subgraph Client["Flutter App (Android / iOS / Web)"]
        UI[Screens & go_router]
        State[Provider state: Basket, User, Order, Picker]
        Analytics[AnalyticsService batch flush]
    end

    subgraph CatalogueAPI["Catalogue API (PHP PDO)"]
        API[index.php — read-only JSON]
        RL[Per-IP rate limit]
    end

    subgraph OpsAPI["Baskit Ops (CI4)"]
        Orders[POST /api/v1/orders]
        Returns[POST/GET /api/v1/returns]
        Placements[GET /api/v1/placements]
        AnalyticsIn[POST /api/v1/analytics/events:batch]
        Admin[Admin console — Returns, Orders, Catalog, Pickers]
    end

    subgraph Data["Data stores"]
        MySQL[(Dokploy MySQL — catalogue + mvp_catalogue)]
        PG[(Ops PostgreSQL — orders, returns, users, PIM)]
    end

    UI --> State
    UI --> Analytics
    State -->|X-Api-Key| API
    State -->|X-App-Key| OpsAPI
    Analytics --> AnalyticsIn
    API --> MySQL
    OpsAPI --> PG
    Admin --> PG
```

| Layer | Tech | Host |
|-------|------|------|
| Customer app | Flutter 3.38 / Dart 3.10 | Device / browser |
| Catalogue API | PHP 8 PDO, read-only | `https://baskitapi.appnotify.co.za` or local WAMP |
| Ops backend | CodeIgniter 4 + Shield | `https://baskitops.appnotify.co.za` or Docker `:8080` |
| Price pipeline DB | MySQL 8 | Dokploy `102.37.22.152:3307` / database `baskit` |
| Transactional DB | PostgreSQL | Ops Docker stack |

---

## 3. Build Timeline (Chronological)

### 2026-07-08 — TASK-001: Customer Browsing MVP ✅

| Deliverable | Detail |
|-------------|--------|
| **Thin PHP catalogue API** | Routes: `aisles`, `products`, `product`, `deals`, `stats`. API key auth + per-IP rate limit. |
| **Flutter app scaffold** | `go_router`, `provider`, Baskit theme (olive `#2a2e13`, cream `#f5f2e3`, wheat `#ece4b5`, green `#5e891e`). |
| **Onboarding** | Splash → intro carousel → welcome-back (auto-advance 2.5s). |
| **Browse flow** | Home (aisles + deals), Shop tab, category grids, type-ahead search, product detail with cross-store compare. |
| **Local basket** | In-memory `BasketModel`; grouped by retailer; checkout stubbed. |
| **Live data** | ~825 MVP products from `mvp_catalogue`; verified against Dokploy MySQL. |

**Version:** `0.1.0`

---

### 2026-07-09 — Phase 2 UI & Demo Flows

| Version | What shipped |
|---------|--------------|
| **0.2.0** | Local auth (sign-in, register, address verify); My Baskit redesign; Smart Split; checkout demo; Group Buy tab; Profile tab; Smart Split unit test. |
| **0.2.1** | Order history; delivery address editor; delivery slot picker; Help & support FAQ; search/category/product polish. |
| **0.2.2** | PayFast sandbox checkout; last-shop strip; order detail with reorder/copy; picker app demo (`/picker`); local order persistence. |
| **0.2.3** | App launcher icon (Android, iOS, web). |
| **0.2.4** | Splash with real logo (green bg + cream B mark). |
| **0.2.5** | Welcome-back screen with lifetime savings hero. |
| **0.2.6** | Home screen design alignment (deal carousel, category tiles, bottom nav). |
| **0.2.7** | Official category artwork + inverted variants for home tiles. |
| **0.2.8** | Category screens (subcategory strips, related aisles); search grouped by aisle. |
| **0.2.9** | Product detail design alignment. |
| **0.2.3–0.3.0** | Group Buy feed + join deal screens; basket screen design (price arrows, BEST PRICE, OPTIMISE, CHECKOUT). |

---

### 2026-07-10 — Ops Backend Foundation (sibling `baskit_ops`)

PostgreSQL schema and admin console for the full platform:

- Retailers & stores
- PIM catalog (categories, products, retailer prices, SKUs, substitutions)
- Customers (profiles, addresses, wallets, devices)
- Pickers (profiles, store coverage, shifts, earnings, prepaid cards)
- Orders spine (orders → legs → items → events → refunds)
- Payments (webhook log, leg reconciliation)
- Placements (sponsored ad slots)
- Support tickets
- Ingestion / pipeline alerts / heartbeat

---

### 2026-07-12–13 — Integrations

| Feature | Detail |
|---------|--------|
| **Docker catalogue API** | Deploy bundle at `baskitapi.appnotify.co.za`. |
| **Sponsored placements** | Home carousel pulls live creatives from Ops `GET /api/v1/placements`. |
| **Canonical categories** | 7 shop buckets (`vas_airtime`, `home_essentials`, `fresh_produce`, `bakery`, `dairy_eggs`, `meat_poultry`, `other`) — API normalizes pipeline buckets. |
| **App analytics** | `AnalyticsService` batches screen views + tap heatmaps to Ops `app_analytics_events`. |
| **Picker API support** | Ops migrations for picker job feed. |

---

### 2026-07-14 — TASK-002: Customer Returns ✅ (in progress in docs)

| Component | Detail |
|-----------|--------|
| **Ops `returns` table** | Post-delivery return requests (photo + reason), separate from `refunds`. |
| **Ops admin** | Returns inbox — list, detail, approve/reject, photo stream. Permissions: `returns.view`, `returns.manage`. |
| **Customer API** | `POST /api/v1/returns` (multipart), `GET /api/v1/returns?order_code=`. |
| **Flutter** | Delivered order → Return → select line → photo + comment → submit; line marked `returnRequested`. |
| **Ops order sync** | `ApiClient.placeOrder()` POSTs checkout basket to Ops; pickers see same `order_code`. |

**Version:** `0.4.0`

---

### 2026-07-28 — Current State (uncommitted WIP)

Working tree changes (not yet committed) touch navigation and checkout polish:

| File | Change |
|------|--------|
| `lib/main.dart` | Route structure refinements (shell vs overlay routes; basket redirect fix). |
| `lib/features/checkout/payfast_screen.dart` | PayFast flow expansion. |
| `lib/features/orders/return_screens.dart` | Return UI tweaks. |
| `lib/core/browse_nav.dart` | Browse navigation helpers. |
| `lib/features/auth/onboarding/profile` | Minor screen updates. |

Navigation test screenshots exist under `docs/ui/screenshots/_nav_*.png` (boot through PayFast, slots, paid, basket).

---

## 4. Flutter App — Features & Routes

### 4.1 State management (`provider`)

| Model | Storage | Purpose |
|-------|---------|---------|
| `BasketModel` | In-memory | Lines, qty, per-line retailer selection, Smart Split math |
| `UserModel` | In-memory | Sign-in stub, delivery address, lifetime savings display |
| `OrderModel` | `SharedPreferences` + Ops API | Order history, active order, `placeFromBasket()` → Ops |
| `PickerModel` | In-memory | Demo picker jobs |
| `AnalyticsService` | Session in prefs, event queue | Screen views, taps, commerce events → Ops |

### 4.2 Tab shell (bottom nav)

| Tab | Path | Screens |
|-----|------|---------|
| Home | `/home` | Deals carousel, category tiles, last-shop strip, sponsored placements |
| Shop | `/shop` | All aisles list |
| Group Buy | `/group-buy` | Deals feed → deal detail → success |
| Profile | `/profile` | Savings, address, orders link, sign in/out |

Nested under Home/Shop: `category/:bucket`, `search`, `product/:barcode`, `basket`.

### 4.3 Full route map

| Route | Screen | Data source |
|-------|--------|-------------|
| `/` | Splash | Local |
| `/intro` | Intro carousel | Local |
| `/welcome` | Welcome back | Local (hardcoded savings until ledger) |
| `/sign-in`, `/register`, `/register/address` | Auth flow | Local stub |
| `/home`, `/shop` + nested | Browse | **Live catalogue API** |
| `/group-buy`, `/group-buy/:dealId/success` | Group Buy | Local mock deals |
| `/profile` | Profile | Local + order history |
| `/smart-split` | Smart Split optimiser | BasketModel |
| `/checkout` | Checkout summary | Local |
| `/checkout/slots` | Delivery slot picker | Local |
| `/checkout/payfast` | PayFast sandbox | Local → places order |
| `/order/success`, `/order/track` | Post-checkout | Local |
| `/orders`, `/orders/:orderId` | Order history & detail | Local prefs (+ Ops sync on place) |
| `/orders/:orderId/return` | Return item select | Local |
| `/orders/:orderId/return/:lineIndex` | Return submit (photo) | **Ops returns API** |
| `/delivery-address`, `/help` | Profile utilities | Local |
| `/picker`, `/picker/home`, `/picker/active` | Picker demo | Local mock jobs |

### 4.4 Key user journeys

```mermaid
sequenceDiagram
    participant U as Shopper
    participant App as Flutter App
    participant Cat as Catalogue API
    participant Ops as Baskit Ops

    Note over U,Ops: Browse & basket
    U->>App: Open app
    App->>Cat: GET aisles, deals, products
    Cat-->>App: JSON catalogue
    U->>App: Add to Baskit
    App->>App: BasketModel (local)

    Note over U,Ops: Checkout
    U->>App: Smart Split → Checkout → PayFast
    App->>Ops: POST /api/v1/orders
    Ops-->>App: order_code (e.g. BK-xxxx)
    App->>App: Persist order locally

    Note over U,Ops: Return (post-delivery)
    U->>App: Order detail → Return → photo + reason
    App->>Ops: POST /api/v1/returns (multipart)
    Ops-->>App: { id, status: pending }
    App->>App: markReturnRequested on line
```

---

## 5. APIs

### 5.1 Catalogue API (`api/index.php`)

**Base:** `https://baskitapi.appnotify.co.za/index.php`  
**Auth:** `X-Api-Key` header or `?key=`  
**Rate limit:** Configurable per-IP (default 120/min)

| Route | Params | Returns |
|-------|--------|---------|
| `?r=aisles` | — | 7 canonical categories + product counts |
| `?r=products` | `bucket`, `q`, `page`, `limit` | Paginated MVP products, sorted by savings |
| `?r=product` | `barcode` | Product + per-retailer offers from `catalogue` |
| `?r=deals` | `limit` | Top cross-store savings |
| `?r=stats` | — | MVP + catalogue row counts |

### 5.2 Baskit Ops API (customer-facing)

**Base:** `https://baskitops.appnotify.co.za`  
**Auth:** `X-App-Key` (same key as catalogue for now)

| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/v1/orders` | Place order from checkout (creates legs for pickers) |
| GET | `/api/v1/orders/{code}` | Order status (e.g. unlock Return when delivered) |
| POST | `/api/v1/orders/{code}/delivered` | Mark delivered (simulator / ops hook) |
| POST | `/api/v1/returns` | Submit return (multipart: order_code, item_name, reason, photo) |
| GET | `/api/v1/returns` | List returns for order_code |
| GET | `/api/v1/placements` | Live ad creatives (hero, deals slots) |
| POST | `/api/v1/analytics/events:batch` | Batched app analytics |

---

## 6. Entity Relationship Diagrams

### 6.1 Dokploy MySQL — Price Pipeline (`baskit` database)

Read-only source for the catalogue API. No orders or users here.

```mermaid
erDiagram
    catalogue {
        varchar retailer PK
        varchar product_key PK
        varchar barcode_raw
        varchar barcode_norm
        tinyint is_instore_bc
        varchar name
        varchar brand
        decimal price
        decimal was_price
        text image_url
        text url
        varchar bucket
    }

    mvp_catalogue {
        varchar barcode PK
        varchar bucket
        varchar name
        varchar brand
        tinyint n_retailers
        decimal pnp_price
        decimal checkers_price
        decimal woolworths_price
        decimal min_price
        decimal max_price
    }

    catalogue ||--o| mvp_catalogue : "barcode_norm = barcode"
```

**Relationship:** Many `catalogue` rows (one per retailer SKU) join to one `mvp_catalogue` row on normalized barcode. MVP row holds pivoted cross-store prices; `catalogue` holds raw scrape rows with images and URLs.

**Retailers in data:** `pnp`, `checkers`, `woolworths` (Spar mocked in UI only).

---

### 6.2 Baskit Ops PostgreSQL — Platform Schema

Full transactional model in sibling repo `baskit_ops`.

```mermaid
erDiagram
    users ||--o| customer_profiles : extends
    users ||--o| picker_profiles : extends
    users ||--o{ addresses : has
    users ||--o| wallets : has
    wallets ||--o{ wallet_transactions : ledger
    users ||--o{ devices : push

    retailers ||--o{ stores : has
    retailers ||--o{ retailer_prices : prices
    categories ||--o{ products : contains
    products ||--o{ retailer_prices : priced_at
    products ||--o{ product_tag : tagged
    tags ||--o{ product_tag : tagged
    products ||--o{ retailer_skus : mapped
    retailers ||--o{ retailer_skus : sku
    substitution_groups ||--o{ substitution_members : contains
    products ||--o{ substitution_members : substitute

    users ||--o{ orders : places
    orders ||--o{ order_legs : split_by_retailer
    retailers ||--o{ order_legs : fulfilled_at
    stores ||--o{ order_legs : store
    users ||--o{ order_legs : picker
    order_legs ||--o{ order_items : contains
    orders ||--o{ order_items : contains
    products ||--o{ order_items : product
    orders ||--o{ order_events : audit
    orders ||--o{ refunds : money_back

    orders ||--o{ returns : return_request
    order_items ||--o{ returns : line
    users ||--o{ returns : reviewed_by

    products ||--o{ placements : sponsored
    orders ||--o{ payment_webhooks : payment

    picker_profiles ||--o{ picker_store_coverage : covers
    stores ||--o{ picker_store_coverage : covered
    users ||--o{ picker_shifts : works
    users ||--o{ picker_earnings : paid
    prepaid_cards ||--o{ card_transactions : ledger
    users ||--o{ prepaid_cards : assigned

    users {
        bigint id PK
        varchar email
        varchar username
    }

    orders {
        bigint id PK
        varchar code UK
        bigint customer_id FK
        varchar status
        numeric charged_total
        numeric smart_split_saving
        varchar delivery_window
        timestamp placed_at
        timestamp delivered_at
    }

    order_legs {
        bigint id PK
        bigint order_id FK
        bigint retailer_id FK
        bigint store_id FK
        bigint picker_id FK
        varchar status
        varchar handoff_code
    }

    order_items {
        bigint id PK
        bigint order_id FK
        bigint leg_id FK
        bigint product_id FK
        varchar name
        int qty
        numeric unit_price
        varchar state
    }

    returns {
        bigint id PK
        bigint order_id FK
        bigint order_item_id FK
        varchar order_code
        varchar customer_email
        varchar item_name
        varchar reason
        varchar photo_path
        varchar status
        bigint reviewed_by FK
    }

    catalogue {
        note "MySQL only — not in Ops PG"
    }

    mvp_catalogue {
        note "MySQL only — read via PHP API"
    }
```

---

### 6.3 App ↔ Backend Linkage (logical)

```mermaid
flowchart LR
    subgraph FlutterLocal["Flutter (local)"]
        Basket[BasketModel]
        Orders[OrderModel / SharedPreferences]
        User[UserModel stub]
    end

    subgraph MySQL["Dokploy MySQL"]
        MVP[mvp_catalogue]
        CAT[catalogue]
    end

    subgraph OpsPG["Ops PostgreSQL"]
        O[orders]
        R[returns]
        P[placements]
        A[app_analytics_events]
    end

    Basket -->|browse| MVP
    Basket -->|images/offers| CAT
    Orders -->|placeOrder| O
    Orders -->|submitReturn| R
    FlutterLocal -->|fetchPlacements| P
    FlutterLocal -->|analytics batch| A
    R -.->|soft link order_code| O
```

---

## 7. Processes & Operations

### 7.1 Price ingestion (pipeline → MySQL)

1. Scraper workers pull retailer catalogues (PnP, Checkers, Woolworths).
2. Rows land in `catalogue` (per retailer, per product_key).
3. Matching job builds `mvp_catalogue` (barcode-keyed, cross-store prices).
4. Thin PHP API reads MySQL; Flutter never connects directly.
5. Ops console has separate PIM tables for future canonical product management + ingestion alerts.

### 7.2 Shopper checkout (current)

1. Build basket from live prices; optionally run Smart Split.
2. Select delivery slot (local UI).
3. PayFast sandbox — demo merchant login.
4. `OrderModel.placeFromBasket()` → `POST /api/v1/orders` to Ops.
5. Order stored locally in `SharedPreferences`; Ops creates `orders` + `order_legs` + `order_items`.
6. Success / tracking screens (local status simulation per retailer).

### 7.3 Returns (live v1)

1. Order must be **delivered** (local status or Ops `delivered_at`).
2. Shopper taps **Return** on order detail → selects line item.
3. Captures photo (`image_picker`) + reason text.
4. `ApiClient.submitReturn()` → Ops stores row in `returns` (status `pending`).
5. Ops agent reviews in **Returns** inbox → approve or reject.
6. **No auto wallet credit** in v1 — money movement stays on separate `refunds` path.

### 7.4 Picker demo (local + Ops-ready)

1. `/picker` gate → sign in as `sipho@baskit.co.za`.
2. Job feed, accept job, aisle pick, substitute sheet, bag/handoff code.
3. Ops schema supports real picker legs; Flutter uses mock data for demo.

### 7.5 Analytics

1. `AnalyticsService` logs screen views on route change.
2. `AnalyticsTapLayer` captures normalized tap coordinates.
3. Events queue locally; flush every 30s or on app pause.
4. Batch POST to Ops → `app_analytics_events` (heatmaps, funnels).

### 7.6 Admin console (Ops)

Modules include: Dashboard, Orders, Returns, Customers, Pickers, Catalog, Placements, Payments, Support, Pipeline, Users/Roles (Shield).

Returns permissions: `returns.view`, `returns.manage`.

---

## 8. What's Live vs Stubbed

| Capability | Status | Notes |
|------------|--------|-------|
| Browse catalogue | **Live** | Dokploy MySQL via PHP API |
| Cross-store compare | **Live** | PnP, Checkers, Woolworths |
| Local basket | **Live** | In-memory only until sync |
| Auth / accounts | **Stub** | Local `UserModel`; no JWT |
| Checkout payment | **Demo** | PayFast sandbox |
| Order placement | **Live** | POST to Ops on pay success |
| Order history | **Hybrid** | Local prefs + Ops order_code |
| Returns submit | **Live** | Ops PostgreSQL |
| Returns approve | **Live** | Ops admin |
| Wallet / refunds auto | **Not built** | Manual refunds path in Ops |
| Group Buy | **Stub** | Mock deals, local state |
| Lifetime savings ledger | **Stub** | Hardcoded welcome screen |
| Picker app | **Demo** | Mock jobs; Ops API exists |
| Spar retailer | **UI only** | No price rows in DB |
| Customer JWT | **Future** | Uses `X-App-Key` today |

---

## 9. Repository Layout

```
baskit_app/
├── api/                    Thin PHP catalogue gateway + Docker deploy
│   ├── index.php           Route handler (aisles, products, …)
│   ├── config.php          PDO + env loader
│   └── .env.php.example    Secrets template (never commit .env.php)
├── lib/
│   ├── main.dart           App entry, go_router, providers
│   ├── theme/              Design tokens
│   ├── core/               ApiClient, AnalyticsService, browse_nav
│   ├── models/             Catalogue, Order, GroupBuy, Checkout, Picker
│   ├── state/              BasketModel, OrderModel, UserModel, PickerModel
│   ├── features/           onboarding, auth, home, shop, product, basket,
│   │                       smart-split, checkout, group-buy, orders, profile,
│   │                       picker, shell
│   └── widgets/            Shared UI components
├── assets/                 Category art, logos, retailer icons
├── docs/                   Specs, tasks, DB notes, this file
└── scope/                  Task cases + email templates

baskit_ops/                 (sibling repo)
├── app/Controllers/        Admin + Api controllers
├── app/Database/Migrations/  PostgreSQL schema
├── app/Models/             Domain models
└── app/Views/admin/        Console UI
```

---

## 10. Run & Deploy

### Flutter (defaults to production APIs)

```bash
flutter pub get
flutter run -d chrome

# Local WAMP override
flutter run --dart-define=BASKIT_API=http://localhost/baskit_app/api \
            --dart-define=BASKIT_API_KEY=baskit-dev-key-change-me
```

### Catalogue API smoke test

```
https://baskitapi.appnotify.co.za/index.php?r=stats&key=baskit-prod-key-appnotify-2026
```

### Ops (Docker)

```bash
docker compose up -d
docker compose exec app php spark migrate
```

App reachable at `http://localhost:8080/` for TestSprite / manual QA.

---

## 11. Security & Blockers

| Item | Severity | Detail |
|------|----------|--------|
| MySQL public IP + weak root password | **High** | Firewall / tunnel before production |
| API keys in app binary | **Medium** | Acceptable for beta; move to customer JWT |
| Cleartext HTTP on local WAMP | **Low** | Dev only; production uses HTTPS |
| Returns auth | **Medium** | `X-App-Key` shared with analytics until Shield shopper auth |

---

## 12. Tasks & Version History

| Task | Title | Status |
|------|-------|--------|
| TASK-001 | Customer browsing MVP | ✅ Completed 2026-07-08 |
| TASK-002 | Customer returns (app + Ops) | 🔄 In progress (core shipped 2026-07-14) |

**App changelog versions:** `0.1.0` → `0.4.0` (see `docs/CHANGELOG.md`).

**Recent commits (main branch):**

1. `[TASK-002] feat: customer returns flow and Ops place-order sync`
2. `fix(ui): restore inverted category icons and polish splash/onboarding`
3. `feat(analytics): batch app events to Baskit Ops with tap heatmaps`
4. `feat(catalog): canonical shop categories for app and API`
5. `feat(app): sponsored placements carousel and retailer logos on home`
6. `feat(app): Flutter customer app MVP with design-aligned screens`
7. `feat(api): add Docker deploy bundle for Dokploy`

---

## 13. Design & Documentation References

| Asset | Location |
|-------|----------|
| UI handoff / mockups | `docs/ui/` |
| Brand decks | `docs/BASKIT Brand Ideation.pptx`, `docs/BASKIT UX Navigation.pptx` |
| Browsing MVP scope | `docs/scope_browsing_mvp.html` |
| Returns scope | `docs/scope_customer_returns.html` |
| Category mapping | `docs/CATEGORIES.md` |
| MySQL connection | `docs/dokploy-mysql-remote.md` |
| Task tracking | `docs/index.md`, `docs/tasks/TASK-*.md` |
| Navigation test shots | `docs/ui/screenshots/_nav_*.png` |

**Brand colours:** olive `#2a2e13`, cream `#f5f2e3`, wheat `#ece4b5`, green `#5e891e`, lime accents for CTAs and savings.

---

## 14. Recommended Next Steps

1. Commit and test uncommitted navigation / PayFast WIP (2026-07-28).
2. Lock down Dokploy MySQL (firewall, strong credentials).
3. Ship Shield customer auth → replace `X-App-Key` on returns and orders.
4. Sync Flutter order status from Ops (poll `GET /api/v1/orders/{code}`).
5. Optional: approve return → auto-create `refunds` row + wallet credit.
6. Promote catalogue PHP API into CI4 when unified backend is ready.
7. Add Spar when pipeline has live price rows.

---

*This document is a point-in-time snapshot. Update with `-docs` at session end or regenerate when major features ship.*
