@dforge-core/dforge-mcp 0.2.46-next → 0.2.48
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 +68 -1
- package/dist/server.js +71 -3
- package/package.json +47 -48
- package/resources/docs/dsl-reference.md +1 -1
- package/resources/schemas/data-views.schema.json +50 -0
- package/skills/dforge-1c-import/SKILL.md +3 -2
- package/skills/dforge-1c-import/references/environment.md +2 -1
- package/skills/dforge-1c-import/references/loading.md +5 -2
- package/skills/dforge-1c-import/references/measurements.md +1 -1
- package/skills/dforge-1c-import/scripts/README.md +88 -7
- package/skills/dforge-1c-import/scripts/lib/__pycache__/attrs.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/delta.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/meta.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/migrate.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/paths.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/posted.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/posting.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/seedstate.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/store.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/totals.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/__pycache__/transactions.cpython-313.pyc +0 -0
- package/skills/dforge-1c-import/scripts/lib/attrs.py +2 -2
- package/skills/dforge-1c-import/scripts/lib/changes.py +326 -0
- package/skills/dforge-1c-import/scripts/lib/delta.py +109 -52
- package/skills/dforge-1c-import/scripts/lib/docusage.py +5 -4
- package/skills/dforge-1c-import/scripts/lib/dump1c.py +249 -92
- package/skills/dforge-1c-import/scripts/lib/external.py +4 -3
- package/skills/dforge-1c-import/scripts/lib/meta.py +12 -2
- package/skills/dforge-1c-import/scripts/lib/migrate.py +93 -74
- package/skills/dforge-1c-import/scripts/lib/onecd.py +269 -0
- package/skills/dforge-1c-import/scripts/lib/paths.py +21 -0
- package/skills/dforge-1c-import/scripts/lib/peek.py +3 -5
- package/skills/dforge-1c-import/scripts/lib/recorded.py +11 -11
- package/skills/dforge-1c-import/scripts/lib/recorders.py +9 -12
- package/skills/dforge-1c-import/scripts/lib/seedstate.py +21 -16
- package/skills/dforge-1c-import/scripts/lib/source_file.py +227 -0
- package/skills/dforge-1c-import/scripts/lib/source_mssql.py +158 -0
- package/skills/dforge-1c-import/scripts/lib/store.py +126 -0
- package/skills/dforge-1c-import/scripts/lib/totals.py +2 -4
- package/skills/dforge-1c-import/scripts/lib/transactions.py +28 -0
- package/skills/dforge-1c-import/scripts/project-template/.env.example +24 -0
- package/skills/dforge-1c-import/scripts/project-template/sources.py +10 -0
- package/skills/dforge-mcp-author/references/action-dsl.md +49 -46
- package/skills/dforge-mcp-author/references/data-views.md +38 -2
- package/skills/dforge-mcp-author/references/formulas.md +38 -6
- package/skills/dforge-mcp-author/references/jobs.md +1 -1
- package/skills/dforge-mcp-author/references/print-templates.md +30 -5
- package/skills/dforge-mcp-author/references/queries.md +17 -2
- package/skills/dforge-mcp-author/references/registers.md +3 -0
- package/skills/dforge-mcp-author/references/reports.md +1 -1
- package/skills/dforge-mcp-author/references/security.md +2 -0
- package/skills/dforge-mcp-author/references/validation-checklist.md +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -6,16 +6,83 @@ the release workflow, so committed `package.json` versions are placeholders.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
**1C migration readiness and regressions.** `delta --apply` now reports per-module
|
|
10
|
+
`ready` / `partial` status and exits 2 if state, totals or imported markers remain
|
|
11
|
+
unsettled; retained retry markers are settled on a later run even without new rows.
|
|
12
|
+
Dry runs do not certify readiness. `pnpm test:1c` covers conversion precision,
|
|
13
|
+
reference/date semantics, failed settlement/retry and the per-entity reload restart
|
|
14
|
+
contract using synthetic fixtures and mocked database boundaries. The loader remains
|
|
15
|
+
a non-atomic migration-target reload; its restart procedure is documented.
|
|
16
|
+
A new CI job (`.github/workflows/onec-tests.yml`) runs it on changes to the 1C scripts or tests. A module's retry
|
|
17
|
+
markers (`df1c.unsettled`) are now cleared only for the entities it settled, not for the whole module.
|
|
18
|
+
Placeholder, balance-state and yearly totals writers roll back failed transactions before
|
|
19
|
+
restoring autocommit; settlement continues to the next module only after connection
|
|
20
|
+
recovery, and failed recovery stops the command. Strict transaction-state tests cover
|
|
21
|
+
original-error preservation, partial yearly commits, continuation and unrecoverable connections.
|
|
22
|
+
Jobs authoring/schema guidance now describes `idempotencyKey` as reserved and not enforced.
|
|
23
|
+
|
|
24
|
+
**Action scripts bypass entity rights.** `action-dsl.md` and `security.md` now state that `insert()` / `update()` /
|
|
25
|
+
`delete()` / `query()` in an action script check neither entity rights nor column security nor the folder's row filter
|
|
26
|
+
(core #1580): only `E` on the action is checked, so treat `E` as write access to everything the script touches.
|
|
27
|
+
|
|
28
|
+
**`canExecute` is enforced in full on the server.** `action.execute` now computes arithmetic, `IN`, `BETWEEN`, formula
|
|
29
|
+
functions, a bare boolean column, `[ref].[col]` navigation and extension columns in SQL, instead of letting them through, and
|
|
30
|
+
`TODAY()` names the requester's day. A condition it cannot compute refuses the action. Install rejects `$[setting]`,
|
|
31
|
+
`SUM` / `COUNT` / `AVG` and unknown function names in `canExecute`, so a module that used them stops installing: read
|
|
32
|
+
settings and count children in `execute:` instead (a count belongs in a `G` column). `references/action-dsl.md`
|
|
33
|
+
(`canExecute:`), `formulas.md` and `validation-checklist.md` are rewritten to match. The button still evaluates over the
|
|
34
|
+
grid row, which has no extension columns and throws on `[ref].[col]`, so keep both out of a gate that must also disable the
|
|
35
|
+
button. **Needs platform 1.28.12.**
|
|
36
|
+
|
|
37
|
+
**Registers: `postAction` / `unpostAction`.** `references/registers.md` documents the register-binding column params that make a direct
|
|
38
|
+
`data.insert` / `data.update` flipping the state flag need `E` on the post or unpost action, and the "May edit is not may
|
|
39
|
+
post" pitfall: declare both and grant `E` only to the roles that may post. Each direction is checked only when its own key
|
|
40
|
+
is set; actions and DSL `update()` are not re-checked. A binding with neither gets a pack / validate warning, silenced by
|
|
41
|
+
`"editorMayPost": true`. **Needs a platform release after 1.28.12 (not in it).**
|
|
42
|
+
|
|
43
|
+
## 0.2.46 — 2026-10-29
|
|
44
|
+
|
|
45
|
+
Requires `@dforge-core/dforge-cli` 0.2.37 and `@dforge-core/metadata` 0.0.47; the vendored `dforge://schema/data-views` now accepts the grouping keys.
|
|
46
|
+
|
|
47
|
+
**`SLICE()`: an information register's value on a date, per row.** A formula function for `F` columns —
|
|
48
|
+
`SLICE('module.register', 'resource', date, 'dimension', value, …)`, 1C's `СрезПоследних` — so a print form prints each
|
|
49
|
+
line's price on its document's date, read in the query that loads the lines. Database-only (the browser keeps the loaded
|
|
50
|
+
value), read with the user's rights on the register, refused in `G` columns; install checks the register, resource and
|
|
51
|
+
dimensions. `references/formulas.md` documents it, `print-templates.md` and `registers.md` point to it. Print responses
|
|
52
|
+
(`print.render`, `print.renderPdf`, DSL `printPdf()`) now carry `warnings` for what printed empty, and an upgrade deletes
|
|
53
|
+
the print templates a package no longer declares. **Needs a platform with the `SLICE()` release (CLI 0.2.37).**
|
|
54
|
+
|
|
55
|
+
**Grids and lists group by a column.** A `grid` or `list` view's `viewConfig` can ship a default grouping:
|
|
56
|
+
`groupByField`, `groupByOptions` (narrows the Group menu) and `collapsedGroups`, plus `inlineAdd` on a list.
|
|
57
|
+
`dforge_module_validate` checks them as the install does (core `ViewGroupingValidator`, #1556): each named column is a
|
|
58
|
+
dropdown, flags, reference or user column, `groupByOptions` includes `groupByField`, and `collapsedGroups` lists option
|
|
59
|
+
values of a valid `groupByField`. `references/data-views.md` documents the keys and the validation checklist lists the
|
|
60
|
+
rules. The vendored `dforge://schema/data-views` carries them from metadata 0.0.47. **Needs platform 1.28.11.**
|
|
61
|
+
|
|
9
62
|
**`dforge-1c-import`: porting a 1C infobase.** A fifth skill. It carries the decisions a 1C port turns on, each with the
|
|
10
63
|
measurement that settles it: a register's kind, where a posting takes each value, what to carry, physical columns through
|
|
11
64
|
DBNames, vendor against client. It adds `references/loading.md` (copying the rows: swapped uuid halves, the empty reference
|
|
12
65
|
and the empty date as values in register keys and periods, the year offset, enums by `_EnumOrder`, composite references,
|
|
13
66
|
register state, triggers, dangling references, verification) and `references/print-forms.md` (MXL templates, the print
|
|
14
67
|
procedure, `Строка`/`Формат` in SQL, reproducing 1C's slips, datasets as queries). `scripts/` ships the Python toolkit
|
|
15
|
-
from a real port: it
|
|
68
|
+
from a real port: it exports the infobase without 1C, generates and validates modules, copies and
|
|
16
69
|
reconciles the rows, checks postings and reads print templates, pointed at a port by `DF1C_ROOT` / `DF1C_PROJECT` and
|
|
17
70
|
started from `scripts/project-template/`. `install-skills` installs it with the other four.
|
|
18
71
|
|
|
72
|
+
The toolkit has two layers. `dump1c` is the only part that reads 1C. It reads either a server infobase on MS SQL or a
|
|
73
|
+
file infobase (`1Cv8.1CD`, formats 8.2.14 and 8.3.8, through a pure-Python reader), and writes one export to `out/`:
|
|
74
|
+
the configuration, DBNames, the schema, every table's rows as Parquet, and a manifest with the year offset and the
|
|
75
|
+
horizons. Everything else reads that export, with DuckDB for the rows (`store.py`). The export is the same whichever
|
|
76
|
+
kind of infobase it came from, so a project's `sources.QUERIES` are DuckDB SQL. **Breaking for an existing port:** a
|
|
77
|
+
query written in T-SQL (`[_Fld12]`, `TOP`, `0x01`, `CONVERT`) fails until it is rewritten (`"_Fld12"`, `LIMIT`,
|
|
78
|
+
`from_hex('01')`, `CAST`). `dump1c data --changes [--since <day>]` brings an MS SQL export up to date by reading
|
|
79
|
+
only what 1C changed. It rebuilds each table's file in place, so `delta` follows a cutover in minutes rather than
|
|
80
|
+
after a full export. It also catches accounting entries switched on or off without the document being written.
|
|
81
|
+
Register totals and document journals are left out of the export by default; name one, or pass `--derived`, to
|
|
82
|
+
export it. `delta` needs an MS SQL export: a file
|
|
83
|
+
infobase has no database-wide rowversion. `meta` now finds the configuration's root from `root` instead of a GUID
|
|
84
|
+
fixed to one configuration.
|
|
85
|
+
|
|
19
86
|
## 0.2.45 — 2026-10-25
|
|
20
87
|
|
|
21
88
|
**English is the base locale, and it lives in the definitions.** Install never reads
|
package/dist/server.js
CHANGED
|
@@ -31002,7 +31002,7 @@ var import_node_crypto = require("crypto");
|
|
|
31002
31002
|
var fs2 = __toESM(require("fs"));
|
|
31003
31003
|
var path2 = __toESM(require("path"));
|
|
31004
31004
|
|
|
31005
|
-
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.
|
|
31005
|
+
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.37/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
|
|
31006
31006
|
function buildManifest(opts, moduleId) {
|
|
31007
31007
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31008
31008
|
const manifest = {
|
|
@@ -31243,7 +31243,7 @@ function buildZedTasks() {
|
|
|
31243
31243
|
var fs = __toESM(require("fs"));
|
|
31244
31244
|
var path = __toESM(require("path"));
|
|
31245
31245
|
|
|
31246
|
-
// node_modules/.pnpm/@dforge-core+metadata@0.0.
|
|
31246
|
+
// node_modules/.pnpm/@dforge-core+metadata@0.0.47/node_modules/@dforge-core/metadata/dist/index.js
|
|
31247
31247
|
var AggType = {
|
|
31248
31248
|
Sum: "sum",
|
|
31249
31249
|
Count: "count",
|
|
@@ -34612,7 +34612,7 @@ var path10 = __toESM(require("path"));
|
|
|
34612
34612
|
var fs7 = __toESM(require("fs"));
|
|
34613
34613
|
var path9 = __toESM(require("path"));
|
|
34614
34614
|
|
|
34615
|
-
// node_modules/.pnpm/@dforge-core+metadata@0.0.
|
|
34615
|
+
// node_modules/.pnpm/@dforge-core+metadata@0.0.47/node_modules/@dforge-core/metadata/dist/dsl/index.js
|
|
34616
34616
|
var PUNCT3 = ["===", "!=="];
|
|
34617
34617
|
var PUNCT2 = [
|
|
34618
34618
|
"==",
|
|
@@ -38255,6 +38255,73 @@ function checkMenus(ctx) {
|
|
|
38255
38255
|
};
|
|
38256
38256
|
for (const [mcode, m] of Object.entries(menus)) walk(m, `menus \u2192 ${mcode}`);
|
|
38257
38257
|
}
|
|
38258
|
+
function checkViewGrouping(ctx) {
|
|
38259
|
+
const { err, entities, fieldDefsOf, views } = ctx;
|
|
38260
|
+
for (const [vcode, v] of Object.entries(views)) {
|
|
38261
|
+
const viewType = v.viewType ?? "grid";
|
|
38262
|
+
if (viewType !== "grid" && viewType !== "list") continue;
|
|
38263
|
+
const config2 = v.viewConfig;
|
|
38264
|
+
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) continue;
|
|
38265
|
+
const cfg = config2;
|
|
38266
|
+
const ent = v.dataSources?.[0]?.entityCode;
|
|
38267
|
+
if (!ent) continue;
|
|
38268
|
+
const defs = fieldDefsOf[ent];
|
|
38269
|
+
if (!defs || entities[ent]?.extends) continue;
|
|
38270
|
+
const where = `data_views \u2192 ${vcode}`;
|
|
38271
|
+
const resolve8 = (key, cd) => {
|
|
38272
|
+
const f = defs[cd];
|
|
38273
|
+
if (!f) err(where, `names '${cd}' in '${key}', which entity '${ent}' does not define`);
|
|
38274
|
+
else if (!isGroupable(f)) err(where, `names '${cd}' in '${key}', which is not a dropdown, reference or user column`);
|
|
38275
|
+
else return f;
|
|
38276
|
+
return void 0;
|
|
38277
|
+
};
|
|
38278
|
+
const readStrings = (key) => {
|
|
38279
|
+
const val = cfg[key];
|
|
38280
|
+
if (!Array.isArray(val)) return void 0;
|
|
38281
|
+
const items = [];
|
|
38282
|
+
for (const item of val) {
|
|
38283
|
+
if (typeof item === "string") items.push(item);
|
|
38284
|
+
else err(where, `has a non-string item in '${key}'`);
|
|
38285
|
+
}
|
|
38286
|
+
return items;
|
|
38287
|
+
};
|
|
38288
|
+
const groupBy = typeof cfg.groupByField === "string" ? cfg.groupByField : void 0;
|
|
38289
|
+
const groupField = groupBy !== void 0 ? resolve8("groupByField", groupBy) : void 0;
|
|
38290
|
+
const options = readStrings("groupByOptions");
|
|
38291
|
+
if (options) {
|
|
38292
|
+
for (const cd of options) resolve8("groupByOptions", cd);
|
|
38293
|
+
if (groupBy !== void 0 && !options.includes(groupBy)) {
|
|
38294
|
+
err(where, `has 'groupByOptions' without its 'groupByField' '${groupBy}'`);
|
|
38295
|
+
}
|
|
38296
|
+
}
|
|
38297
|
+
const collapsed = readStrings("collapsedGroups");
|
|
38298
|
+
if (collapsed) {
|
|
38299
|
+
if (!groupField) err(where, "sets 'collapsedGroups' without a valid 'groupByField'");
|
|
38300
|
+
else if (isDropdown(groupField)) {
|
|
38301
|
+
const values = optionValues(groupField);
|
|
38302
|
+
for (const val of collapsed.filter((c) => !values.has(c))) {
|
|
38303
|
+
err(where, `lists '${val}' in 'collapsedGroups', which is not an option of '${groupBy}'`);
|
|
38304
|
+
}
|
|
38305
|
+
}
|
|
38306
|
+
}
|
|
38307
|
+
}
|
|
38308
|
+
}
|
|
38309
|
+
function isDropdown(f) {
|
|
38310
|
+
return f.fieldTypeCd === "dropdown" || f.fieldTypeCd === "flags";
|
|
38311
|
+
}
|
|
38312
|
+
function isGroupable(f) {
|
|
38313
|
+
return isDropdown(f) || f.fieldTypeCd === "user" || f.columnType === "R";
|
|
38314
|
+
}
|
|
38315
|
+
function optionValues(f) {
|
|
38316
|
+
const result = /* @__PURE__ */ new Set();
|
|
38317
|
+
const options = f.params?.options;
|
|
38318
|
+
if (!Array.isArray(options)) return result;
|
|
38319
|
+
for (const item of options) {
|
|
38320
|
+
const val = item && typeof item === "object" ? item.value : item;
|
|
38321
|
+
if (typeof val === "string" || typeof val === "number") result.add(String(val));
|
|
38322
|
+
}
|
|
38323
|
+
return result;
|
|
38324
|
+
}
|
|
38258
38325
|
|
|
38259
38326
|
// src/tools/validate/security.ts
|
|
38260
38327
|
var fs14 = __toESM(require("fs"));
|
|
@@ -38525,6 +38592,7 @@ var CHECKS = [
|
|
|
38525
38592
|
checkEntityViews,
|
|
38526
38593
|
checkDataViews,
|
|
38527
38594
|
checkVisibleColumns,
|
|
38595
|
+
checkViewGrouping,
|
|
38528
38596
|
checkMenus,
|
|
38529
38597
|
checkRoleRights,
|
|
38530
38598
|
checkSelectCoverage,
|
package/package.json
CHANGED
|
@@ -1,49 +1,48 @@
|
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
2
|
+
"name": "@dforge-core/dforge-mcp",
|
|
3
|
+
"version": "0.2.48",
|
|
4
|
+
"description": "MCP server for dForge module authoring. Exposes 35 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
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dforge-core/dforge-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"dforge-mcp": "dist/server.js",
|
|
13
|
+
"dforge-install-skills": "scripts/install-skills.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"docs/",
|
|
18
|
+
"resources/",
|
|
19
|
+
"skills/",
|
|
20
|
+
"scripts/install-skills.mjs",
|
|
21
|
+
"README.md",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
24
|
+
"main": "dist/server.js",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@dforge-core/dforge-cli": "^0.2.37",
|
|
30
|
+
"@dforge-core/metadata": "^0.0.47",
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
+
"zod": "^4.4.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.2.0",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "^6.0.3",
|
|
38
|
+
"vitest": "^4.1.10"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"sync-schemas": "node scripts/vendor-schemas.cjs",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:1c": "python3 -m unittest discover -s test/onec -v",
|
|
46
|
+
"install-skills": "node scripts/install-skills.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -569,7 +569,7 @@ if (info.mimeType != 'application/pdf') { error('Receipt must be PDF') }
|
|
|
569
569
|
```
|
|
570
570
|
|
|
571
571
|
#### `printPdf(entityCd, key, templateCd)`
|
|
572
|
-
Render a print template to a PDF as the current user and return the archived file as `{ fileId, fileName, mimeType, size, checksum }
|
|
572
|
+
Render a print template to a PDF as the current user and return the archived file as `{ fileId, fileName, mimeType, size, checksum }`, plus `warnings` when a value printed empty because the user may not read it; attach it with `sendEmail()`. Pass a qualified entity code. Not in a scheduled job or a background action; max 10 per run.
|
|
573
573
|
|
|
574
574
|
```dsl
|
|
575
575
|
execute:
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"additionalProperties": false,
|
|
54
54
|
"allOf": [
|
|
55
|
+
{ "$ref": "#/$defs/rules/grid" },
|
|
55
56
|
{ "$ref": "#/$defs/rules/kanban" },
|
|
56
57
|
{ "$ref": "#/$defs/rules/calendar" },
|
|
57
58
|
{ "$ref": "#/$defs/rules/tree-grid" },
|
|
@@ -62,6 +63,17 @@
|
|
|
62
63
|
},
|
|
63
64
|
|
|
64
65
|
"rules": {
|
|
66
|
+
"grid": {
|
|
67
|
+
"if": {
|
|
68
|
+
"properties": { "viewType": { "const": "grid" } },
|
|
69
|
+
"required": ["viewType"]
|
|
70
|
+
},
|
|
71
|
+
"then": {
|
|
72
|
+
"properties": {
|
|
73
|
+
"viewConfig": { "$ref": "#/$defs/gridViewConfig" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
65
77
|
"kanban": {
|
|
66
78
|
"if": {
|
|
67
79
|
"properties": { "viewType": { "const": "kanban" } },
|
|
@@ -131,6 +143,26 @@
|
|
|
131
143
|
}
|
|
132
144
|
},
|
|
133
145
|
|
|
146
|
+
"gridViewConfig": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"groupByField": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"description": "Dropdown, reference or user column the rows are grouped by under header rows"
|
|
152
|
+
},
|
|
153
|
+
"groupByOptions": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"items": { "type": "string" },
|
|
156
|
+
"description": "Columns the Group by menu offers; must include groupByField"
|
|
157
|
+
},
|
|
158
|
+
"collapsedGroups": {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"items": { "type": "string" },
|
|
161
|
+
"description": "Option values of groupByField whose groups start collapsed"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"additionalProperties": false
|
|
165
|
+
},
|
|
134
166
|
"kanbanViewConfig": {
|
|
135
167
|
"type": "object",
|
|
136
168
|
"required": ["groupByField"],
|
|
@@ -195,6 +227,24 @@
|
|
|
195
227
|
"type": "array",
|
|
196
228
|
"items": { "$ref": "#/$defs/listLevelConfig" },
|
|
197
229
|
"description": "Hierarchical child levels for expandable list rows"
|
|
230
|
+
},
|
|
231
|
+
"groupByField": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"description": "Dropdown, reference or user column the rows are grouped by"
|
|
234
|
+
},
|
|
235
|
+
"groupByOptions": {
|
|
236
|
+
"type": "array",
|
|
237
|
+
"items": { "type": "string" },
|
|
238
|
+
"description": "Columns the user may regroup by; must include groupByField"
|
|
239
|
+
},
|
|
240
|
+
"collapsedGroups": {
|
|
241
|
+
"type": "array",
|
|
242
|
+
"items": { "type": "string" },
|
|
243
|
+
"description": "Option values of groupByField whose groups start collapsed"
|
|
244
|
+
},
|
|
245
|
+
"inlineAdd": {
|
|
246
|
+
"type": "boolean",
|
|
247
|
+
"description": "Show an add row in each group (at the top when ungrouped)"
|
|
198
248
|
}
|
|
199
249
|
},
|
|
200
250
|
"additionalProperties": false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dforge-1c-import
|
|
3
|
-
description: How to port a 1C:Enterprise 8 infobase (УТП, УПП, УТ, БП or a configuration built on them) to dForge modules — how to decide a register's kind, where a posting takes each value from, what to carry and what to drop, and how to check every one of those decisions against the data instead of guessing. Use this whenever the work involves a 1C infobase, a 1C configuration, MS SQL tables named _Reference/_Document/_AccumRg/_InfoRg/_AccRg, регистры, проведение, ОбработкаПроведения, Движения, партионный учёт, or migrating a client off 1C onto dForge — including when the user only says "the client's old system" and the tables turn out to be 1C's, and including when they ask only for a piece of it such as "which register kind should this be" or "why does this document write more rows than 1C does". Also covers copying the rows (keys, dates, enums, composite references, empty values, reconciliation) and porting 1C print forms (MXL templates, Строка/Формат, НСтр) to dForge print templates.
|
|
3
|
+
description: How to port a 1C:Enterprise 8 infobase (УТП, УПП, УТ, БП or a configuration built on them) to dForge modules — how to decide a register's kind, where a posting takes each value from, what to carry and what to drop, and how to check every one of those decisions against the data instead of guessing. Use this whenever the work involves a 1C infobase, a 1C configuration, a file infobase (1Cv8.1CD), MS SQL tables named _Reference/_Document/_AccumRg/_InfoRg/_AccRg, регистры, проведение, ОбработкаПроведения, Движения, партионный учёт, or migrating a client off 1C onto dForge — including when the user only says "the client's old system" and the tables turn out to be 1C's, and including when they ask only for a piece of it such as "which register kind should this be" or "why does this document write more rows than 1C does". Also covers copying the rows (keys, dates, enums, composite references, empty values, reconciliation) and porting 1C print forms (MXL templates, Строка/Формат, НСтр) to dForge print templates.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Porting a 1C infobase to dForge
|
|
@@ -341,7 +341,8 @@ judgement above, written down.
|
|
|
341
341
|
Do not write the mechanical half again. `scripts/` in this skill's directory holds the
|
|
342
342
|
Python library that did it on a real port:
|
|
343
343
|
|
|
344
|
-
-
|
|
344
|
+
- exporting the infobase (configuration, DBNames, schema, rows) to `out/`, from MS SQL or
|
|
345
|
+
from a file infobase (`1Cv8.1CD`). Every later step reads the export, never 1C;
|
|
345
346
|
- parsing the metadata;
|
|
346
347
|
- inventorying registers and handlers;
|
|
347
348
|
- generating and validating modules;
|
|
@@ -41,7 +41,8 @@ runs, and every number downstream is confidently wrong.
|
|
|
41
41
|
|
|
42
42
|
1C shifts stored dates by the infobase's `_YearOffset` — commonly 2000, so `4012` in the
|
|
43
43
|
column is 2012, and the empty date `0001-01-01` lands on `2001-01-01` and should become
|
|
44
|
-
NULL.
|
|
44
|
+
NULL. That is a server infobase. A file infobase stores real dates (offset 0). The export
|
|
45
|
+
records which applies in `export.json`, and `store.year_offset()` reads it from there.
|
|
45
46
|
|
|
46
47
|
It applies to **stored datetime columns only**. A date the configuration formatted into a
|
|
47
48
|
string (`НачалоДня(Дата)` assigned to a `Строка(10)` dimension) carries the real year, and
|
|
@@ -47,7 +47,7 @@ Everything else is column-level and mechanical. Write each conversion once, in t
|
|
|
47
47
|
|---|---|---|---|
|
|
48
48
|
| 1 | `_IDRRef binary(16)` | `uuid` | 1C stores the two 8-byte halves swapped relative to the printed GUID. In SQL: `substr(h,17)\|\|substr(h,1,16)` over the hex. Check it once by joining a line to its catalog and seeing the expected name. |
|
|
49
49
|
| 2 | sixteen zero bytes | `NULL` in a link | Left as-is, it is a uuid pointing at nothing, and the FK rejects the load. **In a register dimension it is a value, not an absence:** keep it as the nil uuid in a key column, because NULL ≠ NULL in a balance, a GROUP BY or a unique key. |
|
|
50
|
-
| 3 | `datetime` | date/timestamp minus `_YearOffset` | The offset is commonly 2000. The empty date `0001-01-01` lands on `2001-01-01` and becomes NULL. **On an information register's period it is a value:** "in force since the beginning". Load it as NULL and the slice (`period <= @date`) never finds the row. |
|
|
50
|
+
| 3 | `datetime` | date/timestamp minus `_YearOffset` | The offset is commonly 2000 on MS SQL, and 0 in a file infobase (`export.json` says which). The empty date `0001-01-01` lands on `2001-01-01` and becomes NULL. **On an information register's period it is a value:** "in force since the beginning". Load it as NULL and the slice (`period <= @date`) never finds the row. |
|
|
51
51
|
| 4 | a date 1C formatted into a string (`13.01.2016` in `nvarchar`) | `date` | No offset: nothing stored a datetime there. |
|
|
52
52
|
| 5 | a number in a string column | numeric | Decimal comma, nbsp or space as the thousands separator. |
|
|
53
53
|
| 6 | `binary(1)` flags | `bool` | `_Marked`, `_Posted`, `_Folder`. Check `_Folder`'s polarity against a known group and a known item. Do not trust the name. |
|
|
@@ -158,4 +158,7 @@ and alter the pairs that disagree. Never narrow one that holds something longer.
|
|
|
158
158
|
## Incremental re-runs
|
|
159
159
|
|
|
160
160
|
1C's `_Version` (rowversion) gives a cheap delta. Take the high-water mark **before** the
|
|
161
|
-
read, so a row 1C changes during the read is picked up next time rather than lost.
|
|
161
|
+
read, so a row 1C changes during the read is picked up next time rather than lost. The
|
|
162
|
+
export takes it before each table and records it in `export.json`. Only a server infobase
|
|
163
|
+
has one. In a file infobase `_Version` is a counter per table, so a file infobase is
|
|
164
|
+
reloaded whole, which its size allows.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Each decision in SKILL.md has a query that settles it. They are written against a loaded
|
|
4
4
|
dForge tenant (Postgres) because that is where both sides are comparable — the 1C rows and
|
|
5
5
|
what the platform writes. Where the question can only be asked of the infobase, the query
|
|
6
|
-
is against
|
|
6
|
+
is against the export (DuckDB over `out/data/`, via `store.connect()`) and says so.
|
|
7
7
|
|
|
8
8
|
Adapt names; the shapes are the point.
|
|
9
9
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# The 1C porting toolkit
|
|
2
2
|
|
|
3
|
-
A Python library that reads a 1C:Enterprise 8 infobase
|
|
4
|
-
no 1C software and no licence. From the
|
|
3
|
+
A Python library that reads a 1C:Enterprise 8 infobase, either a server infobase on MS SQL
|
|
4
|
+
Server or a file infobase (`1Cv8.1CD`). It needs no 1C software and no licence. From the
|
|
5
|
+
infobase it:
|
|
5
6
|
|
|
6
7
|
- generates dForge modules;
|
|
7
8
|
- copies the rows;
|
|
@@ -18,6 +19,55 @@ scripts/
|
|
|
18
19
|
project-template/ what a new port starts its project package from
|
|
19
20
|
```
|
|
20
21
|
|
|
22
|
+
## Two layers: export, then everything else
|
|
23
|
+
|
|
24
|
+
Only one step reads 1C itself. `dump1c` exports the infobase to `out/`, and every later
|
|
25
|
+
step reads that export, never the infobase.
|
|
26
|
+
|
|
27
|
+
| layer | modules | reads | writes |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| export | `dump1c` · `source_mssql` · `source_file` · `onecd` | the infobase: MS SQL (`DF1C_SERVER`/`DF1C_DB`) or a `.1CD` file (`DF1C_FILE`) | `out/config/`, `dbnames.json`, `schema.json`, `data/<table>.parquet`, `export.json` |
|
|
30
|
+
| everything else | `store` and every other module | `out/`. Rows are queried with DuckDB through `store.connect()` | modules, the tenant, reports |
|
|
31
|
+
|
|
32
|
+
The export looks the same whichever kind of infobase it came from:
|
|
33
|
+
|
|
34
|
+
- names are spelt the MS SQL way (`_Reference12`, `_Fld345RRef`). A file infobase keeps
|
|
35
|
+
them in capitals, and DBNames supplies the spelling back;
|
|
36
|
+
- the schema uses MS SQL type names (`binary(16)`, `nvarchar`, `numeric`, `datetime`);
|
|
37
|
+
- booleans are `binary(1)`.
|
|
38
|
+
|
|
39
|
+
Rows are kept as 1C stores them. `export.json` records the infobase's year offset: usually
|
|
40
|
+
2000 on MS SQL, always 0 in a file infobase. It also records the horizon (`MIN_ACTIVE_ROWVERSION()`)
|
|
41
|
+
taken before each table was read.
|
|
42
|
+
|
|
43
|
+
- **SQL over the export is DuckDB's.** That includes a project's `sources.QUERIES`. Quote
|
|
44
|
+
names with double quotes (case does not matter), use `LIMIT` instead of `TOP`, write
|
|
45
|
+
a binary constant as `from_hex('01')` instead of `0x01`, and use `CAST` instead of
|
|
46
|
+
`CONVERT`. A port whose queries were written in T-SQL has to rewrite them.
|
|
47
|
+
- **`dump1c data --changes` reads only what 1C changed** since the export (or `--since
|
|
48
|
+
<day>`). It works table by table:
|
|
49
|
+
- objects are re-read by `_Version` above the mark, and deletions are found by key;
|
|
50
|
+
- tabular parts are re-read through their changed owners;
|
|
51
|
+
- register movements are re-read through their changed recorders;
|
|
52
|
+
- anything else is re-read whole.
|
|
53
|
+
|
|
54
|
+
It rewrites each table's file to what a full export would hold. On an infobase of 2.7 M
|
|
55
|
+
objects, it reads the keys in a few seconds instead of a full export of 1.5 h.
|
|
56
|
+
|
|
57
|
+
On an accounting register it also compares each recorder's count of inactive entries.
|
|
58
|
+
Switching entries on or off ("Включить/выключить проводки") rewrites them without
|
|
59
|
+
writing the document, so `_Version` does not show it.
|
|
60
|
+
- **Totals and document journals are left out by default** (`_AccumRgT`, `_AccumRgTn`,
|
|
61
|
+
`_AccRgAT`, `_AccRgCT`, `_DocumentJournal`). 1C derives them from other tables, they
|
|
62
|
+
have no `_Version`, and `--changes` would read them in full on every run: 53.7 M of
|
|
63
|
+
313 M rows. Name one, or pass `--derived`, to export it. `seedstate verify` says which
|
|
64
|
+
totals table it needs.
|
|
65
|
+
- **`delta` needs an MS SQL export.** In a file infobase `_Version` is a counter per table,
|
|
66
|
+
not a rowversion for the whole database, so a file export has no horizon. Re-export it
|
|
67
|
+
and reload with `migrate run <module> --truncate`.
|
|
68
|
+
- **Read a file infobase while 1C is not writing to it.** Copy the `.1CD` first, or close
|
|
69
|
+
the infobase.
|
|
70
|
+
|
|
21
71
|
## Library and project
|
|
22
72
|
|
|
23
73
|
- **`lib/` knows 1C and dForge**: how a configuration is stored, how its metadata
|
|
@@ -42,14 +92,17 @@ Then regenerate. If a port keeps needing hand edits, the generator is wrong.
|
|
|
42
92
|
## Setup
|
|
43
93
|
|
|
44
94
|
- Python 3.7+.
|
|
45
|
-
- `
|
|
95
|
+
- `pyarrow` and `duckdb`, to write and read the export (on 3.7: `pyarrow==12.0.1`,
|
|
96
|
+
`duckdb==1.1.3`).
|
|
97
|
+
- `pyodbc` and an ODBC driver for SQL Server (driver 17 or 18), only to export a server
|
|
98
|
+
infobase. A file infobase needs nothing more.
|
|
46
99
|
- `psycopg2`, to load and check the tenant.
|
|
47
100
|
- `jsonschema`, for `validate`.
|
|
48
101
|
- Node with `liquidjs` in `PRINT_NODE_DIR`, only for `printgen preview`.
|
|
49
102
|
|
|
50
103
|
On Windows, set `PYTHONIOENCODING=utf-8`.
|
|
51
104
|
|
|
52
|
-
Lay out a port directory and
|
|
105
|
+
Lay out a port directory, copy `project-template/.env.example` to `<root>/.env` and fill it in, and export `DF1C_ROOT` / `DF1C_PROJECT` in the shell:
|
|
53
106
|
|
|
54
107
|
```
|
|
55
108
|
myport/
|
|
@@ -63,6 +116,7 @@ myport/
|
|
|
63
116
|
DF1C_ROOT=/path/to/myport
|
|
64
117
|
DF1C_PROJECT=/path/to/myport/project
|
|
65
118
|
DF1C_SERVER=<sql server> DF1C_DB=<infobase db> DF1C_USER= DF1C_PASS= # read-only login; Windows auth when USER is empty
|
|
119
|
+
DF1C_FILE=<path to 1Cv8.1CD, or its folder> # a file infobase instead of the two above
|
|
66
120
|
DF_TENANT=<tenant code> DF_PG_HOST= DF_PG_PORT= DF_PG_USER= DF_PG_PASS= # the tenant's Postgres, for load and checks
|
|
67
121
|
```
|
|
68
122
|
|
|
@@ -76,7 +130,7 @@ docstring with its options, and `python 1c.py --list` lists them all.
|
|
|
76
130
|
|
|
77
131
|
| # | step | command |
|
|
78
132
|
|---|---|---|
|
|
79
|
-
| 1 |
|
|
133
|
+
| 1 | Export the infobase: configuration, DBNames, schema, rows | `dump1c probe` · `dump1c all`, or one at a time: `dump1c dbnames` · `schema` · `extract` · `data [table ...]` |
|
|
80
134
|
| 2 | Parse the metadata, resolve type GUIDs, join the physical schema | `meta allobjects` · `typemap` · `analyze` · `attrs <Object>` · `explore <Object>` |
|
|
81
135
|
| 3 | Extract the module texts (posting handlers, print procedures) | `extract_code` · `unpack <Object> --code` |
|
|
82
136
|
| 4 | Inventory: what exists, what is used, what is the client's | `inventory` · `docusage` · `custom` · `sizing` · `subsystems` |
|
|
@@ -86,7 +140,7 @@ docstring with its options, and `python 1c.py --list` lists them all.
|
|
|
86
140
|
| 8 | Install them | `npx -y @dforge-core/dforge-cli@latest module install ...`, or `rebuild --code <tenant> --install` |
|
|
87
141
|
| 9 | Copy the rows and reconcile | `migrate plan\|check\|run\|verify\|fill <module>` · `integrity` · `columnmap <module>` |
|
|
88
142
|
| 10 | Post state and registers | `posted [--mark]` · `posting` · `seedstate` · `totals` · `coverage` |
|
|
89
|
-
| 11 | Catch up with what 1C changed | `delta --baseline` · `delta [--apply]` |
|
|
143
|
+
| 11 | Catch up with what 1C changed (MS SQL exports only) | once: `dump1c data --objects` (delta needs every object's keys); then `dump1c data --changes`, `delta --baseline` · `delta [--apply]` |
|
|
90
144
|
| 12 | Print forms and reports | `mxl list [--client]` · `mxl dump <Object>` · `printgen <spec>` · `printgen preview <spec> <form> <key>` · `reportgen <spec>` |
|
|
91
145
|
| 13 | External reports and processings stored in the infobase | `external` |
|
|
92
146
|
|
|
@@ -95,7 +149,7 @@ reproducible.
|
|
|
95
149
|
|
|
96
150
|
## Rules the toolkit assumes
|
|
97
151
|
|
|
98
|
-
- **The infobase is read-only.** Use a SELECT-only login.
|
|
152
|
+
- **The infobase is read-only.** Use a SELECT-only login. Read a file infobase from a copy.
|
|
99
153
|
- **Open ad-hoc database connections in autocommit.** A read transaction left open
|
|
100
154
|
blocks the next install's `ALTER`.
|
|
101
155
|
- **Resolve a physical column (`_FldNNN`) only through DBNames** (`attrs`, `columnmap`).
|
|
@@ -108,3 +162,30 @@ reproducible.
|
|
|
108
162
|
This copy is kept in step with the port it was developed on (`tools/lib`, `tools/1c.py`
|
|
109
163
|
there). Fixes made in a port go back to the library here. A project's own files never
|
|
110
164
|
come here.
|
|
165
|
+
|
|
166
|
+
### Regression checks and restart contract
|
|
167
|
+
|
|
168
|
+
From the MCP repository root: `pnpm test:1c` (Python 3, standard library only).
|
|
169
|
+
These tests use synthetic inputs and mocked database boundaries, never a tenant or
|
|
170
|
+
source infobase. They cover conversions, COPY encoding, settlement failure/retry and
|
|
171
|
+
the initial loader's committed-per-entity restart contract; they do not replace
|
|
172
|
+
Parquet/DuckDB/PostgreSQL integration or real source-to-target reconciliation.
|
|
173
|
+
|
|
174
|
+
`delta --apply` prints each module's `ready` or `partial` status. Exit 0 means all
|
|
175
|
+
selected modules settled; exit 2 means data may have committed but settlement is
|
|
176
|
+
incomplete (state/totals drift or markers not converged, including `--no-mark`
|
|
177
|
+
when markers are needed). Resolve the reported problem and rerun `--apply`;
|
|
178
|
+
persisted unsettled entities are checked even when the new delta has no changes.
|
|
179
|
+
Dry runs report `dry_run` and do not certify readiness. Settlement write failures
|
|
180
|
+
roll back the current transaction before restoring autocommit; ordinary failures
|
|
181
|
+
report `partial` and allow the next module to run on a clean connection. Failure to
|
|
182
|
+
rollback or restore the connection stops the command immediately. Previously
|
|
183
|
+
committed entities/years remain committed; a rollback does not undo the whole import.
|
|
184
|
+
Exit 0 does not replace the wider migration reconciliation.
|
|
185
|
+
|
|
186
|
+
`migrate run --truncate` is a destructive reload into an isolated migration target,
|
|
187
|
+
not an atomic refresh of a live application: clearing commits before loading and
|
|
188
|
+
each entity commits separately. If interrupted, keep users out, repair the cause,
|
|
189
|
+
rerun the same complete selection with `--truncate` from the same export, then
|
|
190
|
+
reconcile again. Do not use an append rerun to recover a partially loaded target.
|
|
191
|
+
For an active target, load/reconcile a separate database and switch only afterward.
|
|
Binary file
|
|
@@ -224,8 +224,8 @@ def main():
|
|
|
224
224
|
if sys.argv[1] == "--model":
|
|
225
225
|
out_path, names = sys.argv[2], sys.argv[3:]
|
|
226
226
|
lines = ["# Transport & shipping contour - extracted model", "",
|
|
227
|
-
"Generated from the `%s`
|
|
228
|
-
% os.environ.get("DF1C_DB"
|
|
227
|
+
"Generated from the `%s` infobase without 1C software."
|
|
228
|
+
% (os.environ.get("DF1C_DB") or os.path.basename(os.environ.get("DF1C_FILE") or "source")), ""]
|
|
229
229
|
for name in names:
|
|
230
230
|
guid, fields = fields_of(name, objects, types)
|
|
231
231
|
row = report.get(name, {})
|