Skip to content
English

مرجع BI — EnhancedTable

مرجع مكمّل لـ bi-module-technical-reference.md. استخدمه عند تأليف widget من نوع type: "EnhancedTable" (أو وضع الـ pivot/cross-tab).

EnhancedTable هو جدول مدفوع بـ JSON — كل عمود معرَّف في chartConfigJSON له تنسيقه الخاص، وrenderer، وتنسيق شرطي، وتثبيت، وتجميع، وإجماليات. يعيد استخدام آلية تفاعل BI (clickEmitMapping، drillDownMapping، linkMappings، clickAction — المرجع الرئيسي §4–5b) مع حقل column اختياري في كل مدخل للتحديد إلى خلية بعينها. يبقى widget الكلاسيكي Table متاحاً؛ للانتقال استخدم "type": "EnhancedTable".

1. بنية chartConfigJSON

json
{
  "tableOptions": { },
  "columnGroups": [ ],
  "columns": [ ],
  "rowConditionalFormatting": { "cascade": false, "rules": [ ] },
  "clickEmitMapping":  [ ],
  "drillDownMapping":  [ ],
  "linkMappings":      [ ],
  "clickAction":       { }
}
المفتاحمطلوبالوصف
columnsنعمتعريفات الأعمدة مرتبةً (§3 أدناه). يحدد ترتيب أعمدة الشبكة.
columnGroupsلامصفوفة من {id, headerArTitle, headerEnTitle, marryChildren, openByDefault}. تستعين الأعمدة بها عبر groupId.
tableOptionsلاخيارات مستوى الشبكة (§2). القيم الافتراضية مناسبة.
rowConditionalFormattingلاتنسيق على مستوى الصف (§5).
clickEmitMapping / drillDownMapping / linkMappings / clickActionلاحقل column في كل مدخل يضيّق النطاق على عمود واحد؛ onCellClick: true يُطلَق عند النقر الأيسر. يُفضَّل استخدام onCellClick لكل مدخل بدلاً من clickAction على مستوى الـ widget للجداول — انظر المرجع الرئيسي §5a.1.

لا يوجد dataMapping / echartOption — لا يستخدم EnhancedTable مكتبة ECharts.

2. tableOptions

json
"tableOptions": {
  "pagination":         false,
  "pageSize":           25,
  "pageSizes":          [25, 50, 100, 250],
  "rowNumbers":         true,
  "enableRowGroup":     true,
  "enablePivot":        false,
  "pivotOpenByDefault": false,
  "grandTotalRow":      "bottom",
  "defaultColWidth":    null,
  "defaultFlex":        1,
  "defaultMinWidth":    130,
  "wrapText":           true,
  "autoHeight":         true,
  "enableRtl":          "auto",
  "cellSelection":      true
}
الحقلالافتراضيملاحظات
paginationfalseمُعطَّل افتراضياً — تحميل كامل مع التمرير. عند تفعيله، يُقسِّم AG Grid البيانات المحمَّلة في الذاكرة (لا ترقيم صفحات من جانب الخادم).
grandTotalRownull"top" / "bottom" / null. يُحسَب من جانب العميل من الصفوف المرئية. الأعمدة التي لها aggFunc فقط تُظهر قيمة في صف الإجمالي الكلي — غيرها يبقى فارغاً.
enableRowGroup / enablePivottrue / falseوضعَا تجميع الصفوف والـ pivot في AG Grid. الأعمدة تحتاج rowGroup: true / pivot: true لتُجمَّع/تُحوَّل pivot افتراضياً.

3. تعريف العمود (Column definition)

json
{
  "id":            "amountCol",
  "field":         "netValue",
  "wizardFieldId": "price.netValue",
  "groupId":       "salesGroup",
  "headerArTitle": "الصافي",
  "headerEnTitle": "Net Value",
  "hide":          false,
  "width":         120,
  "minWidth":      80,
  "maxWidth":      240,
  "flex":          null,
  "pinned":        null,
  "sort":          null,
  "sortIndex":     null,
  "rowGroup":      false,
  "rowGroupIndex": null,
  "pivot":         false,
  "aggFunc":       "sum",
  "tooltipField":  null,
  "formatting":    { },
  "renderer":      { },
  "conditionalFormatting": { }
}
الحقلمطلوبالوصف
idنعممعرِّف ثابت. يُستخدم بواسطة الـ mappings (حقل column)، وcompareColumn، ومعرِّف عمود AG Grid.
fieldأحدهمااسم عمود SQL من dataSource الـ widget.
wizardFieldIdأحدهمامعرِّف حقل الـ wizard (وضع wizard). يُحلَّل عبر displayAlias المخزَّن مؤقتاً. انظر bi-reference-wizard-mode.md.
groupIdلايُشير إلى columnGroups[].id.
headerArTitle / headerEnTitleلارأس العمود بلغتين. يرجع إلى id في حالة الغياب.
hide، width، minWidth، maxWidth، flexلاتخطيط AG Grid.
pinnedلا"start" / "end" / null. تثبيت منطقي يتقلب مع اتجاه القراءة.
sort، sortIndexلاترتيب أولي.
rowGroup، rowGroupIndex، pivot، aggFuncلاالتجميع. aggFunc: "sum"، "avg"، "min"، "max"، "count"، "first"، "last".
tooltipFieldلاid عمود آخر تُعرض قيمته كـ tooltip لهذه الخلية.
formattingلا§4.1 أدناه.
rendererلا§4.2 أدناه. الافتراضي text.
conditionalFormattingلا§5 أدناه.

4. التنسيق والـ renderers (Formatting & renderers)

4.1 formatting

سلسلة عرض تُحسَب من جانب الخادم؛ يستخدمها العميل كما هي.

json
"formatting": {
  "type":              "currency",
  "decimals":          2,
  "thousandSeparator": true,
  "currencyCode":      "SAR",
  "currencySymbol":    "SAR",
  "currencyPlacement": "suffix",
  "dateFormat":        "yyyy-MM-dd",
  "percentScale":      "asIs",
  "nullDisplay":       "—",
  "prefix":            "",
  "suffix":            ""
}
typeالناتجخيارات إضافية
textبدون تغيير
number1,234.50decimals، thousandSeparator
currency1,234.50 SARdecimals، currencySymbol، currencyPlacement (prefix/suffix)
percent45.00%decimals، percentScale (asIs: 45→45%; fraction: 0.45→45%)
date2026-04-19dateFormat (SimpleDateFormat، الافتراضي yyyy-MM-dd)
datetime2026-04-19 14:30dateFormat (الافتراضي yyyy-MM-dd HH:mm)
duration1:23:45المدخل بالثواني

prefix/suffix تُلتصق بالسلسلة المنسَّقة. nullDisplay تحلّ محل القيم الفارغة (الافتراضي "").

ملاحظة: حقل رمز العملة هنا هو currencySymbol. الكتلة dataMapping.series[].format في ECharts تستخدم currency بدلاً منه — ليسا قابلَين للتبادل. انظر المرجع الرئيسي §7.

4.2 renderer

json
"renderer": {
  "type": "text" | "html" | "badge" | "bar" | "sparkline" | "progress" | "icon",
  "badge":    { "shape": "pill" | "square", "variant": "solid" | "outline" },
  "bar":      { "min": 0, "max": 100, "color": "#4caf50", "style": "simple" | "interactive" },
  "progress": { "min": 0, "max": 100, "style": "simple" | "interactive" },
  "icon":     { "mapping": [ { "when": "Approved", "icon": "check_circle", "color": "#2e7d32" } ], "position": "prefix" | "replace" },
  "sparkline":{ "type": "line" | "column" | "area", "lineColor": "#2196F3", "fill": "rgba(33,150,243,0.2)" }
}

حقل style في bar وprogress يختار محرك الرسم:

styleالمظهر البصري
simple (الافتراضي)div بـ CSS خالص مع تعبئة ملوَّنة — خفيف الوزن، بدون محرك رسم، واضح بأي حجم
interactiveشريط أفقي بـ ECharts مع tooltip عند التمرير

تُرسَم الـ sparklines دائماً عبر ECharts (رسم مصغَّر line / area / column مع tooltip)؛ لا يوجد خيار style.

دمج simple وinteractive في صف واحد مدعوم ومفيد في أغلب الأحيان — مثلاً: شريط CSS لـ invoiced % بجانب شريط ECharts تفاعلي لـ revenue يُشير بصرياً إلى أيهما يستحق الاستكشاف.

النوعالمظهر البصريملاحظات
textنص عاديالافتراضي. لا حاجة لكتلة renderer.
htmlHTML خام عبر v-htmlنموذج الثقة يطابق Param_INHTML القديم — لا تطهير من جانب العميل.
badgepill/مربع بنص الخليةbg/color تأتي من conditionalFormatting؛ يرجع إلى أزرق فاتح. مع variant outline يتناسب مع bg الشرطي لتأثير pill مصبوغ.
barشريط أفقي مملوء (CSS افتراضياً، ECharts عند style: "interactive")القيمة مُدرجَة من min إلى max. max اختياري — عند حذفه يُضبط الـ renderer تلقائياً على الحد الأقصى لبيانات العمود (النمط الشائع).
progressنفس آلية bar، تعبئة زرقاءدلالياً "تقدّم نحو الهدف".
sparklineرسم ECharts مصغَّر متعدد النقاطيقرأ السلسلة من حقل SQL الخاص بالعمود — سلسلة CSV مثل 1,5,9,12 أو مصفوفة JSON [1,5,9,12].
iconقيمة الخلية مطابَقة مع mapping[].whenالمدخل المطابق يُظهر أيقونة (يحلّ محل النص عند position: "replace").

5. التنسيق الشرطي (Conditional formatting)

القواعد تُقيَّم من جانب الخادم؛ النمط الفائز مضمَّن في حمولة الاتصال — العميل لا يُقيِّم أي قواعد.

json
"conditionalFormatting": {
  "cascade": false,
  "rules": [
    { "when": { "type": "threshold", "op": ">=", "value": 1000 },
      "style": { "bg": "#e8f5e9", "color": "#1b5e20", "bold": true } },
    { "when": { "type": "threshold", "op": "<", "value": 0 },
      "style": { "bg": "#ffebee", "color": "#b71c1c", "bold": true } },
    { "when": { "type": "range", "min": 0, "max": 100, "maxExclusive": true },
      "style": { "bg": "#fff3e0" } },
    { "when": { "type": "enum", "values": ["Cancelled", "Rejected"] },
      "style": { "bg": "#eeeeee", "italic": true } },
    { "when": { "type": "compareColumn", "op": ">", "column": "budgetCol" },
      "style": { "bg": "#ffebee", "color": "#b71c1c" } },
    { "when": { "type": "isNull" },
      "style": { "italic": true, "color": "#9e9e9e" } }
  ]
}

أنواع القواعد:

typeالمعاملاتالمقارنة
thresholdop، valueرقمي (أو تاريخ إذا كان formatting.type للعمود هو date/datetime). يُحوِّل كلا الطرفين؛ تُتجاهَل القاعدة إذا فشل التحويل — لا يمر أبداً إلى مقارنة نصية.
rangemin، max، minExclusive?، maxExclusive?نفس التحويل.
compareColumnop، column (id عمود آخر)مقارنة بخلية أخرى في نفس الصف.
enumvalues[]مساواة نصية حساسة لحالة الأحرف على قيمة الخلية بعد trim.
isNull / isNotNullيختبر القيمة الخام قبل التحويل.

العوامل: >، >=، <، <=، =، !=.

cascade: false (الافتراضي) ← أول تطابق يفوز وتتوقف المعالجة. true ← كل تطابق يُضاف فوق السابق (المفاتيح اللاحقة تُلغي السابقة).

وصفة إشارة المرور: مع cascade: false (الافتراضي)، رتّب القواعد بالأشد تحديداً أولاً حتى تفوز الحالة الأشد:

json
"rules": [
  { "when": { "type": "threshold", "op": "<",  "value": 0   }, "style": { "bg": "#991b1b", "color": "#ffffff", "bold": true } },
  { "when": { "type": "threshold", "op": "<=", "value": 30  }, "style": { "bg": "#fecaca", "color": "#991b1b", "bold": true } },
  { "when": { "type": "threshold", "op": "<=", "value": 60  }, "style": { "bg": "#fde68a", "color": "#92400e" } },
  { "when": { "type": "threshold", "op": ">",  "value": 60  }, "style": { "bg": "#bfdbfe", "color": "#1e40af" } }
]

مفردات الـ style (لا CSS خام — مجموعة ثابتة محدودة):

المفتاحCSS المقابل
bgbackground-color
colorcolor
boldfont-weight: 700
italicfont-style: italic
underlinetext-decoration: underline
borderborder (اختصار، سلسلة موثوقة)
aligntext-align (start/center/end)

5.1 rowConditionalFormatting

نفس المفردات، تُطبَّق على مستوى الصف. يجب أن تسمّي كل قاعدة عمود الاختبار عبر when.column — لا يوجد "هذه الخلية" ضمني. النمط الفائز يُطبَّق على الصف كاملاً عبر getRowStyle في AG Grid.

json
"rowConditionalFormatting": {
  "cascade": false,
  "rules": [
    { "when": { "type": "threshold", "column": "marginCol", "op": "<", "value": 0 },
      "style": { "bg": "#ffebee" } },
    { "when": { "type": "enum", "column": "statusCol", "values": ["Cancelled"] },
      "style": { "bg": "#fafafa", "italic": true } }
  ]
}

6. مثال كامل (Complete example)

json
{
  "tableOptions": {
    "pagination": false, "rowNumbers": true,
    "enableRowGroup": true, "grandTotalRow": "bottom"
  },
  "columnGroups": [
    { "id": "customerGroup", "headerArTitle": "بيانات العميل", "headerEnTitle": "Customer", "marryChildren": true }
  ],
  "columns": [
    { "id": "invoiceCode",  "field": "invoiceCode",  "headerArTitle": "الفاتورة", "headerEnTitle": "Invoice", "pinned": "start", "width": 120 },
    { "id": "valueDate",    "field": "valueDate",    "headerArTitle": "التاريخ",  "headerEnTitle": "Date", "formatting": { "type": "date" }, "width": 110 },
    { "id": "customerName", "field": "customerName", "groupId": "customerGroup", "headerArTitle": "العميل", "headerEnTitle": "Customer" },
    { "id": "branchName",   "field": "branchName",   "groupId": "customerGroup", "headerArTitle": "الفرع",  "headerEnTitle": "Branch" },
    { "id": "qty", "field": "qty", "headerArTitle": "الكمية", "headerEnTitle": "Qty",
      "formatting": { "type": "number", "decimals": 0 },
      "renderer":   { "type": "progress", "progress": { "min": 0, "max": 50 } },
      "aggFunc": "sum", "width": 140 },
    { "id": "netValue", "field": "netValue", "headerArTitle": "الصافي", "headerEnTitle": "Net",
      "formatting": { "type": "currency", "decimals": 2, "currencySymbol": "SAR" },
      "aggFunc": "sum",
      "conditionalFormatting": { "rules": [
        { "when": { "type": "threshold", "op": ">=", "value": 10000 }, "style": { "bg": "#e8f5e9", "color": "#1b5e20", "bold": true } },
        { "when": { "type": "threshold", "op": "<",  "value": 0 },     "style": { "bg": "#ffebee", "color": "#b71c1c", "bold": true } }
      ] } },
    { "id": "tier", "field": "tier", "headerArTitle": "الفئة", "headerEnTitle": "Tier",
      "renderer": { "type": "badge", "badge": { "shape": "pill" } },
      "conditionalFormatting": { "rules": [
        { "when": { "type": "enum", "values": ["High"] },   "style": { "bg": "#2e7d32", "color": "#ffffff" } },
        { "when": { "type": "enum", "values": ["Medium"] }, "style": { "bg": "#f9a825", "color": "#ffffff" } },
        { "when": { "type": "enum", "values": ["Low"] },    "style": { "bg": "#9e9e9e", "color": "#ffffff" } }
      ] } }
  ],
  "rowConditionalFormatting": { "rules": [
    { "when": { "type": "threshold", "column": "netValue", "op": ">=", "value": 50000 }, "style": { "bg": "#fff8e1", "bold": true } }
  ] },
  "clickEmitMapping": [
    { "crossFilterCode": "customerFilter", "column": "customerName", "valueColumn": "customerName" }
  ]
}

7. الترحيل من Table

لا يوجد ترقية تلقائية. التفعيل يتم لكل widget على حدة:

  1. غيّر type من Table إلى EnhancedTable.
  2. افتح محرر chart-config ← تبويب Table Columns ← انقر Generate Columns From Result Set.
  3. خصِّص التنسيق / الـ renderers / التنسيق الشرطي.

تستمر clickEmitMapping / drillDownMapping / linkMappings الموجودة في العمل — يطابق column معرِّفات الأعمدة المُولَّدة تلقائياً (التي تأخذ اسم عمود SQL افتراضياً).

8. تخطيط الـ Pivot (cross-tab)

يحوِّل وضع الـ pivot الـ EnhancedTable إلى cross-tab بأسلوب JasperReports: محددات الصفوف تصبح مجموعات صفوف، ومحددات الأعمدة تصبح رؤوس أعمدة متداخلة، والمقاييس تملأ تقاطعات الخلايا — مع إجماليات جزئية اختيارية في كل مستوى وإجمالي كلي. تُتجاهَل كتلة columns المكتوبة يدوياً عند تعيين pivot؛ يُركِّب المحرك الأعمدة والمجموعات المتداخلة وخطة الصفوف من جانب الخادم.

8.1 الإعداد (Configuration)

json
{
  "pivot": {
    "rowDimensions":      [ { "field": "sCode", "headerEnTitle": "Section" }, { "field": "iCode", "headerEnTitle": "Item" } ],
    "colDimensions":      [ { "field": "bCode", "headerEnTitle": "Branch" }, { "field": "wCode", "headerEnTitle": "Warehouse" } ],
    "measures": [
      { "field": "netQty",  "headerEnTitle": "Qty",  "aggFunc": "sum", "formatting": { "type": "number",   "decimals": 2 } },
      { "field": "netCost", "headerEnTitle": "Cost", "aggFunc": "sum", "formatting": { "type": "currency", "decimals": 2, "currencySymbol": "SAR" } }
    ],
    "useRowGrouping":     true,
    "rowSubtotals":       true,
    "colSubtotals":       true,
    "rowGrandTotal":      "bottom",
    "colGrandTotal":      "end",
    "subtotalLabelKey":   "biSubtotal",
    "grandTotalLabelKey": "biGrandTotal",
    "emptyCellAs":        null,
    "zeroAsEmpty":        true
  }
}
الحقلالافتراضيملاحظات
rowDimensionsمطلوبمرتبة من الخارج للداخل. كل مدخل: field (اسم alias في SQL) أو wizardFieldId، رؤوس اختيارية + formatting.
colDimensionsمطلوبنفس الشكل؛ الترتيب يتحكم في تداخل الرؤوس.
measuresمطلوبكل مدخل: field/wizardFieldId، رؤوس، aggFunc (الافتراضي sum؛ الإصدار الأول يدعم sum فقط)، formatting، conditionalFormatting اختياري (يُطبَّق على أعمدة الورقة والإجماليات الجزئية والكلية المشتقة من هذا المقياس).
useRowGroupingtrueعند true وعدد محددات الصفوف ≥2، تصبح N-1 المحددات الخارجية مجموعات صفوف في AG Grid؛ أدناها هو صف الورقة. مع محدد صف واحد، التجميع لا فائدة منه — يعود الـ backend لصفوف مسطحة مع صفوف إجمالية جزئية/كلية مضمَّنة.
rowSubtotalsfalseصفوف تذييل إجمالي جزئي لكل مجموعة. مع التجميع النشط ← AG Grid يُنشئها من جانب العميل. بدونه ← الـ backend يضمِّنها في النتائج.
colSubtotalsfalseأعمدة إجمالي جزئي في كل مستوى col-dim غير ورقي. تُنشأ دائماً من جانب الـ backend (AG Grid لا يستطيع تركيب أعمدة cross-tab).
rowGrandTotalnull"top" / "bottom" / null. مع التجميع: grandTotalRow في AG Grid. بدونه: مضمَّن في مجموعة النتائج.
colGrandTotalnull"start" / "end" / null. منطقيstart = الحافة الأمامية بأي اتجاه قراءة.
subtotalLabelKey / grandTotalLabelKeyمُضمَّنمفاتيح i18n؛ الواجهة الأمامية تحلّها عبر Translator.translate(). يرجع إلى Subtotal / Grand Total (والعربية).
emptyCellAsnullما تعرضه خلايا بلا بيانات: null (فارغة)، 0، "-"، إلخ.
zeroAsEmptyfalseالمقياس الذي يُجمِّع إلى صفر بالضبط ← يُعرَض كـ emptyCellAs.

8.2 ما يُصدره المحرك

يمر على الصفوف مرة واحدة، يبني أشجاراً متميزة من tuples الصفوف/الأعمدة (مرتبة أبجدياً لكل مستوى)، يُعيد تجميع (rowTuple, colTuple) → measures عبر sum، ويُصدر:

  • columns مُركَّبة: M أعمدة من محددات الصفوف (مثبتة start) + أعمدة مقاييس الورقة متخللة مع أعمدة إجمالي جزئي للأعمدة (لكل بادئة غير ورقية) + أعمدة إجمالي كلي للأعمدة في الطرف المختار. معرِّفات ثابتة مثل pv:bCode=B1.wCode=W1:m=netQty.
  • columnGroups متداخلة مع parentGroupId: واحدة لكل بادئة tuple أعمدة غير ورقية.
  • صفوف مُركَّبة: مع التجميع، صفوف البيانات الورقية فقط؛ بدونه، صفوف ورقية + إجمالية جزئية + إجمالية كلية متداخلة.
  • enhancedTableData.styles: الصفوف/الأعمدة الإجمالية مُوسَمة عبر علامة t (0=كلي، n>0=جزئي عند العمق n)؛ يُطبَّق عريض + صبغة افتراضياً. conditionalFormatting الصريح يفوز دائماً.
  • agColumnCellDataTypes: كل عمود مقياس مُوسَم "number" حتى تتدفق قيم الخلايا كأرقام JSON (مطلوب لتجميعات aggFunc في صفوف المجموعات).

8.3 وضع Wizard

في وضع wizard (الـ widget له wizardDataSource)، استخدم wizardFieldId بدلاً من field في كل محدد/مقياس. WizardChartConfigRewriter يحلّه إلى SQL alias المخزَّن مؤقتاً (displayAlias) قبل تشغيل المحرك.

8.4 تخطي التفاعل على الإجماليات

صفوف وأعمدة الإجمالي الجزئي والكلي هي تجميعات مشتقة بلا صف مصدر أساسي، لذا تتجاوزها click-emit / drill-down / link / drill-down-by جميعها. تُكشَف عبر علامة t في بيانات الصف (_totalLevel) وإعداد العمود (t). تلميح المؤشر مُعطَّل أيضاً.

8.5 التعايش مع pivot من جانب العميل في AG Grid

tableOptions.enablePivot (pivot بالسحب في وقت التشغيل) مستقل تماماً ويبقى كما هو. استخدمه للاستكشاف الحر؛ استخدم كتلة pivot للتقارير cross-tab التي يحددها المؤلف.

8.6 مثال pivot كامل

sql
select w.code wCode, b.code bCode, i.code iCode, s.code sCode,
       sum(q.net) netQty, sum(q.netCost) netCost
from   ItemDimensionsQty q
left join InvItem      i on i.id = q.item_id
left join ItemSection  s on s.id = i.section_id
left join Warehouse    w on w.id = q.warehouse_id
left join Branch       b on b.id = w.branch_id
group  by w.code, b.code, i.code, s.code
json
{
  "pivot": {
    "rowDimensions": [
      { "field": "sCode", "headerEnTitle": "Section",   "headerArTitle": "القسم" },
      { "field": "iCode", "headerEnTitle": "Item",      "headerArTitle": "الصنف" }
    ],
    "colDimensions": [
      { "field": "bCode", "headerEnTitle": "Branch",    "headerArTitle": "الفرع" },
      { "field": "wCode", "headerEnTitle": "Warehouse", "headerArTitle": "المخزن" }
    ],
    "measures": [
      { "field": "netQty",  "headerEnTitle": "Qty",  "headerArTitle": "الكمية",  "aggFunc": "sum", "formatting": { "type": "number", "decimals": 0 } },
      { "field": "netCost", "headerEnTitle": "Cost", "headerArTitle": "التكلفة", "aggFunc": "sum",
        "formatting": { "type": "currency", "decimals": 2, "currencySymbol": "SAR" },
        "conditionalFormatting": { "rules": [
          { "when": { "type": "threshold", "op": ">=", "value": 1000000 }, "style": { "bold": true, "color": "#1b5e20" } }
        ] } }
    ],
    "useRowGrouping": true, "rowSubtotals": true, "colSubtotals": true,
    "rowGrandTotal": "bottom", "colGrandTotal": "end",
    "zeroAsEmpty": true, "emptyCellAs": "-"
  }
}