@dforge-core/dforge-mcp 0.2.3 → 0.2.5
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 +81 -0
- package/dist/server.js +362 -332
- package/package.json +47 -46
- package/resources/docs/conventions.md +4 -0
- package/resources/docs/dsl-reference.md +47 -1
- package/resources/schemas/data-views.schema.json +16 -3
- package/resources/schemas/folders.schema.json +18 -2
- package/resources/schemas/reports.schema.json +25 -4
- package/skills/dforge-mcp-author/references/action-dsl.md +36 -1
- package/skills/dforge-mcp-author/references/column-domains.md +21 -2
- package/skills/dforge-mcp-author/references/reports.md +9 -3
- package/skills/dforge-mcp-author/references/translations.md +29 -3
package/package.json
CHANGED
|
@@ -1,48 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
2
|
+
"name": "@dforge-core/dforge-mcp",
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "MCP server for dForge module authoring. Exposes 34 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/iash44/dForge-core",
|
|
7
|
+
"packageManager": "pnpm@11.21.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dforge-core/dforge-mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"dforge-mcp": "dist/server.js",
|
|
14
|
+
"dforge-install-skills": "scripts/install-skills.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"resources/",
|
|
20
|
+
"skills/",
|
|
21
|
+
"scripts/install-skills.mjs",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"main": "dist/server.js",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"sync-schemas": "node scripts/vendor-schemas.cjs",
|
|
32
|
+
"prepublishOnly": "node scripts/vendor-schemas.cjs && tsup",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"install-skills": "node scripts/install-skills.mjs"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@dforge-core/dforge-cli": "^0.2.13",
|
|
39
|
+
"@dforge-core/metadata": "^0.0.14",
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
41
|
+
"zod": "^4.4.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^26.2.0",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"vitest": "^4.1.10"
|
|
48
|
+
}
|
|
48
49
|
}
|
|
@@ -869,6 +869,10 @@ For `dropdown` fields, define available options in `params.options`:
|
|
|
869
869
|
|
|
870
870
|
Both formats can be mixed in the same array, but this is not recommended.
|
|
871
871
|
|
|
872
|
+
**Action / report parameters carry the same option list.** A `dropdown` param declares its choices in the DSL (`method: dropdown required "Method" options=[bank_transfer:Bank transfer, cash:Cash]`) or, for report params, under `params.options` in JSON — the identical `{value, label, icon, color}` shape. Bare codes (`options=bank_transfer,cash`) render as raw codes in **every** locale, English included, so label them. Per-locale labels go under `actions.<cd>.params.<param_cd>.options` / `reports.<cd>.params.<param_cd>.options`.
|
|
873
|
+
|
|
874
|
+
**Prefer a shared column domain over restating a list.** When the choices are the same set a column uses — the usual case, since the param's value normally ends up written to that column — bind the param to the domain instead: `method: domain payment_method required "Method"` in the DSL, `{"domain": "fin.payment_method"}` for a report param. The options and their translations then live in exactly one place for the column and every parameter on it. Keep inline `options=` for a list that belongs to one parameter and matches no column. See the `column-domains.md` and `translations.md` skill references.
|
|
875
|
+
|
|
872
876
|
---
|
|
873
877
|
|
|
874
878
|
## Version Management
|
|
@@ -59,10 +59,56 @@ Common field types + their extra keys:
|
|
|
59
59
|
| `date` | (none) | `due_date: date required "Due"` |
|
|
60
60
|
| `dateTime` | (none) | `scheduled_at: dateTime required "Scheduled at"` |
|
|
61
61
|
| `boolean` | (none) | `urgent: boolean optional "Urgent?"` |
|
|
62
|
-
| `dropdown` | `options` | `priority: dropdown required "Priority" options=low,medium,high` |
|
|
62
|
+
| `dropdown` | `options` | `priority: dropdown required "Priority" options=[low:Low, medium:Medium, high:High]` |
|
|
63
63
|
| `lookup` | (use ref form instead — see below) | |
|
|
64
64
|
| `file` | `accept` | `attachment: file optional "Attachment" accept=.pdf,.png` |
|
|
65
65
|
|
|
66
|
+
### Dropdown options — label them, or prefer a domain
|
|
67
|
+
|
|
68
|
+
`options=low,medium,high` stores bare codes, and the dialog then shows `low` / `medium` /
|
|
69
|
+
`high` in **every** locale, English included. Give each choice a label:
|
|
70
|
+
|
|
71
|
+
```dsl
|
|
72
|
+
params:
|
|
73
|
+
// value:Label — wrap the list in [ ] (or " ") when a label contains a space,
|
|
74
|
+
// because the trailing key=value scan is whitespace-delimited.
|
|
75
|
+
method: dropdown required "Method" options=[bank_transfer:Bank transfer, cash:Cash]
|
|
76
|
+
|
|
77
|
+
// JSON form — for values with commas, or to set icon/color.
|
|
78
|
+
priority: dropdown required "Priority" options=[{"value":"high","label":"High","color":"#c00"},"normal"]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
An option object must carry a non-empty string `value` — that's the stored code and the key
|
|
82
|
+
translations match on. Per-locale labels go under
|
|
83
|
+
`actions.<cd>.params.<param_cd>.options` (see `translations.md`).
|
|
84
|
+
|
|
85
|
+
### Domain form (borrow a shared option list)
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
paramName: domain domainCd [required|optional] ["description"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```dsl
|
|
92
|
+
params:
|
|
93
|
+
payment_method: domain payment_method required "Payment method for this batch"
|
|
94
|
+
status: domain fin.doc_status optional "Status" // module-qualified
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Prefer this whenever the choices are (or could be) the same list a column uses** — which
|
|
98
|
+
is the usual case, since the param's value normally ends up written to that column. The
|
|
99
|
+
param takes its control and options from the `column_domain`, so the list and its
|
|
100
|
+
translations are authored **once** instead of restated in the DSL and drifting from the
|
|
101
|
+
column's.
|
|
102
|
+
|
|
103
|
+
- The param keeps its **own caption**; only the choices come from the domain.
|
|
104
|
+
- Nothing may follow the description: `options=`, `min=` and friends are a compile error
|
|
105
|
+
here — the domain owns them.
|
|
106
|
+
- An unknown domain code fails the install; it never degrades to an empty dropdown.
|
|
107
|
+
- Report params take the same binding as JSON: `{"domain": "fin.payment_method", "label": "…"}`.
|
|
108
|
+
|
|
109
|
+
Use inline `options=` only when the list belongs to that one parameter and matches no
|
|
110
|
+
column (a run mode, an export format).
|
|
111
|
+
|
|
66
112
|
### Reference form (FK to another entity)
|
|
67
113
|
|
|
68
114
|
```
|
|
@@ -492,7 +492,20 @@
|
|
|
492
492
|
},
|
|
493
493
|
"o": {
|
|
494
494
|
"type": "string",
|
|
495
|
-
"
|
|
495
|
+
"enum": [
|
|
496
|
+
"=", "eq", "!=", "notEq",
|
|
497
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
498
|
+
"eqRef",
|
|
499
|
+
"contains", "!contains", "nContain",
|
|
500
|
+
"start", "!start", "nStart",
|
|
501
|
+
"end", "!end", "nEnd",
|
|
502
|
+
"mask", "!mask", "nMask",
|
|
503
|
+
"null", "empty", "!null", "nEmpty",
|
|
504
|
+
"in", "!in", "nIn",
|
|
505
|
+
"between", "btw", "!between", "nBetween",
|
|
506
|
+
"f", "func"
|
|
507
|
+
],
|
|
508
|
+
"description": "Operator. Closed set — this is exactly what the server's FilterBuilder translates, and an operator outside it is rejected at install. Symbolic forms and their word aliases are equivalent ('=' / 'eq', '!=' / 'notEq', '>' / 'gr', '>=' / 'grEq', '<' / 'less', '<=' / 'lessEq'); a leading '!' negates, with 'n'-prefixed spellings as the alias ('!in' / 'nIn'). Null checks are 'null' / 'empty' and '!null' / 'nEmpty' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). 'eqRef' compares against another column (v = column code), 'f' / 'func' takes v = {fn: 'isToday' | 'isYesterday' | 'isThisWeek' | 'isThisMonth' | 'isThisYear' | 'isPast' | 'isFuture' | 'isCurrentUser'}."
|
|
496
509
|
},
|
|
497
510
|
"v": {
|
|
498
511
|
"description": "Comparison value. Scalars, arrays (for in/nIn), or parameter references like '@param_code'."
|
|
@@ -506,8 +519,8 @@
|
|
|
506
519
|
"properties": {
|
|
507
520
|
"g": {
|
|
508
521
|
"type": "string",
|
|
509
|
-
"enum": ["and", "or"],
|
|
510
|
-
"description": "Group combinator"
|
|
522
|
+
"enum": ["and", "or", "!and", "nAnd", "!or", "nOr"],
|
|
523
|
+
"description": "Group combinator. '!and' / '!or' negate the whole group ('nAnd' / 'nOr' are the alias spellings). Closed set — an unknown combinator is rejected at install (at query time it would silently fall back to AND)."
|
|
511
524
|
},
|
|
512
525
|
"i": {
|
|
513
526
|
"type": "array",
|
|
@@ -87,7 +87,23 @@
|
|
|
87
87
|
"required": ["c", "o"],
|
|
88
88
|
"properties": {
|
|
89
89
|
"c": { "type": "string" },
|
|
90
|
-
"o": {
|
|
90
|
+
"o": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"=", "eq", "!=", "notEq",
|
|
94
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
95
|
+
"eqRef",
|
|
96
|
+
"contains", "!contains", "nContain",
|
|
97
|
+
"start", "!start", "nStart",
|
|
98
|
+
"end", "!end", "nEnd",
|
|
99
|
+
"mask", "!mask", "nMask",
|
|
100
|
+
"null", "empty", "!null", "nEmpty",
|
|
101
|
+
"in", "!in", "nIn",
|
|
102
|
+
"between", "btw", "!between", "nBetween",
|
|
103
|
+
"f", "func"
|
|
104
|
+
],
|
|
105
|
+
"description": "Operator — same closed set as data_views.json. Null checks are 'null' / '!null' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). An operator outside this set is rejected at install; at query time it would be dropped, widening the row filter it was meant to enforce."
|
|
106
|
+
},
|
|
91
107
|
"v": {}
|
|
92
108
|
},
|
|
93
109
|
"additionalProperties": false
|
|
@@ -96,7 +112,7 @@
|
|
|
96
112
|
"type": "object",
|
|
97
113
|
"required": ["g", "i"],
|
|
98
114
|
"properties": {
|
|
99
|
-
"g": { "type": "string", "enum": ["and", "or"] },
|
|
115
|
+
"g": { "type": "string", "enum": ["and", "or", "!and", "nAnd", "!or", "nOr"] },
|
|
100
116
|
"i": {
|
|
101
117
|
"type": "array",
|
|
102
118
|
"items": { "$ref": "#/$defs/filter" }
|
|
@@ -314,11 +314,16 @@
|
|
|
314
314
|
|
|
315
315
|
"paramDef": {
|
|
316
316
|
"type": "object",
|
|
317
|
-
"required": ["fieldTypeCd"],
|
|
317
|
+
"not": { "required": ["fieldTypeCd", "domain"] },
|
|
318
318
|
"properties": {
|
|
319
319
|
"fieldTypeCd": {
|
|
320
320
|
"type": "string",
|
|
321
|
-
"description": "Field type code (see settings.schema.json for the common list)"
|
|
321
|
+
"description": "Field type code (see settings.schema.json for the common list). Omit when `domain` is set — the domain supplies the control. May also be omitted entirely: a param declaring neither key installs as `text` (ReportRegistrar.DefaultParamFieldTypeCd). The one rejected combination is declaring both."
|
|
322
|
+
},
|
|
323
|
+
"domain": {
|
|
324
|
+
"type": "string",
|
|
325
|
+
"description": "Column domain (`domain_cd` or `module_cd.domain_cd`) the parameter borrows its control and option list from. The options and their translations stay on the domain, so a list shared with an entity column is authored once. The DSL equivalent for action params is `paramCd: domain <domainCd>`.",
|
|
326
|
+
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)?$"
|
|
322
327
|
},
|
|
323
328
|
"label": { "type": "string" },
|
|
324
329
|
"orderNum": {
|
|
@@ -366,7 +371,23 @@
|
|
|
366
371
|
"required": ["c", "o"],
|
|
367
372
|
"properties": {
|
|
368
373
|
"c": { "type": "string" },
|
|
369
|
-
"o": {
|
|
374
|
+
"o": {
|
|
375
|
+
"type": "string",
|
|
376
|
+
"enum": [
|
|
377
|
+
"=", "eq", "!=", "notEq",
|
|
378
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
379
|
+
"eqRef",
|
|
380
|
+
"contains", "!contains", "nContain",
|
|
381
|
+
"start", "!start", "nStart",
|
|
382
|
+
"end", "!end", "nEnd",
|
|
383
|
+
"mask", "!mask", "nMask",
|
|
384
|
+
"null", "empty", "!null", "nEmpty",
|
|
385
|
+
"in", "!in", "nIn",
|
|
386
|
+
"between", "btw", "!between", "nBetween",
|
|
387
|
+
"f", "func"
|
|
388
|
+
],
|
|
389
|
+
"description": "Operator — same closed set as data_views.json. Null checks are 'null' / '!null' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). An operator outside this set is rejected at install; at query time it would be dropped, silently widening the dataset."
|
|
390
|
+
},
|
|
370
391
|
"v": {}
|
|
371
392
|
},
|
|
372
393
|
"additionalProperties": false
|
|
@@ -375,7 +396,7 @@
|
|
|
375
396
|
"type": "object",
|
|
376
397
|
"required": ["g", "i"],
|
|
377
398
|
"properties": {
|
|
378
|
-
"g": { "type": "string", "enum": ["and", "or"] },
|
|
399
|
+
"g": { "type": "string", "enum": ["and", "or", "!and", "nAnd", "!or", "nOr"] },
|
|
379
400
|
"i": {
|
|
380
401
|
"type": "array",
|
|
381
402
|
"items": { "$ref": "#/$defs/filter" }
|
|
@@ -48,7 +48,7 @@ Each param is one line: `name: type [required] ["Label"]`
|
|
|
48
48
|
|
|
49
49
|
```
|
|
50
50
|
params:
|
|
51
|
-
new_stage: dropdown required "New Stage"
|
|
51
|
+
new_stage: dropdown required "New Stage" options=[open:Open, won:Won, lost:Lost]
|
|
52
52
|
note: textarea "Follow-up Note"
|
|
53
53
|
adjustment_qty: number required "Adjustment Quantity"
|
|
54
54
|
reason: text required "Reason"
|
|
@@ -58,6 +58,41 @@ Types are a subset of column field types: `text`, `textarea`, `number`, `currenc
|
|
|
58
58
|
|
|
59
59
|
Access in execute block: `params[param_name]` (bracket syntax, NOT dot syntax).
|
|
60
60
|
|
|
61
|
+
#### Dropdown params: label the options, or borrow a domain
|
|
62
|
+
|
|
63
|
+
A bare `options=open,won,lost` stores codes with no labels, and the dialog shows `open` /
|
|
64
|
+
`won` / `lost` in **every** locale, English included. Two ways to do better, in order of
|
|
65
|
+
preference:
|
|
66
|
+
|
|
67
|
+
1. **`domain <domainCd>`** — the param borrows a shared `column_domain`:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
params:
|
|
71
|
+
payment_method: domain payment_method required "Payment method for this batch"
|
|
72
|
+
status: domain fin.doc_status optional "Status" // module-qualified
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use this whenever the choices are the same list a column uses — the usual case, since
|
|
76
|
+
the param's value normally ends up written to that column. Control and options come off
|
|
77
|
+
the domain, so the list and its translations live in **one** place instead of being
|
|
78
|
+
restated here and drifting from the column's. The param keeps its own caption. Nothing
|
|
79
|
+
may follow the description (`options=`, `min=` … are a compile error — the domain owns
|
|
80
|
+
them), and an unknown domain code fails the install.
|
|
81
|
+
|
|
82
|
+
2. **Inline labels** — for a list that belongs to this parameter alone (a run mode, an
|
|
83
|
+
export format) and matches no column:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
params:
|
|
87
|
+
method: dropdown required "Method" options=[bank_transfer:Bank transfer, cash:Cash]
|
|
88
|
+
priority: dropdown required "Priority" options=[{"value":"high","label":"High","color":"#c00"},"normal"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`value:Label` splits on the first colon; wrap the list in `[ ]` (or `" "`) when a label
|
|
92
|
+
contains a space, since the trailing `key=value` scan is whitespace-delimited. An option
|
|
93
|
+
object needs a non-empty string `value` — the stored code, and the key translations match
|
|
94
|
+
on. Per-locale labels: `actions.<cd>.params.<param_cd>.options` (`translations.md`).
|
|
95
|
+
|
|
61
96
|
### `canExecute:` — availability formula
|
|
62
97
|
|
|
63
98
|
A formula expression (same grammar as formula columns) that evaluates to true/false. When false, the action button is hidden or disabled in the UI.
|
|
@@ -8,7 +8,7 @@ Schema: `dforge://schema/domains`.
|
|
|
8
8
|
|
|
9
9
|
## Why
|
|
10
10
|
|
|
11
|
-
Without domains, every "status" column restates the same `fieldTypeCd: "dropdown"` + the full `params.options` list
|
|
11
|
+
Without domains, every "status" column — and every action param that writes to one — restates the same `fieldTypeCd: "dropdown"` + the full `params.options` list, and, if you localize, the same option translations, once per consumer. A domain declares that once; every consuming column **and parameter** inherits the type, the control, the options, **and** their translations. Localize `fin.doc_status` a single time and all its uses update together.
|
|
12
12
|
|
|
13
13
|
## `domains.json`
|
|
14
14
|
|
|
@@ -54,6 +54,24 @@ A column swaps its whole type block for a single `domain` key — qualified as `
|
|
|
54
54
|
|
|
55
55
|
The column keeps what is genuinely its own — `description` (its label), `flags`, `orderNum`, `isNullable`, `isPk`, `columnGroupCd`. The domain supplies the rest.
|
|
56
56
|
|
|
57
|
+
## Using a domain in an action / report param
|
|
58
|
+
|
|
59
|
+
Parameters consume the same domain, so an action that writes to a domain-backed column offers exactly that column's choices instead of restating them:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
params:
|
|
63
|
+
status: domain fin.doc_status required "New status"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
"status": { "domain": "fin.doc_status", "label": "New status", "required": true }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Prefer this over inline `options=`** whenever the param's value ends up on a domain-backed column — otherwise the same enum is authored twice, translated twice, and free to drift (a param option whose code isn't on the column is a value the grid can't label).
|
|
71
|
+
- Install materializes the domain's `fieldTypeCd` onto the param; its datatype and sizing don't apply, because a parameter has no storage. A domain with no `fieldTypeCd` is rejected — the param would have no control to render.
|
|
72
|
+
- The param keeps its **own caption** (`"New status"` above) and translates it under `actions.<cd>.params.<param_cd>.label` as usual. Only the choices come from the domain, options translations included.
|
|
73
|
+
- Nothing may follow the description in the DSL form — `options=`, `min=` and friends are a compile error there, same authority rule columns get. A report param declaring both `domain` and `fieldTypeCd` is rejected for the same reason.
|
|
74
|
+
|
|
57
75
|
## Rules (these fail the install)
|
|
58
76
|
|
|
59
77
|
1. **Don't restate what the domain owns.** Declaring `dbDatatype`, `fieldTypeCd`, `maxLen`, `precision`, `baseDatatypeCd` or `params` alongside `domain` is rejected, naming the conflicting key — it is never silently overridden. Domains are authoritative.
|
|
@@ -78,7 +96,7 @@ An option override is a partial merged over the base — a bare string is shorth
|
|
|
78
96
|
## How it resolves (mental model)
|
|
79
97
|
|
|
80
98
|
- **Install** materializes the domain's structural fields (datatype/control/sizing) onto each consuming column, because DDL generation needs them locally.
|
|
81
|
-
- **Runtime** resolves `params` (the options) — and their per-culture overrides — from the domain, not the column
|
|
99
|
+
- **Runtime** resolves `params` (the options) — and their per-culture overrides — from the domain, not the column *or the param*. That is what lets a shared list be authored and translated once.
|
|
82
100
|
|
|
83
101
|
You don't act on this split; just know that the option list stays on the domain, so **don't** paste `params.options` onto a domain-backed column (the install rejects it).
|
|
84
102
|
|
|
@@ -90,5 +108,6 @@ You don't act on this split; just know that the option list stays on the domain,
|
|
|
90
108
|
## Common mistakes
|
|
91
109
|
|
|
92
110
|
- Putting `params.options` on the column **and** naming a `domain` → install error. Options belong on the domain.
|
|
111
|
+
- Restating a domain's list inline on an action param (`options=…`) instead of binding the param to the domain → compiles, but you now maintain and translate the same enum twice.
|
|
93
112
|
- Using a cross-module domain without declaring the dependency → "unknown column domain" at install.
|
|
94
113
|
- Expecting per-column option overrides — the domain is authoritative in v1; narrow with `optionSets` (which filters by `value`) if you need a conditional subset.
|
|
@@ -61,7 +61,7 @@ A report has `description`, a `datasets` map, a `layout` object (`{ panels: [...
|
|
|
61
61
|
"query": {
|
|
62
62
|
"entityCd": "opportunity",
|
|
63
63
|
"columns": ["stage", "amount", "close_date", "customer.name"],
|
|
64
|
-
"filter": { "c": "stage", "o": "
|
|
64
|
+
"filter": { "c": "stage", "o": "!=", "v": "Closed Lost" },
|
|
65
65
|
"sort": [ { "c": "close_date", "d": "desc" } ]
|
|
66
66
|
},
|
|
67
67
|
"columnsDef": { "amount": { "label": "Deal Value" }, "customer.name": { "label": "Account" } }
|
|
@@ -144,20 +144,26 @@ Parameters prompt the user before running the report. Declare them per-report (`
|
|
|
144
144
|
```json
|
|
145
145
|
"parameters": {
|
|
146
146
|
"start_date": { "fieldTypeCd": "date", "label": "Start Date", "required": true, "default": "=STARTMONTH()" },
|
|
147
|
-
"
|
|
147
|
+
"status": { "domain": "fin.doc_status", "label": "Status" },
|
|
148
|
+
"region": { "fieldTypeCd": "dropdown", "label": "Region", "params": { "options": [
|
|
149
|
+
{ "value": "north", "label": "North" }, { "value": "south", "label": "South" }
|
|
150
|
+
] } },
|
|
148
151
|
"customer": { "fieldTypeCd": "lookup", "label": "Customer", "params": { "entityCd": "account" } }
|
|
149
152
|
}
|
|
150
153
|
```
|
|
151
154
|
|
|
152
155
|
| Property | Description |
|
|
153
156
|
|---|---|
|
|
154
|
-
| `fieldTypeCd` | Control type: `date`, `datetime`, `text`, `number`, `dropdown`, `lookup`, `user`, `checkbox` |
|
|
157
|
+
| `fieldTypeCd` | Control type: `date`, `datetime`, `text`, `number`, `dropdown`, `lookup`, `user`, `checkbox`. Omit when `domain` is set. |
|
|
158
|
+
| `domain` | Column domain (`domain_cd` or `module_cd.domain_cd`) supplying the control **and** the option list. Mutually exclusive with `fieldTypeCd` — declaring both is rejected at install. |
|
|
155
159
|
| `label` | Display label in the parameter dialog |
|
|
156
160
|
| `required` | Whether the user must fill this before running |
|
|
157
161
|
| `default` | Plain value or `=`-prefixed formula (`"=NOW()"`, `"=STARTMONTH()"`, `"=TODAY()"`) |
|
|
158
162
|
| `params` | Extra config — `options` for dropdowns, `entityCd` for lookups |
|
|
159
163
|
| `orderNum` | Display order in the parameter form (falls back to declaration order) |
|
|
160
164
|
|
|
165
|
+
**Dropdown params: prefer `domain`, and never ship bare codes.** A list shared with a column belongs on a `column_domain` — bind the param to it and the options plus their translations are authored once (`column-domains.md`). For a list that exists only for this report, write the rich option form (`{ "value": "north", "label": "North" }`); a bare `"options": ["north", "south"]` renders the raw codes in **every** locale, English included. Per-locale labels go under `reports.<cd>.params.<param_cd>.options` (`translations.md`).
|
|
166
|
+
|
|
161
167
|
Reference a parameter with `@param_code` inside a query filter value:
|
|
162
168
|
|
|
163
169
|
```json
|
|
@@ -82,8 +82,8 @@ Translation files mirror the module's content structure. Each translatable objec
|
|
|
82
82
|
| `roles` | Role display labels | Role codes (keys from `security/roles.json`, already module-qualified — e.g. `crm.admin`) → `{ label }` |
|
|
83
83
|
| `views` | Data view labels | View codes (keys from `ui/data_views.json`) |
|
|
84
84
|
| `menus` | Menu node labels (root + nested items) | Menu root key → `{ label, items: { item_code: { label } } }` matching `ui/menus.json` structure |
|
|
85
|
-
| `actions` | Action labels, descriptions, and param labels | Action codes (keys from `ui/actions.json`) → `{ label, desc, params: { param_cd: { label } } }` |
|
|
86
|
-
| `reports` | Report dataset captions and param labels | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label } } }` |
|
|
85
|
+
| `actions` | Action labels, descriptions, param labels **and param dropdown option labels** | Action codes (keys from `ui/actions.json`) → `{ label, desc, params: { param_cd: { label, options: { value: … } } } }` |
|
|
86
|
+
| `reports` | Report dataset captions, param labels **and param dropdown option labels** | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label, options: { value: … } } } }` |
|
|
87
87
|
| `entities.<e>.constraints` | Check/unique constraint violation messages (opt-in — warned, not enforced) | Constraint codes → `{ message }` under the owning entity |
|
|
88
88
|
| `entities.<e>.fields.<f>.options` | Dropdown/radio/flags **option labels** (opt-in) | Option `value` → localized label (string) or `{ label, icon, color }` |
|
|
89
89
|
| `domains` | Column domain labels **and** their shared option labels (opt-in) | Domain codes → `{ label, options: { value: … } }` |
|
|
@@ -146,7 +146,33 @@ If a column uses a **domain** (see `column-domains.md`), translate the domain **
|
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
- Keys are domain codes from this module's `domains.json`. The `options` map uses the same value-keyed, partial-override shape as field options above.
|
|
149
|
-
- This is the whole point of domains for localization: the shared option list is authored and translated in one place. Don't also translate the consuming columns' options — they inherit from the domain.
|
|
149
|
+
- This is the whole point of domains for localization: the shared option list is authored and translated in one place. Don't also translate the consuming columns' options — they inherit from the domain. **Nor the params bound to it** — an action or report parameter declared as `domain <domainCd>` inherits from the same place.
|
|
150
|
+
|
|
151
|
+
### Action / report PARAM option labels ARE translatable (opt-in)
|
|
152
|
+
|
|
153
|
+
A `dropdown` **parameter** carries per-value labels exactly like a column, and translates them under an `options` map next to the param's `label`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
"actions": {
|
|
157
|
+
"distribute_payment": {
|
|
158
|
+
"params": {
|
|
159
|
+
"payment_method": {
|
|
160
|
+
"label": "Zahlungsart",
|
|
161
|
+
"options": {
|
|
162
|
+
"bank_transfer": { "label": "Überweisung" },
|
|
163
|
+
"cash": "Bar"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Same shape and rules as field options: value-keyed, partial override merged over the base, bare string = `{ "label": … }`, only `label`/`icon`/`color` translatable, `value` never.
|
|
172
|
+
- The same section works for `reports.<report_cd>.params.<param_cd>.options`.
|
|
173
|
+
- A param may translate **only** its options and keep the authored caption — omit `label`.
|
|
174
|
+
- **This requires base labels in the DSL.** `options=bank_transfer,cash` stores bare codes, and the dialog shows `bank_transfer` in *every* locale including English until you write `options=[bank_transfer:Bank transfer, cash:Cash]` (or the JSON object form). See `action-dsl.md`.
|
|
175
|
+
- **Prefer a domain.** If the param's choices are the same list a column uses — which they usually are, since the value normally ends up written to that column — declare the param as `domain <domainCd>` and skip this section entirely: it inherits the domain's translated options. Reach for per-param `options` only when the list belongs to that one parameter and matches no column.
|
|
150
176
|
|
|
151
177
|
### Roles ARE translated — and completeness is enforced
|
|
152
178
|
|