دليل Jasper Reports الشامل لنظام Nama ERP
كل تقرير وكل نموذج طباعة يخرج من نظام نما هو تصميم JasperReports: ملف تصميم خلفه استعلام SQL وأمامه مجموعة من المدخلات يملؤها المستخدم. وهذه الصفحة مكتوبة لمن يبني هذه التصاميم ويصونها — للمنفّذ الذي عليه أن يجعل الفاتورة المطبوعة مطابقة لترويسة العميل، ولموظف الدعم الذي عليه أن يعرف لماذا لم تطابقها.
وهناك طريقان للوصول إلى تقرير. أداة إنشاء تقرير (Report Wizard) تبني لك التصميم من جدول رئيسي وقائمة حقول وبضعة جداول إعداد، ولا تفتح أنت ملف التصميم أصلاً. أو ترسم التصميم بنفسك في Jaspersoft Studio وترفعه، فتملك تحكماً كاملاً في كل حزمة وكل تعبير وكل صفحة. الأداة تغطي معظم احتياجات التقارير اليومية؛ أما التصميم المرسوم يدوياً فهو ما تلجأ إليه حين يجب أن يخرج المستند بشكل بعينه لا غير. وكلا الطريقين ينتهي إلى تعريف تقرير من النوع نفسه، فمعظم ما يلي ينطبق أياً كان الطريق الذي سلكته.
وحيثما رأيت تعبير Groovy في هذه الصفحة فهو استدعاء لمساعد التقارير الذي يتيحه نما داخل كل تقرير. أما الفهرس الكامل لتلك الاستدعاءات — الأسماء، والتواريخ، والأسعار، والروابط، والأمان، ورموز QR، ومدخلات $P{} الجاهزة التي يتلقاها كل تقرير — ففي مرجع تعبيرات NamaRep. هذه الصفحة تشرح المهام، وتلك الصفحة تسرد الاستدعاءات.
أين تسكن التقارير
كل ما يتعلق بتعريفات التقارير تجده تحت إدارة النظام ← التقارير:
| الشاشة | ما تحتويه |
|---|---|
| مجموعة تقارير | التجميعات التي تحدد تحت أي قائمة يظهر التقرير، ومن ثَمّ من الذي سيعثر عليه. |
| تعريف تقرير | التقرير نفسه — ملف التصميم المرفوع، وكوده، ومجموعته، وتقاريره الفرعية وموارده. هنا يُسجَّل التصميم المرسوم يدوياً حتى يستطيع المستخدمون تشغيله. |
| أداة إنشاء تقرير | طريق «ابنِ لي إياه»: اختر جدولاً، واختر حقولاً، واحفظ، فيُولَّد لك تعريف تقرير. |
| أداة إنشاء نموذج طباعة | الفكرة نفسها، لكن لنماذج الطباعة بدل تقارير القوائم. |
| مصدر بيانات | كتلة استعلام قابلة لإعادة الاستخدام يسحب منها تقرير الأداة أعمدة إضافية. |
| كيان افتراضي | جملة SQL محفوظة تتصرف كأنها جدول — انظر الكيانات الافتراضية. |
| Report Style | تنسيقات مسمّاة تتشاركها التقارير، بدل أن يحمل كل تصميم خطوطه وحدوده الخاصة. |
| قائمة تقارير مخصصة | قائمة تقارير تبنيها بيدك، حين لا يكون الترتيب المبني على المجموعات هو ما تريد. |
وإذا أخبرك مستخدم أن تقريراً «غير موجود» فهنا أول ما تبحث: التقرير موجود غالباً، لكن مجموعته تضعه في قائمة لا يراها ذلك المستخدم.
البناء بالأداة بدلاً من ذلك
إن لم تكن ستَرسم التصميم بيدك فابدأ من دليل أداة إنشاء التقارير — فهو يمشي بك خطوة خطوة في بناء تقرير من الشاشة، حقلاً حقلاً. ثم عُد إلى هنا لما لا تغطيه الأداة: المدخلات المكتوبة يدوياً، والتقارير الفرعية، وأحجام الصفحات، والخطوط، وقيود الأمان.
كيف يعرف النموذج أي سجل يطبع
التقرير الذي يُشغَّل من قائمة يسأل المستخدم عن كل ما يحتاجه — مدى تاريخي، وفرع، وعميل. أما نموذج الطباعة فلا يسأل عن شيء البتة: فالمستخدم يفتح السجل أمامه ثم يضغط طباعة. ومع ذلك فالاستعلام الذي خلف النموذج لا يعلم أي فاتورة مبيعات يرسم، ولا شيء في التصميم يخبره. هوية السجل تصل من الخارج، في مدخل واحد، وتوصيل هذا المدخل هو الشيء الوحيد الذي لا يستقيم نموذج مرسوم يدوياً بدونه.
نموذج يُطبع من سجل واحد
عرّف مدخلاً اسمه id، ثم رشّح الجدول الرئيسي به:
<parameter name="id" class="java.lang.Object">
<property name="entityType" value="SalesInvoice"/>
</parameter>select inv.code, inv.valueDate, cust.name1
from SalesInvoice inv
left join Customer cust on cust.id = inv.customer_id
where inv.id = $P{id}هذا هو العقد كله، وهو ما تفعله كل النماذج التي تأتي مع المنتج. أما الخاصية entityType فتسمّي نوع السجل الذي تشير إليه القيمة، تماماً كما تفعل في حقل مرجع يُسأل عنه المستخدم.
النموذج يتسلّم قيمة واحدة لا غير، والربط فيها بالموضع
نموذج الطباعة لا يعرض على المستخدم أي سؤال — فقد ضغط طباعة على سجل مفتوح أمامه وانتهى الأمر. والقيمة الوحيدة التي يتسلّمها هي معرّف السجل، ويكتبها نما في أول مدخل يطرحه التصميم بوصفه سؤالاً. والاسم ليس هو الحَكَم: فبعض النماذج التي تأتي مع المنتج تسمّي هذا المدخل Id أو ID لا id، وتطبع على أكمل وجه.
ولذلك ينبغي أن يكون مدخل المعرّف هو السؤال المفتوح الوحيد في التصميم. وأي مدخل آخر يحتاجه التصميم — قيمة يحسبها، أو راية يشغّل بها شيئاً — يجب إغلاقه، وإلا التقط هو معرّف السجل فرسم النموذج سجلاً خاطئاً دون أن تظهر رسالة تدل على السبب. وثلاثة أمور يُغلق بها المدخل، ويكفي واحد منها: isForPrompting="false" على المدخل، أو <property name="ignore" value="true"/> بداخله، أو خاصية src تسمّي المدخل الذي يأخذ عنه قيمته. أما مدخلات النظام الجاهزة — loginUserName1 و loginLegalEntityLogo وسائرها — فيتعرّف عليها النظام بأسمائها ويتخطاها كذلك، فلها أن تقع في أي موضع من التصميم.
نموذج يُطبع من شاشة قائمة
اجعل نوع التقرير في التعريف نموذج قائمة (List) فيصير النموذج معروضاً من شاشة القائمة بدلاً من ذلك، حيث يؤشّر المستخدم عدة صفوف ثم يضغط طباعة — انظر طباعة قائمة بدل سجل.
النموذج هنا يتسلّم كل السجلات المختارة لا سجلاً واحداً، فيصل المدخل نفسه بوصفه قائمة معرّفات. ولذلك يتغير تعريفه ويتغير الاستعلام الذي يقرؤه معاً:
<parameter name="id" class="java.util.List">
<property name="entityType" value="SalesInvoice"/>
</parameter>where $X{IN, inv.id, id}و $X{IN, column, parameter} هي دالة الشرط التي يقدّمها Jasper لهذه الحالة بعينها: تكتب لك in (?, ?, ?) بعدد ما أشّر عليه المستخدم من صفوف. ولا سبيل إلى كتابة تلك القائمة بيدك، لأن عدد الصفوف التي سيختارها المستخدم غير معلوم وقت رسم التصميم.
والطريقان يختلفان كذلك في طريقة الربط، وهو ما يحسن معرفته قبل أن تنسخ نموذجاً يعمل: الطباعة من القائمة تربط بـالاسم أياً كان موضع المدخل في التصميم، ويجب أن يكون الاسم id بحروفه هذه. أما الصيغتان Id و ID اللتان يتسامح معهما نموذج السجل الواحد فلا تتسلّمان هنا شيئاً.
= $P{id} مع قائمة يفشل
هذا أشيع سبب لنموذج يُطبع من السجل على أكمل وجه ثم يفشل من شاشة القائمة. فـ $P{} يربط قيمة واحدة، وقائمة من خمسة معرّفات ليس لها قيمة واحدة تُربط، فيسقط الاستعلام عند قاعدة البيانات بخطأ في النوع لا يذكر شيئاً عن القوائم. غيّر النوع إلى java.util.List و غيّر المقارنة إلى $X{IN, …} معاً — فأي تغيير منهما وحده يبقي العطل قائماً.
الأداة تقلب الاثنين نيابةً عنك
خيار طباعة كقائمة في أداة إنشاء نموذج طباعة هو هذا المفتاح نفسه. إن تركته مطفأً رشّح النموذج المولَّد معرّفاً واحداً، وإن أشّرته ولّد مدخل قائمة وشرط IN. والذي تكتبه الأداة في حالة السجل الواحد هو $X{EQUAL, column, parameter} — وهي صياغة column = $P{parameter} نفسها بدالة شرط. وكلتا الصياغتين مقبولة في تصميم ترسمه بنفسك.
وضع شعار الشركة على التقرير
الشعار أول ما يطلبه الجميع، ولا يحتاج استعلاماً ولا إعداداً — فالنظام يسلّمه لكل تقرير يطلبه.
- عرّف مدخلاً باسم
loginLegalEntityLogoمن النوعjava.lang.Objectأوjava.io.InputStream. - أضف عنصر صورة إلى التصميم.
- اجعل تعبير الصورة هو
$P{loginLegalEntityLogo}.
هذا كل شيء. فعند تشغيل التقرير يصل إلى ذلك المدخل شعارُ الشركة التي سجّل المستخدم دخوله بها. وهناك أربعة شعارات إضافية بالطريقة نفسها — من loginLegalEntityLogo2 إلى loginLegalEntityLogo5 — وبها تضع المنشآت التي تحتاج علامة ثانية، كشهادة جودة أو شعار امتياز، تلك العلامة على الصفحة.
أما أيّ شركة يؤخذ شعارها حين يكون المستند تابعاً لشركة غير شركة المستخدم فيُحدَّد في الإعداد العام، في تبويب التقارير والطباعة.
أي صورة أو مرفق آخر
للصورة التي ليست الشعار — توقيع مختوم محفوظ على السجل، أو شهادة ممسوحة ضوئياً — استجلب المرفق بمعرّفه وسلّم الناتج لعنصر الصورة:
NamaRep.getFile($F{attachmentId})
// أو
NamaRep.getAttachment($F{attachmentId})التقارير الفرعية والموارد الإضافية
يستطيع التقرير أن يضمّن تقريراً آخر داخله. وبهذا يطبع إذنُ التسليم أسطرَه من تصميم وشروطَه وأحكامَه من تصميم آخر، ويطبع كشفُ الحساب كتلة ملخّص مختلفة لكل فرع.
سجّل التقرير الفرعي على تعريف التقرير، ثم اربطه بالتصميم:
- أعطِ التقرير الفرعي معرّف تقرير فرعي على تعريف التقرير. وهو نص حر — أنت من يختاره.
- في التصميم، عرّف مدخلاً بهذا الاسم بالضبط.
- اجعل فئة ذلك المدخل
java.lang.Object. - استخدم المدخل تعبيراً للتقرير الفرعي.
عرّفه java.lang.Object لا java.io.InputStream
ما يضعه النظام في ذلك المدخل تقريرٌ مُصرَّف جاهز، لا الملف الخام. والمدخل المعرَّف java.io.InputStream يفشل عند التعبئة برسالة خطأ في الأنواع لا تدل على السبب بشيء. وكل تقرير يأتي مع المنتج يعرّف مدخلات تقاريره الفرعية java.lang.Object.
والموارد الإضافية — صورة يستخدمها التصميم، أو ملف يحتاجه — تعمل بالطريقة نفسها: سجّل المورد على تعريف التقرير، وعرّف مدخلاً بالاسم نفسه وبالفئة java.lang.Object، وأشر إليه حيث يحتاجه التصميم.
تعديل التصميم في مكانه
معظم التعديلات على تصميم مرسوم يدوياً تصل بالطريقة التي بُني بها التصميم: تصلح التصميم في Jaspersoft Studio، وتصدّره، وترفع الملف الجديد في ملف التقرير. لكن التعريف يحتفظ أيضاً بنسخة نصية من ذلك الملف، وفي التصحيح الصغير — اسم عمود مكتوب خطأً، أو عنوان، أو عرض — يكون تعديل النص أسرع من رحلة ذهاب وعودة إلى الاستوديو.
افتح صفحة متقدم. في أسفلها حقلان:
| الحقل | ما يحمله |
|---|---|
| محتوي ملف التقرير (Report File Content) | نص ملف التصميم المرفوع. يُعاد ملؤه من الملف مع كل حفظ للتعريف، فما تقرؤه هنا هو دائماً ما يعمل منه التقرير. |
| نسخ المحتوي النصي الي الملف (Copy Content To Report) | تعليمة للحفظ التالي: اكتب النص من جديد في الملف. |
عدّل النص واحفظ، ويتولى النظام الباقي. يُعاد كتابة الملف من النص، ويُعاد تصريف التقرير من الملف، ثم يُقرأ النص من الملف مرة أخرى فيعودان متطابقين. وحقل نسخ المحتوي النصي الي الملف يضع العلامة بنفسه حين يجد الحفظ أن النص يختلف عما كان محفوظاً، فلا تحتاج عادةً إلى لمسه، وستجده قد أُزيلت علامته بعد الحفظ: فهو يصف إجراءً يُنفَّذ مرة واحدة لا إعداداً دائماً.
وينطبق الأمر نفسه حين يصل التعديل عبر استيراد أو تكامل لا من الشاشة. فالتعريف المحفوظ الذي يختلف محتواه عن النسخة المخزنة يُعامَل تماماً كما لو كنت كتبت التعديل بيدك. والتعريف الجديد كلياً الذي أُنشئ من نص ملصوق دون رفع أي ملف يُعامَل بالطريقة نفسها: فأول حفظ ينشئ الملف من النص ويسمّيه باسم كود التقرير (أو باسمه إن لم يكن في الكود حروف صالحة) بامتداد .jrxml، فيمكن إنشاء تعريف كامل من النص وحده.
وتحمل أسطر التقارير الفرعية والموارد الزوج نفسه من الحقول، وإن كانت الجداول على الشاشة لا تعرض إلا المعرّف والملف. فبالنسبة لأداة استيراد أو لأدوات MCP أو لواجهة API، يتيح كل سطر في التقارير الفرعية وكل سطر في الموارد الحقلين reportContent وreportFileName إلى جانب ملفه: سطر التقرير الفرعي يحمل نص تصميمه كما هو، وسطر المورد يحمل بايتات الملف مرمَّزة Base64. وحين توضع علامة نسخ المحتوي النصي الي الملف يكتب حفظ واحد محتوى الرأس ومحتوى كل سطر في ملفاتها معاً. والعلامة الآلية تراقب محتوى الرأس وأي سطر له محتوى ولا ملف له بعد، فالسطر المنشأ من نص يحصل على ملفه مع أول حفظ. أما الاستيراد الذي يغيّر نص سطر تقرير فرعي أو مورد موجود من قبل فيجب أن يضبط copyContentToReport صراحةً وإلا بقي ملف ذلك السطر كما كان.
ما الذي يعمل فعلاً
لا يعمل التقرير أبداً من الحقل النصي. فكل حفظ يعيد بناء نسخة مصرَّفة من ملف التقرير، وتلك النسخة المصرَّفة هي ما يشغّله المستخدمون. الحقل النصي طريق إلى الملف لا مصدر ثانٍ، ولهذا يتطابقان دائماً بعد الحفظ.
سؤال المستخدم: مدخلات التقرير
المدخلات هي ما يملؤه المستخدم قبل تشغيل التقرير، وهي نفسها القيم التي يقرؤها استعلام SQL. يُعرَّف المدخل مرة واحدة في التصميم ويؤدي المهمتين معاً.
مدخلات التحديد المتعدد (القوائم)
أحياناً لا تكفي قيمة واحدة — فالمستخدم يريد خمسة موظفين، أو كل الفروع إلا اثنين. ويستطيع المدخل أن يقبل قائمة:
- اضبط الخاصية
list = true. - ولكل ما ليس مرجعاً إلى سجل، اضبط كذلك
listType(مثلاًjava.util.Date). - ولطباعة ما اختاره المستخدم، عرّف مدخلات نصية مرافقة يملؤها النظام تلقائياً:
<parameterName>_csv— القيم المترجمة مفصولة بفواصل<parameterName>_codecsv— الأكواد<parameterName>_name1csv— الأسماء العربية<parameterName>_name2csv— الأسماء الإنجليزية
- و
doNotAutoShowList = trueيمنع سرد القيم المختارة تلقائياً على التقرير. - و
listDisplayTypeيحدد الأداة التي يراها المستخدم:Default— إدخال التحديد المتعدد القياسي، وهو المستخدَم عند حذف الخاصيةDropdown— تظهر القيم المختارة شرائحَ قابلة للإزالة داخل الإدخال، وتُفتح قائمة الخيارات الكاملة في قائمة منسدلة قابلة للبحث. وهو الخيار الصحيح حين تكون مجموعة القيم كبيرةChips— تظهر كل القيم المسموحة شرائحَ قابلة للنقر، ويُبدَّل التحديد بالنقر. وهو الخيار الصحيح لعدد قليل من الخيارات تريدها ظاهرة دون فتح شيء
<parameter name="MultiEmployee" class="java.util.List">
<property name="entityType" value="Employee"/>
<property name="list" value="true"/>
<property name="listDisplayType" value="Chips"/>
</parameter>مثال أوسع — مدخلا قائمة
<!-- قائمة سجلات -->
<parameter name="MultiEmployee" class="java.util.List">
<property name="entityType" value="Employee"/>
<property name="arabic" value="الموظفين"/>
<property name="english" value="Employees"/>
<property name="property" value="code"/>
<property name="list" value="true"/>
<property name="doNotAutoShowList" value="false"/>
</parameter>
<parameter name="MultiEmployee_csv" class="java.lang.String" isForPrompting="false"/>
<!-- قائمة قيم عادية -->
<parameter name="MultiDate" class="java.util.Date">
<property name="english" value="Dates"/>
<property name="arabic" value="التواريخ"/>
<property name="defaultValue" value="$monthStart()"/>
<property name="list" value="true"/>
<property name="listType" value="java.util.Date"/>
</parameter>
<parameter name="MultiDate_csv" class="java.lang.String" isForPrompting="false"/>مدخلات نطاق التاريخ
مدخلا «من تاريخ» و«إلى تاريخ» منفصلين يعملان بلا مشكلة، لكنهما يشغلان صفَّين ولا شيء على الشاشة يخبر المستخدم أنهما مرتبطان. وخاصية showAsDateRange تعطي المستخدم منتقيَ نطاق واحداً بينما يحتفظ الاستعلام بمدخلَي التاريخ الحقيقيين اللذين يحتاجهما.
تتعاون ثلاثة مدخلات:
- مدخل تحكّم — نصي، بخاصية
showAsDateRange = true. هذا ما يراه المستخدم، ولا يحمل قيمة خاصة به. - مدخل «من تاريخ» بخاصية
isForPrompting="false"، يسمّيه مدخل التحكّم فيfromDateId. - مدخل «إلى تاريخ» بخاصية
isForPrompting="false"، يسمّيه مدخل التحكّم فيtoDateId.
وعندما يختار المستخدم نطاقاً تُكتب التواريخ المختارة في المدخلين الأساسيين. ومن وجهة نظر الاستعلام لم يتغير شيء — فأنت تشير إلى $P{FromValueDate} و$P{ToValueDate} تماماً كأي مدخل تاريخ آخر.
<parameter name="FromValueDate" class="java.util.Date" isForPrompting="false">
<property name="arabic" value="من التاريخ الفعلي"/>
<property name="english" value="From Value Date"/>
</parameter>
<parameter name="ToValueDate" class="java.util.Date" isForPrompting="false">
<property name="arabic" value="إلى التاريخ الفعلي"/>
<property name="english" value="To Value Date"/>
</parameter>
<parameter name="ValueDate" class="java.lang.String">
<property name="showAsDateRange" value="true"/>
<property name="fromDateId" value="FromValueDate"/>
<property name="toDateId" value="ToValueDate"/>
<property name="arabic" value="التاريخ الفعلي"/>
<property name="english" value="Value Date"/>
</parameter>ثم في الاستعلام:
WHERE valueDate >= $P{FromValueDate}
AND valueDate <= $P{ToValueDate}أو بصيغة between:
where $X{[BETWEEN],valueDate,FromValueDate,ToValueDate}ولا يظهر المنتقي ما لم تتحقق ثلاثة أمور: أن يكون مدخل التحكّم من النوع java.lang.String، وأن يضبط مدخلا التاريخ isForPrompting="false" حتى لا يظهرا مطالبتين منفصلتين بجانب المنتقي، وأن تطابق قيمتا fromDateId وtoDateId اسمَي المدخلين بالضبط.
مرجع خصائص المدخلات
الخصائص الأساسية
list—true/false، يفعّل التحديد المتعددlistType— مطلوب لكل ما ليس مرجعاً (مثلاًjava.util.Date)listDisplayType— الأداة التي يُعرَض بها مدخل القائمة:DefaultأوDropdownأوChipsshowAsDateRange—true/false، يعرض مدخلاً نصياً كمنتقي نطاق موحّد؛ يُستخدم معfromDateIdوtoDateId، وتفصيله في قسم «مدخلات نطاق التاريخ» أعلاهfromDateId/toDateId— اسما مدخلَي التاريخ الأساسيين عند تفعيلshowAsDateRangelayout— كيف تُرتَّب المطالبة:aloneأوspannedأوnormalأوspanned2required—true/false، يجعل المطالبة إلزاميةrequiredGroup— يجمع مدخلات يجب ملء واحد منها على الأقلhijri—true/false، يطالب بتاريخ هجريnama-id— معرّف داخلي تستخدمه أداة إنشاء التقارير، ولا تضبطه أنت بيدك
الاقتراحات في الحقول النصية
suggestionquery— استعلام SQL يغذّي الإكمال التلقائي. عمودان يعنيان الكود مع عرض عربي؛ وثلاثة أعمدة تعني الكود والعربي والإنجليزي.
SELECT DISTINCT TOP 25 revisionId, revisionName
FROM ItemRevision
WHERE invItem_id = {fItem}
AND (revisionId LIKE '%' + {revision} + '%'
OR revisionName LIKE '%' + {revision} + '%')اختيار سجل
entityType— ما الذي يختار المستخدم منهproperty— أي حقل من السجل المختار يصل إلى الاستعلام:codeأوname1أوname2أوstartDate
في الحالة المعتادة يسمي entityType نوعاً واحداً ثابتاً — Employee أو Customer — فتكون المطالبة اختياراً من ذلك النوع. لكن كثيراً من الأعمدة تحمل مرجعاً عاماً (generic reference): ذمة قد تكون عميلاً أو مورداً أو موظفاً، أو مالكاً قد يكون فرعاً أو إدارة. والتقرير الذي يفلتر على عمود كهذا لا بد أن يسأل المستخدم سؤالين، وإجابة الأول هي التي تحدد ما يجوز للثاني أن يعرضه.
وجّه entityType إلى مدخل آخر مسبوقاً بعلامة $، فيأخذ المُنتقي نوعه مما يحمله ذلك المدخل في تلك اللحظة:
<parameter name="subsidiaryType" class="java.lang.String">
<property name="enumType" value="EntityTypeDF"/>
<property name="allowedValues" value="Customer,Supplier,Employee"/>
<property name="arabic" value="نوع الذمة"/>
<property name="english" value="Subsidiary Type"/>
</parameter>
<parameter name="subsidiary" class="java.lang.Object">
<property name="entityType" value="$subsidiaryType"/>
<property name="property" value="id"/>
<property name="arabic" value="الذمة"/>
<property name="english" value="Subsidiary"/>
</parameter>يختار المستخدم مورد في المطالبة الأولى فتصير الثانية منتقي موردين؛ ويبدّلها إلى عميل فتبدأ المطالبة نفسها تعرض العملاء. وما دامت مطالبة النوع بلا إجابة فليس أمام المنتقي شيء يبحث فيه، لذلك عرّف مدخل النوع قبل المدخل الذي يعتمد عليه — فالمطالبات تظهر بترتيب التعريف — وفكّر في جعله required.
ومعالج التقارير (Report Wizard) يكتب هذا الزوج نيابةً عنك كلما فلترت على حقل مرجع عام: يضيف مطالبة نوع تحمل اسم الحقل ولاحقة EntityTypeSelector — أي subsidiaryEntityTypeSelector لحقل اسمه subsidiary — ويحصر قائمتها المنسدلة في الأنواع التي يقبلها ذلك العمود فعلاً. وحين لا يقبل العمود إلا نوعاً واحداً فلا سؤال أصلاً، فيتخطى المعالج المطالبة الإضافية ويكتب ذلك النوع مباشرةً في entityType.
القوائم المنسدلة
enumType— مجموعة الخيارات المعروضةallowedValues— القيم المسموحة مفصولة بفواصل، وتُعرض قائمة منسدلةallowedValuesAr/allowedValuesEn— التسميات العربية والإنجليزية لتلك القيم، مفصولة بفواصل وبالترتيب نفسه
<parameter name="entityType" class="java.lang.String">
<property name="enumType" value="EntityTypeDF"/>
<property name="allowedValues" value="Employee,Supplier"/>
<property name="allowedValuesAr" value="موظف,مورد"/>
<property name="allowedValuesEn" value="Employee,Supplier"/>
</parameter>التسميات التي تكتبها هنا لا تمر على الترجمة أبدًا
خاصيتا التسمية ليستا مفتاح ترجمة، بل هما النص النهائي. اكتبهما فتظهر المطالبة بهذه الكلمات بعينها إلى الأبد، مهما قالت شاشة تعديل الترجمات (Translation OverRider) عن القيمة نفسها في باقي النظام. وبذلك تحتفظ القيمة التي أُعيدت تسميتها على مستوى النظام بتسميتها القديمة في هذا التقرير وحده، دون أي تنبيه على الفرق؛ وعلى كاتب التقرير أن يعود ويعدّل ملف .jrxml.
أما إن تركتهما فتُقرأ التسميات حيّة: enumType وحدها، أو allowedValues وحدها، تعرض كل قيمة بالاسم الذي يعطيه النظام لها حاليًا، فتتبع أي إعادة تسمية لاحقة من تلقاء نفسها. فاجعل ذلك هو الأصل، واحتفظ بـ allowedValuesAr/allowedValuesEn للحالات التي لا يفي بها — قيمة ليس لها اسم في النظام أصلًا، أو صياغة يجب أن تختلف في هذا التقرير عنها في كل مكان آخر.
تضييق ما يستطيع المستخدم اختياره
منتقي السجلات يعرض كل سجلات نوعه، وهذا قلّ أن يكون ما يريده التقرير: مطالبة المخزن في تقرير تحويل ينبغي أن تعرض مخازن الفرع المختار سلفاً، ومطالبة الحساب ينبغي أن تعرض الحسابات التفصيلية لا العناوين. وخصيصة filter هي التي تضيّق المنتقي.
filter— تعبير واحد أو أكثر بالصيغةfield,operator,value[,relation].
فـ field حقلٌ في السجل الذي يُنتقى — لا مدخل من مدخلات التقرير ولا عمود من أعمدته. وoperator أحد الأسماء المذكورة أدناه بحرفه. وvalue القيمة التي تُقارن بها. وrelation تربط التعبير بما قبله وهي AND إلا أن تكتب OR، وإن حذفتها فُهمت AND. وتُفصل التعبيرات المتعددة بفواصل منقوطة أو بأسطر جديدة، والفاصلة المنقوطة الزائدة في آخر السطر لا تضر. ولا تضع مسافات حول الفواصل — فالمسافة تصبح جزءاً من اسم العامل أو من القيمة.
forType,Equal,Department,AND;isLeaf,Equal,true
documentType,Equal,SalesInvoice,OR;documentType,Equal,SalesReturn
type,Equal,Detailالعوامل:
Equal, NotEqual,
GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual,
StartsWith, NotStartsWith, EndsWith, NotEndWith,
Contains, NotContain, ContainsWithAnyOrder,
In, NotIn, WithinPeriod, OutsidePeriod,
OpenBracket, CloseBracketويجوز أن يُكتب أي منها ولاحقته OrEmpty — EqualOrEmpty وContainsOrEmpty وInOrEmpty — وهذا يصبح مهماً في اللحظة التي تأتي فيها القيمة من مدخل آخر بدلاً من أن تكون مكتوبة في التقرير.
الفلترة بما أجاب به المستخدم في مدخل آخر
اكتب ${parameterId} في أي موضع من القيمة فيُستبدل بالجواب الذي أدخله المستخدم في ذلك المدخل، فيضيّق مدخلٌ نطاقَ مدخل آخر:
<parameter name="FromWarehouse" class="java.lang.Object">
<property name="entityType" value="Warehouse"/>
<property name="arabic" value="من مخزن"/>
<property name="english" value="From Warehouse"/>
</parameter>
<parameter name="OverdraftPolicy" class="java.lang.Object">
<property name="entityType" value="OverDraftPolicy"/>
<property name="filter" value="warehouse,Equal,${FromWarehouse}"/>
<property name="arabic" value="سياسة السالب"/>
<property name="english" value="Overdraft Policy"/>
</parameter>فيختار المستخدم مخزناً في المدخل الأول، فلا يعرض المدخل الثاني إلا سياسات السالب التابعة له. ولا يحتاج الربط إلى إعلان شيء آخر غير ${…} نفسها، وللمدخل الواحد أن تقرأه فلاتر كثيرة.
وهذا هو النظير القيمي لثنائية الحقل المرجع العام الموصوفة أعلاه، حيث entityType = $subsidiaryType تأخذ نوع المنتقي من مدخل آخر. فتنبّه للصيغتين فإنهما لا تتبادلان: النوع يُقرأ بـ $name بغير أقواس معقوفة، والقيمة بـ ${name} بها.
وثمة أمور يحسن معرفتها قبل الاتكال على هذا:
- ما بين المعقوفتين هو اسم المدخل كما أُعلن في التقرير، يُطابَق بحرفه وبحالة أحرفه — فـ
${FromWarehouse}و${fromwarehouse}شيئان مختلفان، وواحد منهما فقط موجود. ولا يُستبدل إلا${…}؛ أما$(FromWarehouse)بالأقواس المستديرة فيبقى نصاً كما هو، فيقارن الفلتر بذلك النص فلا يجد شيئاً. - والاسم الذي لا يطابق أي مدخل يُستبدل بلا شيء، وكذلك المدخل الذي لم يُجب المستخدم عنه بعد. لا تظهر رسالة خطأ ولا يُكتب شيء في السجل، وإنما يتصرف المنتقي كأن الفلتر يقول
warehouse,Equal,— أي يطلب السجلات التي مخزنها فارغ، وهذه في العادة لا وجود لها. والمنتقي الذي يرجع فارغاً بإصرار سببه هذا في الأغلب. - وهذا بالضبط ما وُضعت له عوامل
OrEmpty. فـwarehouse,EqualOrEmpty,${FromWarehouse}يُسقط ذلك التعبير بتمامه ما دامت مطالبة المخزن فارغة، ويطبّقه بمجرد الإجابة عنها — وهو الفرق بين تضييق اختياري ومطالبة لا يستطيع أحد استخدامها. وبقية الفلتر تعمل في أثناء ذلك؛ ومن التقارير الجاهزة ما يقرن ارتباطاً اختيارياً بشرط ثابت في سطر واحد:subsidiaryType,EqualOrEmpty,${SubsidiaryType},AND;trackDebtAges,Equal,true. - والأجوبة تُستبدل نصاً على الهيئة التي تحملها داخلياً: التاريخ بصيغة
dd-MM-yyyy، والتاريخ والوقت بصيغةyyyy-MM-ddTHH:mm:ss.SSS، ونعم/لا بـtrueأوfalse، وقيمة القائمة المنسدلة أو نوع الكيان بالقيمة نفسها، والسجل المختار بصيغةid:entityType:code:actualCode:name1:name2مع إسقاط الأجزاء الفارغة. - وهذه الصيغة المركّبة للسجل تُفهم حين تُقارن بحقل يحمل هو نفسه سجلاً — وهي الحالة المعتادة،
warehouse,Equal,${FromWarehouse}. أما مقارنتها بحقل نصي أو حقل كود فتفشل، لأن النص كله يُؤخذ حينئذٍ حرفياً. وخصيصةpropertyلا تنقذ الموقف: فهي تحدد ما يستقبله استعلام التقرير، لا ما تستبدله${…}. - والمدخل متعدد القيم (
list=true) يستبدل كل قيمه مرة واحدة مفصولةً بـ@A=@X، وهذا بعينه ما يقرأه العاملIn: فـwarehouse,In,${Warehouses}يضيّق بكل مخزن أشّر عليه المستخدم. - وأعلن المدخل المقروء قبل المدخل الذي يقرؤه. فالمطالبات تظهر بترتيب إعلانها، والمستخدم الذي يلقى المنتقي التابع أولاً يلقاه مفلتراً بقيمة فارغة. وتأشير المدخل القائد بـ
requiredيجعل الترتيب ملزماً لا متعارَفاً عليه فحسب.
اجعل الارتباط الحيّ على مدخل مفرد القيمة
يُحسب الاستبدال من جديد في كل مرة يبحث فيها المنتقي التابع، فالكتابة في مدخل مفرد القيمة تراعي دائماً الجواب الظاهر على الشاشة حينها — غيّر المخزن فتبحث الكبسة التالية في مدخل السياسة بالمخزن الجديد. أما المدخل متعدد القيم (list = true) فلا يُعاد حسابه على هذا النحو: إذ يُحسم فلتره مرة واحدة عند بناء شاشة المدخلات، ولا يلتقط جواباً يعطيه المستخدم بعد ذلك. فحيث يلزم أن يتبع التضييقُ المستخدمَ، اجعله على مدخل مفرد القيمة.
فلتر مختلف لكل نوع
المدخل الذي تشير entityType فيه إلى مدخل آخر يعرض نوعاً مختلفاً من السجلات بحسب جواب ذلك المدخل، والحقل الجدير بالفلترة قلّ أن يحمل الاسم نفسه في جميعها. فصدّر كل تعبير بالنوع الذي يخصه بالصيغة #Type{expression}:
#Customer{allowCredit,Equal,true}#Employee{salesMan,Equal,true}فيُستخدم الفرع المطابق للنوع القائم حينئذٍ ويُهمل ما سواه. والفلتر المكتوب على الوجه المعتاد بغير # يُطبَّق على كل نوع — فاستخدم الصيغة البسيطة لشرط يصحّ في كل حال، وصيغة # حيث تختلف الأنواع اختلافاً حقيقياً. والصيغتان لا تُخلطان: فمتى بدأت القيمة بـ # لم تُقرأ إلا فروع #، والنوع الذي لا فرع له يبقى بلا فلتر أصلاً.
والنظام يضيّق منتقي السجلات من تلقاء نفسه أيضاً، ويجدر أن تعرف ذلك قبل أن تبحث عن فلتر غير موجود. فما اختاره المستخدم من شركة أو فرع أو قطاع أو إدارة أو مجموعة تحليلية عند الدخول يحدّ كذلك ما يجده أي منتقي: المستخدم الداخل على فرع القاهرة حين يبحث عن مخزن تُعرض عليه مخازن القاهرة. وهذا هو الصواب في العادة، وهو الخطأ بعينه أحياناً — فالتقرير المجمّع المبني لمقارنة الفروع يحتاج مطالبة فرع تصل إلى كل الفروع، لا إلى الفرع الذي يجلس فيه المستخدم فحسب.
وهناك خمس خصائص ترخي هذا القيد، واحدة لكل محدد:
ignoreLoginLegalEntityignoreLoginBranchignoreLoginSectorignoreLoginDepartmentignoreLoginAnalysisSet
اضبط إحداها على true فتتوقف المطالبة عن الفلترة بقيمة المستخدم نفسه لذلك المحدد وتستعمل القيمة العامة بدلاً منها، فتصبح السجلات التابعة لأي قيمة منه قابلةً للإيجاد.
<parameter name="branch" class="java.lang.Object">
<property name="entityType" value="Branch"/>
<property name="ignoreLoginBranch" value="true"/>
<property name="arabic" value="الفرع"/>
<property name="english" value="Branch"/>
</parameter>هذا يوسّع المطالبة لا التقرير
هذه الخصائص لا تغيّر إلا ما تسمح المطالبة للمستخدم بإيجاده. فهي لا تمنح صلاحية ولا تمس استعلاماً. والمستخدم الذي صار قادراً على اختيار فرع لا يحق له قراءته سيسترجع مع ذلك ما تسمح به جملة WHERE في التقرير وقيود الأمان فيه — وقد لا يكون ذلك شيئاً على الإطلاق، فيخرج له تقرير فارغ بلا تفسير. فإن كان المقصود أن يصل المنتقي إلى كل شيء، فالاستعلام خلفه يحتاج مراجعة في الوقت نفسه.
القيم الافتراضية
defaultValue— نص يُقرأ بحسب نوع المدخل: التاريخ بصيغةdd-MM-yyyy، والوقت بصيغةyyyy-MM-dd'T'HH:mm:ss.SSS، والمرجع بصيغةid:entityType:code.
دوال القيم الافتراضية الديناميكية
$now() $today()
$monthStart() $monthEnd()
$yearStart() $yearEnd()
$quarterStart() $quarterEnd()
$thirdStart() $thirdEnd()
$halveStart() $halveEnd()
$previousMonthStart() $previousMonthEnd()
$nextMonthStart() $nextMonthEnd()
$previousYearStart() $previousYearEnd()
$nextYearStart() $nextYearEnd()
$currentFiscalPeriod() $currentUser()
$currentEmployee()
$todayPlusDays(n) $todayPlusWeeks(n)
$todayPlusMonths(n) $todayPlusYears(n)وهذه الدوال تخص defaultValue وحدها. و$currentUser() على وجه الخصوص يملأ قيمة افتراضية — وليست قيمة تستطيع قراءتها بـ $P{} من داخل التقرير.
وللقيمة الافتراضية متعددة القيم، افصل بينها بـ @A=@X:
id:entityType:code@A=@Xid:entityType:code@A=@X...ما الذي تتحول إليه الإجابة الفارغة
المطالبة التي يتركها المستخدم فارغة تصل إلى الاستعلام بقيمة null، وnull في أي مقارنة SQL عادية لا يطابق شيئاً — فالتقرير الذي ظن المستخدم أنه سيغطي كل الفروع يخرج فارغاً تماماً. وخاصية type تعالج ذلك من منبعه، إذ تستبدل بالفراغ قيمةً طرفيةً قبل أن يراه الاستعلام أصلاً:
type=>— الإجابة الفارغة تصير أصغر قيمة في نوعها: النص الفارغ، أو أصغر رقم، أو أول يناير 1900.type=<— الإجابة الفارغة تصير أكبر قيمة: أكبر رقم، أو أول يناير 2999، وفي النصوص سلسلة من حرف الياء تأتي في الترتيب بعد أي نص عادي.
اقرأ الخاصية على أنها المقارنة التي يقع عليها هذا المدخل. فمدخل «من تاريخ» المستعمل في sales.date >= $P{fromDate} يأخذ >، فتصير قيمته الفارغة سنة 1900 ويكف الشرط عن استبعاد أي شيء. ومدخل «إلى تاريخ» المقابل له يأخذ < للسبب نفسه عند الطرف الآخر. وفي XML لا بد أن تُكتب علامة < هكذا <:
<parameter name="fromDate" class="java.util.Date">
<property name="type" value=">"/>
<property name="arabic" value="من تاريخ"/>
<property name="english" value="From Date"/>
</parameter>
<parameter name="toDate" class="java.util.Date">
<property name="type" value="<"/>
<property name="arabic" value="إلى تاريخ"/>
<property name="english" value="To Date"/>
</parameter>واحذف type كلياً فتبقى الإجابة الفارغة null، وهو الصواب كلما كان الاستعلام نفسه يتعامل مع null — كما تفعل دوال $X{}، وكما تفعل الصيغة ($P{p} is null or …) التي يولّدها معالج التقارير.
وأمران في هذا الباب يوقعان الناس. المدخل الرقمي الذي يُجاب بـ صفر يُعامل معاملة الفارغ ويُستبدل مثله تماماً. والاستبدال يقع سواء كان له معنى في سياقه أو لم يكن: فالفلتر النصي من نوع يحتوي على الذي يبنيه المعالج يحمل type = >، فتصير الإجابة الفارغة نصاً فارغاً ويطابق like '%%' كل الصفوف بهدوء — وهو المقصود هناك، لكنه يفسر كثيراً من أسئلة «لماذا لم يفعل هذا الفلتر شيئاً؟».
الإظهار والإخفاء والتحقق
NamaRep.canDisplay($P{param})— استخدمه فيprintWhenExpressionليختفي العنصر عن المستخدمين غير المسموح لهم برؤية موضوع ذلك المدخلno-mirror = true— يمنع انعكاس العنصر في التصاميم من اليمين إلى اليسارfromParam— يربط مدخل «إلى» بمدخل «من» المقابل لهfromParamMaxGapInDays— أوسع مدى مسموح للمستخدم بين التاريخين
<parameter name="toDate" class="java.util.Date">
<property name="arabic" value="إلى تاريخ"/>
<property name="english" value="To Date"/>
<property name="fromParam" value="fromDate"/>
<property name="fromParamMaxGapInDays" value="30"/>
</parameter>التسميات وما بقي
arabic/english— تسميتا المطالبةresource— مفتاح ترجمة، حين يجب أن تأتي التسمية من ملفات الترجمةsrc— إعادة استخدام خاصية معرَّفة على مدخل آخرignore— إبقاء المدخل خارج المطالبة كلياًtype— ما الذي تتحول إليه الإجابة الفارغة؛ راجع قسم ما الذي تتحول إليه الإجابة الفارغة أعلاهignoreMinDates— يستثني مدخل تاريخ من رفع أقل تاريخ الموصوف في قسم حين يغيّر النظام إجابة المستخدم أدناه
حين يغيّر النظام إجابة المستخدم
ليست كل قيمة تصل إلى الاستعلام هي التي كتبها المستخدم. فقبيل تشغيل التقرير مباشرةً يمر النظام على المطالبات المُجابة ويعيد كتابة بعضها بصمت. ولا شيء على الشاشة يقول ذلك — ولهذا يستطيع التقرير الواحد، بالإجابات نفسها، أن يعطي مستخدمَين رقمين مختلفين ويبدو من الخارج كأن استعلامه معطوب.
وهناك ثلاث عمليات إعادة كتابة، وكلها جديرة بأن تُحفظ.
التواريخ تُرفع إلى أقل تاريخ مسموح للمستخدم. شاشة قصر المستخدم على سنة مالية تتيح للمسؤول أن يضع لمستخدم أو مجموعة أو ملف صلاحيات حدّاً أدنى: تاريخاً ثابتاً، أو متحركاً بعدد أيام قبل اليوم. وعندها تُرفع كل مطالبة تاريخ في كل تقرير إلى ذلك الحد — فالإجابة الأقدم منه تصير هو، والإجابة الفارغة تصير هو كذلك. والمستخدم المقصور على آخر 90 يوماً حين يطلب السنة حتى تاريخه يحصل على آخر 90 يوماً، ولا يُخبَر بذلك.
ولا بد أن تُترك مطالبات «إلى تاريخ» وشأنها وإلا انهارت الفترة إلى لا شيء. والنظام يستنتج أي المطالبات هي تلك من أسمائها لا من معناها: تُعد المطالبة «إلى تاريخ» حين يحتوي اسم المدخل على to ولا يحتوي على from (بغض النظر عن حالة الأحرف)، أو حين يكون الاسم tdate بالضبط. فـ toDate وdateTo وToDocDate في مأمن، وfromDate يُرفع كما ينبغي. أما مدخل «إلى تاريخ» المسمى endDate أو until أو dueDate فلا ينطبق عليه شيء من ذلك، فيُرفع كأنه «من تاريخ»، فتنقلب فترة المستخدم رأساً على عقب ويخرج التقرير فارغاً. وحين لا يسترجع مستخدم مقيَّد شيئاً من تقرير يعمل عند غيره بلا مشكلة، فأسماء مدخلات التاريخ فيه هي أول ما يُنظر فيه.
وأمران يفلتان من إعادة الكتابة. مطالبة التاريخ المعرَّفة كقائمة (list) لا تُمس أبداً. وignoreMinDates = true تستثني مطالبةً واحدة بعينها — وهي المخرج للتاريخ الذي ليس حدّ فترة أصلاً، أو الذي تقرأ القاعدة اسمه بالمقلوب.
<parameter name="dueDate" class="java.util.Date">
<property name="ignoreMinDates" value="true"/>
<property name="arabic" value="تاريخ الاستحقاق"/>
<property name="english" value="Due Date"/>
</parameter>السنوات والفترات المالية تُقدَّم للأمام. المطالبة التي تختار سنة مالية أو فترة محاسبية تُقارن بالسنوات والفترات المسموحة لذلك المستخدم. فإن كانت المختارة تبدأ قبل أقدم ما يُسمح له به، استُبدلت بتلك السنة أو الفترة الأقدم المسموحة. وقاعدة الأسماء to/from نفسها تستثني مطالبات «إلى» هنا أيضاً.
مطالبات المحددات تُفرض عليها محددات الدخول. المطالبة التي تختار شركة أو قطاعاً أو فرعاً أو إدارة أو مجموعة تحليلية يمكن أن تُستبدل بما اختاره المستخدم عند الدخول. وهل تُستبدل أم لا؟ ذلك يعتمد على تعديل الشركة المختارة وأخواتها الأربع، وتُضبط في صفحة «الرئيسية» من تعريف التقرير نفسه، أو — حين يتركها التقرير فارغة — في الإعدادات العامة:
- أبداً — اترك إجابة المستخدم كما هي.
- دائماً — استبدلها بمحدد الدخول في كل مرة، فتصير المطالبة زينةً لا أكثر.
- حين لا يكون عاماً — استبدلها إلا إذا كان المستخدم داخلاً على محدد مركّب (composite) وأجاب بذلك المحدد نفسه أو بأحد المحددات تحته. والمستخدم الداخل على المحدد العام لا يُستبدل له شيء أبداً. وهذا هو الضبط الذي يتيح لمدير فرع مركّب أن يستعرض الفروع التي تحته، ولا يتجاوزها.
نشر عدة صور من تقرير واحد
عاجلاً أو آجلاً يُطلب التصميم نفسه عدة مرات وإجابة واحدة فيه محسومة سلفاً: تقرير المبيعات مقصوراً على القاهرة، والتقرير نفسه مقصوراً على الإسكندرية، وتقرير أعمار الديون الذي يعمل دائماً على السنة المالية الحالية. ورفع ثلاثة ملفات تصميم شبه متطابقة هو الحل الخطأ — فيوم يتغير عمود واحد تتغير ثلاثة ملفات معه، وسيُنسى أحدها.
والبديل أن يكون تعريف التقرير مبنياً على تقرير آخر. في صفحة «الرئيسية» اضبط نوع التقرير على مبني على تقرير اخر واترك التعريف فارغاً — فملف التصميم ملك التقرير الأساس، والنظام يرفض حفظ صورة تحمل ملفاً خاصاً بها. ثم في صفحة متقدم وجّه التقرير المبني عليه إلى التقرير الذي يحمل التصميم الحقيقي.
وترث الصورة كل ما يصنع التقرير: التصميم وتقاريره الفرعية وموارده ومجموعة مطالباته كاملةً. وما تأتي به من عندها هو كودها واسمها ومجموعة تقاريرها وأمانها — فتظهر في القوائم تقريراً مستقلاً ويمكن أن تُعطى لمجموعة مستخدمين أخرى.
حسم إجابة سلفاً
جدول تعديل المدخلات في صفحة «متقدم» أيضاً هو موضع إعطاء المطالبات الموروثة إجاباتها الثابتة — سطر لكل مدخل:
| العمود | ما يفعله |
|---|---|
| المعرف (Parameter Id) | اسم المدخل كما عرّفه التصميم بالحرف |
| القيمة (Param Value) | القيمة المستعملة، تُكتب كما تُكتب defaultValue — بما في ذلك الدوال الديناميكية مثل $yearStart() و$currentEmployee() |
| طريقة الاستعمال (Usage) | Default Value أو Replace Value — انظر أدناه |
| القيم المسموحة (Allowed Values) | قائمة مفصولة بفواصل تحصر منسدلة هذه المطالبة في جزء مما يعرضه التصميم |
| نوع الكيان (Entity Type) | لمطالبة السجل، النوع الذي يختار المستخدم منه — وهي وسيلة لإعادة توجيه منتقٍ دون لمس التصميم |
| النوع (Param Type) | لا يُحتاج إلا حين لا يكون المدخل من مطالبات التصميم؛ انظر أدناه |
وطريقة الاستعمال هي العمود الذي يقرر صرامة الصورة:
- Default Value — تُملأ المطالبة للمستخدم وتبقى ظاهرة، وله أن يغيّرها. وهو الخيار الصحيح لـ«هذا التقرير يُشغَّل غالباً على السنة الحالية».
- Replace Value — تُملأ المطالبة وتُخفى. فلا يراها المستخدم ولا يستطيع تغييرها. وهكذا يُثبَّت التقرير على فرع واحد تثبيتاً حقيقياً.
Replace Value تيسير لا صلاحية
المطالبة المخفية مخفية في هذه الصورة وحدها. وهي لا تمنع المستخدم نفسه من فتح التقرير الأساس أو صورة أخرى منه والإجابة عن السؤال بنفسه. فإن كان الواجب ألا يرى مستخدم أرقام فرع آخر أبداً، فمجموعة التقارير وسطور أمان التقرير وقيود الأمان فيه هي التي تفرض ذلك. وتعديل المدخلات يوفر على الجميع الكتابة، لكنه لا يبني جداراً.
ويجوز أن يسمي سطر التعديل مدخلاً ليس من مطالبات التصميم — مدخلاً عرّفه التصميم بـ isForPrompting="false" أو ignore فلا يُسأل عنه المستخدم أصلاً. املأ النوع ليعرف النظام أي قيمة يحمل، فتغذيه الصورة بالقيمة في صمت.
اختيار الأساس وقت التشغيل
جدول مبنى على حسب المستخدم الحالى يمضي خطوة أبعد. فبدلاً من أساس واحد ثابت تسرد الصورة عدة أسس وتختار أول سطر ينطبق على من يشغّل التقرير. ويمكن أن ينطبق السطر بمحددات الدخول — الشركة والفرع والقطاع والإدارة والمجموعة التحليلية — وبالموظف صاحب الدخول، مطابقةً مباشرة أو عبر مجموعته الرئيسية أو عبر تعريف معايير. وكل ما يُترك فارغاً في السطر ينطبق على الجميع، وإن لم ينطبق أي سطر استُعمل التقرير المبني عليه العادي أساساً احتياطياً.
وبذلك يستطيع كود تقرير واحد في القائمة أن يطبع تصميماً مختلفاً لكل فرع، وهو السبب المعتاد للجوء إليه: الفاتورة نفسها على ترويسة كل شركة تابعة.
استبدال التصميم لاحقاً
حفظ التقرير الأساس يعيد حفظ كل تقرير مبني عليه، فتُعاد التعديلات على التصميم الجديد من تلقاء نفسها. فليست هناك قائمة صور تُطاف عليها وتُحدَّث يدوياً — ورفع ملف jrxml مصحح على التقرير الأساس هو العمل كله.
واحضار الملفات الإضافية من هو ابن العم الأصغر للفكرة نفسها: يترك تصميم التقرير كما هو ولا يستعير من تقرير آخر إلا صوره وخطوطه وتقاريره الفرعية، فيسكن الطابع العام في مكان واحد بدل رفعه على كل تقرير يستعمله.
تحديد ما يراه كل مستخدم
التقرير الذي يستعلم من قاعدة البيانات مباشرةً يتجاوز كل صلاحيات المستخدم، ما لم تُعِد أنت تلك الصلاحيات إلى الاستعلام بنفسك. وهذا ما وُجدت له قيود الأمان: مدخل مخفي قيمته جزء من SQL يصف ما يُسمح لهذا المستخدم بالذات برؤيته، يُدرَج في جملة WHERE عندك.
لنفترض أنك تريد تقرير حسابات مصفّى بحسب منشئ كل سجل، وبحسب صلاحيات العرض والتعديل، وكذلك بحسب الشركة أو القطاع أو الفرع أو أي مُحدد آخر على الحساب.
١. عرّف مدخلاً مخفياً باسم SECURITY_CONSTRAINTS
أنشئ مدخل String معلَّماً بـ Not For Prompting، وتعبير قيمته الافتراضية:
NamaRep.security()
.fieldEntityType("Account")
.tableAlias("acc")
.capabilities("firstAuthor", "viewCapability", "usageCapability",
"updateCapability", "legalEntity", "branch",
"sector", "department", "analysisSet").fieldEntityType("Account")— أي نوع من السجلات يجري تصفيته.tableAlias("acc")— الاسم المستعار الذي يحمله جدول ذلك السجل في استعلامك.capabilities(...)— ما الذي يُصفّى به. فـfirstAuthorيقصر النتيجة على السجلات التي أنشأها المستخدم؛ وviewCapabilityوupdateCapabilityوusageCapabilityتطبّق الصلاحيات المقابلة؛ وlegalEntityوbranchوsectorوdepartmentوanalysisSetتطبّق المحددات التنظيمية
٢. أدرجه في الاستعلام
يحمل المدخل نص SQL خاماً، فيُدرَج بـ $P!{} — وعلامة التعجب هي التي تخبر Jasper بلصق النص بدل ربطه كقيمة:
SELECT a, b, c
FROM Account acc
LEFT JOIN Table2 t2 ON t2.id = acc.someId
WHERE acc.code <> 'abc'
AND $P!{SECURITY_CONSTRAINTS}٣. أكثر من جدول
ابنِ جزءاً لكل جدول واربطها بـ AND:
NamaRep.security()
.fieldEntityType("Account")
.tableAlias("Account")
.capabilities("firstAuthor", "viewCapability")
+ " AND " +
NamaRep.security()
.fieldEntityType("FiscalYear")
.tableAlias("FiscalYear")
.capabilities("legalEntity", "branch", "sector")أنت من يعرّفه — وليس تلقائياً
لا شيء يحقن مدخل الأمان في تقرير مرسوم يدوياً نيابةً عنك. والتقرير الخالي منه يعرض كل صف يُرجعه الاستعلام، لكل من يستطيع تشغيل التقرير. أما التقارير المبنية بأداة إنشاء التقارير فتُطبَّق عليها التصفية نفسها خلف الكواليس.
مشاركة تقرير مع شخص بلا حساب
رابط التقرير عادةً يشترط على من يستقبله تسجيل الدخول. وهذا لا ينفع مع عميل يستقبل فاتورة بالبريد، فيمكن تحويل رابط التقرير إلى رابط لا يحتاج مصادقة:
NamaRep.repLinkByCode($P{REPORT_PARAMETERS_MAP}, "ARG000046-report")
.p("Code_Equals").ref($F{entityType}, $F{id})
.toNoAuthResultLink()أما البنّاء الذي خلف ذلك — تمرير المدخلات والمراجع، ونسخ مدخلات التقرير الحالي — فموثّق في مرجع تعبيرات NamaRep.
نشر نموذج مطبوع ليجلبه العميل بنفسه
الرابط العام لا ينفع ما لم يكن لديك ما ترسله إليه. فحدِّد النموذج المطبوع الذي يعرضه الرابط العام في أعدادات الحقول و الشاشات — فهناك تقول أي تصميم يفتحه رابط الفاتورة العام، فينزّل العميلُ التابعُ لرابط في بريد أو لرمز QR النموذجَ الذي قصدته بالضبط دون حساب.
خلط أحجام الصفحات في مستند واحد
لتصميم التقرير الواحد حجم صفحة واحد. وحين يحتاج مستند واحد حجمين فعلاً — صفحة غلاف A4 يتبعها جدول عريض A3 — فالحل هو تقرير Book: هيكل محتواه سلسلة أجزاء، كل جزء تقرير قائم بذاته بحجم صفحته الخاص.
- ابدأ بتقرير رئيسي من النوع Book Report.
- أعطه استعلام SQL بسيطاً يُرجع الحقول التي تحتاجها الأجزاء للتمييز بينها.
- استخدم Add Part to Content لإضافة كل تقرير فرعي جزءاً.
- أعطِ كل جزء Print When Expression حتى لا تُطبع إلا الأجزاء التي تنطبق على هذا المستند.
- عرّف المدخلات التي تحتاجها الأجزاء ومرّرها إلى كل جزء.
والنتيجة المعتادة قالب من جزأين — جزء A4 وجزء A3 — يُختار بشرط على حقل ملاحظات المستند، ولكل جزء أن يحتوي تقاريره الفرعية.
الخطوط وإخراج PDF العربي
يأتي نظام نما وفيه Times New Roman مسجَّلاً للنص العربي، وهو ما يحصل عليه التقرير ما لم تقل غير ذلك. ولاستخدام أي خط آخر — Cairo أو Amiri أو Droid Arabic Naskh — يجب تحزيم الخط وتثبيته على الخادم، لأن ملف PDF لا بد أن يحمل الخط معه.
وهناك أيضاً فيديو يمشي بك في هذه الخطوات.
١. أضف الخط في Jaspersoft Studio
افتح Jaspersoft Studio، واذهب إلى Window > Preferences، ثم Jaspersoft Studio > Fonts، وانقر Add.
٢. اضبط خصائص الخط
في مربع حوار Font Family:
- اختر ملف الخط
.ttfأو.otf، أو الملفات - ضع علامة على Embed this font in PDF documents
- اضبط PDF Encoding على
Identity-H

ثم انقر Finish.
٣. صدّر الخط ملفَ JAR
بعد إضافة الخط، انقر Export واحفظ ملف .jar المولَّد.
٤. انشر ملف JAR
انسخ ملف .jar المصدَّر إلى مجلد tomcat/lib وأعد تشغيل خدمة Tomcat. وإلى أن تُعيد التشغيل، ترجع التقارير التي تطلب الخط الجديد إلى ما هو مثبَّت أصلاً.
الخطوط على أجهزة نقاط البيع
أجهزة نقاط البيع تعرض إيصالاتها بنفسها، فلا بد أن يصلها الخط هي أيضاً. ارفع ملف JAR نفسه إلى حقل Jasper Fonts («خطوط خاصة ب Jasper») في شاشة إعدادات واجهة نقاط البيع الجديده (Pos UI Settings).
والرفع لا يدفع الخط إلى أي جهاز بذاته: فكل جهاز نقطة بيع يلتقطه في مزامنة البيانات الرئيسية التالية، ويكتبه في مجلد jasper-fonts-extension عنده، ويبدأ استخدامه فوراً — أو عند إعادة تشغيل المشغّل التالية إن كان الملف الحالي قيد الاستعمال. لذلك بعد تحديث أجهزة نقاط البيع إلى إصدار يدعم امتداد الخطوط، أعد حفظ سجل إعدادات واجهة نقاط البيع ليكون لديها ما تلتقطه، ثم امهلها دورة مزامنة قبل أن تفحص جهازاً.
٥. استخدم الخط
عيّن عائلة الخط الجديدة لعناصر النص في التصميم. فيُضمَّن الخط في ملف PDF، ويظهر العربي صحيحاً أينما فُتح الملف.
كم يُسمح للتقرير أن يعمل
التقرير الذي لا ينتهي يُبقي اتصالاً بقاعدة البيانات مفتوحاً، وبالكثرة يجعل النظام غير مستجيب للجميع. والحارس ضد ذلك حدٌّ زمني على الاستعلام نفسه: اقصى مدة بالثواني لتنفيذ استعلامات التقارير في الإعداد العام، ضمن الأداء والبحث. فاستعلام التقرير الذي يتجاوز الحد توقفه قاعدة البيانات، ويحصل المستخدم على رسالة خطأ بدل انتظار لا ينتهي.
وهذا الحد يُضبط عادةً أكرم من بقية حدود الاستعلامات، لأن تقرير نهاية الشهر يستغرق دقائق بحق. فإن بدأت التقارير تتوقف بعد تغيير ما فهذا أول ما تفحصه؛ وإن كان الخادم يئنّ تحت حِمل التقارير فتضييقه هو الرافعة الآمنة.
ويمكنك كذلك تسجيل المدة التي استغرقها كل تقرير، وبأي مدخلات، بتفعيل تسجيل أداء التقارير — وخياراته في تبويب التقارير والطباعة. وهذا السجل هو ما يحوّل «التقرير بطيء» إلى «التقرير بطيء عند مجموعة مدخلات بعينها».
كتابة التعبيرات
كل ما تستطيع استدعاءه من داخل تعبير في التقرير — الأسماء والترجمة، والتواريخ الهجرية، وتفقيط الأرقام، واستخراج الأسعار، والروابط الراجعة إلى النظام، ومنشئات السجلات، وروابط الموافقة، ورموز QR، والقائمة الكاملة لمدخلات $P{} الجاهزة — مفهرس في مرجع تعبيرات NamaRep.