@dforge-core/dforge-mcp 0.1.0-rc.9 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/README.md +88 -29
- package/dist/server.js +2735 -351
- package/docs/creating-modules.md +15 -7
- package/package.json +11 -6
- package/resources/docs/conventions.md +22 -16
- package/resources/docs/dsl-reference.md +767 -0
- package/resources/schemas/entity.schema.json +8 -1
- package/resources/schemas/print-templates.schema.json +82 -0
- package/resources/schemas/reports.schema.json +4 -0
- package/resources/schemas/triggers.schema.json +59 -0
- package/skills/dforge-mcp-author/SKILL.md +284 -73
- package/skills/dforge-mcp-author/examples/matrix-budget/README.md +43 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_category.json +24 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_line.json +56 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/manifest.json +30 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/security/roles.json +9 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/seed-data/01-categories.json +8 -0
- package/skills/dforge-mcp-author/examples/matrix-budget/ui/data_views.json +42 -0
- package/skills/dforge-mcp-author/examples/simple-todo/README.md +38 -0
- package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +83 -0
- package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_list.json +43 -0
- package/skills/dforge-mcp-author/examples/simple-todo/logic/actions/mark_done.dsl +6 -0
- package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +32 -0
- package/skills/dforge-mcp-author/examples/simple-todo/security/roles.json +10 -0
- package/skills/dforge-mcp-author/examples/simple-todo/seed-data/01-lists.json +17 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/actions.json +11 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/data_views.json +35 -0
- package/skills/dforge-mcp-author/examples/simple-todo/ui/menus.json +28 -0
- package/skills/dforge-mcp-author/references/action-dsl.md +397 -0
- package/skills/dforge-mcp-author/references/column-types.md +168 -0
- package/skills/dforge-mcp-author/references/conventions.md +177 -0
- package/skills/dforge-mcp-author/references/data-migration.md +270 -0
- package/skills/dforge-mcp-author/references/data-views.md +245 -0
- package/skills/dforge-mcp-author/references/excel-import.md +61 -0
- package/skills/dforge-mcp-author/references/field-types.md +144 -0
- package/skills/dforge-mcp-author/references/filters.md +326 -0
- package/skills/dforge-mcp-author/references/flags.md +73 -0
- package/skills/dforge-mcp-author/references/formulas.md +206 -0
- package/skills/dforge-mcp-author/references/jobs.md +149 -0
- package/skills/dforge-mcp-author/references/manifest.md +123 -0
- package/skills/dforge-mcp-author/references/menus.md +164 -0
- package/skills/dforge-mcp-author/references/number-sequences.md +117 -0
- package/skills/dforge-mcp-author/references/print-templates.md +159 -0
- package/skills/dforge-mcp-author/references/queries.md +312 -0
- package/skills/dforge-mcp-author/references/reports.md +398 -0
- package/skills/dforge-mcp-author/references/schema-import.md +331 -0
- package/skills/dforge-mcp-author/references/security.md +244 -0
- package/skills/dforge-mcp-author/references/settings.md +120 -0
- package/skills/dforge-mcp-author/references/traits.md +153 -0
- package/skills/dforge-mcp-author/references/translations.md +158 -0
- package/skills/dforge-mcp-author/references/validation-checklist.md +184 -0
- package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Print Templates Reference
|
|
2
|
+
|
|
3
|
+
dForge ships Liquid-based print templates. A record + its child sets are rendered to HTML, which the browser prints or saves as PDF.
|
|
4
|
+
|
|
5
|
+
Lives in: `ui/print_templates.json` + HTML/CSS files under `print_templates/`. Schema: [`docs/schemas/print_templates.schema.json`](../../../docs/schemas/print_templates.schema.json). Full guide: [`docs/ui/print-templates.md`](../../../docs/ui/print-templates.md). QR + snippets: [`docs/ui/print-qr-codes.md`](../../../docs/ui/print-qr-codes.md).
|
|
6
|
+
|
|
7
|
+
## Two render paths
|
|
8
|
+
|
|
9
|
+
| Path | Engine | Used by |
|
|
10
|
+
| ----------- | --------------- | -------------------------------------------------------- |
|
|
11
|
+
| **Server** | Scriban (Liquid) | `print.render` RPC — final print / save-as-PDF |
|
|
12
|
+
| **Client** | LiquidJS | Editor preview — fast iteration |
|
|
13
|
+
|
|
14
|
+
Filters are mirrored on both sides so the preview matches the print.
|
|
15
|
+
|
|
16
|
+
## Manifest structure
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"invoice": {
|
|
21
|
+
"entityCd": "invoice",
|
|
22
|
+
"description": "Invoice Print Form",
|
|
23
|
+
"file": "invoice.html",
|
|
24
|
+
"css": "invoice.css",
|
|
25
|
+
"pageSettings": {
|
|
26
|
+
"size": "A4",
|
|
27
|
+
"orientation": "portrait",
|
|
28
|
+
"margins": { "top": "15mm", "right": "15mm", "bottom": "15mm", "left": "15mm" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"footer": {
|
|
32
|
+
"type": "snippet",
|
|
33
|
+
"description": "Shared invoice footer block",
|
|
34
|
+
"file": "footer.html"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The key (`"invoice"`, `"footer"`) becomes `template_cd` on the row.
|
|
40
|
+
|
|
41
|
+
## Field reference
|
|
42
|
+
|
|
43
|
+
| Field | Required | Notes |
|
|
44
|
+
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `type` | No | `"template"` (default) or `"snippet"`. |
|
|
46
|
+
| `entityCd` | Yes (templates) | Entity code. Cross-module form `"module.entity"` supported (bridge modules). Must be omitted on snippets. |
|
|
47
|
+
| `description` | No | Label shown in the print menu. |
|
|
48
|
+
| `template` | One of these | Inline Liquid HTML. |
|
|
49
|
+
| `file` | One of these | Path relative to `print_templates/`. |
|
|
50
|
+
| `css` | No | Inline CSS or a `.css` filename under `print_templates/`. Snippets typically inherit and omit. |
|
|
51
|
+
| `pageSettings` | No | `{ size, orientation, margins }`. `size` ∈ `A4` / `letter` / `legal`. `orientation` ∈ `portrait` / `landscape`. Margins are CSS lengths. |
|
|
52
|
+
|
|
53
|
+
## Template context (what you can read in Liquid)
|
|
54
|
+
|
|
55
|
+
Every column on the entity is a top-level variable. Display formatting is pre-applied for dropdown / radio / flags / user columns.
|
|
56
|
+
|
|
57
|
+
| Variable | What it holds |
|
|
58
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
59
|
+
| `{{ <column> }}` | Display value: dropdown labels resolved, user names resolved, refs resolved to their display column |
|
|
60
|
+
| `{{ _fmt.<column> }}` | Locale-aware **string**: `currency` → `"$1,234.56"`, `date` → `"5/22/2026"`, `bool` → `"Yes"`/`"No"` |
|
|
61
|
+
| `{{ _raw.<column> }}` | Original code / id (use for CSS classes, predicates, wire-format payloads) |
|
|
62
|
+
| `{{ _color.<column> }}` | Option color hex for single-value dropdown/radio columns |
|
|
63
|
+
| `{{ _settings.<cd> }}` | Folder-resolved module setting. Image/file settings come as base64 data URIs (drop into `<img src>`). |
|
|
64
|
+
| `{{ _today }}` | Typed `DateTimeOffset` (UTC); `{{ _fmt._today }}` is the pre-formatted string |
|
|
65
|
+
| `{{ <set_name> }}` | Child set — array. Iterate with `{% for x in <set_name> %}`. Only loaded if the template iterates it. |
|
|
66
|
+
| `{{ _css }}` | The CSS string attached to the template (rarely needed in template body) |
|
|
67
|
+
|
|
68
|
+
Inside a child-set loop, the same shape applies per row: `line._fmt.<col>`, `line._raw.<col>`, `line._color.<col>`.
|
|
69
|
+
|
|
70
|
+
## Custom filters
|
|
71
|
+
|
|
72
|
+
| Filter | Example | Output |
|
|
73
|
+
| ----------------------------------------------- | ------------------------------------------------------ | ---------------------------- |
|
|
74
|
+
| `format_currency: <currency>, <digits?>` | `{{ amount | format_currency: 'USD' }}` | `"$1,234.56"` (en-US) |
|
|
75
|
+
| `format_number: <decimals?>` | `{{ rate | format_number: 4 }}` | `"0.0125"` |
|
|
76
|
+
| `format_date: <strftime>` | `{{ invoice_date | format_date: '%B %d, %Y' }}` | `"May 22, 2026"` |
|
|
77
|
+
| `qr_code: <size?>, <ecc?>, <margin?>, <format?>`| `{{ payment_url | qr_code: 200, 'M' }}` | `data:image/png;base64,…` |
|
|
78
|
+
|
|
79
|
+
`format_date` supports strftime: `%Y %m %d %B %b %H %M %S %I %p %A %a %e`.
|
|
80
|
+
|
|
81
|
+
## Snippets
|
|
82
|
+
|
|
83
|
+
Module-scoped Liquid partials. Pull one in via `{% include 'module_cd.snippet_cd' %}`. Snippets see all variables from the host template by default; pass overrides with `with`:
|
|
84
|
+
|
|
85
|
+
```liquid
|
|
86
|
+
{% include 'fin-sepa.epc_qr_block' %}
|
|
87
|
+
{% include 'fin-sepa.epc_qr_block' with currency: 'EUR' %}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Declare a snippet with `"type": "snippet"`. No `entityCd`. The `template_cd` must not contain `.` (collides with the `module_cd.snippet_cd` separator).
|
|
91
|
+
|
|
92
|
+
Full mechanics + an EPC SEPA QR worked example: [`docs/ui/print-qr-codes.md`](../../../docs/ui/print-qr-codes.md).
|
|
93
|
+
|
|
94
|
+
## Module package layout
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
my_module/
|
|
98
|
+
├── manifest.json
|
|
99
|
+
├── ui/
|
|
100
|
+
│ └── print_templates.json
|
|
101
|
+
└── print_templates/
|
|
102
|
+
├── invoice.html
|
|
103
|
+
├── invoice.css
|
|
104
|
+
└── footer.html (snippet)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Example template
|
|
108
|
+
|
|
109
|
+
```liquid
|
|
110
|
+
<div class="invoice-header">
|
|
111
|
+
{% if _settings.company_logo %}
|
|
112
|
+
<img class="company-logo" src="{{ _settings.company_logo }}" />
|
|
113
|
+
{% endif %}
|
|
114
|
+
<h1>INVOICE {{ invoice_number }}</h1>
|
|
115
|
+
<p>Date: {{ _fmt.invoice_date }} · Due: {{ _fmt.due_date }}</p>
|
|
116
|
+
<p>Status: <span class="badge" style="--c: {{ _color.status }}">{{ status }}</span></p>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<table>
|
|
120
|
+
{% for line in lines %}
|
|
121
|
+
<tr>
|
|
122
|
+
<td>{{ line.description }}</td>
|
|
123
|
+
<td>{{ line.quantity }}</td>
|
|
124
|
+
<td>{{ line._fmt.unit_price }}</td>
|
|
125
|
+
<td>{{ line._fmt.line_total }}</td>
|
|
126
|
+
</tr>
|
|
127
|
+
{% endfor %}
|
|
128
|
+
</table>
|
|
129
|
+
|
|
130
|
+
<p>Total: <strong>{{ _fmt.total_amount }}</strong></p>
|
|
131
|
+
|
|
132
|
+
{% if amount_paid > 0 %}
|
|
133
|
+
<p>Paid: -{{ _fmt.amount_paid }}</p>
|
|
134
|
+
<p>Due: {{ _fmt.amount_due }}</p>
|
|
135
|
+
{% endif %}
|
|
136
|
+
|
|
137
|
+
{% include 'fin.footer' %}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Common mistakes
|
|
141
|
+
|
|
142
|
+
- **Using `_fmt` in a predicate.** `{% if _fmt.amount_paid > 0 %}` compares strings. Use the bare reference: `{% if amount_paid > 0 %}`.
|
|
143
|
+
- **Forgetting `_raw` for codes.** Direct `{{ status }}` is the label (`"Partially Paid"`). For CSS classes / wire-format payloads / predicates against codes, use `{{ _raw.status }}` (`"P"`).
|
|
144
|
+
- **Manually formatting dates/currency.** Don't reach for `format_currency` when the column is already `fieldTypeCd: "currency"` — `_fmt.<col>` already matches the form. Use the filter only for derived values.
|
|
145
|
+
- **Referencing a child set outside a `{% for %}`.** The platform only loads sets that appear in a `for` loop. `{{ lines.size }}` won't trigger the load.
|
|
146
|
+
- **Snippet `template_cd` with a dot.** `{% include 'my_module.foo.bar' %}` — the resolver splits on the last dot only, so `my_module.foo` becomes the module name and breaks. No dots in snippet codes.
|
|
147
|
+
- **Dropping CSS in `<style>` blocks inline.** The renderer wires `<style>` for you; declare CSS in the separate `.css` file (or inline string via the `css` field).
|
|
148
|
+
- **Trying to use `TODAY()` / `NOW()` in a formula column expecting it to render in print.** The print-time formula evaluator skips those. Pre-compute via a formula column at insert time, or use `{{ _today | format_date: '...' }}` directly in Liquid.
|
|
149
|
+
- **Module-installed template renamed in editor.** The editor preserves the original `template_cd` so `{% include %}` references stay intact — don't try to work around it.
|
|
150
|
+
|
|
151
|
+
Module-installed print-template rows can't be deleted from the editor — uninstall the owning module to remove them.
|
|
152
|
+
|
|
153
|
+
## Reference
|
|
154
|
+
|
|
155
|
+
- Full developer guide: [`docs/ui/print-templates.md`](../../../docs/ui/print-templates.md)
|
|
156
|
+
- QR codes + snippets guide: [`docs/ui/print-qr-codes.md`](../../../docs/ui/print-qr-codes.md)
|
|
157
|
+
- JSON Schema: [`docs/schemas/print_templates.schema.json`](../../../docs/schemas/print_templates.schema.json)
|
|
158
|
+
- Reference modules: [`modules/fin/`](../../../modules/fin/), [`modules/fin-ch/`](../../../modules/fin-ch/) (Swiss QR-bill), [`modules/crm/`](../../../modules/crm/), [`modules/wms/`](../../../modules/wms/), [`modules/hr/`](../../../modules/hr/)
|
|
159
|
+
- Source: `server/src/dForge.Api/Services/PrintTemplateService.cs`, `server/src/dForge.Admin/Services/ModuleInstall/PrintTemplateRegistrar.cs`
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Pre-Built Queries Reference
|
|
2
|
+
|
|
3
|
+
Modules can ship **pre-built saved queries** — lightweight, explorable analytics that appear in the Query Builder alongside user-created queries. Think of them as "light reports" that are easier to author than full reports.
|
|
4
|
+
|
|
5
|
+
Lives in: `ui/queries.json`
|
|
6
|
+
|
|
7
|
+
## When to use queries vs reports vs views
|
|
8
|
+
|
|
9
|
+
| Need | Use |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Show entity records with filters/sort | **Data view** (`ui/data_views.json`) |
|
|
12
|
+
| Simple aggregation — "totals by X" | **Pre-built query** (`ui/queries.json`) |
|
|
13
|
+
| Ad-hoc exploration by the user | **Query Builder** (runtime, no module authoring needed) |
|
|
14
|
+
| Complex multi-dataset visualizations with charts/KPIs | **Report** (`ui/reports.json`) |
|
|
15
|
+
| Full SQL with window functions, CTEs, cross-schema | **Report with stored procedure** |
|
|
16
|
+
|
|
17
|
+
Queries fill the gap between views (flat, no aggregation) and reports (heavy, need layout config).
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"overdue_invoices": {
|
|
24
|
+
"name": "Overdue Invoices",
|
|
25
|
+
"description": "All invoices past due date, not yet paid",
|
|
26
|
+
"entityCode": "invoice",
|
|
27
|
+
"query": {
|
|
28
|
+
"entityCd": "invoice",
|
|
29
|
+
"columns": [
|
|
30
|
+
"invoice_number",
|
|
31
|
+
"customer",
|
|
32
|
+
"total_amount",
|
|
33
|
+
"amount_due",
|
|
34
|
+
"due_date",
|
|
35
|
+
"days_overdue",
|
|
36
|
+
"owner_id"
|
|
37
|
+
],
|
|
38
|
+
"filter": {
|
|
39
|
+
"g": "and",
|
|
40
|
+
"i": [
|
|
41
|
+
{ "c": "status", "o": "!=", "v": "Paid" },
|
|
42
|
+
{ "c": "status", "o": "!=", "v": "Cancelled" },
|
|
43
|
+
{ "c": "due_date", "o": "f", "v": { "fn": "isPast" } }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"sort": [
|
|
47
|
+
{ "c": "days_overdue", "d": "desc" }
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"revenue_by_customer": {
|
|
52
|
+
"name": "Revenue by Customer",
|
|
53
|
+
"description": "Total invoiced revenue grouped by customer",
|
|
54
|
+
"entityCode": "invoice",
|
|
55
|
+
"query": {
|
|
56
|
+
"entityCd": "invoice",
|
|
57
|
+
"columns": [
|
|
58
|
+
{ "c": "customer", "agg": "group", "alias": "Customer" },
|
|
59
|
+
{ "c": "total_amount", "agg": "sum", "alias": "Total Revenue" },
|
|
60
|
+
{ "c": "invoice_number", "agg": "count", "alias": "Invoice Count" }
|
|
61
|
+
],
|
|
62
|
+
"filter": {
|
|
63
|
+
"c": "status", "o": "!=", "v": "Cancelled"
|
|
64
|
+
},
|
|
65
|
+
"sort": [
|
|
66
|
+
{ "c": "total_amount", "d": "desc" }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Query JSON format
|
|
74
|
+
|
|
75
|
+
The `query` object uses the **exact same format** as the Query Builder produces at runtime. This means you can:
|
|
76
|
+
1. Design a query in the Query Builder UI
|
|
77
|
+
2. Copy the JSON
|
|
78
|
+
3. Paste it into `queries.json`
|
|
79
|
+
|
|
80
|
+
### Flat mode (no aggregation)
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"entityCd": "contact",
|
|
85
|
+
"columns": ["first_name", "last_name", "email", "account", "owner_id"],
|
|
86
|
+
"filter": { "c": "email", "o": "!null" },
|
|
87
|
+
"sort": [{ "c": "last_name", "d": "asc" }]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Columns are plain strings — field codes or reference paths (`"account.name"`, `"project.client.short_name"`).
|
|
92
|
+
|
|
93
|
+
### Grouped mode (with aggregation)
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"entityCd": "opportunity",
|
|
98
|
+
"columns": [
|
|
99
|
+
{ "c": "stage", "agg": "group", "alias": "Stage" },
|
|
100
|
+
{ "c": "owner_id", "agg": "group", "alias": "Owner" },
|
|
101
|
+
{ "c": "total_amount", "agg": "sum", "alias": "Total Value" },
|
|
102
|
+
{ "c": "opportunity_id", "agg": "count", "alias": "Deal Count" },
|
|
103
|
+
{ "c": "total_amount", "agg": "avg", "alias": "Avg Deal Size" }
|
|
104
|
+
],
|
|
105
|
+
"filter": {
|
|
106
|
+
"c": "stage", "o": "!in", "v": ["Closed Won", "Closed Lost"]
|
|
107
|
+
},
|
|
108
|
+
"sort": [{ "c": "total_amount", "d": "desc" }]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
In grouped mode, each column is an object with:
|
|
113
|
+
- `c` — column code (or reference path)
|
|
114
|
+
- `agg` — aggregation: `group` (dimension), `sum`, `count`, `avg`, `min`, `max`
|
|
115
|
+
- `alias` — display name for the result column
|
|
116
|
+
|
|
117
|
+
At least one column must be `"agg": "group"` and at least one must be an aggregate.
|
|
118
|
+
|
|
119
|
+
### Reference paths in columns
|
|
120
|
+
|
|
121
|
+
Columns can traverse references up to 2 levels deep:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
"columns": [
|
|
125
|
+
"invoice_number",
|
|
126
|
+
"customer.account_name",
|
|
127
|
+
"customer.industry",
|
|
128
|
+
"owner_id"
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The query engine automatically JOINs the referenced entities.
|
|
133
|
+
|
|
134
|
+
### Filters
|
|
135
|
+
|
|
136
|
+
Filters use the standard JSON filter format (see `references/filters.md`): `{ "c": column, "o": operator, "v": value }`.
|
|
137
|
+
|
|
138
|
+
### Dynamic date and user filters (function operator)
|
|
139
|
+
|
|
140
|
+
Instead of hardcoded date values, use the **function operator** `"f"` with a `{ "fn": "..." }` value. This evaluates at query time on the server.
|
|
141
|
+
|
|
142
|
+
**A function IS the whole condition — it bakes in the comparison.** You cannot pass a function as the value of `<`, `>`, `=`, `!=`, `between`, etc. Functions are ONLY valid with the `"o": "f"` operator, and each function encodes its own comparison logic (for example, `isPast` already means `column < NOW()`).
|
|
143
|
+
|
|
144
|
+
| Function | Applies to | Description |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `isToday` | date, timestamp | Current date |
|
|
147
|
+
| `isYesterday` | date, timestamp | Previous day |
|
|
148
|
+
| `isThisWeek` | date, timestamp | Current ISO week |
|
|
149
|
+
| `isThisMonth` | date, timestamp | Current month |
|
|
150
|
+
| `isThisYear` | date, timestamp | Current year |
|
|
151
|
+
| `isPast` | date, timestamp | Before now |
|
|
152
|
+
| `isFuture` | date, timestamp | After now |
|
|
153
|
+
| `isCurrentUser` | guid | Matches the logged-in user |
|
|
154
|
+
|
|
155
|
+
**Example — overdue items (due date is in the past):**
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "c": "due_date", "o": "f", "v": { "fn": "isPast" } }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Example — records created this month:**
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{ "c": "created_date", "o": "f", "v": { "fn": "isThisMonth" } }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Example — assigned to current user:**
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{ "c": "owner_id", "o": "f", "v": { "fn": "isCurrentUser" } }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Sort
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
"sort": [
|
|
177
|
+
{ "c": "total_amount", "d": "desc" },
|
|
178
|
+
{ "c": "customer", "d": "asc" }
|
|
179
|
+
]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`d` is direction: `"asc"` or `"desc"`.
|
|
183
|
+
|
|
184
|
+
## Wrapper properties
|
|
185
|
+
|
|
186
|
+
Each query in `queries.json` has:
|
|
187
|
+
|
|
188
|
+
| Property | Required | Description |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `name` | Yes | Display name in the query list |
|
|
191
|
+
| `description` | Recommended | Help text |
|
|
192
|
+
| `entityCode` | Yes | The root entity this query targets |
|
|
193
|
+
| `query` | Yes | The query JSON (flat or grouped format above) |
|
|
194
|
+
|
|
195
|
+
## Menu integration
|
|
196
|
+
|
|
197
|
+
Queries are not in menu and not integrated into menu, they have separate section in sidebar, nothing need to be configured additionally.
|
|
198
|
+
|
|
199
|
+
## Translations
|
|
200
|
+
|
|
201
|
+
Query names and descriptions are translated in `translations/en-US.json`:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"queries": {
|
|
206
|
+
"overdue_invoices": {
|
|
207
|
+
"name": "Overdue Invoices",
|
|
208
|
+
"description": "All invoices past due date, not yet paid"
|
|
209
|
+
},
|
|
210
|
+
"revenue_by_customer": {
|
|
211
|
+
"name": "Revenue by Customer",
|
|
212
|
+
"description": "Total invoiced revenue grouped by customer"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Manifest declaration
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"queries": "./ui/queries.json"
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Example queries for common module types
|
|
227
|
+
|
|
228
|
+
### CRM — Pipeline by stage and owner
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"entityCd": "opportunity",
|
|
232
|
+
"columns": [
|
|
233
|
+
{ "c": "stage", "agg": "group", "alias": "Stage" },
|
|
234
|
+
{ "c": "owner_id", "agg": "group", "alias": "Owner" },
|
|
235
|
+
{ "c": "total_amount", "agg": "sum", "alias": "Total Value" },
|
|
236
|
+
{ "c": "opportunity_id", "agg": "count", "alias": "Count" }
|
|
237
|
+
],
|
|
238
|
+
"sort": [{ "c": "total_amount", "d": "desc" }]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### HR — Headcount by department
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"entityCd": "employee",
|
|
246
|
+
"columns": [
|
|
247
|
+
{ "c": "department", "agg": "group", "alias": "Department" },
|
|
248
|
+
{ "c": "employee_id", "agg": "count", "alias": "Headcount" },
|
|
249
|
+
{ "c": "salary", "agg": "avg", "alias": "Avg Salary" }
|
|
250
|
+
],
|
|
251
|
+
"filter": { "c": "status", "o": "=", "v": "Active" },
|
|
252
|
+
"sort": [{ "c": "employee_id", "d": "desc" }]
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Finance — Receivables aging summary
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"entityCd": "invoice",
|
|
260
|
+
"columns": [
|
|
261
|
+
"invoice_number",
|
|
262
|
+
"customer",
|
|
263
|
+
"total_amount",
|
|
264
|
+
"amount_due",
|
|
265
|
+
"due_date",
|
|
266
|
+
"days_overdue",
|
|
267
|
+
"status"
|
|
268
|
+
],
|
|
269
|
+
"filter": {
|
|
270
|
+
"g": "and",
|
|
271
|
+
"i": [
|
|
272
|
+
{ "c": "status", "o": "!in", "v": ["Paid", "Cancelled"] },
|
|
273
|
+
{ "c": "amount_due", "o": ">", "v": 0 }
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
"sort": [{ "c": "days_overdue", "d": "desc" }]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### WMS — Low stock items
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"entityCd": "stock",
|
|
284
|
+
"columns": [
|
|
285
|
+
"product",
|
|
286
|
+
"warehouse",
|
|
287
|
+
"quantity",
|
|
288
|
+
"reserved_qty",
|
|
289
|
+
"available_qty"
|
|
290
|
+
],
|
|
291
|
+
"filter": { "c": "available_qty", "o": "<", "v": 10 },
|
|
292
|
+
"sort": [{ "c": "available_qty", "d": "asc" }]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Rules
|
|
297
|
+
|
|
298
|
+
- The `query` object must use the same JSON format as the Query Builder produces.
|
|
299
|
+
- Filters use the standard `c`/`o`/`v` format (see `references/filters.md`).
|
|
300
|
+
- Module queries are **read-only** for users — they can run them and "Copy to My Queries" to modify.
|
|
301
|
+
- Security is automatic — the query execution path respects entity, column, and row-level security.
|
|
302
|
+
- Grouped queries need at least one `"agg": "group"` column and one aggregate column.
|
|
303
|
+
- Reference paths go up to 2 levels deep (e.g. `"customer.country"` works, deeper may not).
|
|
304
|
+
|
|
305
|
+
## Common mistakes
|
|
306
|
+
|
|
307
|
+
- Putting SQL in the `query` object — **wrong**. Queries use the JSON format, not raw SQL. For raw SQL, use stored procedures in reports.
|
|
308
|
+
- Using `"column"` instead of `"c"` in columns/filters — **wrong**. Use the short keys.
|
|
309
|
+
- Forgetting `"agg"` on columns in grouped mode — every column needs an aggregation assignment.
|
|
310
|
+
- Referencing columns from unrelated entities — queries can only traverse the root entity's references.
|
|
311
|
+
- Using `@TODAY`, `@CURRENT_USER`, or any `@placeholder` syntax in filter values — **wrong**. Queries do not support parameter substitution. Use the function operator instead: `{ "c": "due_date", "o": "f", "v": { "fn": "isPast" } }`.
|
|
312
|
+
- Using a function as the value of a comparison operator (`<`, `>`, `=`, `!=`, `between`) — **wrong**. Functions are standalone conditions, valid only with `"o": "f"` (full detail + examples under "Dynamic date and user filters" above). `{ "c": "due_date", "o": "<", "v": { "fn": "isToday" } }` is invalid; use `{ "c": "due_date", "o": "f", "v": { "fn": "isPast" } }`.
|