@dforge-core/dforge-mcp 0.1.0-rc.12 → 0.1.0-rc.13
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 +132 -0
- package/README.md +75 -21
- package/dist/server.js +2405 -272
- package/docs/creating-modules.md +7 -3
- package/package.json +11 -6
- package/resources/docs/conventions.md +12 -9
- package/resources/schemas/entity.schema.json +4 -0
- package/resources/schemas/reports.schema.json +4 -0
- package/skills/dforge-mcp-author/SKILL.md +220 -91
- 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 +192 -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 +182 -0
- package/skills/dforge-mcp-author/scripts/xlsx_to_model.py +198 -0
|
@@ -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.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Number Sequences Reference
|
|
2
|
+
|
|
3
|
+
dForge has built-in **automatic document numbering**. Declare a sequence on an entity, and any INSERT that leaves the target column empty gets the next value assigned automatically — no DSL, no manual counter, no race conditions.
|
|
4
|
+
|
|
5
|
+
## Declaring on an entity
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"description": "Invoices",
|
|
10
|
+
"dbObject": "invoice",
|
|
11
|
+
"toString": "{number}",
|
|
12
|
+
"traits": ["identity", "audit"],
|
|
13
|
+
"numberSequence": {
|
|
14
|
+
"column": "number",
|
|
15
|
+
"pattern": "INV-{yyyy}-{seq:4}",
|
|
16
|
+
"resetPeriod": "year"
|
|
17
|
+
},
|
|
18
|
+
"fields": {
|
|
19
|
+
"number": {
|
|
20
|
+
"dbDatatype": "varchar",
|
|
21
|
+
"fieldTypeCd": "text",
|
|
22
|
+
"flags": "VEM",
|
|
23
|
+
"maxLen": 50,
|
|
24
|
+
"orderNum": 10,
|
|
25
|
+
"description": "Invoice Number"
|
|
26
|
+
},
|
|
27
|
+
/* ... */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The platform:
|
|
33
|
+
|
|
34
|
+
1. On INSERT, checks if the `number` column is empty.
|
|
35
|
+
2. If empty, atomically increments the counter and formats the value.
|
|
36
|
+
3. Writes it into the column before the row is committed.
|
|
37
|
+
|
|
38
|
+
No code needed on your side. No DSL. No race conditions (the counter uses a database-level atomic operation).
|
|
39
|
+
|
|
40
|
+
## Pattern placeholders
|
|
41
|
+
|
|
42
|
+
| Placeholder | Meaning | Example |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `{yyyy}` | 4-digit year | `2026` |
|
|
45
|
+
| `{yy}` | 2-digit year | `26` |
|
|
46
|
+
| `{mm}` | 2-digit month | `04` |
|
|
47
|
+
| `{dd}` | 2-digit day | `08` |
|
|
48
|
+
| `{seq:N}` | Zero-padded counter with N digits | `{seq:3}` → `001`, `002`, ... |
|
|
49
|
+
|
|
50
|
+
You can combine them freely:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
"INV-{yyyy}-{seq:4}" → "INV-2026-0001"
|
|
54
|
+
"Q{yyyy}{mm}-{seq:3}" → "Q202604-001"
|
|
55
|
+
"{dd}/{mm}/{yyyy}-{seq:2}" → "08/04/2026-01"
|
|
56
|
+
"PO-{seq:6}" → "PO-000001"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Reset periods
|
|
60
|
+
|
|
61
|
+
`resetPeriod` controls when the counter resets to 1:
|
|
62
|
+
|
|
63
|
+
| Value | Resets |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `"never"` | Counter never resets (monotonic forever) |
|
|
66
|
+
| `"year"` | Counter resets on Jan 1 |
|
|
67
|
+
| `"month"` | Counter resets on the 1st of each month |
|
|
68
|
+
| `"day"` | Counter resets daily |
|
|
69
|
+
|
|
70
|
+
If the pattern uses `{yyyy}` but `resetPeriod` is `"never"`, you get the same number each year — probably not what you want. Match the reset period to the pattern.
|
|
71
|
+
|
|
72
|
+
## Prefix resolution via settings
|
|
73
|
+
|
|
74
|
+
For multi-tenant or multi-folder setups, prefixes can come from module settings instead of being hardcoded. Declare a setting:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
// settings.json
|
|
78
|
+
{
|
|
79
|
+
"invoice_prefix": {
|
|
80
|
+
"fieldTypeCd": "text",
|
|
81
|
+
"defaultValue": "INV",
|
|
82
|
+
"label": "Invoice Prefix"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
And reference it in the pattern:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
"pattern": "$[invoice_prefix]-{yyyy}-{seq:4}"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Folder-scoped settings mean different folders can have different prefixes (e.g. `US-INV-2026-0001` in the US folder, `EU-INV-2026-0001` in the EU folder).
|
|
94
|
+
|
|
95
|
+
## Cross-module sequences
|
|
96
|
+
|
|
97
|
+
Sequences can be called from other modules via `nextNumber('<module>.<entity>')` in DSL actions:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
execute:
|
|
101
|
+
invoice_num = nextNumber("fin.invoice")
|
|
102
|
+
notify("Next invoice number: " + invoice_num)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
But most of the time, you don't need this — the automatic INSERT behaviour is enough.
|
|
106
|
+
|
|
107
|
+
## Storage
|
|
108
|
+
|
|
109
|
+
Counters are stored in `dForge.number_sequence` (definitions) and `dForge.number_sequence_counter` (atomic values). Don't touch these tables directly.
|
|
110
|
+
|
|
111
|
+
## Common mistakes
|
|
112
|
+
|
|
113
|
+
- Declaring a number column without a `numberSequence` and manually counting in a DSL — **unnecessary**. Use the platform.
|
|
114
|
+
- Using `{seq}` without a width — the counter has no padding. Use `{seq:N}`.
|
|
115
|
+
- Mismatching `resetPeriod` and placeholders — e.g. `resetPeriod: "month"` with a pattern like `{yyyy}-{seq:4}` (no month) means Jan and Feb start at 1 producing duplicate numbers.
|
|
116
|
+
- Trying to set the number column manually on INSERT — it will be overwritten (unless you provide a non-null value, in which case the sequence respects it).
|
|
117
|
+
- Forgetting `numberSequence` declaration and expecting it to "just work" — needs to be declared per entity.
|