Files
meezi/docs/MEEZI_FEATURE_ROADMAP_PLAN.md
soroush.asadi 03376b3ea1 feat(docker): multi-stage Dockerfiles with npmmirror registry
Rewrites dashboard and finder Dockerfiles to use a clean multi-stage
build (deps → builder → runner) that installs npm packages inside
Alpine Linux, avoiding the SWC musl binary issue when building from
Windows host. Uses registry.npmmirror.com for reliable installs from
restricted networks (Iran).

- docker/api/Dockerfile: .NET 10 multi-stage build
- docker/web/Dockerfile: Node 20-alpine multi-stage, npmmirror
- docker/finder/Dockerfile: Node 20-alpine multi-stage, npmmirror
- docker/website/Dockerfile: marketing website build
- scripts/: PowerShell helper scripts for local dev

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:33:29 +03:30

394 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Meezi — Feature Roadmap Plan
> **Purpose:** Implementation plan for growth, operations, integrations, platform, and quality items.
> **Audience:** Product + engineering (solo/small team with Cursor).
> **Conventions:** `.cursorrules`, `ApiResponse<T>`, multi-tenant `CafeId`, `messages/{fa,ar,en}.json`, plan tiers Free / Pro / Business / Enterprise.
> **Last updated:** 2026-05-22
### Implementation status (started)
| Item | Status |
|------|--------|
| Q-1 `docs/SECURITY.md` | Done |
| Q-2 Playwright (`web/dashboard/e2e/`) | Done (API smoke + discover page) |
| Q-3 k6 `tests/load/public-abuse.js` | Done |
| G-1 API discover filters | Done |
| G-1 UI `/[locale]/discover` | Done (MVP) |
| Order `DisplayNumber` (digits-only) | Done |
| Table board SignalR (via KDS hub) | Done |
| O-4 Terminal Redis enforcement | Done (API + settings UI) |
| P-1 Admin split (`web/admin` + compose) | Partial (redirect from dashboard) |
| G-1 discover detail + Neshan embed | Done |
| G-6 Review owner reply (dashboard + public) | Done |
| G-4 Loyalty earn on pay | Done (1 pt / 10k ت) |
| O-1 Public queue ticket + plan gate + SMS | Done |
| O-3 Shift close UI | Done (`/shifts`) |
| I-2 Snappfood outbound on status/pay | Done |
---
## How to use this doc
- Work in **phases** (below); each phase is 24 weeks of focused PRs.
- Split work into **small PRs**: `api` | `dashboard` | `mobile` | `infra` | `docs`.
- Mark items **Built (thin)** vs **Greenfield** — dont rebuild what already exists.
- **Plan gates** are called out per feature; wire via `IPlatformCatalogService` + `PlanLimitMiddleware` / `IPlanLimitChecker`.
---
## Current baseline (relevant to this roadmap)
| Area | Already in repo |
|------|------------------|
| Discover profile | `CafeDiscoverProfile`, merchant/admin editor, `GET /api/public/discover`, taxonomy |
| Reviews | `CafeReview`, public create; **`OwnerReply` on entity** — UI/public display thin |
| Queue | `QueueController`, `queue-screen.tsx`, feature flag `queue` in seeder |
| Loyalty | `Customer.LoyaltyPoints` field — **no earn/redeem rules or UI** |
| Delivery | Inbound webhooks (Snappfood/Tap30/Digikala), `DeliveryStatusSyncService`**outbound Snappfood partial** |
| Terminals | `PlanLimits.MaxTerminals`, JWT/header patterns — **enforcement incomplete** |
| Security | Turnstile + Redis abuse limits — **`docs/SECURITY.md` missing** |
| Admin | `Meezi.Admin.API` exists; **admin UI still in `web/dashboard`** |
---
## Phase overview
```mermaid
flowchart LR
P0[Phase 0\nQuality + Ops]
P1[Phase 1\nDiscover public]
P2[Phase 2\nGrowth CRM]
P3[Phase 3\nOperations]
P4[Phase 4\nIntegrations]
P5[Phase 5\nPlatform Enterprise]
P0 --> P1
P1 --> P2
P2 --> P3
P3 --> P4
P1 --> P4
P4 --> P5
```
| Phase | Theme | Outcome | Duration |
|-------|--------|---------|----------|
| **0** | Quality & ops docs | Safe public surface, CI confidence | 12 weeks |
| **1** | Public discover | Consumer-facing کافه‌یاب for Tehran/Karaj | 23 weeks |
| **2** | Growth & community | Loyalty, reviews 2.0, badges | 34 weeks |
| **3** | Operations | Queue polish, printers, shifts, terminals | 34 weeks |
| **4** | Integrations | Maps, delivery parity, hardware onboarding | 34 weeks |
| **5** | Platform & Enterprise | Admin split, API keys, audit, export | 46 weeks |
---
## Phase 0 — Quality & operations (do first)
### Q-1 — `docs/SECURITY.md` (ops)
| | |
|--|--|
| **Effort** | S (1 day) |
| **Deliverable** | Turnstile setup, Redis limits, rate-limit table, Arvan WAF/CDN rules (OTP, `/api/public/*`, `/api/q/*`), `X-Forwarded-For`, incident checklist |
| **Acceptance** | On-call can enable CAPTCHA and edge rules without reading source |
### Q-2 — Playwright E2E (dashboard)
| | |
|--|--|
| **Effort** | M (35 days) |
| **Scope** | `web/dashboard/e2e/`: auth OTP mock or test phone, POS happy path (table → item → pay), QR public order smoke (optional second project) |
| **CI** | Job on PR; secrets for test DB/API |
| **Acceptance** | 23 stable tests green in GitHub Actions |
### Q-3 — Load tests (public QR + OTP)
| | |
|--|--|
| **Effort** | M (23 days) |
| **Tool** | k6 or NBomber script in `tests/load/` |
| **Scenarios** | `GET /api/q/{code}`, `GET /api/public/.../menu`, `POST` guest order, `POST /api/auth/send-otp` |
| **Acceptance** | Document p95 targets; verify `429` / `RATE_LIMITED` under abuse |
### Q-4 — Package / Docker hardening
| | |
|--|--|
| **Effort** | S | NU1903 bumps, `nuget.config` + Dockerfile (done), CI `docker compose build api` optional job |
**Phase 0 exit:** SECURITY doc published, 2+ E2E tests, load script runnable locally.
---
## Phase 1 — Growth: public discover homepage
### G-1 — Public discover web app (Tehran / Karaj)
| | |
|--|--|
| **Effort** | L (1.52 weeks) |
| **Plan** | Free to browse; Pro+ cafés appear when `discover_profile` filled & `IsVerified` |
| **Route** | `web/dashboard/src/app/[locale]/(public)/discover/` **or** separate `web/discover` (lighter SEO) — recommend **public routes inside dashboard** first to reuse API client + i18n |
| **API** | Extend `GET /api/public/discover` with filters: `city` (تهران/کرج), `themes[]`, `vibes[]`, `occasions[]`, `spaceFeatures[]`, `noise`, `priceTier`, `minRating`, `sort` (rating, distance later) |
| **Backend** | Filter in SQL/EF on deserialized `DiscoverProfileJson` (JSONB query on PostgreSQL) or materialized columns if perf needed |
| **UI** | Filter chips (taxonomy from `GET /api/public/discover-profile/taxonomy`), café cards (cover, rating, badges, price tier), detail page → menu link / map link |
| **i18n** | `discoverPublic.*` in fa/ar/en |
| **Acceptance** | User can filter “date + outdoor + کرج” and open café detail; RTL correct |
### G-2 — Neshan maps (discover + detail) — *can start in Phase 1 or 4*
See **I-1** below; for discover MVP, static map embed on detail is enough.
**Phase 1 exit:** Public discover listing + detail live at `/fa/discover` (or dedicated host).
---
## Phase 2 — Growth & community (depth)
### G-3 — Customer accounts (lightweight)
| | |
|--|--|
| **Effort** | L (2 weeks) |
| **Plan** | Pro+ for “registered guests”; optional SMS OTP customer auth |
| **Model** | `CustomerAccount` (phone PK per platform or per cafe), link to `Customer` on first order; JWT `role=customer` scoped to optional `cafeId` or global |
| **API** | `POST /api/auth/customer/send-otp`, `verify-otp`, `GET /api/customers/me/orders`, `GET /api/customers/me/reservations` |
| **Apps** | `meezi_app`: login, order history; discover web: “my orders” |
| **Acceptance** | Returning guest sees past orders after OTP; no merge with staff JWT |
### G-4 — Loyalty points (earn / redeem)
| | |
|--|--|
| **Effort** | ML (1.5 weeks) |
| **Built** | `Customer.LoyaltyPoints` |
| **Rules** | `LoyaltyRule` per cafe: earn % of paid order, min redeem, expiry; Business+ feature flag `loyalty` |
| **API** | Earn on order `Paid`; redeem as discount line on POS; `PATCH` adjust (manager) |
| **UI** | CRM customer row, POS pay panel preview, SMS on milestone (optional) |
| **Acceptance** | Closed order increases points; redeem reduces total with audit row |
### G-5 — Café badges (Enterprise)
| | |
|--|--|
| **Effort** | M (1 week) |
| **Plan** | Enterprise only; admin assigns badges |
| **Model** | `CafeBadge` (key, labelFa, icon, assignedAt) or JSON on `Cafe` |
| **API** | Admin CRUD; public discover DTO includes `badges[]` |
| **UI** | Admin café detail; discover cards show badge chips |
| **Acceptance** | Only Enterprise cafés display admin-assigned badges |
### G-6 — Review photos + owner responses (polish)
| | |
|--|--|
| **Effort** | M (1 week) |
| **Built** | `OwnerReply`, `OwnerRepliedAt` on `CafeReview` |
| **Photos** | `CafeReviewPhoto` (url, sort); upload via public or authenticated; max 3 photos, 5MB, MIME validate |
| **API** | `POST /api/public/cafes/{slug}/reviews` multipart; `PATCH /api/cafes/{cafeId}/reviews/{id}/reply` (owner) |
| **UI** | Dashboard reviews screen: reply editor; public discover detail: reviews + photos |
| **Moderation** | `IsHidden` flag; admin can hide (abuse) |
| **Acceptance** | Owner reply visible on public page; photos optional |
**Phase 2 exit:** Loyalty + review 2.0 + badges; customer OTP optional for pilot.
---
## Phase 3 — Operations
### O-1 — Queue / waitlist (polish, not greenfield)
| | |
|--|--|
| **Effort** | SM (35 days) |
| **Built** | `IQueueService`, `queue-screen.tsx` |
| **Gaps** | Plan gate `queue` (Business+); public “take number” QR/tablet; SMS when called (Kavenegar); TV display mode (fullscreen Next page); branch-scoped boards |
| **API** | `POST /api/public/{cafeId}/queue/tickets` (anonymous, rate limited) |
| **Acceptance** | Walk-in gets number; staff calls next; plan limit blocks Free tier |
### O-2 — Kitchen printer routing per station
| | |
|--|--|
| **Effort** | L (2 weeks) |
| **Model** | `KitchenStation` (name, printerAddress/bluetoothId), `MenuCategory.StationId`, order route split on submit |
| **API** | CRUD stations; KDS ticket includes `stationId` |
| **Dashboard** | Settings → stations; map categories |
| **Mobile POS** | `meezi_pos` Phase 2: `bluetooth_print` per station ticket |
| **Acceptance** | Drink items print to bar printer; food to kitchen |
### O-3 — Cash drawer / shift close reports
| | |
|--|--|
| **Effort** | ML (1.5 weeks) |
| **Model** | `CashShift` (openedAt, closedAt, openingFloat, countedCash, expectedCash, variance, userId) |
| **API** | Open/close shift; Z-report snapshot (orders, payments, voids, discounts) |
| **UI** | POS: “بستن شیفت”; PDF/printable summary; manager-only |
| **HR tie-in** | Optional link to `Employee` clock-out |
| **Acceptance** | Cannot close shift with open tables; report matches day orders |
### O-4 — Multi-terminal enforcement
| | |
|--|--|
| **Effort** | M (1 week) |
| **Built** | `PlanLimits.MaxTerminals`, `X-Meezi-Terminal-Id` mentioned for POS |
| **Implementation** | Redis set `terminals:{cafeId}` with TTL; register on staff login/refresh; reject 4th terminal on Free with `PLAN_LIMIT_REACHED` |
| **Dashboard** | Settings → active terminals list + revoke |
| **Acceptance** | Free cafe blocked on 2nd concurrent terminal session |
**Phase 3 exit:** Queue production-ready; shift close; terminals enforced; printer routing MVP.
---
## Phase 4 — Integrations
### I-1 — Neshan maps (discover + delivery radius)
| | |
|--|--|
| **Effort** | M (1 week) |
| **Config** | `Neshan:ApiKey` in platform settings / cafe settings |
| **Discover** | Geocode café address; embed map on detail; optional “near me” sort (browser geolocation + distance) |
| **Delivery radius** | `Cafe.DeliveryRadiusKm` + circle check for guest delivery orders (future) |
| **Acceptance** | Map loads on café page; cities filtered Tehran/Karaj |
### I-2 — Snappfood outbound status updates
| | |
|--|--|
| **Effort** | M (1 week) |
| **Built** | `DeliveryStatusSyncService`, `ISnappfoodClient` |
| **Work** | On order status → Ready/OutForDelivery/Delivered call Snappfood API; idempotent; Hangfire retry; log failures to `WebhookLog` |
| **Dashboard** | Delivery settings: vendor id, test webhook |
| **Acceptance** | Status change in KDS triggers outbound call when `SnappfoodOrderId` set |
### I-3 — Digikala / Tap30 delivery parity
| | |
|--|--|
| **Effort** | L (2 weeks) |
| **Built** | Normalizers + webhook ingress pattern |
| **Work** | Symmetric outbound sync; commission rules; admin integration toggles; menu mapping table `DeliveryMenuMapping` |
| **Acceptance** | Same lifecycle as Snappfood for each enabled platform |
### I-4 — Hardware bundle onboarding (tablet + printer)
| | |
|--|--|
| **Effort** | M (1 week product + 1 week ops) |
| **Not code-only** | SKU in admin; café `HardwareBundlePurchasedAt` |
| **App flow** | Wizard: download POS APK, pair printer (BLE), register terminal id, test print |
| **Docs** | PDF checklist Farsi; support ticket auto-tag `hardware` |
| **Acceptance** | New Pro signup can complete wizard end-to-end |
**Phase 4 exit:** Maps on discover; delivery platforms symmetric; hardware wizard documented.
---
## Phase 5 — Platform & Enterprise
### P-1 — Separate admin web + Compose services
| | |
|--|--|
| **Effort** | L (23 weeks) |
| **Work** | New `web/admin` Next app; move `src/app/[locale]/admin/**`; env `NEXT_PUBLIC_ADMIN_API_URL`; `docker-compose.admin.yml` (admin-api + admin-web); CORS split |
| **API** | Merchant `Meezi.API` strips `/api/admin/*` when migration complete |
| **Acceptance** | Admin users never hit merchant dashboard origin; two compose profiles documented |
### P-2 — API keys (Enterprise)
| | |
|--|--|
| **Effort** | M (1 week) |
| **Model** | `CafeApiKey` (hash, prefix, scopes, expiresAt, lastUsedAt) |
| **Auth** | `Authorization: Bearer mk_...` middleware path; scopes: `orders:read`, `menu:write`, etc. |
| **UI** | Dashboard settings (Enterprise): create/revoke keys |
| **Acceptance** | External script can `GET` orders with key; keys tenant-scoped |
### P-3 — Audit log for owners
| | |
|--|--|
| **Effort** | M (1 week) |
| **Model** | `AuditEvent` (cafeId, userId, action, entityType, entityId, diffJson, ip, at) |
| **Instrument** | Order void, refund, settings change, plan change, employee role change |
| **UI** | Settings → audit feed; filter by date/user |
| **Acceptance** | Owner sees who voided a line item |
### P-4 — Data export & GDPR-style tooling
| | |
|--|--|
| **Effort** | ML (1.5 weeks) |
| **Export** | Hangfire job: ZIP JSON (customers, orders, reviews) per café; signed download link 24h |
| **Delete** | Soft-delete existing; `POST /api/cafes/{id}/privacy/erase-customer` anonymize PII (phone hash, name redacted) |
| **Retention** | Document policy in privacy page |
| **Acceptance** | Owner can export month of CRM; erase one customer on request |
**Phase 5 exit:** Admin split deployed; Enterprise API keys + audit; export/erase available.
---
## Cross-cutting requirements (every phase)
| Rule | Action |
|------|--------|
| Multi-tenant | All EF queries filter `CafeId` |
| Plans | Feature flags in `PlatformPlanDefinitions` + `IsFeatureEnabledForCafeAsync` |
| Public abuse | Rate limit + optional Turnstile on new `POST` routes |
| i18n | No hardcoded UI strings |
| Tests | At least one integration test per new controller; E2E for critical UX |
---
## Suggested PR order (first 10 PRs)
1. `docs/SECURITY.md` (Q-1)
2. Playwright smoke POS (Q-2)
3. Public discover filters API + page (G-1)
4. Discover map embed Neshan (I-1 minimal)
5. Review reply UI + public display (G-6 partial)
6. Loyalty earn on pay (G-4)
7. Terminal enforcement (O-4)
8. Queue public ticket + plan gate (O-1)
9. Snappfood outbound hardening (I-2)
10. Shift close MVP (O-3)
---
## Effort summary
| Bucket | Items | Rough total |
|--------|-------|-------------|
| Quality | Q-1Q-4 | ~2 weeks |
| Growth & community | G-1G-6 | ~68 weeks |
| Operations | O-1O-4 | ~56 weeks |
| Integrations | I-1I-4 | ~56 weeks |
| Platform | P-1P-4 | ~68 weeks |
**Calendar (1 dev):** ~2024 weeks sequential. **Parallel (2 dev):** Phase 1 + 0 in parallel; Phase 3 + 4 overlap after Phase 2 API stable.
---
## Out of scope (unless product changes)
- Full Sepidz parity
- Native iOS/Android store apps separate from Flutter
- Real-time ML ranking for discover (start with filter + sort)
- Full Taraz production (see `CURRENT_STATE_FOR_PLANNING.md`)
---
## Planning prompts for Cursor
Copy into a session with this file attached:
1. *“Implement G-1 PR-1 only: extend `GET /api/public/discover` filters + tests.”*
2. *“Implement O-4 terminal registration with Redis and PlanLimits.”*
3. *“Scaffold `web/admin` and move admin routes from dashboard.”*
---
*End of roadmap — update phase exit criteria as items ship.*