Skip to content

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

  1. 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.
  2. Segregation of duties: refund requester ≠ approver; moderation appeal handler ≠ original moderator; plan changes by non-super roles are proposals until super approval.
  3. Admins never read customer↔vendor conversations outside an open dispute/abuse case (super override logged).
  4. No hard deletes from the portal: suspend, hide, anonymize — financial and audit records are immutable.
  5. CMS legal pages require both locales before publishing; other content may publish per-locale (Scale blog).
  6. Admin portal is Kuwait-operations bilingual: UI in English, all managed content fields dual AR/EN.
  7. 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

  • Each role sees exactly the matrix above: automated endpoint-level authorization test iterates all portal endpoints × 4 roles.
  • Vendor approval end-to-end: application → changes requested → resubmit → approve, with the vendor notified at each step and every decision in the audit log.
  • A finance admin approves a partial refund and the linked payment/refund records match to 3 dp; a support admin receives 403 on the same endpoint.
  • Plan price change with future effective date leaves an active subscription's renewal price unchanged until its next cycle in a time-travel test.
  • Banner scheduled 10:00–18:00 Asia/Kuwait appears and disappears within 1 minute of boundaries (cache invalidation verified).
  • Audit entry exists for 100% of mutating portal actions in a crawler test, each with actor, diff and IP; entries are un-editable via any API.
  • Terms page publish with only one locale is blocked; with both, the apps' "updated terms" flag activates.
  • Dashboard KPIs match warehouse queries within 1% on a seeded dataset.

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