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 2–4 weeks of focused PRs.
- Split work into small PRs:
api | dashboard | mobile | infra | docs.
- Mark items Built (thin) vs Greenfield — don’t 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
| Phase |
Theme |
Outcome |
Duration |
| 0 |
Quality & ops docs |
Safe public surface, CI confidence |
1–2 weeks |
| 1 |
Public discover |
Consumer-facing کافهیاب for Tehran/Karaj |
2–3 weeks |
| 2 |
Growth & community |
Loyalty, reviews 2.0, badges |
3–4 weeks |
| 3 |
Operations |
Queue polish, printers, shifts, terminals |
3–4 weeks |
| 4 |
Integrations |
Maps, delivery parity, hardware onboarding |
3–4 weeks |
| 5 |
Platform & Enterprise |
Admin split, API keys, audit, export |
4–6 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 (3–5 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 |
2–3 stable tests green in GitHub Actions |
Q-3 — Load tests (public QR + OTP)
|
|
| Effort |
M (2–3 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
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.5–2 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).
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 |
M–L (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 |
S–M (3–5 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 |
M–L (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 (2–3 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 |
M–L (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)
docs/SECURITY.md (Q-1)
- Playwright smoke POS (Q-2)
- Public discover filters API + page (G-1)
- Discover map embed Neshan (I-1 minimal)
- Review reply UI + public display (G-6 partial)
- Loyalty earn on pay (G-4)
- Terminal enforcement (O-4)
- Queue public ticket + plan gate (O-1)
- Snappfood outbound hardening (I-2)
- Shift close MVP (O-3)
Effort summary
| Bucket |
Items |
Rough total |
| Quality |
Q-1–Q-4 |
~2 weeks |
| Growth & community |
G-1–G-6 |
~6–8 weeks |
| Operations |
O-1–O-4 |
~5–6 weeks |
| Integrations |
I-1–I-4 |
~5–6 weeks |
| Platform |
P-1–P-4 |
~6–8 weeks |
Calendar (1 dev): ~20–24 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:
- “Implement G-1 PR-1 only: extend
GET /api/public/discover filters + tests.”
- “Implement O-4 terminal registration with Redis and PlanLimits.”
- “Scaffold
web/admin and move admin routes from dashboard.”
End of roadmap — update phase exit criteria as items ship.