SRS — Admin Portal & CMS
Module owner: Domain 10 — Operations (admin, moderation, support) with CMS pieces in Domain 11 — Platform (localization, audit) and Domain 12 — Growth (banners, blog). One internal web portal (Next.js, separate deployment from the customer/vendor apps, staff SSO + mandatory 2FA) operating the entire marketplace.
Related: Payments · Reviews · Messaging & Notifications · Booking · Quotations
Purpose
Give Yam3at staff one control room: approve vendors, run subscriptions, oversee quotations and bookings, control refunds, moderate content, resolve disputes, read the money, and edit public content in Arabic and English — all under role-scoped permissions and a complete audit trail.
Actors
| Actor |
Role |
| Super Admin |
Full access incl. role management, platform config, PSP switching |
| Operations Admin |
Vendor approvals, marketplace oversight, moderation, CMS, disputes |
| Finance Admin |
Refund approvals, reconciliation, financial reports, subscription billing issues |
| Support Admin |
Customer/vendor assistance, dispute intake, read-mostly access |
| System |
KPI aggregation jobs, SLA timers on queues, audit-log capture |
User stories
- As an operations admin, I want a vendor approval queue with license documents inline, so that I verify Kuwaiti commercial licenses without downloading files one by one.
- As a finance admin, I want a refund queue with booking context and policy math pre-computed, so that each decision takes under a minute.
- As a support admin, I want a 360° view of a customer (events, RFQs, bookings, payments, conversations flagged in disputes), so that I resolve tickets without asking other teams.
- As a super admin, I want to change a subscription plan's RFQ limit and have it apply next cycle, so that pricing evolves without deployments.
- As an operations admin, I want to edit the FAQ in Arabic and English side by side, so that both locales never drift apart.
- As a super admin, I want to see who changed what and when across the portal, so that accountability is absolute.
Functional requirements
Dashboard — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-001 |
KPI dashboard (period selector: today / 7d / 30d / custom; compare vs previous period): new customers, new vendor applications, active vendors by plan, RFQs created, quote response rate & median response time, quote→booking conversion, bookings by status, GMV proxy (Σ booked totals), deposits collected (KWD), subscription MRR, refund rate, open queues (approvals, refunds, moderation, disputes) with SLA breach counts. |
MVP |
| SRS-ADM-002 |
Queue SLA timers: vendor approval 2 business days, refund decision 3, moderation 1, dispute first-response 1 — breaches highlighted and escalation-notified. |
MVP |
Vendor & customer management — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-010 |
Vendor approval queue: application detail with commercial license upload (Kuwait: license number + expiry + document image/PDF), civil ID of authorized signatory, category selections, profile draft. Actions: approve, reject (reason from enum + note, sent to vendor), request changes (returns to vendor with checklist). Duplicate-license detection warns on matching license numbers. |
MVP |
| SRS-ADM-011 |
Vendor lifecycle controls: suspend (with reason; hides listings, blocks new RFQs, keeps confirmed bookings per SRS-BKG), reactivate, feature/unfeature (Scale monetization), edit category assignments, view metrics (response rate, completed jobs, cancellation flags, strikes). |
MVP |
| SRS-ADM-012 |
Customer management: search by name/phone/email; 360° profile (events, RFQs, bookings, payments, refunds, reports filed/received, blocks); actions: suspend/reactivate (reason required), trigger password reset, process account-deletion request (anonymization workflow honoring financial-record retention). No admin ever sees or sets passwords; civil ID is not collected from customers. |
MVP |
| SRS-ADM-013 |
Impersonation is not supported; support uses read-only views. (Deliberate: avoids abuse and audit ambiguity.) |
MVP |
Subscriptions & plans — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-020 |
Plan management: edit plan attributes (price KWD 3 dp, RFQ receive limit/month, featured slots, analytics tier, team seats, ad credits, priority support flag) with versioning — changes apply to new purchases/renewals from an effective date; running subscriptions keep their purchased version until renewal. |
MVP |
| SRS-ADM-021 |
Subscription operations: view any vendor's subscription state & payment history, extend grace period, apply comp/discount (super admin approval above 25%), force downgrade/upgrade with reason, cancel auto-renewal on request. All actions audit-logged. |
MVP |
Marketplace oversight — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-030 |
Quotation oversight: searchable RFQ/quote explorer (filters: status, category, city, date, vendor, customer); compliance actions per SRS-RFQ permissions: void quote, force-expire RFQ — reason mandatory. Blocked-word flag review feed. |
MVP |
| SRS-ADM-031 |
Booking oversight: explorer with status filters; force transitions per SRS-BKG (super/ops only, reason mandatory); reschedule and cancellation histories visible. |
MVP |
| SRS-ADM-032 |
Refund approvals: queue per SRS-PAY-040 — booking context, frozen policy, computed entitlement, dispute evidence; approve full/partial (amount editable within entitlement + goodwill margin ≤ 20% needing super approval) or deny with reason. Finance role only; requester ≠ approver enforced. |
MVP |
| SRS-ADM-033 |
Review moderation: queue per SRS-REV-010..012 — pending, reported, fraud-flagged tabs; decisions approve/hide/redact/escalate; appeal handling assigned away from original moderator. |
MVP |
| SRS-ADM-034 |
Dispute handling: case object linking booking, payment, conversation transcript (access auto-granted while case open, per SRS-MSG-032), evidence uploads from both parties, internal notes, resolution outcomes (refund %, warning, strike, no action) that execute through the owning modules — the dispute case never moves money itself. |
MVP |
Financial reports — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-040 |
Reports (on-screen + CSV/XLSX export): revenue by stream (subscriptions vs deposits held), MRR & churn by plan, payments/refunds ledger by period & method (KNET vs cards vs wallets), reconciliation results (SRS-PAY-052), receivables at risk (grace-period subscriptions), and a monthly close pack. All KWD 3 dp; multi-currency columns appear with expansion. |
MVP |
CMS — MVP (blog Scale)
| ID |
Requirement |
Phase |
| SRS-ADM-050 |
Static pages (About, Terms, Privacy, Vendor agreement, Contact): block-based rich text with side-by-side AR/EN editing, draft → published workflow, version history with rollback, slugs per locale, SEO meta. Legal pages display "last updated" and force re-acceptance flags when terms change materially. |
MVP |
| SRS-ADM-051 |
Banners: home/app hero and category-page banners; image (locale-specific), link target (internal route or URL), schedule (start/end), ordering, audience (all / customers / vendors); mobile + web dimensions validated. |
MVP |
| SRS-ADM-052 |
FAQ: categorized Q&A, AR/EN mandatory pairs, ordering, publish toggle; surfaces in apps and help center. |
MVP |
| SRS-ADM-053 |
Reference-data management: event types, categories (with icons AR/EN names), cities, cancellation-policy bounds, blocked-word lists, notification templates (SRS-MSG-044). |
MVP |
| SRS-ADM-054 |
Blog (content marketing, SEO): posts with cover, tags, author, AR/EN independent publishing (a post may exist in one locale). |
Scale |
Roles, permissions, audit — MVP
| ID |
Requirement |
Phase |
| SRS-ADM-060 |
Role-based access control with the four roles below; permissions enforced server-side per endpoint (Laravel policies), UI merely reflects them. Custom roles = Scale. |
MVP |
| SRS-ADM-061 |
Admin accounts: invite-only by super admin, corporate email required, mandatory TOTP 2FA at first login, session timeout 30 min idle, IP allowlist optional per deployment. Deactivation immediate-revokes tokens. |
MVP |
| SRS-ADM-062 |
Audit log viewer: every admin mutation (and dispute-gated reads per SRS-MSG-032) recorded — actor, role, action, entity type/id, before/after diff (JSON), reason where mandated, IP, timestamp. Viewer filters by actor/entity/action/date; export CSV; entries immutable, retained ≥ 5 years; log writes are same-transaction with the mutation. |
MVP |
Permissions matrix (portal capabilities × role)
| Capability |
Support |
Operations |
Finance |
Super Admin |
| Dashboard KPIs |
view |
view |
view |
view |
| Vendor approval queue |
view |
decide |
view |
decide |
| Vendor suspend/reactivate |
request |
✔ |
✘ |
✔ |
| Customer 360° / suspend |
view / request |
view / ✔ |
view |
✔ |
| Plan editing & versioning |
✘ |
propose |
propose |
✔ |
| Subscription ops (grace, comp) |
✘ |
✔ (comp ≤ 25%) |
✔ (comp ≤ 25%) |
✔ |
| RFQ/booking oversight actions |
view |
✔ (logged) |
view |
✔ (logged) |
| Refund approve/deny |
✘ |
✘ |
✔ |
✔ |
| Review moderation |
✔ |
✔ |
✘ |
✔ |
| Dispute handling |
intake + notes |
resolve |
resolve (financial) |
resolve |
| Conversation transcripts |
dispute-gated |
dispute-gated |
✘ |
override (logged) |
| Financial reports & reconciliation |
✘ |
✘ |
✔ |
✔ |
| CMS pages/banners/FAQ |
✘ |
✔ |
✘ |
✔ |
| Reference data & templates |
✘ |
✔ |
✘ |
✔ |
| Admin user & role management |
✘ |
✘ |
✘ |
✔ |
| Audit log viewer |
own actions |
✔ |
✔ |
✔ + export |
| Platform config (PSP driver, SLA, limits) |
✘ |
✘ |
✘ |
✔ |
Business rules
- Every mutating action requires a reason where it affects a user's money, account standing or content visibility; the reason travels to the audit log and (where user-facing) to the notification.
- Segregation of duties: refund requester ≠ approver; moderation appeal handler ≠ original moderator; plan changes by non-super roles are proposals until super approval.
- Admins never read customer↔vendor conversations outside an open dispute/abuse case (super override logged).
- No hard deletes from the portal: suspend, hide, anonymize — financial and audit records are immutable.
- CMS legal pages require both locales before publishing; other content may publish per-locale (Scale blog).
- Admin portal is Kuwait-operations bilingual: UI in English, all managed content fields dual AR/EN.
- All list explorers paginate server-side and cap exports at 100k rows per file.
Validation rules
| Field |
Rule |
| Rejection/suspension reason |
enum + note 10–1,000 chars |
| Plan price |
decimal(12,3) KWD ≥ 0.000; RFQ limit integer 0–10,000 or unlimited |
| Comp/discount |
1–100%; > 25% requires super approval workflow |
| Refund amount |
per SRS-PAY validation; goodwill margin ≤ 20% of entitlement |
| CMS slug |
per-locale, [a-z0-9-]{3,80} (EN) / URL-encoded Arabic allowed (AR), unique per locale |
| Banner image |
web 1440×480, mobile 750×560 (±5%), jpg/png/webp ≤ 2 MB, per-locale variants |
| FAQ entry |
question 5–200 chars, answer 10–5,000 chars, both locales |
| Admin invite |
corporate-domain email, role required, expires 72 h |
| Audit filters |
date range ≤ 1 year per query |
Status / state machine
Vendor application (the portal's highest-volume workflow):
stateDiagram-v2
[*] --> Submitted : vendor completes registration + license upload
Submitted --> UnderReview : admin opens case
UnderReview --> Approved : license & profile verified
UnderReview --> ChangesRequested : missing/invalid items (checklist sent)
ChangesRequested --> Submitted : vendor resubmits
UnderReview --> Rejected : failed verification (reason sent)
Rejected --> Submitted : vendor may reapply after 30 days
Approved --> Suspended : ops action (reason)
Suspended --> Approved : reactivation
Approved --> [*]
Dispute case: open → investigating → awaiting_party → resolved(outcome) | withdrawn; CMS content: draft → published → archived with version history.
Edge cases
- Vendor's commercial license expires while approved: nightly job flags expiring licenses (D−30, D−7); expired → auto-suspend listings with renewal-upload prompt; bookings in flight follow suspension rules.
- Two admins decide the same queue item: case claim/lock on open; second admin sees "claimed by {name}"; stale claims release after 30 min idle.
- Plan limit lowered below a vendor's current-month usage: current period unaffected; new limit binds from next cycle (plan versioning).
- Admin deactivated mid-session: token revocation is immediate; in-flight request completes, next request 401s.
- CMS rollback of a legal page: rolling back Terms creates a new version (never rewrites history) and re-triggers acceptance flags if the restored text differs materially (super confirm).
- Audit log query over huge ranges: viewer enforces the 1-year filter; exports run async with notification when ready.
- Dispute resolved with partial refund but payment already fully refunded: resolution validator blocks over-refund (reads live refundable remainder from Payments).
Empty / loading / error states
| Surface |
Empty |
Loading |
Error |
| KPI dashboard |
Zero-state numbers with "since launch" copy |
Tile-level skeletons; tiles fail independently |
Per-tile retry; stale-data timestamp shown |
| Queues (approvals/refunds/moderation/disputes) |
"Queue clear" + SLA stats for the period |
Table skeleton |
Retry; item detail independent of list |
| Explorers (RFQ/booking/customer/vendor) |
"No results — adjust filters" |
Server-side pagination spinner |
Preserved filters + retry |
| CMS editor |
Template picker for new content |
Autosave indicator |
Draft preserved locally; conflict (409) shows diff of the newer version |
| Audit viewer |
"No entries match" |
Progressive load |
Export failures retryable from a jobs panel |
Acceptance criteria
Data entities touched
admin_users, admin_roles, admin_invitations, admin_sessions, audit_logs, vendor_applications, vendor_review_checklists, subscription_plans + plan_versions, vendor_subscriptions (ops actions), disputes, dispute_evidence, dispute_notes, moderation_cases (shared with reviews/messages), refunds (decisions), cms_pages + cms_page_versions, banners, faqs, blog_posts (Scale), reference tables (event_types, categories, cities, blocked_words, notification_templates), kpi_snapshots (pre-aggregated), export_jobs.
# dashboard & reports
GET /api/v1/admin/dashboard?period=
GET /api/v1/admin/reports/revenue | /mrr | /ledger | /receivables
GET /api/v1/admin/reports/reconciliation?date=
# vendors & customers
GET /api/v1/admin/vendor-applications?status=
POST /api/v1/admin/vendor-applications/{id}/approve | /reject | /request-changes
POST /api/v1/admin/vendors/{id}/suspend | /reactivate
GET /api/v1/admin/customers?query=
GET /api/v1/admin/customers/{id}/overview
POST /api/v1/admin/customers/{id}/suspend | /reactivate | /anonymize
# plans & subscriptions
GET|POST|PATCH /api/v1/admin/plans # PATCH creates a new version
GET /api/v1/admin/vendors/{id}/subscription
POST /api/v1/admin/subscriptions/{id}/extend-grace | /comp | /change-plan
# oversight
GET /api/v1/admin/quotation-requests | /api/v1/admin/quotes
POST /api/v1/admin/quotes/{id}/void
GET /api/v1/admin/bookings
POST /api/v1/admin/bookings/{id}/transition
GET /api/v1/admin/refunds?status=requested # + approve/deny (see Payments)
GET /api/v1/admin/moderation/reviews | /reports
POST /api/v1/admin/disputes | GET /api/v1/admin/disputes/{id}
POST /api/v1/admin/disputes/{id}/resolve
# cms & reference data
GET|POST|PATCH /api/v1/admin/cms/pages # + /{id}/publish, /{id}/versions, /{id}/rollback
GET|POST|PATCH|DELETE /api/v1/admin/cms/banners
GET|POST|PATCH|DELETE /api/v1/admin/cms/faqs
GET|POST|PATCH /api/v1/admin/reference/{table} # event-types, categories, cities…
# access control & audit
POST /api/v1/admin/users/invite
GET|PATCH /api/v1/admin/users/{id} # role, deactivate
GET /api/v1/admin/audit-logs?actor=&entity=&action=&from=&to=
POST /api/v1/admin/audit-logs/export