تخطي إلى المحتوى
Architecture Decision Record

نظام الأوضاع المتعددة

بنية تدعم أوضاع تشغيل متعددة (خاص، حكومي، مختلط) تُحدد في وقت التشغيل — مع فصل كامل بالملفات بدون أي شروط في الكود.

نظرة عامة

المبدأ الأساسي

فصل كامل — كل ملف نظيف 100% بدون شروط. الـ mode logic موجود في 4 أماكن فقط في النظام بأكمله.

الصفحات المتاحة

بعض الصفحات خاصة بنظام معين — يحميها middleware تلقائياً

عناصر الـ Sidebar

تظهر أو تختفي حسب النظام عبر filterByMode

محتوى الصفحات

نفس الصفحة بحقول/سكيمة/state مختلف لكل نظام

كشف النظام — useMode.js

يقرأ الـ mode من session data ويوحد الصيغة
قواعد التحويل
المصدرالقيمةالنتيجة
privateCompanyModetrueprivate
privateCompanyModefalsegovernment
mode1private
mode2government
mode3mixed
fallbackprivate
useMode.js — Implementationjs
export const MODES = {
  PRIVATE: 'private',
  GOVERNMENT: 'government',
  MIXED: 'mixed'
}

export const useMode = () => {
  const { data: session } = useAuth()

  const mode = computed(() => {
    const raw = session.value
    if (raw?.mode !== undefined) {
      return {
        1: MODES.PRIVATE,
        2: MODES.GOVERNMENT,
        3: MODES.MIXED
      }[raw.mode] || MODES.PRIVATE
    }
    if (raw?.privateCompanyMode !== undefined) {
      return raw.privateCompanyMode
        ? MODES.PRIVATE : MODES.GOVERNMENT
    }
    return MODES.PRIVATE
  })

  const isMode = (...modes) => modes.includes(mode.value)

  const filterByMode = (items) => items.filter(item => {
    if (!item.modes || item.modes.length === 0) return true
    return item.modes.includes(mode.value)
  })

  return { mode, isMode, filterByMode, MODES }
}

مرجع الـ API

الدوال والثوابت التي يصدّرها useMode
MODESObject

ثوابت أسماء الأنظمة — PRIVATE, GOVERNMENT, MIXED.

import { MODES } from '~/composables/core/useMode'

MODES.PRIVATE     // 'private'
MODES.GOVERNMENT  // 'government'
MODES.MIXED       // 'mixed'
modeComputedRef

الـ mode الحالي كـ string — يتحدث تلقائياً عند تغيير session.

const { mode } = useMode()
console.log(mode.value)
// 'private' | 'government' | 'mixed'
isMode(...modes)Function

يتحقق هل النظام الحالي ضمن القائمة المحددة — يقبل عدة أنظمة.

const { isMode } = useMode()

isMode('private')              // true لو خاص
isMode('private', 'mixed')     // true لو خاص أو مختلط
isMode('government')           // true لو حكومي
filterByMode(items)Function

يصفي array من العناصر حسب خاصية modes الاختيارية — عنصر بدون modes يظهر دائماً.

const { filterByMode } = useMode()

const items = [
  { label: 'Dashboard' },
  { label: 'Invoices', modes: ['private'] },
  { label: 'Tenders', modes: ['government'] },
]

filterByMode(items)
// → العناصر المناسبة للنظام الحالي

المكوّنات

مكوّنات Vue للتعامل مع الأوضاع المتعددة
BaseModeSwitchDynamic Component

يستقبل map من المكوّنات ويرسم الصحيح تلقائياً حسب الـ mode. يمرر $attrs و $slots للمكوّن المختار.

Usagevue
<script setup>
import FormPrivate from '~/components/.../FormPrivate.vue'
import FormGovernment from '~/components/.../FormGovernment.vue'

const formComponents = {
  private: FormPrivate,
  government: FormGovernment
}
  </script>

<template>
  <BaseModeSwitch :is="formComponents" />
</template>
BaseModeShowConditional Render

للحالات البسيطة — إظهار/إخفاء محتوى حسب الـ mode. يُستخدم خارج الفورمات فقط.

Usagevue
<!-- إظهار للنظام الخاص فقط -->
<BaseModeShow :modes="['private']">
  <div>محتوى خاص بالنظام الخاص</div>
</BaseModeShow>

<!-- إظهار للجميع ما عدا الحكومي -->
<BaseModeShow :except="['government']">
  <div>يظهر لكل الأنظمة إلا الحكومي</div>
</BaseModeShow>

حماية الصفحات — Middleware

middleware عام يحمي الصفحات المقيدة بنظام معين
صفحة بدون modes

مسموحة لكل الأنظمة — لا حاجة لتحديد modes

صفحة مع modes

مسموحة فقط للأنظمة المحددة — باقي الأنظمة يتم تحويلها لـ /

definePageMeta — أمثلةjs
// صفحة متاحة لكل الأنظمة
definePageMeta({
  title: 'pages.activities'
})

// صفحة خاصة بالنظام الخاص فقط
definePageMeta({
  title: 'pages.invoices',
  modes: ['private']
})

// صفحة متاحة للخاص والمختلط
definePageMeta({
  title: 'pages.tax',
  modes: ['private', 'mixed']
})

نمط الموديول — الفصل الكامل

للموديولات التي تختلف بين الأنظمة: ملف منفصل لكل نظام
موديول مختلف
composables/modules/settings/signatures/
├── index.js              ← الأوركسترا
├── schema.private.js     ← سكيمة نظيفة
├── schema.government.js  ← سكيمة نظيفة
├── form.private.js       ← state نظيف
├── form.government.js    ← state نظيف
├── table.js              ← مشترك
└── columns.js            ← مشترك

components/modules/settings/signatures/
├── SignaturesFormPrivate.vue
├── SignaturesFormGovernment.vue
└── SignaturesTable.vue
موديول مشترك
composables/modules/settings/activities/
├── index.js
├── schema.js       ← ملف واحد
├── form.js         ← ملف واحد
├── table.js
└── columns.js

components/modules/settings/activities/
├── ActivitiesForm.vue   ← ملف واحد
└── ActivitiesTable.vue
Naming Conventions
النوعالمشتركالخاص بنظام
Schema composableschema.jsschema.private.js / schema.government.js
Form composableform.jsform.private.js / form.government.js
Columns composablecolumns.jscolumns.private.js / columns.government.js
Vue ComponentSignaturesForm.vueSignaturesFormPrivate.vue / SignaturesFormGovernment.vue
Export functionuseSignaturesSchema()useSignaturesSchemaPrivate() / ...Government()

نمط الأوركسترا — index.js

المكان الوحيد في الموديول فيه mode logic — يستخدم map بسيط لاختيار الملفات الصحيحة
Orchestrator — index.jsjs
export const useSignatures = () => {
  const { mode } = useMode()

  // ← الشرط الوحيد: اختيار النسخة الصحيحة
  const schemaMap = {
    private: useSignaturesSchemaPrivate,
    government: useSignaturesSchemaGovernment
  }
  const formMap = {
    private: useSignaturesFormPrivate,
    government: useSignaturesFormGovernment
  }

  const { schema } = schemaMap[mode.value]()
  const form = formMap[mode.value]()

  // ← مشترك — بدون أي شرط
  const table = useSignaturesTable()
  const { createColumns } = useSignaturesColumns()

  const editHandler = (item) => {
    form.state.id = item.id
  }

  const columns = createColumns({ onEdit: editHandler })

  const resetState = () => form.resetForm()

  const submitHandler = async () => {
    table.loadingSave.value = true
    try {
      const body = form.prepareSubmitData()
      await table.updateSignature(form.state.id, body)
      form.resetForm()
    } catch {
      // handled by base service
    } finally {
      table.loadingSave.value = false
    }
  }

  return {
    schema, state: form.state,
    reportsList: form.reportsList,
    items: table.items,
    loadingSave: table.loadingSave,
    columns, resetState, submitHandler
  }
}

أين يوجد Mode Logic؟

في النظام بأكمله، الـ mode logic موجود في 4 أماكن فقط
1composables/core/useMode.js

قراءة الـ mode من session وتوحيد الصيغة

2middleware/mode.global.js

حماية الصفحات المقيدة بنظام معين

3composables/layout/useSidebar.js

تصفية عناصر الـ sidebar حسب النظام

4composables/modules/*/index.js

اختيار schema/form/component الصحيح (الأوركسترا)

كل ملف آخر (schema, form, component, table, columns, service) = كود نظيف 100% بدون أي شرط.

إضافة نظام جديد

خطوات إضافة نظام mixed (mode: 3) — بدون إعادة هيكلة أو تعديل ملفات الأنظمة الموجودة
1تحديث useMode.js

إضافة النظام الجديد للثوابت وقاعدة التحويل.

export const MODES = {
  PRIVATE: 'private',
  GOVERNMENT: 'government',
  MIXED: 'mixed'  // ← جديد
}

// في computed:
{ 1: MODES.PRIVATE, 2: MODES.GOVERNMENT, 3: MODES.MIXED }
2إنشاء ملفات النظام الجديد

لكل موديول مختلف — schema, form, component.

schema.mixed.js
form.mixed.js
SignaturesFormMixed.vue
3تحديث الأوركسترا

إضافة النظام الجديد في الـ map.

const schemaMap = {
  private: useSignaturesSchemaPrivate,
  government: useSignaturesSchemaGovernment,
  mixed: useSignaturesSchemaMixed  // ← جديد
}
4تحديث الصفحة

إضافة المكوّن الجديد في formComponents.

const formComponents = {
  private: FormPrivate,
  government: FormGovernment,
  mixed: FormMixed  // ← جديد
}
5تحديث Sidebar

إضافة عناصر جديدة مع modes: ['mixed'].

{
  label: t('sidebar.mixedReport'),
  to: localePath('/mixed-report'),
  modes: ['mixed']  // ← جديد
}
6تحديث Page Meta

حماية الصفحات المقيدة بالنظام الجديد.

definePageMeta({
  title: 'pages.mixedReport',
  modes: ['mixed']  // ← حماية بالـ middleware
})

القرارات التصميمية

لماذا اخترنا الفصل بالملفات وليس البدائل الأخرى
لماذا الفصل الكامل وليس شروط في template؟
المعيارModeShow / شروطفصل بالملفات
مع مئات الحقولمئات الشروط في templateكل ملف نظيف 100%
مع 3+ أنظمةتعقيد أسّيملف جديد فقط
فهم نظام واحدفلترة ذهنية لكل سطرفتح ملف واحد = كل شيء
إضافة نظام جديدتعديل ملفات موجودةإضافة ملفات فقط
التكرار الوحيد = HTML الـ template
ما يتكررخطورته
HTML الحقول في templateمنخفضة — تصريحي وليس لوجك
Labelsصفر — من i18n (ملف واحد مشترك)
سلوك الحقولصفر — في Base Components
Schemaصفر — ملف مستقل لكل نظام
Form stateصفر — ملف مستقل لكل نظام
Services/APIصفر — مشترك دائماً
Save/Submitصفر — مشترك في الأوركسترا
لماذا ليس Config-Driven أو Nuxt Layers؟
البديلالمشكلة
Config-Driven (حقول كـ array)يفقد مرونة الـ template — لا slots مخصصة، لا layouts معقدة، كل نوع حقل جديد يحتاج case
Nuxt Layers (طبقة لكل نظام)الـ mode يأتي runtime من API بعد تسجيل الدخول — Layers تعمل build-time فقط