@dforge-core/dforge-mcp 0.1.0-rc.9 → 0.1.1

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +84 -27
  3. package/dist/server.js +2957 -616
  4. package/docs/creating-modules.md +11 -4
  5. package/package.json +11 -6
  6. package/resources/docs/conventions.md +22 -16
  7. package/resources/docs/dsl-reference.md +767 -0
  8. package/resources/schemas/entity.schema.json +8 -1
  9. package/resources/schemas/print-templates.schema.json +82 -0
  10. package/resources/schemas/reports.schema.json +4 -0
  11. package/resources/schemas/triggers.schema.json +59 -0
  12. package/skills/dforge-mcp-author/SKILL.md +269 -69
  13. package/skills/dforge-mcp-author/examples/matrix-budget/README.md +43 -0
  14. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_category.json +24 -0
  15. package/skills/dforge-mcp-author/examples/matrix-budget/entities/budget_line.json +56 -0
  16. package/skills/dforge-mcp-author/examples/matrix-budget/manifest.json +30 -0
  17. package/skills/dforge-mcp-author/examples/matrix-budget/security/roles.json +9 -0
  18. package/skills/dforge-mcp-author/examples/matrix-budget/seed-data/01-categories.json +8 -0
  19. package/skills/dforge-mcp-author/examples/matrix-budget/ui/data_views.json +42 -0
  20. package/skills/dforge-mcp-author/examples/simple-todo/README.md +38 -0
  21. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_item.json +83 -0
  22. package/skills/dforge-mcp-author/examples/simple-todo/entities/todo_list.json +43 -0
  23. package/skills/dforge-mcp-author/examples/simple-todo/logic/actions/mark_done.dsl +6 -0
  24. package/skills/dforge-mcp-author/examples/simple-todo/manifest.json +32 -0
  25. package/skills/dforge-mcp-author/examples/simple-todo/security/roles.json +10 -0
  26. package/skills/dforge-mcp-author/examples/simple-todo/seed-data/01-lists.json +17 -0
  27. package/skills/dforge-mcp-author/examples/simple-todo/ui/actions.json +11 -0
  28. package/skills/dforge-mcp-author/examples/simple-todo/ui/data_views.json +35 -0
  29. package/skills/dforge-mcp-author/examples/simple-todo/ui/menus.json +28 -0
  30. package/skills/dforge-mcp-author/references/action-dsl.md +397 -0
  31. package/skills/dforge-mcp-author/references/column-types.md +168 -0
  32. package/skills/dforge-mcp-author/references/conventions.md +177 -0
  33. package/skills/dforge-mcp-author/references/data-migration.md +270 -0
  34. package/skills/dforge-mcp-author/references/data-views.md +243 -0
  35. package/skills/dforge-mcp-author/references/excel-import.md +61 -0
  36. package/skills/dforge-mcp-author/references/field-types.md +144 -0
  37. package/skills/dforge-mcp-author/references/filters.md +326 -0
  38. package/skills/dforge-mcp-author/references/flags.md +73 -0
  39. package/skills/dforge-mcp-author/references/formulas.md +206 -0
  40. package/skills/dforge-mcp-author/references/jobs.md +149 -0
  41. package/skills/dforge-mcp-author/references/manifest.md +123 -0
  42. package/skills/dforge-mcp-author/references/menus.md +164 -0
  43. package/skills/dforge-mcp-author/references/number-sequences.md +117 -0
  44. package/skills/dforge-mcp-author/references/print-templates.md +159 -0
  45. package/skills/dforge-mcp-author/references/queries.md +312 -0
  46. package/skills/dforge-mcp-author/references/reports.md +398 -0
  47. package/skills/dforge-mcp-author/references/schema-import.md +331 -0
  48. package/skills/dforge-mcp-author/references/security.md +244 -0
  49. package/skills/dforge-mcp-author/references/settings.md +120 -0
  50. package/skills/dforge-mcp-author/references/traits.md +153 -0
  51. package/skills/dforge-mcp-author/references/translations.md +158 -0
  52. package/skills/dforge-mcp-author/references/validation-checklist.md +183 -0
  53. package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
@@ -0,0 +1,206 @@
1
+ # Formula Engine Reference
2
+
3
+ Formulas are used in:
4
+
5
+ - **Formula columns** (`columnType: "F"`) — computed values
6
+ - **Action `canExecute:` blocks** — availability checks
7
+ - **Default values** — a setting's `formula` (e.g. `"formula": "TODAY()"`) or a formula (`F`) column. Note: entity *data* columns have **no** `default`/`defaultValue` key — model a default with an `F` column or set it in action/trigger logic.
8
+ - **Filter expressions** (partially)
9
+ - **Validation expressions** (partially)
10
+
11
+ > These are **formula** contexts: date helpers are uppercase `TODAY()` / `NOW()` here. Action
12
+ > `execute:` blocks are **not** a formula context — they run as JavaScript and use lowercase
13
+ > `now()` (see `action-dsl.md`).
14
+
15
+ The same grammar applies everywhere.
16
+
17
+ ## Basic syntax
18
+
19
+ - **Field references**: `[column_name]` — the value of another column on the same entity
20
+ - **Navigation**: `[reference_column].[field]` — traverse a reference to a related entity's field
21
+ - **Literals**: numbers (`42`, `3.14`), strings (`'hello'` or `"hello"`), booleans (`true`, `false`), null (`null`)
22
+ - **Operators**: `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `AND`, `OR`, `NOT`
23
+ - **Function calls**: `FUNCTION_NAME(arg1, arg2, ...)` — uppercase by convention
24
+ - **Parentheses** for grouping
25
+
26
+ ## Examples
27
+
28
+ ```
29
+ [first_name] + ' ' + [last_name]
30
+
31
+ [quantity] * [unit_price]
32
+
33
+ [account].[name]
34
+
35
+ [account].[billing_address].[country]
36
+
37
+ [status] == "active" AND [balance] > 0
38
+
39
+ CASE([priority], "high", 3, "medium", 2, "low", 1, 0)
40
+
41
+ IF([total] > 1000, "large", "small")
42
+
43
+ COALESCE([nickname], [first_name], "Unknown")
44
+
45
+ FORMAT([created_date], "yyyy-MM-dd")
46
+ ```
47
+
48
+ ## Built-in functions
49
+
50
+ ### String functions
51
+
52
+ - `CONCAT(a, b, ...)` — concatenate strings
53
+ - `LEN(s)` — string length
54
+ - `UPPER(s)`, `LOWER(s)` — case conversion
55
+ - `TRIM(s)`, `LTRIM(s)`, `RTRIM(s)` — whitespace removal
56
+ - `SUBSTRING(s, start, len)` — substring
57
+ - `REPLACE(s, find, with)` — replace substring
58
+ - `CONTAINS(s, find)` — boolean substring check
59
+ - `STARTSWITH(s, prefix)`, `ENDSWITH(s, suffix)` — boolean
60
+ - `FORMAT(value, pattern)` — format dates/numbers
61
+
62
+ ### Number functions
63
+
64
+ - `ABS(n)`, `ROUND(n, digits)`, `FLOOR(n)`, `CEIL(n)`, `CEILING(n)`
65
+ - `MIN(a, b)`, `MAX(a, b)` — binary min/max
66
+ - `POW(base, exp)`, `SQRT(n)`
67
+ - `MOD(a, b)` — modulo
68
+
69
+ ### Date functions
70
+
71
+ - `TODAY()` — current date
72
+ - `NOW()` — current datetime with timezone
73
+ - `YEAR(d)`, `MONTH(d)`, `DAY(d)` — date parts
74
+ - `HOUR(dt)`, `MINUTE(dt)`, `SECOND(dt)` — time parts
75
+ - `DATEADD(d, count, unit)` — add time (unit: `day`, `month`, `year`, `hour`, `minute`)
76
+ - `DATEDIFF(a, b, unit)` — difference between dates
77
+
78
+ ### Logical functions
79
+
80
+ - `IF(cond, then, else)` — ternary
81
+ - `CASE(expr, val1, result1, val2, result2, ..., default)` — multi-branch
82
+ - `COALESCE(a, b, c, ...)` — first non-null
83
+ - `NULLIF(a, b)` — null if equal, else a
84
+ - `ISNULL(x)` — boolean null check
85
+
86
+ ### Aggregation (on set columns)
87
+
88
+ - `COUNT([set_column])` — count related rows
89
+ - `SUM([set_column].[field])` — sum a child field over a set
90
+ - `AVG`, `MIN`, `MAX` similarly
91
+
92
+ Put set aggregations in a **Formula (`F`) column** — they evaluate at query time and may reference
93
+ any child column, including the child's own formula columns, e.g. `SUM([lines].[line_total])`.
94
+
95
+ > ⛔ Do **not** put a `SUM([set].[field])` in a **Generated (`G`)** column unless `field` is a
96
+ > *physical* (`D`) column. A `G` aggregate is maintained by a DB trigger that reads the child's
97
+ > `OLD`/`NEW` physical values; aggregating a virtual `F` child fails at install with
98
+ > `db_error: column old.<field> does not exist`. See `column-types.md` → "Roll-up totals over child rows".
99
+
100
+ ## Navigation (dot notation)
101
+
102
+ `[reference].[field]` traverses a reference column to a target entity:
103
+
104
+ ```
105
+ [account].[name] -- contact's account's name
106
+ [owner].[email] -- owner user's email
107
+ [account].[primary_contact].[phone] -- chained
108
+ ```
109
+
110
+ Navigation works through `columnType: "R"` columns (reference columns). Chains of length 1 are **synchronous** and resolved instantly. Chains of length ≥ 2 are **asynchronous** — the formula engine resolves them after the initial data load.
111
+
112
+ ## Sync vs async formulas
113
+
114
+ - **Sync formula**: pure local math, no navigation, or one-level navigation that can be JOINed. Evaluated on load and on every edit to a dependency.
115
+ - **Async formula**: multi-hop navigation. Evaluated after the initial data load, re-evaluated when dependencies change.
116
+
117
+ You don't declare which is which — the engine detects it from the formula's AST.
118
+
119
+ ## Setting references
120
+
121
+ In modules that use settings, formulas can reference setting values with `$[SettingName]` syntax:
122
+
123
+ ```
124
+ [total] * $[VAT_Rate]
125
+ ```
126
+
127
+ ## Validation and CHECK constraints
128
+
129
+ Check constraints use a subset of the formula grammar. The server parses them to AST and converts to SQL. Common patterns:
130
+
131
+ ```
132
+ [quantity] > 0
133
+ [end_date] >= [start_date]
134
+ [email] LIKE '%@%'
135
+ ```
136
+
137
+ ## Examples — full formula columns
138
+
139
+ ### Full name
140
+
141
+ ```json
142
+ "full_name": {
143
+ "columnType": "F",
144
+ "fieldTypeCd": "text",
145
+ "baseDatatypeCd": "string",
146
+ "flags": "V",
147
+ "orderNum": 25,
148
+ "formula": "[first_name] + ' ' + [last_name]",
149
+ "description": "Full Name"
150
+ }
151
+ ```
152
+
153
+ ### Line total (quantity × price)
154
+
155
+ ```json
156
+ "line_total": {
157
+ "columnType": "F",
158
+ "fieldTypeCd": "currency",
159
+ "baseDatatypeCd": "number",
160
+ "flags": "V",
161
+ "orderNum": 60,
162
+ "formula": "[quantity] * [unit_price]",
163
+ "description": "Line Total"
164
+ }
165
+ ```
166
+
167
+ ### Customer country (via navigation)
168
+
169
+ ```json
170
+ "customer_country": {
171
+ "columnType": "F",
172
+ "fieldTypeCd": "text",
173
+ "baseDatatypeCd": "string",
174
+ "flags": "V",
175
+ "orderNum": 70,
176
+ "formula": "[account].[billing_country]",
177
+ "description": "Customer Country"
178
+ }
179
+ ```
180
+
181
+ ### Days since created
182
+
183
+ ```json
184
+ "days_open": {
185
+ "columnType": "F",
186
+ "fieldTypeCd": "number",
187
+ "baseDatatypeCd": "number",
188
+ "flags": "V",
189
+ "orderNum": 200,
190
+ "formula": "DATEDIFF([created_date], NOW(), 'day')",
191
+ "description": "Days Open"
192
+ }
193
+ ```
194
+
195
+ ## Common mistakes
196
+
197
+ - Using `column_name` without brackets — **wrong**. Always `[column_name]`.
198
+ - Inventing functions like `HAS_PERMISSION()`, `IS_ADMIN()`, `GET_USER()` — **do not exist**. Do not use unless confirmed to exist in the actual dForge engine.
199
+ - Using JavaScript syntax like `row.field` or `this.field` — **wrong**. Only `[field]`.
200
+ - Forgetting `baseDatatypeCd` on formula columns — **required**. Without it, filters and SQL don't work.
201
+ - Using SQL syntax like `SELECT`, `JOIN`, `WHERE` — **wrong**. Formulas are expressions, not queries.
202
+ - String concatenation with commas — **wrong**. Use `+` or `CONCAT()`.
203
+
204
+ ## Reference
205
+
206
+ This file covers the most common formula functions and patterns. If you encounter an edge case not covered here, ask the user to check their dForge version's formula documentation.
@@ -0,0 +1,149 @@
1
+ # Scheduled Jobs Reference
2
+
3
+ A scheduled job is **a cron schedule + an action** declared in `logic/jobs.json`. The `dForge.Scheduler` worker process fires the action on schedule. No new DSL surface — jobs reuse existing actions.
4
+
5
+ Lives in: `logic/jobs.json` at the module root. Schema: [`docs/schemas/jobs.schema.json`](../../../docs/schemas/jobs.schema.json). Full guide: [`docs/business-logic/jobs.md`](../../../docs/business-logic/jobs.md).
6
+
7
+ ## Structure
8
+
9
+ ```json
10
+ {
11
+ "jobs": [
12
+ {
13
+ "code": "nightly_invoice_run",
14
+ "description": "Generate recurring invoices for active subscriptions.",
15
+ "action": "generate_invoices",
16
+ "schedule": "0 2 * * *",
17
+ "timeout": 600,
18
+ "class": "long_running",
19
+ "concurrency": 1,
20
+ "timeZone": "Europe/Zurich"
21
+ }
22
+ ]
23
+ }
24
+ ```
25
+
26
+ ## Field reference
27
+
28
+ | Field | Required | Default | Notes |
29
+ | ---------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
30
+ | `code` | Yes | | Unique within the module. `^[a-z][a-z0-9_]*$`. |
31
+ | `action` | Yes | | Action `code` from this module's `ui/actions.json`. Cross-module references are rejected — wrap in a local action. |
32
+ | `schedule` | Yes | | Five-field cron (minute granularity). Sub-minute schedules rejected. |
33
+ | `timeout` | Yes | | Hard timeout in seconds. Range `(0, 3600]`. **No implicit default.** `timeout > 300` requires `class: "long_running"`. |
34
+ | `class` | No | `"standard"` | `"standard"` (≤ 300 s) or `"long_running"` (≤ 3600 s). Explicit opt-in for heavy jobs. |
35
+ | `concurrency` | No | `1` | Max parallel runs `[1, 5]`. `1` = single-instance. |
36
+ | `idempotencyKey` | No | | Template like `"{job_cd}:{run.month}"`. Successful run with same key blocks re-dispatch. |
37
+ | `timeZone` | No | tenant tz | IANA name. Falls back to `auth.tenant.time_zone`. |
38
+ | `params` | No | `{}` | Static parameters passed to the action as `__params`. For runtime values, use folder-scoped module settings. |
39
+ | `enabled` | No | `true` | `false` fully disables — no scheduler, no manual trigger. |
40
+ | `paused` | No | `false` | Soft pause. Scheduler skips, but "Run now" still works. |
41
+ | `description` | No | | Human-readable description. |
42
+
43
+ **Hard cap: 50 jobs per module.**
44
+
45
+ ## Cron — five fields
46
+
47
+ ```
48
+ minute hour day-of-month month day-of-week
49
+
50
+ 0 2 * * * # daily at 02:00 in the job's tz
51
+ */15 * * * * # every 15 minutes
52
+ 0 9 * * 1-5 # 09:00 weekdays
53
+ 0 0 1 * * # midnight on the first of each month
54
+ ```
55
+
56
+ ## Writing the action
57
+
58
+ Scheduled actions run with **no record bound**. Specifically:
59
+
60
+ - `user_id = 0` (system user sentinel)
61
+ - No `folder_id` — query / insert against the tenant DB directly
62
+ - `notify()` writes to inbox only (no live SSE in Phase 1)
63
+ - `sendEmail()` raw mode only in Phase 1
64
+
65
+ **Record context is forbidden.** The install pipeline rejects any action whose compiled DSL references `[field]` or `for x in records { … }` — those compile to `__r.` / `__records.` which would `ReferenceError` at fire time. Refactor to operate via `query()` / `insert()`:
66
+
67
+ ```
68
+ execute:
69
+ let cutoff = now() - days(30)
70
+ let drafts = query("SELECT invoice_id FROM invoice WHERE status = 'draft' AND created_date < @cutoff",
71
+ { cutoff: cutoff })
72
+ for d in drafts {
73
+ callAction('archive_invoice', { invoice_id: d.invoice_id })
74
+ }
75
+ ```
76
+
77
+ Hide the action from the UI with `canExecute: false` — the scheduler bypasses the check:
78
+
79
+ ```
80
+ canExecute:
81
+ false
82
+
83
+ execute:
84
+ // cron-only logic
85
+ ```
86
+
87
+ ## Reference example (the `chore` module)
88
+
89
+ **`logic/jobs.json`**
90
+ ```json
91
+ {
92
+ "jobs": [
93
+ {
94
+ "code": "tick",
95
+ "description": "Smoke test — fires every minute.",
96
+ "action": "log_tick",
97
+ "schedule": "* * * * *",
98
+ "timeout": 30
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ **`ui/actions.json`**
105
+ ```json
106
+ {
107
+ "log_tick": {
108
+ "description": "Insert a chore_log row marking a scheduler tick",
109
+ "label": "Log Tick",
110
+ "icon": "bi-clock-history",
111
+ "entityCode": "chore_log",
112
+ "executionMode": "single",
113
+ "script": "log_tick",
114
+ "isTransacted": true,
115
+ "orderNum": 10
116
+ }
117
+ }
118
+ ```
119
+
120
+ **`logic/actions/log_tick.dsl`**
121
+ ```
122
+ execute:
123
+ insert('chore_log', {
124
+ ran_at: now(),
125
+ message: 'Scheduler tick',
126
+ source: 'cron'
127
+ })
128
+ ```
129
+
130
+ Full source: [`modules/chore/`](../../../modules/chore/).
131
+
132
+ ## Common mistakes
133
+
134
+ - **Cross-module action reference.** A job in `crm` cannot reference an action in `fin`. Declare a thin `crm.run_fin_thing` action in your own module and bind the job to that.
135
+ - **Record-bound action.** Any `[field]` or `for x in records` in the DSL fails install — the action has no record at fire time. Refactor to `query()`/`insert()`.
136
+ - **Missing `timeout`.** Required, no default. Pick 30 s if you don't know; bump when you have evidence.
137
+ - **`timeout > 300` without `class: "long_running"`.** Install fails. Heavy jobs are an explicit opt-in.
138
+ - **Sub-minute cron.** The scheduler ticks every 60 s. Anything finer is dishonest — declare a minute cron.
139
+ - **Renaming `code` between versions.** Treats it as a new job and leaves the old `scheduled_job` row orphaned until uninstall (actually reaped on upgrade — but you lose `job_run` history). Keep `code` stable.
140
+ - **"Schedule this 5 minutes from now" pattern.** There is no per-call `schedule()`. Use a cron-scan job that watches a queue table — see [memory: cron-scan pattern](../../../docs/business-logic/jobs.md#when-to-use-a-job-vs-a-trigger).
141
+ - **Confusing `enabled` and `paused`.** `enabled: false` → "Run now" no-ops. `paused: true` → cron skips but "Run now" works. Use `paused` for incident response; use `enabled: false` when you mean "turn this off entirely".
142
+
143
+ ## Reference
144
+
145
+ - Full developer guide: [`docs/business-logic/jobs.md`](../../../docs/business-logic/jobs.md)
146
+ - JSON Schema: [`docs/schemas/jobs.schema.json`](../../../docs/schemas/jobs.schema.json)
147
+ - Reference module: [`modules/chore/`](../../../modules/chore/)
148
+ - Source (registrar): `server/src/dForge.Admin/Services/ModuleInstall/JobRegistrar.cs`
149
+ - Source (scheduler worker): `server/src/dForge.Scheduler/SchedulerWorker.cs`
@@ -0,0 +1,123 @@
1
+ # Module Manifest Reference
2
+
3
+ Every dForge module has a `manifest.json` at its root. This file declares the module's identity, version, dependencies, and the list of files that make up the package.
4
+
5
+ ## Minimal example
6
+
7
+ ```json
8
+ {
9
+ "packageFormat": 1,
10
+ "moduleId": "10000000-0000-0000-0000-000000000001",
11
+ "code": "my_module",
12
+ "version": "0.1.0",
13
+ "dbSchemaVersion": "0.0.1",
14
+ "displayName": "My Module",
15
+ "description": "A module that does a thing.",
16
+ "author": { "name": "Your Name" },
17
+ "license": "MIT",
18
+ "category": "other",
19
+ "tags": ["tag1", "tag2"],
20
+ "icon": "bi-box",
21
+ "dependencies": {
22
+ "admin": ">=0.0.1"
23
+ },
24
+ "created": "2026-04-08",
25
+ "updated": "2026-04-08",
26
+ "entities": {
27
+ "my_entity": "./entities/my_entity.json"
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Required fields
33
+
34
+ | Field | Type | Description |
35
+ |---|---|---|
36
+ | `packageFormat` | number | Package format version. Always `1` for now. |
37
+ | `moduleId` | UUID | Globally unique module identifier. Generate a fresh UUID for new modules; never reuse. |
38
+ | `code` | string | Short identifier, lowercase, letters+digits+underscores. **Becomes the DB schema name.** Cannot be changed after install. |
39
+ | `version` | semver | Module version. Bump on every release. |
40
+ | `dbSchemaVersion` | semver | Schema version. Bump when the DB schema changes (column add/remove, new entity, etc.). |
41
+ | `displayName` | string | Human-readable name shown in the UI and marketplace. |
42
+ | `description` | string | One-line summary of what the module does. |
43
+
44
+ ## Optional but recommended
45
+
46
+ | Field | Type | Description |
47
+ |---|---|---|
48
+ | `author` | object | `{name, email?, url?}` |
49
+ | `license` | string | SPDX identifier (e.g. `"MIT"`, `"Apache-2.0"`) |
50
+ | `category` | string | Marketplace category slug (e.g. `"sales"`, `"hr"`, `"other"`) |
51
+ | `tags` | string[] | Search tags |
52
+ | `icon` | string | Bootstrap icon name (e.g. `"bi-briefcase"`) or similar |
53
+ | `dependencies` | object | Map of `module_code → semver range` |
54
+ | `created`, `updated` | ISO date | Timestamps |
55
+
56
+ ## Content declarations
57
+
58
+ The manifest lists every file in the package by logical key. The installer reads each path relative to the manifest.
59
+
60
+ | Key | Type | Shape |
61
+ |---|---|---|
62
+ | `entities` | object | `{ "entity_code": "./entities/entity.json" }` |
63
+ | `entityViews` | object | `{ "view_name": "./ui/entity_views/view.json" }` (optional) |
64
+ | `dataViews` | string | path to `./ui/data_views.json` (or object with per-view files) |
65
+ | `menus` | string | path to `./ui/menus.json` |
66
+ | `actions` | object | Actions defined in `./ui/actions.json`, DSL in `./logic/actions/*.dsl` |
67
+ | `reports` | object | `{ "report_code": "./ui/reports/report.json" }` |
68
+ | `settings` | string | path to `./settings.json` |
69
+ | `security` | object | `{ "roles": "./security/roles.json", "folders": "./ui/folders.json" }` |
70
+ | `seedData` | array | List of paths to seed files, in install order |
71
+ | `supportedLocales` | string[] | IETF locale tags that the module ships translations for, e.g. `["de-DE", "uk-UA"]`. Files are **auto-discovered** at `./translations/{locale}.json` — there is no per-file manifest entry. English (`en`/`en-*`) is the default and must not be listed. |
72
+ | `printTemplates` | object | `{ "template_code": "./print_templates/template.scriban" }` |
73
+ | `webhooks` | string | path to `./webhooks.json` |
74
+
75
+ ## Dependencies
76
+
77
+ Dependencies are a map of module codes to semver ranges. The installer checks that all listed dependencies are installed with compatible versions before installing your module.
78
+
79
+ ```json
80
+ "dependencies": {
81
+ "admin": ">=0.0.1",
82
+ "fin": ">=0.1.0 <0.2.0"
83
+ }
84
+ ```
85
+
86
+ **Always depend on `admin`** unless you have a very unusual module — admin provides the user/role system every other module needs.
87
+
88
+ **For bridge modules** (`crm-fin`, `wms-fin`, etc.), depend on both sides:
89
+
90
+ ```json
91
+ "dependencies": {
92
+ "admin": ">=0.0.1",
93
+ "crm": ">=0.1.0",
94
+ "fin": ">=0.1.0"
95
+ }
96
+ ```
97
+
98
+ ## Extension entities
99
+
100
+ Modules can extend entities owned by other modules. Declare these in `entities` with dotted keys:
101
+
102
+ ```json
103
+ "entities": {
104
+ "my_extra_entity": "./entities/my_extra_entity.json",
105
+ "fin.invoice": "./entities/fin.invoice.json" // extends fin's invoice
106
+ }
107
+ ```
108
+
109
+ The extension file has `"extends": "fin.invoice"` inside. See MODULE_CONVENTIONS.md for details.
110
+
111
+ ## What NOT to put in the manifest
112
+
113
+ - **Do not** add a `translations` key (e.g. `"translations": { "en-US": "..." }`). There is **no** such manifest field — translation files are auto-discovered at `./translations/{locale}.json`, and non-English locales are declared in `supportedLocales` (English is never listed). The manifest schema is `additionalProperties: false`, so a stray `translations` key fails install.
114
+ - **Do not** put entity definitions inline. Always reference external files.
115
+ - **Do not** list sample or test files — only content that ships with the module.
116
+ - **Do not** include `system: true` unless this is a dForge platform module (`admin`, `metadata`). Regular modules omit it (defaults to `false`).
117
+ - **Do not** hardcode absolute paths. Everything is relative to the manifest.
118
+
119
+ ## Versioning notes
120
+
121
+ - Bump `version` on every release (bug fix, feature add, etc.).
122
+ - Bump `dbSchemaVersion` **only** when the DB schema changes. If you added an action but no new columns, `version` goes up but `dbSchemaVersion` stays the same.
123
+ - The installer uses `dbSchemaVersion` to decide whether to run migrations.
@@ -0,0 +1,164 @@
1
+ # Menus Reference
2
+
3
+ Menus define the module's navigation tree. Shown in the sidebar when the module is installed.
4
+
5
+ Lives in: `ui/menus.json`
6
+
7
+ ## Structure — root wrapper with `items`, then nested dictionaries with `children`
8
+
9
+ **The real format** (from all reference modules — CRM, HR, Fin, WMS):
10
+
11
+ ```json
12
+ {
13
+ "todo_menu": {
14
+ "label": "Todos",
15
+ "items": {
16
+ "lists": {
17
+ "orderNum": 1,
18
+ "label": "Lists",
19
+ "icon": "list-ul",
20
+ "children": {
21
+ "all_lists": {
22
+ "itemType": "V",
23
+ "dataViewCode": "todo_list_grid",
24
+ "orderNum": 1,
25
+ "label": "All Lists",
26
+ "icon": "list-ul"
27
+ }
28
+ }
29
+ },
30
+ "items": {
31
+ "orderNum": 2,
32
+ "label": "Items",
33
+ "icon": "check2-square",
34
+ "children": {
35
+ "all_items": {
36
+ "itemType": "V",
37
+ "dataViewCode": "todo_item_grid",
38
+ "orderNum": 1,
39
+ "label": "All Items",
40
+ "icon": "check-square"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ Structure: **root key** → `label` + `items` → **section nodes** (with `children`) → **leaf nodes** (with `itemType` + data view/report code).
50
+
51
+ ## Item types (leaf nodes only)
52
+
53
+ | `itemType` | Description | Required field |
54
+ |---|---|---|
55
+ | `"V"` | Data view | `dataViewCode` |
56
+ | `"R"` | Report | `reportCode` |
57
+ | `"D"` | Dashboard | `dashboardCode` |
58
+ | `"A"` | Action | `actionCode` |
59
+
60
+ **Section nodes** (with `children`) **omit `itemType`**. Only leaf items have it.
61
+
62
+ ## Icons
63
+
64
+ In `ui/menus.json`, icons use Bootstrap icon names **without the `bi-` prefix**:
65
+
66
+ - `"graph-up-arrow"` (not `"bi-graph-up-arrow"`)
67
+ - `"people"` (not `"bi-people"`)
68
+ - `"briefcase"` (not `"bi-briefcase"`)
69
+ - `"receipt"`, `"building"`, `"calendar-event"`, `"box-seam"`, etc.
70
+
71
+ **Note:** This no-prefix rule applies to module menu definitions. In other contexts, such as action registration, the icon value may need the full Bootstrap class name **with** the `bi-` prefix (for example, `"bi-graph-up-arrow"`). Always follow the format required by the specific configuration you are editing.
72
+ See https://icons.getbootstrap.com/ for the full catalog. Common choices:
73
+
74
+ - `people` — users/contacts
75
+ - `briefcase` — work/business
76
+ - `building` — companies/accounts
77
+ - `cart` — sales/orders
78
+ - `graph-up` — reports/analytics
79
+ - `gear` — settings
80
+ - `receipt` — invoices/quotes
81
+ - `calendar-event` — activities/dates
82
+ - `box-seam` — products/items
83
+ - `kanban` — pipeline/board views
84
+
85
+ ## Real example from CRM module
86
+
87
+ ```json
88
+ {
89
+ "crm_menu": {
90
+ "label": "Sales CRM",
91
+ "items": {
92
+ "sales": {
93
+ "orderNum": 1,
94
+ "label": "Sales",
95
+ "icon": "graph-up-arrow",
96
+ "children": {
97
+ "leads": {
98
+ "itemType": "V",
99
+ "dataViewCode": "leads",
100
+ "orderNum": 1,
101
+ "label": "Leads",
102
+ "icon": "person-plus"
103
+ },
104
+ "opportunities": {
105
+ "itemType": "V",
106
+ "dataViewCode": "opportunities",
107
+ "orderNum": 3,
108
+ "label": "Opportunities",
109
+ "icon": "trophy"
110
+ },
111
+ "sales_pipeline_report": {
112
+ "itemType": "R",
113
+ "reportCode": "sales_pipeline",
114
+ "orderNum": 6,
115
+ "label": "Sales Pipeline Report",
116
+ "icon": "file-earmark-bar-graph"
117
+ }
118
+ }
119
+ },
120
+ "customers": {
121
+ "orderNum": 3,
122
+ "label": "Customers",
123
+ "icon": "people",
124
+ "children": {
125
+ "accounts": {
126
+ "itemType": "V",
127
+ "dataViewCode": "accounts",
128
+ "orderNum": 1,
129
+ "label": "Accounts",
130
+ "icon": "building"
131
+ },
132
+ "contacts": {
133
+ "itemType": "V",
134
+ "dataViewCode": "contacts",
135
+ "orderNum": 3,
136
+ "label": "Contacts",
137
+ "icon": "person-lines-fill"
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Required rules
147
+
148
+ 1. **Root wrapper key** (e.g. `"crm_menu"`) with a `label` and `items` property.
149
+ 2. **`items`** contains the section nodes, **not** direct leaf items at the top level.
150
+ 3. **`dataViewCode`**, not `viewCode`. The wrong name silently fails.
151
+ 4. **Section nodes have `children`**, leaf nodes have `itemType` + code.
152
+ 5. **`orderNum` controls display order** within a level.
153
+ 6. **`label` is required** on every node.
154
+ 7. **`icon` is optional** but recommended. Use Bootstrap icon names **without** `bi-` prefix.
155
+
156
+ ## Common mistakes
157
+
158
+ - Skipping the root wrapper (no `{root_key: {label, items}}`) — **wrong**. All reference modules use this structure.
159
+ - Using arrays like `"children": [...]` — **wrong**. Must be a dictionary.
160
+ - Using `viewCode` instead of `dataViewCode` — **wrong**.
161
+ - Putting `itemType: "V"` on a section node with `children` — **wrong**. Omit `itemType` on sections.
162
+ - Using `"bi-people"` as the icon — **wrong in module menus**. Use `"people"` (no prefix).
163
+ - Forgetting `label` — **required**.
164
+ - Putting leaf items directly inside `items` without a section wrapper — **technically works** but doesn't match conventions.