๐๏ธ ุชุตู ูู ู ุนู ุงุฑูุฉ ูุธุงู ู ูุตุฉ “ูุฌูุฉ ุชุตุญูุญ ุฃูุถุงุน ุงููู ูููู” ูู ุงูู ู ููุฉ ุงูุนุฑุจูุฉ ุงูุณุนูุฏูุฉ
ุจูุงุกู ุนูู ุงูู ุนุทูุงุช ุงูู ูุฏู ุฉุ ุณุฃูุฏู ูู ุชุตู ูู ุงู ููุฏุณูุงู ู ุชูุงู ูุงู ูุบุทู ุงูู ุนู ุงุฑูุฉุ ุงูุชูููุงุชุ ุงูุฃู ุงูุ ูุงุฌูุงุช APIุ ูุชุฌุฑุจุฉ ุงูู ุณุชุฎุฏู . ุงูู ุดุฑูุน ุฌุฏูุฏ ููุณุชูุฏู ุฌู ููุฑุงู ู ุญุฏูุฏุงู (ูู ูููู ูู ุงูุณุนูุฏูุฉ) ุจู ุชุทูุจุงุช ุญููู ูุฉ ุญุณูุงุณุฉ.
1๏ธโฃ ุชุญููู ุงูู ุชุทูุจุงุช
ุงูู ุชุทูุจุงุช ุงููุธูููุฉ (Functional)
| # | ุงูู ุชุทูุจ | ุงูุฃููููุฉ |
|---|---|---|
| F1 | ุชุณุฌูู ุจูุงูุงุช ุงูู ุณุชููุฏ (ูู ูุฐุฌ ู ุชุนุฏุฏ ุงูุญููู) | ๐ด ุญุฑุฌุฉ |
| F2 | ุฑูุน ุงูู ุฑููุงุช (ุดุฎุตูุฉ ุฅุฌุจุงุฑูุฉ + ู ุณุชูุฏุงุช ุงุฎุชูุงุฑูุฉ) | ๐ด ุญุฑุฌุฉ |
| F3 | ุงูุชุญูู ู ู ุตุญุฉ ุงูุจูุงูุงุช (Validation) | ๐ด ุญุฑุฌุฉ |
| F4 | ุฅุฑุณุงู ุงูุทูุจ ูุฏูุฑุฉ ู ุฑุงุฌุนุฉ (Workflow) | ๐ด ุญุฑุฌุฉ |
| F5 | ุชูููุฏ ุจุทุงูุฉ ุชูุซูู ู ุคูุชุฉ PDF ู ุน QR/RFID | ๐ด ุญุฑุฌุฉ |
| F6 | ุทุจุงุนุฉ ุญุฒู ุฉ ูุงู ูุฉ (ุจุทุงูุฉ + ุจูุงูุงุช + ู ุณุชูุฏุงุช + ุฅุฑุดุงุฏุงุช) | ๐ ุนุงููุฉ |
| F7 | ุฅุนุงุฏุฉ ุทุจุงุนุฉ ุงูุทูุจ ุจุนุฏ ุงูุฅุฑุณุงู | ๐ ุนุงููุฉ |
| F8 | ุจุญุซ/ุงุณุชุนูุงู ุนู ุญุงูุฉ ุงูุทูุจ (ููู ุณุชููุฏ) | ๐ก ู ุชูุณุทุฉ |
| F9 | ููุญุฉ ุชุญูู ููู ุฑุงุฌุนูู ูุงูู ุฏูููู | ๐ด ุญุฑุฌุฉ |
| F10 | ุชูุงุฑูุฑ ูุฅุญุตุงุกุงุช ููุฌูุฉ | ๐ก ู ุชูุณุทุฉ |
ุงูู ุชุทูุจุงุช ุบูุฑ ุงููุธูููุฉ (NFR)
- ุงูุฃุฏุงุก: ุงุณุชุฌุงุจุฉ < 2s ูู 95% ู ู ุงูุทูุจุงุชุ ุฏุนู 10,000 ู ุณุชุฎุฏู ู ุชุฒุงู ู (ุงูู ุฑุญูุฉ ุงูุฃููู).
- ุงูุชูุงูุฑ: 99.9% Uptime.
- ุงูุฃู ุงู: ุชุดููุฑ TLS 1.3ุ AES-256 at restุ MFA ููู ุฑุงุฌุนูู.
- ุงูุงู ุชุซุงู: NCA (ุงูุณุนูุฏูุฉ) ECCุ SDAIA PDPLุ ISO 27001ุ GDPR (ููุจูุงูุงุช ุงูุญุณุงุณุฉ).
- ุงููุบุงุช: ุงูุนุฑุจูุฉ (RTL) ุฃุณุงุณูุ ุงูุฅูุฌููุฒูุฉ ุซุงููู.
- ุงูุชูุงูู: ุฌูุงู ุฃููุงู (Mobile-first)ุ ุฏุนู ุงูู ุชุตูุญุงุช ุงูุญุฏูุซุฉ.
2๏ธโฃ ุงูู ุนู ุงุฑูุฉ ุงูู ูุชุฑุญุฉ (System Architecture)
ุงููู ุท ุงูู ุนู ุงุฑู: Modular Monolith โ Microservices (ุชุฏุฑูุฌูุงู)
text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CDN + WAF (Cloudflare/Akamai) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ API Gateway (Kong / AWS API Gateway) โ
โ - Rate Limiting - Auth - Routing - Logging โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ
โโโโโโผโโโโ โโโโโโผโโโโ โโโโโโผโโโโ โโโโโโผโโโโโ
โ Identityโ โApplic. โ โDocumentโ โWorkflow โ
โ Service โ โService โ โService โ โ Service โ
โ (Auth) โ โ(CRUD) โ โ(Files) โ โ(Review) โ
โโโโโโฌโโโโโ โโโโโโฌโโโโ โโโโโโฌโโโโ โโโโโโฌโโโโโ
โ โ โ โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโผโโโโโ โโโโโโผโโโโโ โโโโโโผโโโโโ
โPostgreSQLโ โ Redis โ โ MinIO/S3โ
โ (Primary)โ โ (Cache) โ โ (Files) โ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ Message Queue โ
โ (RabbitMQ/Kafka)โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโผโโโโโ โโโโโโผโโโโโ โโโโโโผโโโโโโ
โNotificationโ โ PDF Gen โ โAnalytics โ
โ (SMS/Email)โ โ Service โ โ Service โ
โโโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโ
ุทุจูุงุช ุงููุธุงู :
- ุทุจูุฉ ุงูุนุฑุถ (Presentation): ุชุทุจูู ููุจ (React/Next.js) + PWA ููุฌูุงู.
- ุทุจูุฉ ุงูู API: RESTful + GraphQL ููุงุณุชุนูุงู ุงุช ุงูู ุนูุฏุฉ.
- ุทุจูุฉ ุงูุฎุฏู ุงุช (Business Logic): Modular Monolith ูุจุฏุฃ ุจู 4 ูุญุฏุงุช.
- ุทุจูุฉ ุงูุจูุงูุงุช: PostgreSQL (ุจูุงูุงุช ุนูุงุฆููุฉ) + Redis (Cache) + S3 (ู ููุงุช).
- ุทุจูุฉ ุงูุชูุงู ู: APIs ู ุน ุงูุฃูุธู ุฉ ุงูุณุนูุฏูุฉ (ุฃุจุดุฑ/ูุงุฌุฒ – ุฅู ูุฒู ) + ุงูุฌูุงุฒุงุช ุงููู ููุฉ ููุชุญูู.
3๏ธโฃ ุงูุชูููุงุช ุงูู ูุชุฑุญุฉ (Tech Stack)
ุงููุงุฌูุฉ ุงูุฃู ุงู ูุฉ (Frontend)
| ุงูุชูููุฉ | ุงูุณุจุจ |
|---|---|
| Next.js 14 (App Router) | SSR + SEO + ุฃุฏุงุก ุนุงูู + RTL ู ู ุชุงุฒ |
| TypeScript | ุฃู ุงู ุงูุฃููุงุน ูุชูููู ุงูุฃุฎุทุงุก |
| TailwindCSS + shadcn/ui | ุชุตู ูู ุณุฑูุน + ุฏุนู RTL |
| React Hook Form + Zod | ุฅุฏุงุฑุฉ ุงููู ุงุฐุฌ + Validation ููู |
| i18next | ุฏุนู ู ุชุนุฏุฏ ุงููุบุงุช |
| TanStack Query | ุฅุฏุงุฑุฉ ุญุงูุฉ ุงูุณูุฑูุฑ |
ุงููุงุฌูุฉ ุงูุฎูููุฉ (Backend)
| ุงูุชูููุฉ | ุงูุณุจุจ |
|---|---|
| NestJS (Node.js) ุฃู Spring Boot | Modular + TypeScript + ุฌุงูุฒ ููู ุคุณุณุงุช |
| PostgreSQL 16 | ACID + JSONB + ู ุฑููุฉ |
| Prisma / TypeORM | ORM ุขู ู |
| Redis | Cache + Sessions |
| MinIO (On-Prem) ุฃู AWS S3 | ุชุฎุฒูู ุงูู ููุงุช |
| RabbitMQ / Kafka | ุงูุทูุงุจูุฑ ููุนู ููุงุช ุบูุฑ ุงูู ุชุฒุงู ูุฉ |
| BullMQ | ู ุนุงูุฌุฉ ุงูู ูุงู (PDFุ ุฅุดุนุงุฑุงุช) |
ุงูุจููุฉ ุงูุชุญุชูุฉ
| ุงูู ููู | ุงูุชูููุฉ |
|---|---|
| ุงูุญุงููุงุช | Docker + Kubernetes (K8s) |
| CI/CD | GitLab CI / GitHub Actions + ArgoCD |
| ุงูู ุฑุงูุจุฉ | Prometheus + Grafana + Loki |
| ุงูุชุชุจุน | OpenTelemetry + Jaeger |
| ุฅุฏุงุฑุฉ ุงูุฃุณุฑุงุฑ | HashiCorp Vault |
| ุจูุงุจุฉ API | Kong / Traefik |
ุชูููุฏ PDF
- Puppeteer / Playwright (HTML โ PDF): ูู ุฑููุฉ ุนุงููุฉ ูู ุงูููุงูุจ ุงูุนุฑุจูุฉ RTL.
- pdf-lib: ููุชูุงุนุจ ุจุงูู PDF (ุฅุถุงูุฉ QRุ ุฎุชู ).
- QR Code: ู
ูุชุจุฉ
qrcodeูุชูููุฏ ุฑู ุฒ ุงูุชุญูู.
4๏ธโฃ ุงูุฃู ุงู ูุงูุงู ุชุซุงู (NCA + PDPL + ISO 27001)
ุงูุถูุงุจุท ุงูุฃุณุงุณูุฉ (NCA ECC-1:2018)
| ุงูู ุฌุงู | ุงูุฅุฌุฑุงุก |
|---|---|
| ุฅุฏุงุฑุฉ ุงููููุฉ | MFA ุฅุฌุจุงุฑู ููู ูุธูููุ OTP ููุฌูุงู ููู ุณุชููุฏูู |
| ุชุดููุฑ ุงูุจูุงูุงุช | TLS 1.3ุ AES-256 at restุ ุชุดููุฑ ุงูุฃุนู ุฏุฉ ุงูุญุณุงุณุฉ (ุฑูู ุงูุฌูุงูุ ุฑูู ุงูุจุทุงูุฉ) |
| ุฅุฏุงุฑุฉ ุงููุตูู | RBAC + ABACุ ู ุจุฏุฃ ุฃูู ุตูุงุญูุฉ |
| ุณุฌูุงุช ุงูุชุฏููู | Audit Log ููู ุนู ููุฉ (ู ูุ ู ุงุฐุงุ ู ุชูุ ู ู ุฃูู) |
| ุญู ุงูุฉ ุงูุจูุงูุงุช ุงูุดุฎุตูุฉ | PDPL: ู ูุงููุฉ ุตุฑูุญุฉุ ุญู ุงููุตูู/ุงูุญุฐูุ ุฅุฎูุงุก ุงููููุฉ ูู ุงูุชูุงุฑูุฑ |
| ุฅุฏุงุฑุฉ ุงูุซุบุฑุงุช | SAST/DAST ูู CI/CDุ ูุญุต ุดูุฑู |
| ุงูุงุณุชุฌุงุจุฉ ููุญูุงุฏุซ | SIEM (Wazuh/ELK) + ุฎุทุฉ ุงุณุชุฌุงุจุฉ |
| ุงููุณุฎ ุงูุงุญุชูุงุทู | 3-2-1ุ ุงุฎุชุจุงุฑ ุงุณุชุนุงุฏุฉ ุฑุจุน ุณููู |
ุถูุงุจุท ุฎุงุตุฉ ุจุงูู ุดุฑูุน
- ุงูุชุญูู ู ู ุงููููุฉ ุงููู ููุฉ: ุชูุงู ู ู ุน ุงูุฌูุงุฒุงุช ุงููู ููุฉ (ุฅู ุฃู ูู) ุฃู ุงูุชุญูู ุงููุฏูู.
- ู
ูุน ุงูุชูุฑุงุฑ: ูุญุต
ุฑูู ุงูุจุทุงูุฉ/ุงูุฌูุงุฒูุจู ุงูุญูุธ. - Watermark ุนูู ุงูุจุทุงูุฉ: ุงุณู ุงููุฌูุฉ + ุชุงุฑูุฎ + ุฑูู ุชุณูุณูู.
- QR ููุชุญูู: ูุดูุฑ ุฅูู ููุทุฉ ุชุญูู ุญููู ูุฉ (Verify Endpoint).
- ุชุณุฌูู ูู ุทุจุงุนุฉ: ูู ูุน ุงูุชุฒููุฑ.
- Data Residency: ุงูุจูุงูุงุช ุฏุงุฎู ุงูุณุนูุฏูุฉ (ููู NCA).
5๏ธโฃ ุชุตู ูู ูุงุฌูุงุช API
ูู ุท RESTful ู ุน ุฅุตุฏุงุฑุงุช
text
POST /api/v1/applicants # ุฅูุดุงุก ุทูุจ ุฌุฏูุฏ
GET /api/v1/applicants/{id} # ุฌูุจ ุทูุจ
PATCH /api/v1/applicants/{id} # ุชุนุฏูู ุฌุฒุฆู
POST /api/v1/applicants/{id}/submit # ุฅุฑุณุงู ููู
ุฑุงุฌุนุฉ
GET /api/v1/applicants/{id}/pdf # ุชูููุฏ/ุฌูุจ ุญุฒู
ุฉ ุงูุทุจุงุนุฉ
POST /api/v1/applicants/{id}/reprint # ุฅุนุงุฏุฉ ุทุจุงุนุฉ
GET /api/v1/applicants/{id}/status # ุญุงูุฉ ุงูุทูุจ
POST /api/v1/files/upload # ุฑูุน ู
ุฑูู (presigned URL)
DELETE /api/v1/files/{fileId} # ุญุฐู ู
ุฑูู
GET /api/v1/admin/applications # ูุงุฆู
ุฉ ููู
ุฑุงุฌุนูู (ู
ุน ููุชุฑุฉ)
POST /api/v1/admin/applications/{id}/approve
POST /api/v1/admin/applications/{id}/reject
GET /api/v1/admin/reports # ุชูุงุฑูุฑ
POST /api/v1/auth/login # ุฏุฎูู ุงูู
ุณุชููุฏ (OTP)
POST /api/v1/auth/staff/login # ุฏุฎูู ุงูู
ูุธู (MFA)
POST /api/v1/auth/refresh # ุชุฌุฏูุฏ ุงูุชููู
GET /api/v1/verify/{token} # ุชุญูู ุนุงู
ู
ู ุจุทุงูุฉ
ู ุซุงู Request: ุฅูุดุงุก ุทูุจ
json
POST /api/v1/applicants
{
"fullName": "ู
ุญู
ุฏ ุฃุญู
ุฏ ุนูู ุงูุญุณูู",
"idType": "national_id", // national_id | passport | none
"idNumber": "01234567",
"maritalStatus": "married",
"gender": "male",
"occupation": "engineer",
"birthPlace": "ุตูุนุงุก",
"birthDate": "1990-05-15",
"residencePlace": "ุงูุฑูุงุถ",
"mobile": "966512345678",
"email": "user@example.com",
"region": "riyadh",
"addressYemen": {
"governorate": "ุตูุนุงุก",
"district": "ุงูุณุจุนูู",
"village": "ุจูุช ุจูุณ"
},
"attachments": {
"personalPhoto": "file_id_1",
"passport": "file_id_2"
}
}
ู ุนุงููุฑ ุงูู API
- ุงูู ุตุงุฏูุฉ: OAuth 2.0 + JWT (Access 15min + Refresh 7d).
- ุงูุฅุตุฏุงุฑุงุช:
/v1/ูู ุงูู ุณุงุฑ. - ุงูุฃุฎุทุงุก: RFC 7807 (Problem Details).
- Pagination: Cursor-based.
- Rate Limiting: 100 req/min ููู ุณุชููุฏุ 1000 ููู ูุธู.
- OpenAPI 3.1: ุชูุซูู ุชููุงุฆู (Swagger UI).
6๏ธโฃ ุชุฌุฑุจุฉ ุงูู ุณุชุฎุฏู (UX) ููุฎุฏู ุงุช ุงูุญููู ูุฉ
ู ุจุงุฏุฆ ุงูุชุตู ูู
- ุงูุจุณุงุทุฉ: ุฎุทูุงุช ูุงุถุญุฉุ ูุง ู ุตุทูุญุงุช ุชูููุฉ.
- ุงูุดูุงููุฉ: ุนุฑุถ ุณุจุจ ูู ุฎุทุฃ ููุฑุงู.
- RTL ุฃุตูู: ููุณ ุชุฑุฌู ุฉุ ุจู ุชุตู ูู ุนุฑุจู.
- Mobile-First: 80% ู ู ุงูู ุณุชุฎุฏู ูู ุนูู ุงูุฌูุงู.
- ุฅู ูุงููุฉ ุงููุตูู: WCAG 2.1 AA.
ุฑุญูุฉ ุงูู ุณุชููุฏ (User Journey)
text
[ุงูุตูุญุฉ ุงูุฑุฆูุณูุฉ]
โ
โโโบ ุดุฑุญ ุงูุฎุฏู
ุฉ + ุชูุจูู (ุฌูุงุฒ ูู
ูู = ูุง ูุซููุฉ ู
ุคูุชุฉ)
โ
โผ
[ูู
ูุฐุฌ ู
ุชุนุฏุฏ ุงูุฎุทูุงุช] โโโ ุดุฑูุท ุชูุฏู
โโ 1. ุงูุจูุงูุงุช ุงูุดุฎุตูุฉ
โโ 2. ุงูุนููุงู (ุงููู
ู + ุงูุฅูุงู
ุฉ)
โโ 3. ู
ุนููู
ุงุช ุงูุงุชุตุงู
โโ 4. ุงูู
ุฑููุงุช
โโ 5. ู
ุฑุงุฌุนุฉ + ู
ูุงููุฉ
โ
โผ
[ุฅุฑุณุงู ุงูุทูุจ] โ ุฑูู
ู
ุฑุฌุนู + SMS
โ
โผ
[ุตูุญุฉ ุงููุฌุงุญ]
โโโบ ุทุจุงุนุฉ ุงูุญุฒู
ุฉ (PDF ู
ุชุนุฏุฏ ุงูุตูุญุงุช)
โโโบ ุฅุนุงุฏุฉ ุงูุทุจุงุนุฉ ูุงุญูุงู
โโโบ ุฅุฑุดุงุฏุงุช ู
ุง ุจุนุฏ ุงูุทุจุงุนุฉ
ุชุญุณููุงุช UX ู ูุชุฑุญุฉ
- ุญูุธ ุชููุงุฆู (Draft): ูุง ุชููุฏ ุงูุจูุงูุงุช ุนูุฏ ุงููุทุงุน ุงูุฅูุชุฑูุช.
- Validation ููุฑู: ุนูุฏ
blurูููุณ ุนูุฏ ุงูุฅุฑุณุงู ููุท. - ุฑูุน ุงูู ููุงุช: Drag & Drop + ูุงู ูุฑุง ุงูุฌูุงู ู ุจุงุดุฑุฉ.
- ู ุนุงููุฉ ุงูุตูุฑุฉ: ูุจู ุงูุญูุธ.
- ุดุฑูุท ุชูุฏู : ููุถุญ ุงูุฎุทูุฉ ุงูุญุงููุฉ.
- ุฑุณุงุฆู ุฎุทุฃ ูุงุถุญุฉ: “ุงูุงุณู ูุฌุจ ุฃู ูููู ุฑุจุงุนูุงู” ุจุฏูุงู ู ู “ุฎุทุฃ ูู ุงูุญูู”.
- ูุถุน ูููู: ุงุฎุชูุงุฑู.
- ุฏุนู ุงููุงุฑุฆ ุงูุดุงุดู: ARIA labels.
ูุงูุจ ุญุฒู ุฉ ุงูุทุจุงุนุฉ (PDF)
- ุงูุตูุญุฉ 1: ุจุทุงูุฉ ุงูุชูุซูู ุงูู ุคูุชุฉ (QR + ุฎุชู + ุฑูู ุชุณูุณูู).
- ุงูุตูุญุฉ 2: ุจูุงูุงุช ุงูู ุณุชููุฏ ูุงู ูุฉ.
- ุงูุตูุญุฉ 3+: ุงูู ุณุชูุฏุงุช ุงูู ุฑููุฉ (ุนุฏุง ุงูุตูุฑุฉ ุงูุดุฎุตูุฉ).
- ุงูุตูุญุฉ ุงูุฃุฎูุฑุฉ: ุฅุฑุดุงุฏุงุช ู ุง ุจุนุฏ ุงูุทุจุงุนุฉ.
7๏ธโฃ ู ุฑุงุฌุนุฉ ุงูููุฏ ูุงูู ุนู ุงุฑูุฉ
ูุงุฆู ุฉ ุชุฏููู ููู ุฑุงุฌุนุฉ (Checklist)
- โก ุงูุฃู
ุงู: ูุง ุชูุฌุฏ ุจูุงูุงุช ุญุณุงุณุฉ ูู ุงูู logsุ ูุง
evalุ ุฅุฏุฎุงู ู ูุนููู . - โก ุงูุฃุฏุงุก: ุงุณุชุนูุงู ุงุช N+1ุ ููุงุฑุณ (Indexes)ุ Cache.
- โก ุงูุงุฎุชุจุงุฑุงุช: Unit โฅ 80%ุ Integrationุ E2E (Playwright).
- โก ุงูุชูุซูู: OpenAPI ู ุญุฏูุซุ READMEุ ADRs.
- โก ุงูุงู ุชุซุงู: Audit Logุ Data Retention Policy.
- โก ูุงุจููุฉ ุงูุชูุณุน: Statelessุ Horizontal Scaling.
- โก ุงูุชุนุงูู: Circuit Breakerุ Retryุ Timeout.
ุฃุฏูุงุช ู ูุชุฑุญุฉ
- Linting: ESLint + Prettier + SonarQube.
- Testing: Jest + Supertest + Playwright.
- Security: Snyk + OWASP ZAP + Trivy.
- Performance: k6 + Lighthouse CI.
8๏ธโฃ ุฎุงุฑุทุฉ ุงูุทุฑูู ุงูู ูุชุฑุญุฉ (Roadmap)
| ุงูู ุฑุญูุฉ | ุงูู ุฏุฉ | ุงูู ุฎุฑุฌุงุช |
|---|---|---|
| 1. Discovery | 2-3 ุฃุณุงุจูุน | ู ุชุทูุจุงุช ู ูุตูุฉุ Wireframesุ ู ุฎุทุท ERD |
| 2. MVP | 8-10 ุฃุณุงุจูุน | ูู ูุฐุฌ + ุฑูุน ู ุฑููุงุช + ู ุฑุงุฌุนุฉ + PDF |
| 3. Hardening | 3-4 ุฃุณุงุจูุน | ุฃู ุงูุ ุงุฎุชุจุงุฑุงุชุ ุชุฏููู NCA |
| 4. Pilot | 2 ุฃุณุงุจูุน | 100 ู ุณุชุฎุฏู ุชุฌุฑูุจู |
| 5. Launch | 1 ุฃุณุจูุน | ุฅุทูุงู ุฑุณู ู + ู ุฑุงูุจุฉ |
| 6. Iterate | ู ุณุชู ุฑ | ุชุญุณููุงุช ุจูุงุกู ุนูู ุงูุชุบุฐูุฉ ุงูุฑุงุฌุนุฉ |
9๏ธโฃ ุชูุตูุงุช ุญุฑุฌุฉ ููู ุดุฑูุน
- โ๏ธ ุงูุงู ุชุซุงู ุฃููุงู: ุงูุชุนุงูู ุงูู ุจูุฑ ู ุน NCA ู SDAIA ูุจู ูุชุงุจุฉ ุณุทุฑ ููุฏ.
- ๐ ุงูุชุญูู ู ู ุงููููุฉ: ุชูุงู ู ู ุน ุงูุฌูุงุช ุงููู ููุฉ ุฅู ุฃู ููุ ูุฅูุง ุนู ููุฉ ุชุญูู ูุฏูู ู ูุซูุฉ.
- ๐ PDF ุนุฑุจู: ุงุฎุชุจุฑ Puppeteer ู ุน ุฎุทูุท ุนุฑุจูุฉ (Cairo, Tajawal) ูุจู ุงูุงูุชุฒุงู .
- ๐ Data Residency: ุงุณุชุถุงูุฉ ุฏุงุฎู ุงูุณุนูุฏูุฉ (STC Cloud, Oracle Jeddah, AWS Bahrain ู ุน ุงุนุชุจุงุฑุงุช).
- ๐ ุงูุชูุงู ู ู ุน ุฃุจุดุฑ: ุงุฎุชูุงุฑู ูููู ูููู ุงูุงุญุชูุงู.
- ๐ ุชุญูููุงุช: ููุญุฉ KPIs (ุนุฏุฏ ุงูุทูุจุงุชุ ู ุนุฏู ุงููุจููุ ุฒู ู ุงูู ุนุงูุฌุฉ).
- ๐ก๏ธ ุฎุทุฉ ุงุณุชุฌุงุจุฉ ููุญูุงุฏุซ: ูุฑูู + ุฃุฏูุงุช + ุณููุงุฑูููุงุช.
- ๐ฑ PWA: ูุชูููู ุงูุงุนุชู ุงุฏ ุนูู App Store.
๐ ุงูุฎุทูุฉ ุงูุชุงููุฉ
ูู ุชุฑูุฏ ุฃู ุฃุชุนู ู ูู ุฃุญุฏ ุงูุฌูุงูุจ ุงูุชุงููุฉุ
- ๐ ู ุฎุทุท ERD ุชูุตููู ููุงุนุฏุฉ ุงูุจูุงูุงุช
- ๐ ู ูุงุตูุงุช OpenAPI ูุงู ูุฉ ูุฌู ูุน ุงูู endpoints
- ๐จ Wireframes / Mockups ููุงุฌูุฉ ุงููู ูุฐุฌ
- ๐ ู ุตูููุฉ ุถูุงุจุท NCA ู ูุตูุฉ ููู ู ูููู
- ๐ฐ ุชูุฏูุฑ ุงูุชูุงููู ูุงูู ูุงุฑุฏ (ูุฑูู + ุจููุฉ ุชุญุชูุฉ)
- ๐งช ุฎุทุฉ ุงุฎุชุจุงุฑุงุช ุดุงู ูุฉ
ุฃุฎุจุฑูู ุจุฃููููุชู ูุณุฃุนู ูู ูููุง. ๐
๐ ู ุฎุทุท ERD ุชูุตููู ููุงุนุฏุฉ ุงูุจูุงูุงุช
๐ ู ุฎุทุท ERD ุชูุตููู ููุงุนุฏุฉ ุจูุงูุงุช ู ูุตุฉ “ูุฌูุฉ ุชุตุญูุญ ุฃูุถุงุน ุงููู ูููู”
1๏ธโฃ ูุธุฑุฉ ุนุงู ุฉ ุนูู ุงูู ุฎุทุท
ูุงุนุฏุฉ ุงูุจูุงูุงุช ู ุตู ู ุฉ ููู 3NF ู ุน ุจุนุถ ุงูุชูุงุฒูุงุช ุงูู ุฏุฑูุณุฉ ููุฃุฏุงุกุ ูุชุชููู ู ู 8 ู ุฌู ูุนุงุช ุฑุฆูุณูุฉ:
text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ DATABASE SCHEMA GROUPS โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ 1. Identity & Auth โ ุงูู ุณุชุฎุฏู ูู ูุงูุตูุงุญูุงุช โ โ 2. Applicants โ ุงูู ุณุชููุฏูู ูุจูุงูุงุชูู โ โ 3. Addresses โ ุงูุนูุงููู (ุงููู ู + ุงูุฅูุงู ุฉ) โ โ 4. Attachments โ ุงูู ุฑููุงุช ูุงูู ููุงุช โ โ 5. Applications โ ุงูุทูุจุงุช ูุฏูุฑุฉ ุญูุงุชูุง โ โ 6. Workflow โ ุงูู ุฑุงุฌุนุฉ ูุงููุฑุงุฑุงุช โ โ 7. Documents โ ุงูุจุทุงูุงุช ูุงูุทุจุงุนุฉ โ โ 8. Audit & Reference โ ุงูุชุฏููู ูุงูุจูุงูุงุช ุงูู ุฑุฌุนูุฉ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
2๏ธโฃ ุงูู ุฎุทุท ุงูุจุตุฑู (ASCII ERD)
text
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
โ users โ โ roles โ
โโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโค
โ PK id โโโโโโโโโโค PK id โ
โ username โ M:N โ name โ
โ email โ โ permissions โ
โ password_hash โ โโโโโโโโโโโโโโโโโโโโ
โ full_name โ โฒ
โ role_id โ โ
โ is_active โ โโโโโโโโโดโโโโโโโโโโโ
โ last_login โ โ user_roles โ
โโโโโโโโโโฌโโโโโโโโโโ โ (junction) โ
โ โโโโโโโโโโโโโโโโโโโโ
โ 1:N (reviewer)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ applications โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PK id โ BIGSERIAL โ
โ UK reference_number โ VARCHAR(20) -- YEM-2026-000123 โ
โ FK applicant_id โ โ applicants.id โ
โ status โ ENUM: draft, submitted, under_ โ
โ โ review, approved, rejected, โ
โ โ printed, cancelled โ
โ current_step โ SMALLINT โ
โ FK assigned_reviewer_id โ โ users.id (nullable) โ
โ submitted_at โ TIMESTAMPTZ โ
โ reviewed_at โ TIMESTAMPTZ โ
โ decided_at โ TIMESTAMPTZ โ
โ printed_count โ SMALLINT DEFAULT 0 โ
โ last_printed_at โ TIMESTAMPTZ โ
โ notes โ TEXT โ
โ created_at โ TIMESTAMPTZ โ
โ updated_at โ TIMESTAMPTZ โ
โ deleted_at โ TIMESTAMPTZ (soft delete) โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1:1
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ applicants โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PK id โ BIGSERIAL โ
โ full_name_ar โ VARCHAR(200) -- ุฑุจุงุนู โ
โ full_name_en โ VARCHAR(200) (nullable) โ
โ id_type โ ENUM: national_id, passport, none โ
โ id_number โ VARCHAR(30) (encrypted) โ
โ id_issue_date โ DATE (nullable) โ
โ id_expiry_date โ DATE (nullable) โ
โ marital_status โ ENUM: single, married, divorced, โ
โ โ widowed โ
โ gender โ ENUM: male, female โ
โ occupation โ VARCHAR(100) โ
โ birth_place โ VARCHAR(100) โ
โ birth_date โ DATE โ
โ FK nationality_id โ โ nationalities.id โ
โ mobile โ VARCHAR(20) (encrypted) โ
โ mobile_hash โ VARCHAR(64) -- ููุจุญุซ ุจุฏูู ูู โ
โ email โ VARCHAR(150) โ
โ has_yemeni_passport โ BOOLEAN DEFAULT false โ
โ created_at โ TIMESTAMPTZ โ
โ updated_at โ TIMESTAMPTZ โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโ 1:N โโโบโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ addresses โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ PK id โ
โ โ FK applicant_id โ
โ โ type ENUM: residence, yemen_ โ
โ โ origin โ
โ โ region_id (ููุณุนูุฏูุฉ ููุท) โ
โ โ governorate_id (ูููู
ู ููุท) โ
โ โ district_id (ูููู
ู ููุท) โ
โ โ village (ูููู
ู ููุท) โ
โ โ street_address โ
โ โ is_primary โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโ 1:N โโโบโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ attachments โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ PK id โ
โ โ FK applicant_id โ
โ โ type ENUM: personal_photo, โ
โ โ passport, national_id, โ
โ โ birth_cert, family_card, โ
โ โ other_1, other_2 โ
โ โ file_name โ
โ โ file_path (S3 key) โ
โ โ file_size โ
โ โ mime_type โ
โ โ checksum_sha256 โ
โ โ uploaded_at โ
โ โ is_verified โ
โ โ FK verified_by โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโ 1:N โโโบโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ workflow_history โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PK id โ
โ FK application_id โ
โ FK actor_id (users) โ
โ action โ
โ from_status โ
โ to_status โ
โ comment โ
โ created_at โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ issued_documents โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PK id โ BIGSERIAL โ
โ FK application_id โ โ applications.id โ
โ UK document_number โ VARCHAR(30) -- DOC-2026-XXXXXX โ
โ document_type โ ENUM: temporary_id_card, full โ
โ verification_token โ UUID (ููู QR) โ
โ qr_payload_hash โ VARCHAR(64) โ
โ pdf_s3_key โ VARCHAR(500) โ
โ issued_at โ TIMESTAMPTZ โ
โ expires_at โ TIMESTAMPTZ โ
โ printed_by โ FK users.id (nullable) โ
โ revoked_at โ TIMESTAMPTZ (nullable) โ
โ revocation_reason โ TEXT โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ audit_logs โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PK id โ BIGSERIAL โ
โ actor_type โ ENUM: user, applicant, system โ
โ actor_id โ BIGINT (nullable) โ
โ action โ VARCHAR(100) โ
โ entity_type โ VARCHAR(50) โ
โ entity_id โ BIGINT โ
โ old_values โ JSONB โ
โ new_values โ JSONB โ
โ ip_address โ INET โ
โ user_agent โ TEXT โ
โ request_id โ UUID โ
โ created_at โ TIMESTAMPTZ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
3๏ธโฃ ุงูุฌุฏุงูู ุงูุชูุตูููุฉ (DDL)
๐น ู ุฌู ูุนุฉ 1: ุงููููุฉ ูุงูุตูุงุญูุงุช
roles
sql
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE, -- admin, reviewer, supervisor, viewer
name_ar VARCHAR(100) NOT NULL,
description TEXT,
permissions JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Seed
INSERT INTO roles (name, name_ar, permissions) VALUES
('super_admin', 'ู
ุฏูุฑ ุนุงู
', '["*"]'),
('reviewer', 'ู
ุฑุงุฌุน', '["applications:read","applications:review"]'),
('supervisor', 'ู
ุดุฑู', '["applications:*","reports:read"]'),
('viewer', 'ู
ุดุงูุฏ', '["applications:read"]');
users
sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(150) NOT NULL UNIQUE,
mobile VARCHAR(20),
password_hash VARCHAR(255) NOT NULL, -- bcrypt/argon2
full_name VARCHAR(200) NOT NULL,
national_id VARCHAR(30), -- ููู
ูุธู ุงูุณุนูุฏู/ุงููู
ูู
role_id INTEGER NOT NULL REFERENCES roles(id),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
is_locked BOOLEAN NOT NULL DEFAULT FALSE,
failed_attempts SMALLINT NOT NULL DEFAULT 0,
mfa_enabled BOOLEAN NOT NULL DEFAULT TRUE,
mfa_secret VARCHAR(255),
last_login_at TIMESTAMPTZ,
last_login_ip INET,
password_changed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_users_email ON users(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_role ON users(role_id) WHERE is_active = TRUE;
sessions
sql
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
applicant_id BIGINT, -- nullableุ ููู
ุณุชููุฏ
refresh_token_hash VARCHAR(255) NOT NULL,
ip_address INET,
user_agent TEXT,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_sessions_user ON sessions(user_id) WHERE revoked_at IS NULL;
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
otp_verifications (ููุชุญูู ู
ู ุงูุฌูุงู/ุงูุจุฑูุฏ)
sql
CREATE TABLE otp_verifications (
id BIGSERIAL PRIMARY KEY,
identifier VARCHAR(150) NOT NULL, -- mobile ุฃู email
identifier_type ENUM('mobile','email') NOT NULL,
otp_hash VARCHAR(255) NOT NULL,
purpose VARCHAR(50) NOT NULL, -- login, verify_mobile, verify_email
attempts SMALLINT NOT NULL DEFAULT 0,
max_attempts SMALLINT NOT NULL DEFAULT 5,
expires_at TIMESTAMPTZ NOT NULL,
consumed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_otp_identifier ON otp_verifications(identifier, purpose)
WHERE consumed_at IS NULL;
๐น ู ุฌู ูุนุฉ 2: ุงูู ุณุชููุฏูู
nationalities
sql
CREATE TABLE nationalities (
id SERIAL PRIMARY KEY,
code CHAR(2) NOT NULL UNIQUE, -- ISO 3166-1 alpha-2
name_ar VARCHAR(100) NOT NULL,
name_en VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
applicants
sql
CREATE TABLE applicants (
id BIGSERIAL PRIMARY KEY,
full_name_ar VARCHAR(200) NOT NULL,
full_name_en VARCHAR(200),
id_type VARCHAR(20) NOT NULL
CHECK (id_type IN ('national_id','passport','none')),
id_number_encrypted BYTEA, -- pgcrypto
id_number_hash VARCHAR(64), -- SHA-256 ููุจุญุซ
id_issue_date DATE,
id_expiry_date DATE,
marital_status VARCHAR(20) NOT NULL
CHECK (marital_status IN ('single','married','divorced','widowed')),
gender VARCHAR(10) NOT NULL
CHECK (gender IN ('male','female')),
occupation VARCHAR(100) NOT NULL,
birth_place VARCHAR(100) NOT NULL,
birth_date DATE NOT NULL,
nationality_id INTEGER REFERENCES nationalities(id),
mobile_encrypted BYTEA, -- pgcrypto
mobile_hash VARCHAR(64) NOT NULL, -- SHA-256
email VARCHAR(150) NOT NULL,
has_yemeni_passport BOOLEAN NOT NULL DEFAULT FALSE,
-- ุงูุชุญูู
is_identity_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,
verified_by BIGINT REFERENCES users(id),
-- timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
CONSTRAINT chk_name_quad CHECK (
array_length(regexp_split_to_array(trim(full_name_ar), '\s+'), 1) >= 4
),
CONSTRAINT chk_id_required CHECK (
(id_type = 'none' AND id_number_encrypted IS NULL)
OR (id_type IN ('national_id','passport') AND id_number_encrypted IS NOT NULL)
)
);
CREATE INDEX idx_applicants_mobile_hash ON applicants(mobile_hash);
CREATE INDEX idx_applicants_id_hash ON applicants(id_number_hash);
CREATE INDEX idx_applicants_email ON applicants(email);
CREATE UNIQUE INDEX uk_applicants_id_hash ON applicants(id_number_hash)
WHERE id_number_hash IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_applicants_created ON applicants(created_at DESC);
ู ูุงุญุธุฉ ุฃู ููุฉ: ูุณุชุฎุฏู
pgcryptoูุชุดููุฑid_numberูmobileุ ู ุน ุงูุงุญุชูุงุธ ุจูhash(SHA-256 + salt) ููุจุญุซ ุงูุณุฑูุน ุฏูู ูู ุงูุชุดููุฑ.
addresses
sql
CREATE TABLE addresses (
id BIGSERIAL PRIMARY KEY,
applicant_id BIGINT NOT NULL REFERENCES applicants(id) ON DELETE CASCADE,
type VARCHAR(20) NOT NULL CHECK (type IN ('residence','yemen_origin')),
-- ููุณุนูุฏูุฉ
region_id INTEGER REFERENCES regions(id),
-- ูููู
ู
governorate_id INTEGER REFERENCES governorates(id),
district_id INTEGER REFERENCES districts(id),
village VARCHAR(150),
street_address VARCHAR(300),
postal_code VARCHAR(20),
is_primary BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_addresses_applicant ON addresses(applicant_id);
CREATE UNIQUE INDEX uk_address_applicant_type ON addresses(applicant_id, type);
๐น ู ุฌู ูุนุฉ 3: ุงูุจูุงูุงุช ุงูุฌุบุฑุงููุฉ
regions (ู
ูุงุทู ุงูุณุนูุฏูุฉ)
sql
CREATE TABLE regions (
id SERIAL PRIMARY KEY,
code VARCHAR(10) NOT NULL UNIQUE,
name_ar VARCHAR(100) NOT NULL,
name_en VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
-- Seed: 13 ู
ูุทูุฉ
INSERT INTO regions (code, name_ar, name_en) VALUES
('RUH','ุงูุฑูุงุถ','Riyadh'),
('MAK','ู
ูุฉ ุงูู
ูุฑู
ุฉ','Makkah'),
('MED','ุงูู
ุฏููุฉ ุงูู
ููุฑุฉ','Madinah'),
('EAP','ุงูู
ูุทูุฉ ุงูุดุฑููุฉ','Eastern Province'),
('ASR','ุนุณูุฑ','Asir'),
('TAB','ุชุจูู','Tabuk'),
('HAI','ุญุงุฆู','Hail'),
('NOR','ุงูุญุฏูุฏ ุงูุดู
ุงููุฉ','Northern Borders'),
('JAZ','ุฌุงุฒุงู','Jazan'),
('NAJ','ูุฌุฑุงู','Najran'),
('BAH','ุงูุจุงุญุฉ','Al-Bahah'),
('JAW','ุงูุฌูู','Al-Jouf'),
('QAS','ุงููุตูู
','Qassim');
governorates (ู
ุญุงูุธุงุช ุงููู
ู)
sql
CREATE TABLE governorates (
id SERIAL PRIMARY KEY,
code VARCHAR(10) NOT NULL UNIQUE,
name_ar VARCHAR(100) NOT NULL,
name_en VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE
);
-- Seed: 22 ู
ุญุงูุธุฉ ูู
ููุฉ
INSERT INTO governorates (code, name_ar) VALUES
('SAN','ุตูุนุงุก'),('ADE','ุนุฏู'),('TAI','ุชุนุฒ'),('HAD','ุงูุญุฏูุฏุฉ'),
('HAD2','ุญุถุฑู
ูุช'),('IBB','ุฅุจ'),('LAH','ูุญุฌ'),('ABY','ุฃุจูู'),
('DHM','ุฐู
ุงุฑ'),('BAY','ุงูุจูุถุงุก'),('MAA','ู
ุฃุฑุจ'),('SAA','ุตุนุฏุฉ'),
('HAJ','ุญุฌุฉ'),('AMR','ุนู
ุฑุงู'),('DAL','ุงูุถุงูุน'),('JOF','ุงูุฌูู'),
('MAH','ุงูู
ุญููุช'),('RAY','ุฑูู
ุฉ'),('SHA','ุดุจูุฉ'),('SOC','ุฃุฑุฎุจูู ุณูุทุฑู'),
('MWS','ุงูู
ูุฑุฉ'),('HOD','ุญุฌุฉ');
districts (ุงูู
ุฏูุฑูุงุช – ~333 ู
ุฏูุฑูุฉ)
sql
CREATE TABLE districts (
id SERIAL PRIMARY KEY,
governorate_id INTEGER NOT NULL REFERENCES governorates(id),
code VARCHAR(15) NOT NULL UNIQUE,
name_ar VARCHAR(100) NOT NULL,
name_en VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE
);
CREATE INDEX idx_districts_gov ON districts(governorate_id);
๐น ู ุฌู ูุนุฉ 4: ุงูู ุฑููุงุช
attachments
sql
CREATE TABLE attachments (
id BIGSERIAL PRIMARY KEY,
applicant_id BIGINT NOT NULL REFERENCES applicants(id) ON DELETE CASCADE,
type VARCHAR(30) NOT NULL CHECK (type IN (
'personal_photo','passport','national_id',
'birth_cert','family_card','other_1','other_2'
)),
original_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL, -- S3 key: applicants/{id}/{uuid}.jpg
file_size BIGINT NOT NULL, -- bytes
mime_type VARCHAR(100) NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
width_px INTEGER, -- ููุตูุฑ
height_px INTEGER, -- ููุตูุฑ
is_verified BOOLEAN DEFAULT FALSE,
verified_by BIGINT REFERENCES users(id),
verified_at TIMESTAMPTZ,
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
CONSTRAINT chk_file_size CHECK (file_size <= 5 * 1024 * 1024), -- 5 MB
CONSTRAINT chk_mime CHECK (mime_type IN (
'image/jpeg','image/png','application/pdf'
))
);
CREATE INDEX idx_attachments_applicant ON attachments(applicant_id)
WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uk_attachment_type ON attachments(applicant_id, type)
WHERE deleted_at IS NULL;
ู ูุงุญุธุฉ: ุงูุตูุฑุฉ ุงูุดุฎุตูุฉ (
personal_photo) ุฅุฌุจุงุฑูุฉุ ูุงูุจุงูู ุงุฎุชูุงุฑู โ ูููุฑุถ ุฐูู ูู ุทุจูุฉ ุงูุชุทุจููุ ูููุณ ูู ูุงุนุฏุฉ ุงูุจูุงูุงุชุ ูู ุฑููุฉ ุฃูุจุฑ.
๐น ู ุฌู ูุนุฉ 5: ุงูุทูุจุงุช
applications
sql
CREATE TABLE applications (
id BIGSERIAL PRIMARY KEY,
reference_number VARCHAR(20) NOT NULL UNIQUE, -- YEM-2026-000123
applicant_id BIGINT NOT NULL REFERENCES applicants(id),
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN (
'draft','submitted','under_review',
'approved','rejected','printed','cancelled'
)),
current_step SMALLINT NOT NULL DEFAULT 1,
assigned_reviewer_id BIGINT REFERENCES users(id),
priority SMALLINT NOT NULL DEFAULT 0, -- 0=normal, 1=high
submitted_at TIMESTAMPTZ,
review_started_at TIMESTAMPTZ,
decided_at TIMESTAMPTZ,
rejection_reason TEXT,
internal_notes TEXT,
-- ุงูุทุจุงุนุฉ
printed_count SMALLINT NOT NULL DEFAULT 0,
last_printed_at TIMESTAMPTZ,
last_printed_by BIGINT REFERENCES users(id),
-- timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_applications_status ON applications(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_applications_reviewer ON applications(assigned_reviewer_id, status);
CREATE INDEX idx_applications_submitted ON applications(submitted_at DESC);
CREATE INDEX idx_applications_applicant ON applications(applicant_id);
application_sequences (ูุชูููุฏ ุฑูู
ู
ุฑุฌุนู)
sql
CREATE TABLE application_sequences (
year SMALLINT PRIMARY KEY,
last_number BIGINT NOT NULL DEFAULT 0
);
-- Function ูุชูููุฏ ุฑูู
ู
ุฑุฌุนู
CREATE OR REPLACE FUNCTION next_reference_number()
RETURNS VARCHAR AS $$
DECLARE
v_year SMALLINT := EXTRACT(YEAR FROM NOW());
v_num BIGINT;
BEGIN
INSERT INTO application_sequences (year, last_number)
VALUES (v_year, 1)
ON CONFLICT (year) DO UPDATE
SET last_number = application_sequences.last_number + 1
RETURNING last_number INTO v_num;
RETURN 'YEM-' || v_year || '-' || LPAD(v_num::TEXT, 6, '0');
END;
$$ LANGUAGE plpgsql;
๐น ู ุฌู ูุนุฉ 6: ุณูุฑ ุงูุนู ู (Workflow)
workflow_history
sql
CREATE TABLE workflow_history (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
actor_id BIGINT REFERENCES users(id),
actor_type VARCHAR(20) NOT NULL CHECK (actor_type IN ('user','applicant','system')),
action VARCHAR(50) NOT NULL, -- submit, assign, approve, reject, print, etc.
from_status VARCHAR(20),
to_status VARCHAR(20),
comment TEXT,
metadata JSONB, -- ุจูุงูุงุช ุฅุถุงููุฉ
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_workflow_app ON workflow_history(application_id, created_at DESC);
CREATE INDEX idx_workflow_actor ON workflow_history(actor_id, created_at DESC);
review_assignments (ุชูุฒูุน ุงูู
ูุงู
)
sql
CREATE TABLE review_assignments (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id),
reviewer_id BIGINT NOT NULL REFERENCES users(id),
assigned_by BIGINT NOT NULL REFERENCES users(id),
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
accepted_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT TRUE
);
CREATE UNIQUE INDEX uk_active_assignment ON review_assignments(application_id)
WHERE is_active = TRUE;
๐น ู ุฌู ูุนุฉ 7: ุงููุซุงุฆู ุงูุตุงุฏุฑุฉ
issued_documents
sql
CREATE TABLE issued_documents (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id),
document_number VARCHAR(30) NOT NULL UNIQUE, -- DOC-2026-000123
document_type VARCHAR(30) NOT NULL CHECK (document_type IN (
'temporary_id_card','full_id_card'
)),
verification_token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
qr_payload_hash VARCHAR(64) NOT NULL,
pdf_s3_key VARCHAR(500) NOT NULL,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
issued_by BIGINT REFERENCES users(id),
revoked_at TIMESTAMPTZ,
revocation_reason TEXT,
is_valid BOOLEAN GENERATED ALWAYS AS (revoked_at IS NULL) STORED
);
CREATE INDEX idx_issued_app ON issued_documents(application_id);
CREATE INDEX idx_issued_token ON issued_documents(verification_token);
print_jobs
sql
CREATE TABLE print_jobs (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id),
document_id BIGINT REFERENCES issued_documents(id),
printed_by BIGINT NOT NULL REFERENCES users(id),
printer_ip INET,
copies SMALLINT NOT NULL DEFAULT 1,
status VARCHAR(20) NOT NULL CHECK (status IN ('queued','processing','completed','failed')),
error_message TEXT,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
CREATE INDEX idx_print_jobs_app ON print_jobs(application_id);
๐น ู ุฌู ูุนุฉ 8: ุงูุชุฏููู ูุงูู ุฑุฌุนูุงุช
audit_logs (Partitioned by month)
sql
CREATE TABLE audit_logs (
id BIGSERIAL,
actor_type VARCHAR(20) NOT NULL,
actor_id BIGINT,
actor_username VARCHAR(50),
action VARCHAR(100) NOT NULL,
entity_type VARCHAR(50) NOT NULL,
entity_id BIGINT,
old_values JSONB,
new_values JSONB,
ip_address INET,
user_agent TEXT,
request_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- Partitions ุดูุฑูุฉ
CREATE TABLE audit_logs_2026_01 PARTITION OF audit_logs
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
-- ... ุฅูุฎ
CREATE INDEX idx_audit_entity ON audit_logs(entity_type, entity_id);
CREATE INDEX idx_audit_actor ON audit_logs(actor_id, created_at DESC);
CREATE INDEX idx_audit_action ON audit_logs(action, created_at DESC);
settings
sql
CREATE TABLE settings (
key VARCHAR(100) PRIMARY KEY,
value JSONB NOT NULL,
description TEXT,
updated_by BIGINT REFERENCES users(id),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Seed
INSERT INTO settings (key, value, description) VALUES
('application.max_file_size', '5242880', 'ุญุฏ ุฃูุตู 5MB'),
('application.allowed_mimes', '["image/jpeg","image/png","application/pdf"]', ''),
('application.otp_ttl_seconds', '300', 'ู
ุฏุฉ ุตูุงุญูุฉ OTP'),
('application.max_otp_attempts', '5', 'ุนุฏุฏ ู
ุญุงููุงุช OTP'),
('workflow.auto_assign', 'true', 'ุชูุฒูุน ุชููุงุฆู ุนูู ุงูู
ุฑุงุฌุนูู');
notifications
sql
CREATE TABLE notifications (
id BIGSERIAL PRIMARY KEY,
recipient_type VARCHAR(20) NOT NULL CHECK (recipient_type IN ('user','applicant')),
recipient_id BIGINT NOT NULL,
channel VARCHAR(20) NOT NULL CHECK (channel IN ('sms','email','push','in_app')),
template_code VARCHAR(50) NOT NULL,
subject VARCHAR(200),
body TEXT NOT NULL,
metadata JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','sent','failed','delivered','read')),
sent_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
read_at TIMESTAMPTZ,
error_message TEXT,
retry_count SMALLINT DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_notif_recipient ON notifications(recipient_type, recipient_id, created_at DESC);
CREATE INDEX idx_notif_status ON notifications(status) WHERE status IN ('pending','failed');
4๏ธโฃ ุงูุนูุงูุงุช (Relationships Summary)
| ุงูุนูุงูุฉ | ุงูููุน | ุงููุตู |
|---|---|---|
| users โ roles | M:1 | ูู ู ุณุชุฎุฏู ูู ุฏูุฑ ูุงุญุฏ |
| users โ applications | 1:N | ุงูู ุฑุงุฌุน ู ุณุคูู ุนู ุนุฏุฉ ุทูุจุงุช |
| applicants โ applications | 1:1 | ูู ู ุณุชููุฏ ูู ุทูุจ ูุงุญุฏ ูุดุท |
| applicants โ addresses | 1:N | ุนููุงู ุงูุฅูุงู ุฉ + ุนููุงู ุงููู ู |
| applicants โ attachments | 1:N | 1 ุดุฎุตูุฉ + 6 ุงุฎุชูุงุฑูุฉ |
| applications โ workflow_history | 1:N | ุณุฌู ูุงู ู ููุชุบููุฑุงุช |
| applications โ issued_documents | 1:N | ุจุทุงูุงุช ุตุงุฏุฑุฉ |
| applications โ print_jobs | 1:N | ุนู ููุงุช ุงูุทุจุงุนุฉ |
| regions/governorates โ districts | 1:N | ูุฑู ูุฉ ุฌุบุฑุงููุฉ |
| issued_documents โ verification | 1:1 | ุฑู ุฒ QR ููุชุญูู |
5๏ธโฃ ุงูููุงุฑุณ ุงูุญุฑุฌุฉ (Critical Indexes)
sql
-- ุจุญุซ ุณุฑูุน ุนู ุทูุจ ุจุฑูู
ู
ุฑุฌุนู (ุดุงุฆุน)
CREATE UNIQUE INDEX CONCURRENTLY uk_app_reference ON applications(reference_number);
-- ูุงุฆู
ุฉ ุงูุชุธุงุฑ ุงูู
ุฑุงุฌุนุฉ (dashboard)
CREATE INDEX CONCURRENTLY idx_review_queue ON applications(status, priority DESC, submitted_at ASC)
WHERE status IN ('submitted','under_review');
-- ุจุญุซ ุงูู
ุณุชููุฏ (ุดุงุฆุน ุฌุฏุงู)
CREATE INDEX CONCURRENTLY idx_applicant_search ON applicants
USING GIN (to_tsvector('arabic', full_name_ar));
CREATE INDEX CONCURRENTLY idx_applicant_mobile ON applicants(mobile_hash);
-- ุชุญูู ู
ู QR
CREATE INDEX CONCURRENTLY idx_verify_token ON issued_documents(verification_token)
WHERE revoked_at IS NULL;
6๏ธโฃ ุงูุนุฑุถ ุงูู ุงุฏู (Materialized Views) ููุชูุงุฑูุฑ
sql
-- ููุญุฉ KPI ุงูููู
ูุฉ
CREATE MATERIALIZED VIEW mv_daily_stats AS
SELECT
DATE(created_at) AS day,
COUNT(*) FILTER (WHERE status = 'submitted') AS submitted,
COUNT(*) FILTER (WHERE status = 'approved') AS approved,
COUNT(*) FILTER (WHERE status = 'rejected') AS rejected,
COUNT(*) FILTER (WHERE status = 'printed') AS printed,
AVG(EXTRACT(EPOCH FROM (decided_at - submitted_at))/3600)::NUMERIC(10,2) AS avg_review_hours
FROM applications
WHERE deleted_at IS NULL
GROUP BY DATE(created_at);
CREATE UNIQUE INDEX ON mv_daily_stats(day);
-- ุชุญุฏูุซ ูู ุณุงุนุฉ
-- REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_stats;
7๏ธโฃ ุณูุงุณุงุช ุงูุงุญุชูุงุธ ุจุงูุจูุงูุงุช (Retention)
| ุงูุฌุฏูู | ุงูู ุฏุฉ | ุงูุฅุฌุฑุงุก |
|---|---|---|
applications | 10 ุณููุงุช | ุฃุฑุดูุฉ (cold storage) |
audit_logs | 7 ุณููุงุช | ุชูุณูู + S3 Glacier |
attachments | 5 ุณููุงุช ุจุนุฏ ุงูุฑูุถ | ุญุฐู ุชููุงุฆู |
issued_documents | 10 ุณููุงุช | ุฃุฑุดูุฉ |
notifications | 2 ุณููุงุช | ุญุฐู |
sessions | 90 ููู | ุญุฐู |
otp_verifications | 30 ููู | ุญุฐู |
sql
-- ู
ุซุงู: ุฏุงูุฉ ุญุฐู OTP ู
ูุชููุฉ
CREATE OR REPLACE FUNCTION cleanup_expired_otps()
RETURNS void AS $$
BEGIN
DELETE FROM otp_verifications
WHERE created_at < NOW() - INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql;
8๏ธโฃ ุงูุฃู ุงู ุนูู ู ุณุชูู ุงูุตููู (RLS)
sql
-- ุงูู
ุฑุงุฌุนูู ูุฑูู ููุท ุงูุทูุจุงุช ุงูู
ุณูุฏุฉ ุฅูููู
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;
CREATE POLICY reviewer_sees_assigned ON applications
FOR SELECT
TO reviewer_role
USING (assigned_reviewer_id = current_setting('app.current_user_id')::BIGINT
OR status = 'submitted');
-- ุงูู
ุดุฑููู ูุฑูู ุงููู
CREATE POLICY supervisor_sees_all ON applications
FOR ALL
TO supervisor_role
USING (TRUE);
9๏ธโฃ ุงุนุชุจุงุฑุงุช ุงูุฃุฏุงุก ูุงูุชูุณุน
ุงูุชูุณูู (Partitioning)
audit_logsโ ุดูุฑู (PostgreSQL native).notificationsโ ุดูุฑู.applicationsโ ุณููู (ุนูุฏ ุชุฌุงูุฒ 5M ุตู).
ุงููุณุฎ ุงูู ุชู ุงุซู (Replication)
- Primary (ูุชุงุจุฉ) + 2 Read Replicas ููุชูุงุฑูุฑ.
- Streaming Replication ู
ุน
synchronous_commit = remote_applyููุจูุงูุงุช ุงูุญุณุงุณุฉ.
ุงููุณุฎ ุงูุงุญุชูุงุทู
- WAL Archiving ู ุณุชู ุฑ โ S3.
- Full Backup ููู ู (pg_basebackup).
- PITR ูู ุฏุฉ 30 ููู .
- ุงุฎุชุจุงุฑ ุงุณุชุนุงุฏุฉ ุฑุจุน ุณููู.
ุงูุงุชุตุงูุงุช
- PgBouncer ูู ูุถุน transaction pooling.
- max_connections = 500 (ู ุน PgBouncer).
๐ ู ุฎุทุท ERD ุจุตูุบุฉ Mermaid (ููุงุณุชุฎุฏุงู ูู ุงูู ุณุชูุฏุงุช)
1๏ธโฃ1๏ธโฃ ุงูุฎุทูุงุช ุงูุชุงููุฉ
ูู ุชุฑูุฏ ุฃู ุฃุชุนู ู ูู:
- ๐ ู ูุงุตูุงุช OpenAPI 3.1 ูุงู ูุฉ ููู endpoint ู ุฑุชุจุท ุจูุฐุง ุงูู ุฎุทุทุ
- ๐ ุฃู ุซูุฉ ุนู ููุฉ ูุชุดููุฑ pgcrypto (ุชุดููุฑ/ูู ุชุดููุฑ/ุจุญุซ)ุ
- ๐ ุงุณุชุนูุงู ุงุช SQL ุฌุงูุฒุฉ ููุชูุงุฑูุฑ ูููุญุงุช ุงูุชุญูู ุ
- ๐ ุณูุฑูุจุชุงุช Migration (Flyway/Prisma)ุ
- ๐งช ุจูุงูุงุช Seed ูุงูุนูุฉ (ู ุญุงูุธุงุชุ ู ุฏูุฑูุงุชุ ุฃุฏูุงุฑ)ุ
- ๐ ูุงูุจ PDF ุญุฒู ุฉ ุงูุทุจุงุนุฉ (HTML/CSS โ PDF)ุ
- ๐ ุขููุฉ Workflow Engine (State Machine)ุ
ุงุฎุชุฑ ุงูุฃููููุฉ ูุณุฃุนู ูู ูููุง. ๐
https://registeryem.somee.com/registerYemen/Register.aspx