نعم. السطور مخزَّنة مرتين، ومن المهم التفريق بين النسختين:
- النسخة المعتمدة — عمود XML واحد على السجل المالك.
- نسخة مرآة قابلة للاستعلام — مجموعة جداول مسطَّحة تُعاد كتابتها في كل مرة يُحفظ فيها السجل المالك.
تطبيق الصلاحيات يقرأ دائمًا من نسخة الـ XML. أما جداول المرآة فوجودها لأغراض الاستعلام والتقارير فقط.
١) النسخة المعتمدة — عمود allLists
كلا السجلين يحتفظ بكل جداول الصلاحيات داخل عمود XML كبير واحد اسمه allLists:
| الجدول |
العمود |
الجداول المخزَّنة داخل الـ XML |
NaMaUser |
allLists |
standardLines, customLines, disabledFields, pageSecurity, extraFilters, searchExtraFilters, loginDimensions, treatAsUsersInFirstAuthor |
SecurityProfile |
allLists |
standardLines, actionLines, disabledFields, customLines, genericStandardLines, genericActionLines, genericDisabledFields, genericCustomLines, extraFilters, searchExtraFilters, pageSecurity, allowDisallowInMenus, listViewSecurityLines |
ولأنه لا يوجد جدول ابن علائقي معرَّف لهذه الجداول، فهي لا تظهر في مرجع نموذج البيانات على dm.namasoft.com. هذا هو السلوك المتوقَّع وليس نقصًا في التوثيق.
ولا تحاول قراءة هذا العمود أو تعديله يدويًا — فهو مستند مسلسل واحد، وأي تعديل جزئي عليه يُفسد كل جداول الصلاحيات في السجل.
٢) نسخة المرآة — جداول سطور النظام
في كل مرة يُعتمد فيها ملف صلاحيات أو مستخدم، تُكتب الجداول المعنية في جداول مسطَّحة. اسم كل جدول مطابق تمامًا لمحتواه:
ملف الصلاحيات (Security Profile)
| الجدول في الشاشة |
جدول المرآة |
standardLines (صلاحيات الكيانات) |
SysSecurityLine |
genericStandardLines |
SysGenericSecurityLine |
customLines (القدرات) |
CapabilityUsageSysLine |
disabledFields (الحقول المعطَّلة) |
SysSecurityFieldAuthority |
pageSecurity (صلاحيات الصفحات) |
SysPageSecurity |
actionLines (سطور الإجراءات) |
SysActionSecurityLine |
المستخدم (User)
يُنسَخ جدولان فقط لا غير:
| الجدول في الشاشة |
جدول المرآة |
settings.standardLines (صلاحيات الكيانات) |
SysSecurityLine |
settings.customLines (القدرات) |
CapabilityUsageSysLine |
الأعمدة المشتركة بين كل جداول المرآة
| العمود |
المعنى |
id |
مفتاح سطر المرآة نفسه (يُعاد توليده مع كل حفظ — لا تعتمد عليه ولا تخزّنه في أي مكان) |
refId |
معرِّف السجل المالك: معرِّف المستخدم أو معرِّف ملف الصلاحيات |
lineNumber |
ترتيب السطر داخل الجدول، بدءًا من صفر |
جدولا SysSecurityLine و CapabilityUsageSysLine مشتركان بين المستخدمين وملفات الصلاحيات، لذلك يجب دائمًا التمييز بين النوعين:
SysSecurityLine — العمود securityProfile_id مملوء في سطور ملف الصلاحيات وفارغ في سطور المستخدم، بينما refId يحمل معرِّف المالك في الحالتين.
CapabilityUsageSysLine — يحمل العمودين user_id و securityProfile_id معًا، ويكون أحدهما فقط مملوءًا.
أما بقية الأعمدة فهي مطابقة لحقول الجدول في الشاشة واحدًا بواحد: targetEntity, targetEntities_id, canEdit, canDelete, canPrint, canApprove, fullAuthority, viewOnlyCreatedRecords, preventSaveDraft, canExport, canImport وغيرها في SysSecurityLine؛ و fieldId, entityType, authorityType, applicabeWhen في SysSecurityFieldAuthority؛ و pageName, entityType, authorityType في SysPageSecurity؛ و actionId, authorityType في SysActionSecurityLine؛ و securityCapability_id, targetEntity, ref1/ref2, date1/date2, description1/description2 في CapabilityUsageSysLine.
سلوك التحديث — للقراءة فقط بحكم التصميم
- مع كل حفظ للسجل المالك تُحذَف كل سطور المرآة الخاصة به ثم تُعاد كتابتها من جديد، فمعرِّفات السطور غير ثابتة.
- عند حذف السجل المالك تُحذَف سطور المرآة الخاصة به.
- تعديل سطر مرآة مباشرةً لا يغيّر شيئًا — لأن تطبيق الصلاحيات يقرأ من الـ XML — كما أن تعديلك سيُمحى مع أول حفظ للسجل المالك.
ونتيجة عملية مهمة: أي مستخدم أو ملف صلاحيات لم يُحفَظ منذ إضافة هذه الجداول لن يكون له أي سطور فيها. يكفي إعادة حفظ السجل (أو إعادة حفظ جماعي) حتى تمتلئ.
ما لا تُنسخ له مرآة
الجداول التالية موجودة داخل allLists فقط ولا يقابلها أي جدول في قاعدة البيانات:
- ملف الصلاحيات:
genericActionLines, genericDisabledFields, genericCustomLines, extraFilters, searchExtraFilters, allowDisallowInMenus, listViewSecurityLines
- المستخدم:
disabledFields, pageSecurity, extraFilters, searchExtraFilters, loginDimensions, treatAsUsersInFirstAuthor
وانتبه أن disabledFields و pageSecurity يُنسخان لملف الصلاحيات لكن لا يُنسخان للمستخدم.
قبل كتابة أي SQL: توجد شاشة جاهزة
للإجابة عن سؤال «من يملك هذه القدرة؟» لا تحتاج إلى SQL أصلًا. افتح سجل القدرة (Security Capability) واستخدم عرض القائمة Capability Usage الموجود بها — فهو يقرأ من CapabilityUsageSysLine ويتيح الفلترة والترتيب حسب المستخدم وملف الصلاحيات والقدرة ونوع الكيان وقائمة أنواع الكيانات.
أمثلة استعلامات
صلاحيات الكيانات الممنوحة مباشرةً على المستخدمين:
select u.code, u.name1, l.targetEntity, l.canEdit, l.canDelete, l.fullAuthority
from SysSecurityLine l
join NaMaUser u on u.id = l.refId
where l.securityProfile_id is null
order by u.code, l.lineNumber
صلاحيات الكيانات القادمة من ملفات الصلاحيات:
select p.code, p.name1, l.targetEntity, l.canEdit, l.canDelete, l.fullAuthority
from SysSecurityLine l
join SecurityProfile p on p.id = l.securityProfile_id
order by p.code, l.lineNumber
كل من يملك قدرة معينة، من أي من المصدرين:
select u.code as userCode, p.code as profileCode, c.code as capability, l.targetEntity
from CapabilityUsageSysLine l
join SecurityCapability c on c.id = l.securityCapability_id
left join NaMaUser u on u.id = l.user_id
left join SecurityProfile p on p.id = l.securityProfile_id
where c.code = 'YOUR_CAPABILITY_CODE'
وتذكَّر أن الصلاحيات الفعلية للمستخدم هي اتحاد السطور الموجودة على سجل المستخدم نفسه مع السطور الموجودة على ملف الصلاحيات المرتبط به، فأي تقرير كامل لا بد أن ينظر في المصدرين معًا.