763 lines
23 KiB
Markdown
763 lines
23 KiB
Markdown
|
|
# راهنمای کامل توسعه Meezi با Cursor
|
|||
|
|
> از صفر تا اولین سفارش واقعی — گام به گام
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## پیشنیازها — قبل از هر چیز نصب کن
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. Node.js 20+
|
|||
|
|
node --version # باید v20 یا بالاتر باشد
|
|||
|
|
# دانلود: https://nodejs.org
|
|||
|
|
|
|||
|
|
# 2. .NET 10 SDK
|
|||
|
|
dotnet --version # باید 8.x باشد
|
|||
|
|
# دانلود: https://dotnet.microsoft.com/download/dotnet/8.0
|
|||
|
|
|
|||
|
|
# 3. Flutter 3.x
|
|||
|
|
flutter --version
|
|||
|
|
# دانلود: https://docs.flutter.dev/get-started/install
|
|||
|
|
|
|||
|
|
# 4. Docker Desktop (برای PostgreSQL و Redis محلی)
|
|||
|
|
docker --version
|
|||
|
|
# دانلود: https://www.docker.com/products/docker-desktop
|
|||
|
|
|
|||
|
|
# 5. pnpm
|
|||
|
|
npm install -g pnpm
|
|||
|
|
|
|||
|
|
# 6. Cursor
|
|||
|
|
# دانلود: https://cursor.com
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۱ — ساختار پروژه را بساز
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# یک پوشه اصلی بساز
|
|||
|
|
mkdir meezi && cd meezi
|
|||
|
|
|
|||
|
|
# Git راهاندازی کن
|
|||
|
|
git init
|
|||
|
|
echo "node_modules/\n.next/\nbuild/\n*.user\n.env*\n!.env.example" > .gitignore
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۲ — فایلهای کلیدی را بریز
|
|||
|
|
|
|||
|
|
### `.cursorrules` — در ریشه پروژه
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
You are building Meezi (میزی) — a Persian-first SaaS POS and community
|
|||
|
|
platform for Iranian cafés in Tehran and Karaj.
|
|||
|
|
|
|||
|
|
CONTEXT: Read MEEZI_PRD.md for full product details before any task.
|
|||
|
|
|
|||
|
|
STACK:
|
|||
|
|
- Backend: ASP.NET Core 10 (C#) in src/Meezi.API
|
|||
|
|
- Web: Next.js 14 TypeScript in web/dashboard
|
|||
|
|
- Mobile: Flutter 3 Dart in mobile/meezi_app
|
|||
|
|
- DB: PostgreSQL + Redis
|
|||
|
|
- ORM: Entity Framework Core 10
|
|||
|
|
|
|||
|
|
PRODUCT:
|
|||
|
|
- Brand: Meezi (میزی) | میزت منتظرته
|
|||
|
|
- Markets: Tehran + Karaj (V1)
|
|||
|
|
- Languages: Farsi (default) + Arabic + English
|
|||
|
|
- Competitor: Sepidz (legacy license, no SaaS)
|
|||
|
|
- Pricing: Free / Pro 1.49M / Business 3.49M / Enterprise
|
|||
|
|
|
|||
|
|
C# RULES:
|
|||
|
|
- Async/await everywhere — never .Result or .Wait()
|
|||
|
|
- EVERY EF query filters by CafeId (multi-tenant)
|
|||
|
|
- Return ApiResponse<T> always: { bool Success, T? Data, ApiError? Error }
|
|||
|
|
- Use record types for DTOs
|
|||
|
|
- FluentValidation for all inputs
|
|||
|
|
- Hangfire for background jobs
|
|||
|
|
- SignalR for real-time KDS
|
|||
|
|
- Never Console.WriteLine — use ILogger<T>
|
|||
|
|
|
|||
|
|
NEXT.JS RULES:
|
|||
|
|
- next-intl for i18n — ALL strings in messages/fa.json, ar.json, en.json
|
|||
|
|
- RTL for fa/ar — LTR for en
|
|||
|
|
- ms-* me-* ps-* pe-* for spacing (never ml mr pl pr)
|
|||
|
|
- TanStack Query v5 for server state
|
|||
|
|
- Zustand for cart and UI state
|
|||
|
|
- date-fns-jalali for ALL dates — never show Gregorian
|
|||
|
|
- Numbers: .toLocaleString('fa-IR')
|
|||
|
|
- Currency: n.toLocaleString('fa-IR') + ' ت'
|
|||
|
|
- shadcn/ui components always
|
|||
|
|
|
|||
|
|
FLUTTER RULES:
|
|||
|
|
- Riverpod 2 for all state
|
|||
|
|
- GoRouter for navigation
|
|||
|
|
- Drift SQLite for offline
|
|||
|
|
- shamsi_date for all dates
|
|||
|
|
- 3 locales: fa (RTL), ar (RTL), en (LTR)
|
|||
|
|
- Feature-first: lib/features/{feature}/
|
|||
|
|
|
|||
|
|
SECURITY:
|
|||
|
|
- Validate CafeId ownership on every protected endpoint
|
|||
|
|
- Rate limit OTP: 5/hour per phone via Redis
|
|||
|
|
- Never log phone, national ID, or payment tokens
|
|||
|
|
- Soft delete only — never hard delete
|
|||
|
|
|
|||
|
|
API FORMAT:
|
|||
|
|
Success: { "success": true, "data": {...} }
|
|||
|
|
Error: { "success": false, "error": { "code": "...", "message": "..." } }
|
|||
|
|
List: { "success": true, "data": [...], "meta": { "total": 0, "page": 1 } }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `docker-compose.yml` — در ریشه پروژه
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
version: '3.8'
|
|||
|
|
services:
|
|||
|
|
postgres:
|
|||
|
|
image: postgres:16-alpine
|
|||
|
|
environment:
|
|||
|
|
POSTGRES_DB: meezi
|
|||
|
|
POSTGRES_USER: meezi
|
|||
|
|
POSTGRES_PASSWORD: meezi_local_pass
|
|||
|
|
ports:
|
|||
|
|
- "5432:5432"
|
|||
|
|
volumes:
|
|||
|
|
- postgres_data:/var/lib/postgresql/data
|
|||
|
|
|
|||
|
|
redis:
|
|||
|
|
image: redis:7-alpine
|
|||
|
|
ports:
|
|||
|
|
- "6379:6379"
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
postgres_data:
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۳ — Backend راهاندازی کن
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# پوشه backend
|
|||
|
|
mkdir src && cd src
|
|||
|
|
|
|||
|
|
# پروژههای .NET بساز
|
|||
|
|
dotnet new webapi -n Meezi.API --use-controllers
|
|||
|
|
dotnet new classlib -n Meezi.Core
|
|||
|
|
dotnet new classlib -n Meezi.Infrastructure
|
|||
|
|
dotnet new classlib -n Meezi.Shared
|
|||
|
|
|
|||
|
|
# Solution بساز
|
|||
|
|
cd ..
|
|||
|
|
dotnet new sln -n Meezi
|
|||
|
|
dotnet sln add src/Meezi.API
|
|||
|
|
dotnet sln add src/Meezi.Core
|
|||
|
|
dotnet sln add src/Meezi.Infrastructure
|
|||
|
|
dotnet sln add src/Meezi.Shared
|
|||
|
|
|
|||
|
|
# Reference ها
|
|||
|
|
cd src/Meezi.API
|
|||
|
|
dotnet add reference ../Meezi.Core
|
|||
|
|
dotnet add reference ../Meezi.Infrastructure
|
|||
|
|
dotnet add reference ../Meezi.Shared
|
|||
|
|
|
|||
|
|
cd ../Meezi.Infrastructure
|
|||
|
|
dotnet add reference ../Meezi.Core
|
|||
|
|
dotnet add reference ../Meezi.Shared
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### حالا Cursor را باز کن و این prompt را بده:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
I'm building Meezi.API — ASP.NET Core 10 Web API for a SaaS POS system.
|
|||
|
|
Read .cursorrules for full context.
|
|||
|
|
|
|||
|
|
Add these NuGet packages to Meezi.API:
|
|||
|
|
- Npgsql.EntityFrameworkCore.PostgreSQL
|
|||
|
|
- Microsoft.EntityFrameworkCore.Design
|
|||
|
|
- FluentValidation + FluentValidation.DependencyInjectionExtensions
|
|||
|
|
- AutoMapper.Extensions.Microsoft.DependencyInjection
|
|||
|
|
- Hangfire.AspNetCore + Hangfire.PostgreSql
|
|||
|
|
- Serilog.AspNetCore + Serilog.Sinks.Console
|
|||
|
|
- StackExchange.Redis
|
|||
|
|
- Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
|
- Microsoft.AspNetCore.SignalR
|
|||
|
|
- QuestPDF (for PDF generation)
|
|||
|
|
- EPPlus (for Excel export)
|
|||
|
|
- CsvHelper
|
|||
|
|
|
|||
|
|
Add these to Meezi.Infrastructure:
|
|||
|
|
- Npgsql.EntityFrameworkCore.PostgreSQL
|
|||
|
|
- Microsoft.EntityFrameworkCore.Tools
|
|||
|
|
|
|||
|
|
Then create this folder structure inside Meezi.API:
|
|||
|
|
Controllers/ Services/ Jobs/ Middleware/ Hubs/ Extensions/
|
|||
|
|
|
|||
|
|
And this in Meezi.Core:
|
|||
|
|
Entities/ Enums/ Interfaces/ Constants/
|
|||
|
|
|
|||
|
|
And this in Meezi.Infrastructure:
|
|||
|
|
Data/ Data/Migrations/ Repositories/ ExternalServices/
|
|||
|
|
|
|||
|
|
Create the base ApiResponse<T> record in Meezi.Shared/ApiResponse.cs
|
|||
|
|
Create Program.cs with full middleware pipeline setup.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۴ — Database Schema
|
|||
|
|
|
|||
|
|
در Cursor بده:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Create all EF Core entity classes in Meezi.Core/Entities/ based on this schema:
|
|||
|
|
|
|||
|
|
Cafe: Id(string/cuid), Name, NameAr, NameEn, Slug(unique), Phone,
|
|||
|
|
Address, City, LogoUrl, PlanTier(enum), PlanExpiresAt, IsVerified,
|
|||
|
|
PreferredLanguage, CreatedAt
|
|||
|
|
|
|||
|
|
Branch: Id, CafeId(FK), Name, Address, City
|
|||
|
|
|
|||
|
|
Table: Id, CafeId, BranchId(nullable), Number, Capacity(default 4),
|
|||
|
|
Floor, QrCode(unique), IsActive
|
|||
|
|
|
|||
|
|
Employee: Id, CafeId, Name, Phone, NationalId, Role(enum EmployeeRole),
|
|||
|
|
BaseSalary, PinCode, CreatedAt
|
|||
|
|
|
|||
|
|
MenuCategory: Id, CafeId, Name, NameAr, NameEn, SortOrder, TaxId,
|
|||
|
|
DiscountPercent, IsActive
|
|||
|
|
|
|||
|
|
MenuItem: Id, CafeId, CategoryId(FK), Name, NameAr, NameEn,
|
|||
|
|
Description, Price, ImageUrl, IsAvailable
|
|||
|
|
|
|||
|
|
Order: Id, CafeId, BranchId, TableId, CustomerId, EmployeeId,
|
|||
|
|
OrderType(enum), Status(enum), CouponId, DiscountAmount,
|
|||
|
|
Subtotal, TaxTotal, Total, SnappfoodOrderId, CreatedAt
|
|||
|
|
|
|||
|
|
OrderItem: Id, OrderId(FK), MenuItemId(FK), Quantity, UnitPrice, Notes
|
|||
|
|
|
|||
|
|
Payment: Id, OrderId(FK), Method(enum PaymentMethod), Amount,
|
|||
|
|
Status(enum), Reference
|
|||
|
|
|
|||
|
|
Customer: Id, CafeId, Name, Phone, NationalId, BirthDateJalali,
|
|||
|
|
Group(enum CustomerGroup), LoyaltyPoints, ReferredBy, CreatedAt
|
|||
|
|
|
|||
|
|
Coupon: Id, CafeId, Code, Type(enum CouponType), Value, MinOrderAmount,
|
|||
|
|
MaxDiscount, UsageLimit, UsedCount, TargetGroup,
|
|||
|
|
StartsAt, ExpiresAt, IsActive
|
|||
|
|
|
|||
|
|
Tax: Id, CafeId, Name, Rate, IsDefault, IsRequired, IsCompound
|
|||
|
|
|
|||
|
|
EmployeeSalary: Id, EmployeeId(FK), MonthYear(string YYYY-MM),
|
|||
|
|
BaseSalary, OvertimePay, Deductions, NetSalary, IsPaid
|
|||
|
|
|
|||
|
|
Attendance: Id, EmployeeId(FK), Date, ClockIn, ClockOut, Notes
|
|||
|
|
|
|||
|
|
Shift: Id, EmployeeId(FK), DayOfWeek(int), ShiftType(enum: Morning/Evening/Off)
|
|||
|
|
|
|||
|
|
LeaveRequest: Id, EmployeeId(FK), StartDate, EndDate, Reason,
|
|||
|
|
Status(enum: Pending/Approved/Rejected), ReviewedBy
|
|||
|
|
|
|||
|
|
Enums to create in Meezi.Core/Enums/:
|
|||
|
|
PlanTier: Free, Pro, Business, Enterprise
|
|||
|
|
OrderType: DineIn, Takeaway, Delivery
|
|||
|
|
OrderStatus: Pending, Confirmed, Preparing, Ready, Delivered, Cancelled
|
|||
|
|
PaymentMethod: Cash, Card, Credit
|
|||
|
|
EmployeeRole: Owner, Manager, Cashier, Waiter, Chef, Delivery
|
|||
|
|
CustomerGroup: Regular, VIP, New, Employee
|
|||
|
|
CouponType: Percentage, FixedAmount, FreeItem
|
|||
|
|
ShiftType: Morning, Evening, DayOff
|
|||
|
|
|
|||
|
|
Then create AppDbContext in Meezi.Infrastructure/Data/AppDbContext.cs
|
|||
|
|
with all DbSets and proper relationships.
|
|||
|
|
Create the initial EF migration called "InitialCreate".
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۵ — Auth System
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Build the complete auth system for Meezi:
|
|||
|
|
|
|||
|
|
1. In Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs:
|
|||
|
|
- HTTP client calling Kavenegar REST API
|
|||
|
|
- Method: SendOtpAsync(string phone, string otp)
|
|||
|
|
- Read API key from IConfiguration
|
|||
|
|
|
|||
|
|
2. In Meezi.API/Services/AuthService.cs:
|
|||
|
|
- SendOtpAsync(string phone):
|
|||
|
|
* Generate 6-digit OTP
|
|||
|
|
* Store in Redis with key "otp:{phone}" TTL 5 minutes
|
|||
|
|
* Store attempt counter "otp:attempts:{phone}" TTL 1 hour
|
|||
|
|
* Block if attempts > 5 (return error RATE_LIMITED)
|
|||
|
|
* Call KavenegarSmsService
|
|||
|
|
- VerifyOtpAsync(string phone, string code, string cafeId):
|
|||
|
|
* Check Redis for code
|
|||
|
|
* If valid: generate JWT with claims { userId, cafeId, role, planTier, lang }
|
|||
|
|
* Delete OTP from Redis
|
|||
|
|
* Return token + refresh token
|
|||
|
|
|
|||
|
|
3. In Meezi.API/Controllers/AuthController.cs:
|
|||
|
|
POST /api/auth/send-otp → body: { phone }
|
|||
|
|
POST /api/auth/verify-otp → body: { phone, code }
|
|||
|
|
POST /api/auth/refresh → body: { refreshToken }
|
|||
|
|
|
|||
|
|
4. In Meezi.API/Middleware/TenantMiddleware.cs:
|
|||
|
|
- Extract cafeId from JWT claims
|
|||
|
|
- Inject ITenantContext into request scope
|
|||
|
|
- Return 401 if cafeId missing on protected routes
|
|||
|
|
|
|||
|
|
5. In Meezi.API/Middleware/PlanLimitMiddleware.cs:
|
|||
|
|
- Read planTier from JWT
|
|||
|
|
- Check against PlanLimits constants
|
|||
|
|
- Return PLAN_LIMIT_REACHED error if exceeded
|
|||
|
|
|
|||
|
|
JWT claims: { sub: userId, cafeId, role, planTier, lang, iat, exp }
|
|||
|
|
Token expiry: 7 days access, 30 days refresh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۶ — Core POS APIs
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Build these API endpoints in Meezi.API/Controllers/:
|
|||
|
|
|
|||
|
|
MenuController.cs:
|
|||
|
|
GET /api/cafes/{cafeId}/menu/categories
|
|||
|
|
POST /api/cafes/{cafeId}/menu/categories
|
|||
|
|
PATCH /api/cafes/{cafeId}/menu/categories/{id}
|
|||
|
|
DELETE /api/cafes/{cafeId}/menu/categories/{id}
|
|||
|
|
GET /api/cafes/{cafeId}/menu/items
|
|||
|
|
POST /api/cafes/{cafeId}/menu/items
|
|||
|
|
PATCH /api/cafes/{cafeId}/menu/items/{id}
|
|||
|
|
PATCH /api/cafes/{cafeId}/menu/items/{id}/availability
|
|||
|
|
|
|||
|
|
TablesController.cs:
|
|||
|
|
GET /api/cafes/{cafeId}/tables
|
|||
|
|
POST /api/cafes/{cafeId}/tables → auto-generate QR code URL
|
|||
|
|
GET /api/q/{qrCode} → public, returns cafeSlug + tableId
|
|||
|
|
|
|||
|
|
OrdersController.cs:
|
|||
|
|
GET /api/cafes/{cafeId}/orders → with status filter
|
|||
|
|
POST /api/cafes/{cafeId}/orders → create + check plan limits
|
|||
|
|
PATCH /api/cafes/{cafeId}/orders/{id}/status
|
|||
|
|
POST /api/cafes/{cafeId}/orders/{id}/payments → record payment(s)
|
|||
|
|
GET /api/cafes/{cafeId}/orders/live → SSE or SignalR for KDS
|
|||
|
|
|
|||
|
|
Every controller must:
|
|||
|
|
- Use [Authorize] attribute
|
|||
|
|
- Validate CafeId matches JWT claim
|
|||
|
|
- Use FluentValidation for request body
|
|||
|
|
- Return ApiResponse<T> format
|
|||
|
|
- Use async/await throughout
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۷ — Next.js Dashboard
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# از ریشه پروژه
|
|||
|
|
mkdir web && cd web
|
|||
|
|
npx create-next-app@latest dashboard --typescript --tailwind --app --src-dir
|
|||
|
|
cd dashboard
|
|||
|
|
|
|||
|
|
# پکیجهای اضافی
|
|||
|
|
pnpm add @tanstack/react-query zustand next-intl date-fns-jalali
|
|||
|
|
pnpm add @hookform/resolvers react-hook-form zod
|
|||
|
|
pnpm add recharts lucide-react
|
|||
|
|
pnpm add -D @types/node
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### در Cursor:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Set up the Next.js 14 dashboard for Meezi with these steps:
|
|||
|
|
|
|||
|
|
1. Configure next-intl for 3 locales: fa (default, RTL), ar (RTL), en (LTR)
|
|||
|
|
- Create messages/fa.json, messages/ar.json, messages/en.json
|
|||
|
|
- Wrap app in [locale] dynamic route
|
|||
|
|
- Set dir="rtl" for fa/ar, dir="ltr" for en
|
|||
|
|
- Load Vazirmatn font for fa/ar, Inter for en
|
|||
|
|
|
|||
|
|
2. Install and configure shadcn/ui:
|
|||
|
|
npx shadcn-ui@latest init
|
|||
|
|
Add components: button, input, card, dialog, table, select,
|
|||
|
|
badge, skeleton, toast, sheet, dropdown-menu
|
|||
|
|
|
|||
|
|
3. Create the app layout at app/[locale]/(dashboard)/layout.tsx:
|
|||
|
|
- Sidebar with icons for: POS, CRM, Coupons, Inventory, HR,
|
|||
|
|
Reports, Reservations, SMS, Taxes, Settings
|
|||
|
|
- Top bar with: cafe name, plan badge, language switcher, user menu
|
|||
|
|
- RTL-aware: sidebar on RIGHT for fa/ar, LEFT for en
|
|||
|
|
|
|||
|
|
4. Create Zustand store at lib/stores/cart.store.ts:
|
|||
|
|
State: { items: CartItem[], couponCode, appliedCoupon, tableId }
|
|||
|
|
Actions: addItem, removeItem, updateQty, applyCoupon, clearCart
|
|||
|
|
|
|||
|
|
5. Create API client at lib/api/client.ts:
|
|||
|
|
- Axios instance with baseURL from env
|
|||
|
|
- JWT interceptor (attach token from localStorage)
|
|||
|
|
- 401 interceptor (redirect to login)
|
|||
|
|
- Response unwrapper (extract .data from ApiResponse<T>)
|
|||
|
|
|
|||
|
|
6. Create the main POS page at app/[locale]/(dashboard)/pos/page.tsx:
|
|||
|
|
- Left panel (60%): category tabs + item grid (3 cols)
|
|||
|
|
- Right panel (40%): order items + coupon + tax + split payment
|
|||
|
|
- All text from fa.json translation keys
|
|||
|
|
- Farsi numbers everywhere
|
|||
|
|
- Toman currency format
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۸ — Flutter App
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# از ریشه پروژه
|
|||
|
|
mkdir mobile && cd mobile
|
|||
|
|
flutter create meezi_app --org ir.meezi --platforms android,ios,windows
|
|||
|
|
cd meezi_app
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `pubspec.yaml` — جایگزین کن:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: meezi_app
|
|||
|
|
description: Meezi - میزی - سیستم کافه و رستوران
|
|||
|
|
|
|||
|
|
environment:
|
|||
|
|
sdk: '>=3.3.0 <4.0.0'
|
|||
|
|
flutter: ">=3.19.0"
|
|||
|
|
|
|||
|
|
dependencies:
|
|||
|
|
flutter:
|
|||
|
|
sdk: flutter
|
|||
|
|
flutter_localizations:
|
|||
|
|
sdk: flutter
|
|||
|
|
|
|||
|
|
# State
|
|||
|
|
flutter_riverpod: ^2.5.1
|
|||
|
|
riverpod_annotation: ^2.3.5
|
|||
|
|
|
|||
|
|
# Navigation
|
|||
|
|
go_router: ^14.2.0
|
|||
|
|
|
|||
|
|
# HTTP
|
|||
|
|
dio: ^5.4.3
|
|||
|
|
retrofit: ^4.3.0
|
|||
|
|
|
|||
|
|
# Local DB (offline)
|
|||
|
|
drift: ^2.18.0
|
|||
|
|
sqlite3_flutter_libs: ^0.5.24
|
|||
|
|
path_provider: ^2.1.3
|
|||
|
|
path: ^1.9.0
|
|||
|
|
|
|||
|
|
# i18n
|
|||
|
|
intl: ^0.19.0
|
|||
|
|
|
|||
|
|
# Shamsi date
|
|||
|
|
shamsi_date: ^1.1.1
|
|||
|
|
|
|||
|
|
# QR
|
|||
|
|
qr_flutter: ^4.1.0
|
|||
|
|
mobile_scanner: ^5.2.3
|
|||
|
|
|
|||
|
|
# Printer
|
|||
|
|
esc_pos_utils_plus: ^2.0.1
|
|||
|
|
bluetooth_print: ^4.4.0
|
|||
|
|
|
|||
|
|
# Notifications
|
|||
|
|
firebase_messaging: ^15.1.0
|
|||
|
|
flutter_local_notifications: ^17.2.3
|
|||
|
|
|
|||
|
|
# Storage
|
|||
|
|
flutter_secure_storage: ^9.2.2
|
|||
|
|
shared_preferences: ^2.3.2
|
|||
|
|
|
|||
|
|
# UI
|
|||
|
|
cached_network_image: ^3.3.1
|
|||
|
|
image_picker: ^1.1.2
|
|||
|
|
shimmer: ^3.0.0
|
|||
|
|
flutter_svg: ^2.0.10+1
|
|||
|
|
|
|||
|
|
# Utils
|
|||
|
|
freezed_annotation: ^2.4.1
|
|||
|
|
json_annotation: ^4.9.0
|
|||
|
|
equatable: ^2.0.5
|
|||
|
|
uuid: ^4.4.2
|
|||
|
|
|
|||
|
|
dev_dependencies:
|
|||
|
|
flutter_test:
|
|||
|
|
sdk: flutter
|
|||
|
|
build_runner: ^2.4.11
|
|||
|
|
freezed: ^2.5.2
|
|||
|
|
json_serializable: ^6.8.0
|
|||
|
|
retrofit_generator: ^8.1.0
|
|||
|
|
riverpod_generator: ^2.4.0
|
|||
|
|
drift_dev: ^2.18.0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### در Cursor:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Set up the Flutter app structure for Meezi following feature-first architecture.
|
|||
|
|
|
|||
|
|
Create this folder structure in lib/:
|
|||
|
|
app/
|
|||
|
|
router.dart → GoRouter with all routes
|
|||
|
|
providers.dart → Riverpod ProviderScope setup
|
|||
|
|
features/
|
|||
|
|
auth/ → OTP login screen
|
|||
|
|
pos/ → Owner POS tablet view
|
|||
|
|
menu/ → Customer menu browse
|
|||
|
|
cart/ → Cart + checkout
|
|||
|
|
track/ → Order tracking
|
|||
|
|
reserve/ → Table reservation
|
|||
|
|
discover/ → کافهیاب discovery
|
|||
|
|
hr/ → Employee clock-in + leave request
|
|||
|
|
profile/ → Customer profile + history
|
|||
|
|
core/
|
|||
|
|
api/
|
|||
|
|
api_client.dart → Dio + Retrofit setup
|
|||
|
|
interceptors.dart → JWT + error interceptors
|
|||
|
|
db/
|
|||
|
|
app_database.dart → Drift database
|
|||
|
|
daos/ → Data access objects
|
|||
|
|
sync/
|
|||
|
|
sync_engine.dart → Offline queue + sync logic
|
|||
|
|
i18n/
|
|||
|
|
app_localizations.dart
|
|||
|
|
utils/
|
|||
|
|
jalali_utils.dart → shamsi_date helpers
|
|||
|
|
currency_utils.dart → Toman formatting
|
|||
|
|
shared/
|
|||
|
|
widgets/
|
|||
|
|
theme/
|
|||
|
|
app_theme.dart → RTL-aware theme
|
|||
|
|
|
|||
|
|
Routes to create in router.dart:
|
|||
|
|
/ → redirect to /discover or /pos based on user role
|
|||
|
|
/login → OTP auth screen
|
|||
|
|
/discover → Café discovery (public)
|
|||
|
|
/cafe/:slug → Café public profile
|
|||
|
|
/cafe/:slug/menu → Menu (reads tableId from QR)
|
|||
|
|
/cafe/:slug/cart → Checkout
|
|||
|
|
/order/:id/track → Delivery tracking
|
|||
|
|
/reserve/:slug → Table reservation
|
|||
|
|
/pos → Owner POS (requires OWNER/MANAGER/CASHIER role)
|
|||
|
|
/profile → Customer profile
|
|||
|
|
|
|||
|
|
Configure localization for fa (RTL), ar (RTL), en (LTR).
|
|||
|
|
Create MaterialApp.router with locale switching support.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۹ — Docker و اجرای محلی
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# از ریشه پروژه
|
|||
|
|
docker-compose up -d
|
|||
|
|
# PostgreSQL روی port 5432 بالا میآید
|
|||
|
|
# Redis روی port 6379
|
|||
|
|
|
|||
|
|
# Migration اجرا کن
|
|||
|
|
cd src/Meezi.API
|
|||
|
|
dotnet ef database update
|
|||
|
|
|
|||
|
|
# Backend اجرا کن
|
|||
|
|
dotnet run
|
|||
|
|
# روی https://localhost:7001 بالا میآید
|
|||
|
|
|
|||
|
|
# Dashboard اجرا کن (terminal جدید)
|
|||
|
|
cd web/dashboard
|
|||
|
|
pnpm dev
|
|||
|
|
# روی http://localhost:3000 بالا میآید
|
|||
|
|
|
|||
|
|
# Flutter اجرا کن (terminal جدید)
|
|||
|
|
cd mobile/meezi_app
|
|||
|
|
flutter run -d chrome # برای تست وب
|
|||
|
|
flutter run # برای اندروید/شبیهساز
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۱۰ — Prompt های آماده برای هر ماژول
|
|||
|
|
|
|||
|
|
### CRM + جستجو با کد ملی:
|
|||
|
|
```
|
|||
|
|
Build the CRM module:
|
|||
|
|
- GET /api/cafes/{cafeId}/customers?q={search}
|
|||
|
|
Search by name, phone, or nationalId (10-digit)
|
|||
|
|
- POST /api/cafes/{cafeId}/customers
|
|||
|
|
Fields: name, phone, nationalId, birthDateJalali (YYYY/MM/DD), group
|
|||
|
|
- Check FREE plan limit: max 50 customers → return PLAN_LIMIT_REACHED
|
|||
|
|
- Next.js page: /crm with search input, customer cards, add modal
|
|||
|
|
- All in Farsi with RTL layout
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### HR + حضور و غیاب Flutter:
|
|||
|
|
```
|
|||
|
|
Build the HR attendance feature in Flutter:
|
|||
|
|
- Screen: features/hr/attendance_screen.dart
|
|||
|
|
- Show employee's today shift (Morning 8-16 / Evening 16-00 / DayOff)
|
|||
|
|
- Clock In button: POST /api/employees/{id}/attendance/clock-in
|
|||
|
|
- Clock Out button: POST /api/employees/{id}/attendance/clock-out
|
|||
|
|
- Leave request form: reason + date range in Shamsi calendar
|
|||
|
|
- Works OFFLINE: queue attendance locally, sync when online
|
|||
|
|
- Shamsi date display throughout
|
|||
|
|
- Farsi UI with RTL layout
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### KDS آشپزخانه realtime:
|
|||
|
|
```
|
|||
|
|
Build the Kitchen Display System:
|
|||
|
|
- SignalR Hub in Meezi.API/Hubs/KdsHub.cs
|
|||
|
|
- Group orders by cafeId
|
|||
|
|
- Broadcast new orders to kitchen screen
|
|||
|
|
- Broadcast status changes
|
|||
|
|
- Next.js page: /kds
|
|||
|
|
- Connect to SignalR hub
|
|||
|
|
- Show live order cards (Pending → Preparing → Ready)
|
|||
|
|
- Color coded: yellow=Pending, blue=Preparing, green=Ready
|
|||
|
|
- Each card: table number, items list, time elapsed
|
|||
|
|
- Click to advance status
|
|||
|
|
- Auto-refresh every 30s as fallback
|
|||
|
|
- All Farsi, RTL layout
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### گزارش مالی + Excel خروجی:
|
|||
|
|
```
|
|||
|
|
Build the reports API:
|
|||
|
|
GET /api/cafes/{cafeId}/reports/daily?date=1403-10-16
|
|||
|
|
Returns: totalOrders, newCustomers, returningCustomers,
|
|||
|
|
revenue, taxTotal, discountTotal, topItems(5)
|
|||
|
|
|
|||
|
|
GET /api/cafes/{cafeId}/reports/monthly?month=1403-10
|
|||
|
|
Returns: daily breakdown, totalRevenue, totalCosts breakdown
|
|||
|
|
(salary from EmployeeSalary, plus manual cost entries)
|
|||
|
|
netProfit = revenue - costs
|
|||
|
|
|
|||
|
|
GET /api/cafes/{cafeId}/reports/export?month=1403-10&format=excel
|
|||
|
|
Returns: Excel file using EPPlus
|
|||
|
|
Sheets: Sales, Items, Customers, Employees
|
|||
|
|
|
|||
|
|
Next.js page /reports:
|
|||
|
|
- Stats cards: today customers (new vs returning)
|
|||
|
|
- Bar chart (Recharts): 7-day revenue vs cost
|
|||
|
|
- Top items list
|
|||
|
|
- Monthly P&L table
|
|||
|
|
- Export button
|
|||
|
|
All Farsi, RTL, Toman currency, Shamsi dates
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### پرداخت ZarinPal:
|
|||
|
|
```
|
|||
|
|
Build ZarinPal subscription payment in C#:
|
|||
|
|
|
|||
|
|
1. PaymentService.cs:
|
|||
|
|
- InitiateSubscriptionAsync(cafeId, planTier, months):
|
|||
|
|
* Call ZarinPal /pg/v4/payment/request.json
|
|||
|
|
* Store pending payment record
|
|||
|
|
* Return payment URL
|
|||
|
|
- VerifyPaymentAsync(authority, status):
|
|||
|
|
* Call ZarinPal /pg/v4/payment/verify.json
|
|||
|
|
* On success: update cafe PlanTier + PlanExpiresAt
|
|||
|
|
* Send confirmation SMS via Kavenegar
|
|||
|
|
|
|||
|
|
2. BillingController.cs:
|
|||
|
|
POST /api/billing/subscribe → body: { planTier, months }
|
|||
|
|
→ returns { paymentUrl }
|
|||
|
|
GET /api/billing/verify?Authority=...&Status=OK
|
|||
|
|
→ ZarinPal callback, redirect to dashboard
|
|||
|
|
GET /api/billing/status
|
|||
|
|
→ current plan, expiry, usage stats
|
|||
|
|
|
|||
|
|
3. Hangfire job: SubscriptionRenewalReminderJob
|
|||
|
|
- Run daily
|
|||
|
|
- Find cafes expiring in 3 days
|
|||
|
|
- Send SMS reminder via Kavenegar
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Snappfood Integration:
|
|||
|
|
```
|
|||
|
|
Build Snappfood webhook receiver:
|
|||
|
|
|
|||
|
|
POST /api/webhooks/snappfood (no auth, verify with HMAC secret)
|
|||
|
|
- Parse Snappfood order payload
|
|||
|
|
- Map to Meezi Order:
|
|||
|
|
* OrderType = Delivery
|
|||
|
|
* Status = Confirmed (already paid via Snappfood)
|
|||
|
|
* SnappfoodOrderId = external ID
|
|||
|
|
* Items mapped from Snappfood items to MenuItems by name match
|
|||
|
|
- Save order to DB
|
|||
|
|
- Broadcast to KDS via SignalR
|
|||
|
|
- Return 200 OK within 5 seconds
|
|||
|
|
|
|||
|
|
Also:
|
|||
|
|
PATCH /api/cafes/{cafeId}/orders/{id}/status
|
|||
|
|
- When status → Delivered: notify Snappfood API if SnappfoodOrderId exists
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۱۱ — اشتباهات رایج که باید از آنها بپرهیزی
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
❌ نکن:
|
|||
|
|
- Console.WriteLine در C# → از ILogger<T> استفاده کن
|
|||
|
|
- ml-4 یا mr-4 در Next.js → از ms-4 و me-4 استفاده کن
|
|||
|
|
- new Date() برای نمایش تاریخ → از date-fns-jalali استفاده کن
|
|||
|
|
- متن فارسی hardcode در کامپوننت → همه در fa.json باشد
|
|||
|
|
- query بدون .Where(x => x.CafeId == cafeId) → داده tenant دیگر برمیگرداند
|
|||
|
|
- setState در Flutter برای business logic → از Riverpod استفاده کن
|
|||
|
|
- .Result یا .Wait() در C# → deadlock میدهد
|
|||
|
|
|
|||
|
|
✅ بکن:
|
|||
|
|
- هر mutation در Flutter اول به Drift ذخیره کن، بعد sync کن
|
|||
|
|
- هر endpoint با [Authorize] + validate CafeId شروع کن
|
|||
|
|
- هر string قابل نمایش را در fa.json/ar.json/en.json بریز
|
|||
|
|
- تمام اعداد نمایشی را با .toLocaleString('fa-IR') فرمت کن
|
|||
|
|
- هر job background را در Hangfire بریز، نه await inline
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## مرحله ۱۲ — ترتیب توسعه پیشنهادی (Solo با Cursor)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
هفته ۱-۲: Backend foundation (auth + schema + tenant middleware)
|
|||
|
|
هفته ۳-۴: POS core (menu + orders + tables + QR + payments)
|
|||
|
|
هفته ۵-۶: Dashboard Next.js (POS screen + KDS + basic reports)
|
|||
|
|
هفته ۷-۸: CRM + Coupons + SMS marketing (Kavenegar)
|
|||
|
|
هفته ۹-۱۰: HR module (shifts + attendance + payroll)
|
|||
|
|
هفته ۱۱-۱۲: Flutter customer app (QR + menu + cart + reservation)
|
|||
|
|
هفته ۱۳-۱۴: Discovery platform (کافهیاب) + Reviews
|
|||
|
|
هفته ۱۵-۱۶: ZarinPal billing + Taraz + Snappfood + production deploy
|
|||
|
|
|
|||
|
|
اولین beta: 5 کافه در شمال تهران — هفته ۸
|
|||
|
|
اولین پول: هفته ۱۶
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## چکلیست شروع روز اول
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[ ] Node 20+ نصب است
|
|||
|
|
[ ] .NET 10 SDK نصب است
|
|||
|
|
[ ] Flutter 3.x نصب است
|
|||
|
|
[ ] Docker Desktop در حال اجرا است
|
|||
|
|
[ ] Cursor نصب است و باز است
|
|||
|
|
[ ] پوشه meezi/ ساخته شده
|
|||
|
|
[ ] .cursorrules در ریشه قرار دارد
|
|||
|
|
[ ] MEEZI_PRD.md در ریشه قرار دارد
|
|||
|
|
[ ] docker-compose up -d اجرا شده
|
|||
|
|
[ ] در Cursor: Cmd+L → "Read .cursorrules and MEEZI_PRD.md,
|
|||
|
|
then start Sprint 1: create the .NET solution structure"
|
|||
|
|
```
|