@dforge-core/dforge-mcp 0.2.44 → 0.2.46-next
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 +73 -0
- package/README.md +3 -2
- package/dist/server.js +109 -84
- package/package.json +48 -46
- package/resources/docs/conventions.md +17 -14
- package/resources/docs/dsl-reference.md +15 -5
- package/resources/schemas/entity.schema.json +1 -1
- package/resources/schemas/registers.schema.json +8 -3
- package/scripts/install-skills.mjs +1 -0
- package/skills/dforge-1c-import/SKILL.md +374 -0
- package/skills/dforge-1c-import/references/environment.md +97 -0
- package/skills/dforge-1c-import/references/loading.md +161 -0
- package/skills/dforge-1c-import/references/measurements.md +210 -0
- package/skills/dforge-1c-import/references/print-forms.md +148 -0
- package/skills/dforge-1c-import/scripts/1c.py +178 -0
- package/skills/dforge-1c-import/scripts/README.md +110 -0
- package/skills/dforge-1c-import/scripts/lib/analyze.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/attrs.py +278 -0
- package/skills/dforge-1c-import/scripts/lib/autospec.py +964 -0
- package/skills/dforge-1c-import/scripts/lib/binding.py +257 -0
- package/skills/dforge-1c-import/scripts/lib/bsl.py +811 -0
- package/skills/dforge-1c-import/scripts/lib/callgraph.py +717 -0
- package/skills/dforge-1c-import/scripts/lib/cli.py +53 -0
- package/skills/dforge-1c-import/scripts/lib/columnmap.py +346 -0
- package/skills/dforge-1c-import/scripts/lib/coverage.py +242 -0
- package/skills/dforge-1c-import/scripts/lib/custom.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/delta.py +741 -0
- package/skills/dforge-1c-import/scripts/lib/deps.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/docusage.py +105 -0
- package/skills/dforge-1c-import/scripts/lib/dump1c.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/explore.py +55 -0
- package/skills/dforge-1c-import/scripts/lib/external.py +201 -0
- package/skills/dforge-1c-import/scripts/lib/extract_code.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/forms.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/genmodule.py +1432 -0
- package/skills/dforge-1c-import/scripts/lib/handlers.py +202 -0
- package/skills/dforge-1c-import/scripts/lib/handmap.py +142 -0
- package/skills/dforge-1c-import/scripts/lib/i18n_coverage.py +87 -0
- package/skills/dforge-1c-import/scripts/lib/idioms.py +273 -0
- package/skills/dforge-1c-import/scripts/lib/integrity.py +264 -0
- package/skills/dforge-1c-import/scripts/lib/inventory.py +535 -0
- package/skills/dforge-1c-import/scripts/lib/layers.py +106 -0
- package/skills/dforge-1c-import/scripts/lib/meta.py +315 -0
- package/skills/dforge-1c-import/scripts/lib/migrate.py +1299 -0
- package/skills/dforge-1c-import/scripts/lib/mxl.py +368 -0
- package/skills/dforge-1c-import/scripts/lib/naming.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/paths.py +90 -0
- package/skills/dforge-1c-import/scripts/lib/peek.py +70 -0
- package/skills/dforge-1c-import/scripts/lib/posted.py +218 -0
- package/skills/dforge-1c-import/scripts/lib/posting.py +655 -0
- package/skills/dforge-1c-import/scripts/lib/printgen.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/rebuild.py +194 -0
- package/skills/dforge-1c-import/scripts/lib/recorded.py +170 -0
- package/skills/dforge-1c-import/scripts/lib/recorders.py +94 -0
- package/skills/dforge-1c-import/scripts/lib/reftargets.py +95 -0
- package/skills/dforge-1c-import/scripts/lib/reportgen.py +157 -0
- package/skills/dforge-1c-import/scripts/lib/roles.py +33 -0
- package/skills/dforge-1c-import/scripts/lib/seedstate.py +488 -0
- package/skills/dforge-1c-import/scripts/lib/sizing.py +184 -0
- package/skills/dforge-1c-import/scripts/lib/spec.py +400 -0
- package/skills/dforge-1c-import/scripts/lib/statements.py +774 -0
- package/skills/dforge-1c-import/scripts/lib/subsystems.py +480 -0
- package/skills/dforge-1c-import/scripts/lib/terms.py +451 -0
- package/skills/dforge-1c-import/scripts/lib/terms_uk.py +555 -0
- package/skills/dforge-1c-import/scripts/lib/totals.py +387 -0
- package/skills/dforge-1c-import/scripts/lib/typemap.py +73 -0
- package/skills/dforge-1c-import/scripts/lib/unpack.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/validate.py +508 -0
- package/skills/dforge-1c-import/scripts/project-template/contours.py +47 -0
- package/skills/dforge-1c-import/scripts/project-template/sources.py +14 -0
- package/skills/dforge-mcp-author/SKILL.md +8 -2
- package/skills/dforge-mcp-author/examples/column-security/entities/product.json +1 -1
- package/skills/dforge-mcp-author/examples/column-security/manifest.json +0 -1
- package/skills/dforge-mcp-author/examples/column-security/security/roles.json +2 -2
- package/skills/dforge-mcp-author/references/action-dsl.md +20 -7
- package/skills/dforge-mcp-author/references/conventions.md +7 -1
- package/skills/dforge-mcp-author/references/field-types.md +3 -2
- package/skills/dforge-mcp-author/references/formulas.md +5 -1
- package/skills/dforge-mcp-author/references/print-templates.md +5 -2
- package/skills/dforge-mcp-author/references/queries.md +1 -1
- package/skills/dforge-mcp-author/references/registers.md +18 -3
- package/skills/dforge-mcp-author/references/reports.md +4 -1
- package/skills/dforge-mcp-author/references/schema-import.md +1 -1
- package/skills/dforge-mcp-author/references/security.md +1 -1
- package/skills/dforge-mcp-author/references/translations.md +38 -15
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -3
- package/skills/dforge-module-build/SKILL.md +10 -1
- package/skills/dforge-module-ship/SKILL.md +31 -3
- package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +0 -35
|
@@ -66,8 +66,8 @@ Use the CRM sample module (`modules/crm/`) as a reference implementation.
|
|
|
66
66
|
├── /security/ # Security definitions
|
|
67
67
|
│ └── roles.json
|
|
68
68
|
├── /translations/ # Translations (optional)
|
|
69
|
-
│ ├──
|
|
70
|
-
│ └──
|
|
69
|
+
│ ├── de-DE.json # one per supportedLocales entry — never en*.json
|
|
70
|
+
│ └── uk-UA.json
|
|
71
71
|
├── /print_templates/ # HTML/CSS print templates (optional)
|
|
72
72
|
│ ├── invoice.html
|
|
73
73
|
│ └── invoice.css
|
|
@@ -106,6 +106,7 @@ Use the CRM sample module (`modules/crm/`) as a reference implementation.
|
|
|
106
106
|
- `entities` maps entity codes to file paths (relative to module root)
|
|
107
107
|
- Omit `"system": true` for regular modules (defaults to false)
|
|
108
108
|
- Omit `schemaName` to use `code` as the schema name
|
|
109
|
+
- The schema and the code must be free: a first install, or an upgrade that moves the module to another schema, is refused when another module already holds that schema or code (compared case-insensitively). Modules that already share a schema keep upgrading.
|
|
109
110
|
|
|
110
111
|
### 1b. Module Extensions
|
|
111
112
|
|
|
@@ -644,37 +645,39 @@ Cron-driven action fires. Each entry pairs an existing action (declared in `ui/a
|
|
|
644
645
|
|
|
645
646
|
**Reference:** `modules/chore/` — single-job smoke-test module.
|
|
646
647
|
|
|
647
|
-
### 8. Translations (`translations/
|
|
648
|
+
### 8. Translations (`translations/de-DE.json`)
|
|
649
|
+
|
|
650
|
+
One file per locale in `manifest.supportedLocales`. There is **no** English file: install skips `translations/en.json` and `en-*.json`, and the English UI shows the definitions' own text — an entity's or field's `description`, a view's, menu's, action's or report's `label`, a role's or setting's `description`. Keep those short labels.
|
|
648
651
|
|
|
649
652
|
```json
|
|
650
653
|
{
|
|
651
654
|
"entities": {
|
|
652
655
|
"department": {
|
|
653
|
-
"label": "
|
|
654
|
-
"desc": "
|
|
656
|
+
"label": "Abteilung",
|
|
657
|
+
"desc": "Abteilungen des Unternehmens",
|
|
655
658
|
"fields": {
|
|
656
|
-
"department_id": { "label": "
|
|
657
|
-
"department_name": { "label": "
|
|
659
|
+
"department_id": { "label": "Abteilungs-ID" },
|
|
660
|
+
"department_name": { "label": "Abteilungsname" }
|
|
658
661
|
}
|
|
659
662
|
},
|
|
660
663
|
"employee": {
|
|
661
664
|
"constraints": {
|
|
662
|
-
"chk_salary_positive": { "message": "
|
|
665
|
+
"chk_salary_positive": { "message": "Das Gehalt muss positiv sein" }
|
|
663
666
|
}
|
|
664
667
|
}
|
|
665
668
|
},
|
|
666
669
|
"folders": {
|
|
667
|
-
"hr": { "label": "
|
|
670
|
+
"hr": { "label": "Personalwesen" }
|
|
668
671
|
},
|
|
669
672
|
"views": {
|
|
670
|
-
"departments": { "label": "
|
|
671
|
-
"employees": { "label": "
|
|
673
|
+
"departments": { "label": "Abteilungen" },
|
|
674
|
+
"employees": { "label": "Alle Mitarbeiter" }
|
|
672
675
|
},
|
|
673
676
|
"menus": {
|
|
674
|
-
"hr_menu": { "label": "
|
|
677
|
+
"hr_menu": { "label": "Personalwesen" }
|
|
675
678
|
},
|
|
676
679
|
"actions": {
|
|
677
|
-
"approve_leave": { "label": "
|
|
680
|
+
"approve_leave": { "label": "Urlaub genehmigen", "desc": "Einen Urlaubsantrag genehmigen" }
|
|
678
681
|
}
|
|
679
682
|
}
|
|
680
683
|
```
|
|
@@ -683,7 +686,7 @@ Cron-driven action fires. Each entry pairs an existing action (declared in `ui/a
|
|
|
683
686
|
|
|
684
687
|
**Constraint violation messages are localizable (opt-in).** The `message` on a check/unique constraint in the entity JSON is the base (fallback) text. To localize it, add a per-locale override under `entities.<entityCd>.constraints.<constraintName>.message` in each `translations/<locale>.json`. The server resolves it with culture fallback (per-locale → base) and it surfaces identically on the client pre-save validator and the server DB-violation path. Unlike labels, constraint overrides are **not** completeness-enforced: a missing override for a declared `supportedLocales` entry emits a **non-fatal warning** (from `dforge_module_validate` pre-flight and at install) and the base message is used as the fallback.
|
|
685
688
|
|
|
686
|
-
**
|
|
689
|
+
**Roles and settings are translated; print templates are not.** `roles.<code>.label` and `settings.<code>.label` are completeness-enforced for each declared locale, over the English `description` in `security/roles.json` and `settings.json` (the registrar reads no `label` there). A print template has no translatable resource: its name is its `description` in every language, and a `print_templates` section is ignored.
|
|
687
690
|
|
|
688
691
|
---
|
|
689
692
|
|
|
@@ -145,6 +145,7 @@ A param declared with a text field type (`text`, `textarea`, ...) stays a string
|
|
|
145
145
|
|
|
146
146
|
- `[fieldName]` — read or write a field on the current record
|
|
147
147
|
- `[refField].[targetField]` — navigate via a Reference (FK) column to a field on the linked entity. `[refField].targetField` (bare target) is accepted as an equivalent spelling. **Single-hop only** — `[a].[b].[c]` is a compile error; use `getRecord()` / `select()` to reach past one reference
|
|
148
|
+
- `[userField].[email]` — a column of `fieldTypeCd: "user"` (an `owner_id`, an approver) is not a Reference column but navigates the same way, to the platform user: `[owner_id].[email]`, `[owner_id].[full_name]`. This is the only way to read a user's name or e-mail — `getRecord()`, `select()` and `query()` cannot reach the user table. Needs a platform with #1499 (v1.28.8+)
|
|
148
149
|
- `old[fieldName]` — the field's value *before* the change, in an action invoked by an event trigger. Read-only; reads `null` on `insert` events and outside triggers
|
|
149
150
|
|
|
150
151
|
```dsl
|
|
@@ -504,7 +505,7 @@ notify([approver_id], 'New purchase request from ' + [requester_name] + ' awaiti
|
|
|
504
505
|
```
|
|
505
506
|
|
|
506
507
|
#### `sendEmail(to, subjectOrTemplateCd, bodyOrData?, attachments?)`
|
|
507
|
-
Two modes (a second argument with no spaces that contains `.` or `_` is read as a template code, so give a raw subject spaces). The optional fourth argument attaches files: a `printPdf()` result, or an array of them (a single file as itself, not `[a]`). Max 5 files and 10 MB
|
|
508
|
+
Two modes (a second argument with no spaces that contains `.` or `_` is read as a template code, so give a raw subject spaces). The optional fourth argument attaches files: a `printPdf()`, `attachText()` or `attachBase64()` result, or an array of them (a single file as itself, not `[a]`). Max 5 files and 10 MB. Only the user who generated a `printPdf()` file, or a workspace admin, can attach it, and a scheduled job cannot; a file built with `attachText()` / `attachBase64()` has no owner and works in a scheduled job too.
|
|
508
509
|
|
|
509
510
|
```dsl
|
|
510
511
|
// Raw mode: (to, subject, body)
|
|
@@ -577,13 +578,22 @@ execute:
|
|
|
577
578
|
```
|
|
578
579
|
|
|
579
580
|
#### `downloadText(content, fileName, encoding?, mimeType?)` / `downloadBase64(base64, fileName, mimeType?)`
|
|
580
|
-
Hand the user a file the script built. `encoding` defaults to `utf-8`; code pages such as `windows-1251` and `cp866` work; line endings are written as given, no BOM. Both return the signed download URL, which a background or scheduled run can pass on in `notify()`. One download per action (a later call replaces the earlier); max 10 MB per file, 10 files per run.
|
|
581
|
+
Hand the user a file the script built. `encoding` defaults to `utf-8`; code pages such as `windows-1251` and `cp866` work; line endings are written as given, no BOM. Both return the signed download URL, which a background or scheduled run can pass on in `notify()`. One download per action (a later call replaces the earlier); max 10 MB per file, 10 files per run (shared with `attachText()` / `attachBase64()`).
|
|
581
582
|
|
|
582
583
|
```dsl
|
|
583
584
|
execute:
|
|
584
585
|
downloadText(csv, 'payments.txt', 'windows-1251')
|
|
585
586
|
```
|
|
586
587
|
|
|
588
|
+
#### `attachText(content, fileName, encoding?, mimeType?)` / `attachBase64(base64, fileName, mimeType?)`
|
|
589
|
+
Build a file to attach to an email: pass the result as `sendEmail()`'s fourth argument. Encodings as `downloadText()`; the file keeps its `mimeType` (`text/plain` for `attachText()` by default, `application/octet-stream` for `attachBase64()`). Returns a descriptor, not a URL, and only the emails of the same run can name it. Works in a scheduled job. Max 10 MB per file; the 10-files-per-run limit is shared with the download functions.
|
|
590
|
+
|
|
591
|
+
```dsl
|
|
592
|
+
execute:
|
|
593
|
+
sendEmail([candidate_email], 'Interview for ' + [job_title], '<p>See you.</p>',
|
|
594
|
+
attachText(ics, 'invite.ics', 'utf-8', 'text/calendar'))
|
|
595
|
+
```
|
|
596
|
+
|
|
587
597
|
#### `download(url, fileName?)`
|
|
588
598
|
Set HTTP download response. Only valid in UI-triggered actions; the user's browser receives the file. It fetches nothing: the URL must already serve the bytes.
|
|
589
599
|
|
|
@@ -739,8 +749,8 @@ The first argument names a **register** (`module_cd.register_cd`, as declared in
|
|
|
739
749
|
| `getBalance('module.register', dims?, asOf?)` | Object, per resource | Balance of an accumulation register. A dimension left out of `dims` is summed over. |
|
|
740
750
|
| `getTurnover('module.register', dims, from, to)` | Object, `{in, out, net}` per resource | Movement over an interval. `dims` may be `null`; `from` and `to` are required. |
|
|
741
751
|
| `getSlice('module.register', dims, onDate?, mode?)` | Object or `null` | One record of an information register, the latest on or before `onDate`, plus `_period`. No dimension may be omitted. |
|
|
742
|
-
| `getAccountBalance('module.register', account, filter?, asOf?)` | Object, `{debit, credit, net}` per resource | Balance of one account of an accounting register. |
|
|
743
|
-
| `getAccountTurnover('module.register', account, filter, from, to)` | Object, `{debit, credit, net}` per resource | Movement of one account over an interval. |
|
|
752
|
+
| `getAccountBalance('module.register', account, filter?, asOf?, options?)` | Object, `{debit, credit, net}` per resource; an array of rows with `groupBy` | Balance of one account of an accounting register. `options`: `groupBy` (a name or array — a row per combination, zero balances left out), `exclude: { entity: 'module.document', key }` (one, or an array of them; not that document's postings), `before` (a moment, exclusive, in place of `asOf`). |
|
|
753
|
+
| `getAccountTurnover('module.register', account, filter, from, to, options?)` | Object, `{debit, credit, net}` per resource; an array of rows with `groupBy` | Movement of one account over an interval. `options` takes `groupBy` and `exclude`. |
|
|
744
754
|
| `getCorrespondence('module.register', debitAccount, creditAccount, filter, from, to)` | Object, per resource | Movement between two accounts. Needs `lineMode: "compound"`; refuses an analytic filter. |
|
|
745
755
|
|
|
746
756
|
An analytic filter is the key alone (`{ cost_center: 42 }`) or `{ entity, value }` to name one entity of a union kind.
|
|
@@ -1197,7 +1207,7 @@ The `audit` trait auto-maintains `last_updated` on INSERT/UPDATE if the column e
|
|
|
1197
1207
|
| Run SQL | `query('SELECT ... WHERE c = @v', { v: value })` |
|
|
1198
1208
|
| Get setting | `getSetting('setting_cd')` |
|
|
1199
1209
|
| Send toast | `info('msg')`, `warn('msg')` |
|
|
1200
|
-
| Send email | `sendEmail(to, subj, body)` or `sendEmail(to, templateCd, dataObj)`; add a `printPdf()` result as the 4th argument to attach |
|
|
1210
|
+
| Send email | `sendEmail(to, subj, body)` or `sendEmail(to, templateCd, dataObj)`; add a `printPdf()`, `attachText()` or `attachBase64()` result as the 4th argument to attach |
|
|
1201
1211
|
| Give the user a built file | `downloadText(content, 'f.txt', 'windows-1251')` |
|
|
1202
1212
|
| Send inbox notification | `notify(userId, 'msg')` |
|
|
1203
1213
|
| Call a shared helper | `round2([amount])` from `logic/functions/*.dsl` |
|
|
@@ -428,7 +428,7 @@
|
|
|
428
428
|
"fieldTypeCd": {
|
|
429
429
|
"type": "string",
|
|
430
430
|
"enum": [
|
|
431
|
-
"text", "avatar", "email", "phone", "url", "textarea", "number", "currency", "percent",
|
|
431
|
+
"text", "avatar", "email", "phone", "url", "textarea", "number", "currency", "percent", "filesize",
|
|
432
432
|
"checkbox", "date", "datetime", "time",
|
|
433
433
|
"dropdown", "flags", "tags", "json", "hidden", "color", "image", "file",
|
|
434
434
|
"markdown", "richtext", "lookup", "user", "grid", "list", "entitylink", "code"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"account": false,
|
|
25
25
|
"debitAccount": false, "creditAccount": false,
|
|
26
26
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
27
|
-
"sideDimensions": false, "analyticTotals": false
|
|
27
|
+
"sideDimensions": false, "analyticTotals": false, "importedKeyField": false
|
|
28
28
|
},
|
|
29
29
|
"description": "A balance register keeps state, so it needs the entity that holds it. It dates its movements on the journal row under 'journalDate'; 'period' belongs to an information register, whose records ARE the dated thing. Its writer is recorded on the journal row, which is what 'recorder' names for an information register that has no journal."
|
|
30
30
|
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"account": false,
|
|
42
42
|
"debitAccount": false, "creditAccount": false,
|
|
43
43
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
44
|
-
"sideDimensions": false, "analyticTotals": false,
|
|
44
|
+
"sideDimensions": false, "analyticTotals": false, "importedKeyField": false,
|
|
45
45
|
"allowNegative": false,
|
|
46
46
|
"autoCreate": false
|
|
47
47
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"account": false,
|
|
61
61
|
"debitAccount": false, "creditAccount": false,
|
|
62
62
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
63
|
-
"sideDimensions": false, "analyticTotals": false,
|
|
63
|
+
"sideDimensions": false, "analyticTotals": false, "importedKeyField": false,
|
|
64
64
|
"totals": false,
|
|
65
65
|
"allowNegative": false,
|
|
66
66
|
"autoCreate": false
|
|
@@ -375,6 +375,11 @@
|
|
|
375
375
|
"items": { "type": "string", "pattern": "^[a-z_][a-z0-9_]*$" },
|
|
376
376
|
"description": "Analytic slot columns for the CREDITED account, in slot order and capped independently of the debit side. Only valid on a compound register."
|
|
377
377
|
},
|
|
378
|
+
"importedKeyField": {
|
|
379
|
+
"type": "string",
|
|
380
|
+
"pattern": "^[a-z_][a-z0-9_]*$",
|
|
381
|
+
"description": "Accounting only. The posting column holding the document's key on a posting LOADED beside a document posted in another system — one with no 'source' stamp. getAccountBalance / getAccountTurnover 'exclude' then leaves such postings out by that key as well as the stamped ones. Matches documents with a one-column key."
|
|
382
|
+
},
|
|
378
383
|
"sideDimensions": {
|
|
379
384
|
"type": "object",
|
|
380
385
|
"minProperties": 1,
|
|
@@ -32,6 +32,7 @@ const SKILLS = [
|
|
|
32
32
|
["dforge-module-design", "Phase 0 — identity, intake, design, validation"],
|
|
33
33
|
["dforge-module-build", "Phases 1-5 — entities, behavior, views, security"],
|
|
34
34
|
["dforge-module-ship", "Phase 6 — validate, pack, install-fix loop"],
|
|
35
|
+
["dforge-1c-import", "porting a 1C infobase — decisions, loading, print forms,\n and the Python toolkit that does the mechanical half"],
|
|
35
36
|
];
|
|
36
37
|
|
|
37
38
|
const PKG = "@dforge-core/dforge-mcp";
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Porting a 1C infobase to dForge
|
|
7
|
+
|
|
8
|
+
## The stance
|
|
9
|
+
|
|
10
|
+
A 1C configuration is two things that disagree: what its author **declared** and what its
|
|
11
|
+
documents **do**. The declaration is a proposal, often stale, sometimes never true. The
|
|
12
|
+
movements are the record of what actually happened, thousands of times, for years.
|
|
13
|
+
|
|
14
|
+
So the rule that makes this work tractable: **when a question can be settled by measuring
|
|
15
|
+
the data, measure it.** Not because the configuration lies on purpose, but because a port
|
|
16
|
+
that copies declarations copies the author's mistakes with them, and a port that copies
|
|
17
|
+
behaviour copies the business.
|
|
18
|
+
|
|
19
|
+
Two worked cases, both from a real port, both where the obvious source was wrong:
|
|
20
|
+
|
|
21
|
+
- The accounting policy register pointed at position 0 of a three-value enum and the
|
|
22
|
+
configuration text would not say which value that was. The write-offs said it in five
|
|
23
|
+
independent ways in under an hour.
|
|
24
|
+
- A register dimension declared `Строка(10)` held `13.01.2016` in 35,799 rows. The
|
|
25
|
+
generator read `nvarchar` faithfully — and faithfully carried the author's mistake, a
|
|
26
|
+
date that does not sort, compare, or match anything a posting would write.
|
|
27
|
+
|
|
28
|
+
The corollary matters as much: **the volume of 1C code is not the size of the port.** Most
|
|
29
|
+
of a posting handler is the platform's job in dForge — clearing movements, locking,
|
|
30
|
+
`Свернуть`, balance control, re-posting. What you port is the part that decides *which
|
|
31
|
+
value goes where*, and that is usually a few lines inside a handler of hundreds. Read the
|
|
32
|
+
code to learn the requirement; do not transcribe it. Concretely: extract only the
|
|
33
|
+
value-assignment and computation lines (for example, `ЗаполнитьЗначения` targets and
|
|
34
|
+
arithmetic expressions); do not port loop structure, locking calls, or platform
|
|
35
|
+
bookkeeping code.
|
|
36
|
+
|
|
37
|
+
## What the tools do, and what you decide
|
|
38
|
+
|
|
39
|
+
Generation and loading are mechanical and should stay that way: unpacking the
|
|
40
|
+
configuration, parsing metadata, mapping types, emitting entities, copying rows,
|
|
41
|
+
reconciling counts. If a port keeps needing hand edits to generated output, the generator
|
|
42
|
+
is wrong, not the output. This skill ships that mechanical half as a toolkit (see **The
|
|
43
|
+
toolkit** below), so a port starts from it rather than from nothing.
|
|
44
|
+
|
|
45
|
+
What no tool can decide:
|
|
46
|
+
|
|
47
|
+
| decision | settled by |
|
|
48
|
+
|---|---|
|
|
49
|
+
| which module an object belongs to | the business, plus the dependency graph |
|
|
50
|
+
| a register's **kind** | measuring the register and reading its writers |
|
|
51
|
+
| where a posting takes each value | where the column physically sits |
|
|
52
|
+
| whether to carry a link at all | who reads it |
|
|
53
|
+
| what a strange column type means | the data in it |
|
|
54
|
+
|
|
55
|
+
The rest of this file is those decisions. Each one has a measurement attached, because a
|
|
56
|
+
decision you can defend with a number survives review and a decision you argued does not.
|
|
57
|
+
|
|
58
|
+
## Read the posting handler first
|
|
59
|
+
|
|
60
|
+
Reading a handler before porting it is the cheapest move available and it repeatedly
|
|
61
|
+
answers in minutes what the data leaves ambiguous for days. Do it even when you do not
|
|
62
|
+
intend to port that handler.
|
|
63
|
+
|
|
64
|
+
What to extract, in order:
|
|
65
|
+
|
|
66
|
+
1. **Which registers it writes** (`Движения.<name>`), and for each, whether the register
|
|
67
|
+
belongs to the client or to the vendor configuration. Vendor registers are usually not
|
|
68
|
+
ported; client registers are the porting unit. A register written by **both** is the
|
|
69
|
+
awkward case: measure what fraction of its writes are client-originated before deciding
|
|
70
|
+
anything. A high fraction makes it a client register with vendor writers to account for;
|
|
71
|
+
a low one usually means the register belongs to machinery you are not carrying, and the
|
|
72
|
+
client's few writes into it are a question for the client rather than a port.
|
|
73
|
+
2. **Where each movement column is filled from.** `ЗаполнитьЗначения(X, "Col")` after
|
|
74
|
+
loading a tabular section means: that column comes from the HEADER while the rest come
|
|
75
|
+
from the lines. This single fact decides the binding (see below) and it is extremely
|
|
76
|
+
common — in the base measured, 24 of 36 register-writing documents do it.
|
|
77
|
+
3. **Whether it collapses.** `Свернуть(dims, resources)` is not tidying. It means the
|
|
78
|
+
document's lines can repeat a key and the register holds one row per key. Getting this
|
|
79
|
+
wrong writes duplicate keys silently.
|
|
80
|
+
4. **What a guard protects.** `Если Стр.Количество <> 0` looks like a condition; measuring
|
|
81
|
+
how often it has fired tells you how much it is worth, not whether to keep it. A guard
|
|
82
|
+
that has never fired in years is a poor reason to ask the platform for a new feature —
|
|
83
|
+
but it is not dead code, because future input can still reach it. Keep the condition
|
|
84
|
+
where it is cheap (a column, a computed value, a validation on the target), and drop it
|
|
85
|
+
only when the target genuinely cannot produce the input or the client agrees the rule
|
|
86
|
+
has changed. Record which of those it was.
|
|
87
|
+
5. **What it computes.** `-(1 - Количество)` is arithmetic, not logic — it becomes a
|
|
88
|
+
computed column, not code.
|
|
89
|
+
|
|
90
|
+
**Size does not measure effort, and using it as a proxy is the trap.** A handler's line
|
|
91
|
+
count is mostly vendor accounting the port does not carry: the largest in the base measured
|
|
92
|
+
here was 1,043 lines writing five registers, four of them the vendor's, and its only
|
|
93
|
+
client-owned movement was a status the port already handled. Its client-relevant part was
|
|
94
|
+
smaller and easier than that of handlers a tenth its size.
|
|
95
|
+
|
|
96
|
+
So scan by size once, to know which handlers you will not read end to end — but do not
|
|
97
|
+
defer the handler itself on that basis, because the client-owned movement buried in a big
|
|
98
|
+
one may be the cheapest thing available. Find the client's registers first and work
|
|
99
|
+
outward from them; order that work by what blocks each handler, so everything unblocked
|
|
100
|
+
ships while the blocked ones wait on one decision each.
|
|
101
|
+
|
|
102
|
+
### What counts as blocked
|
|
103
|
+
|
|
104
|
+
A handler is **blocked** when something it needs is not yet decided or not yet available,
|
|
105
|
+
and no amount of writing declarations will change that. Everything else — long, tedious,
|
|
106
|
+
unfamiliar — is work, not a block.
|
|
107
|
+
|
|
108
|
+
Name the category and the resolver together, because the resolver is what determines when
|
|
109
|
+
it moves:
|
|
110
|
+
|
|
111
|
+
| category | example | who resolves it |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| a platform feature that does not exist | a value the binding cannot reach from where it sits | the platform team |
|
|
114
|
+
| a modelling decision not yet made | which register kind, which dimension is missing | you, by measuring |
|
|
115
|
+
| a business question | which valuation method should apply going forward | the client |
|
|
116
|
+
| data that is absent or untrustworthy | a dimension empty across the whole register | investigation, then one of the above |
|
|
117
|
+
|
|
118
|
+
Two habits make this list useful rather than decorative.
|
|
119
|
+
|
|
120
|
+
**Keep a count against each category, and re-examine it.** A blocker recorded once tends to
|
|
121
|
+
stay recorded. In the port this skill comes from, the list shrank three times and every
|
|
122
|
+
time *away* from "we need more platform": a collapse the engine already did, a condition
|
|
123
|
+
that was a column rather than a feature, a register declared as the wrong kind. Expect
|
|
124
|
+
mislabelling in that direction — it is the expensive one, because asking for a platform
|
|
125
|
+
feature costs weeks and re-reading your own declaration costs an hour.
|
|
126
|
+
|
|
127
|
+
**Distinguish "blocked" from "not started".** A handler waiting on a decision is blocked; a
|
|
128
|
+
handler nobody has read is not. The second is much more common early, and conflating them
|
|
129
|
+
makes the port look stuck when it is merely young.
|
|
130
|
+
|
|
131
|
+
## Choosing a register kind
|
|
132
|
+
|
|
133
|
+
The decision most likely to be made wrong, because the obvious answer — copy the 1C family
|
|
134
|
+
— is wrong often enough to matter. A 1C `РегистрСведений` is not automatically dForge's
|
|
135
|
+
`kind: "info"`.
|
|
136
|
+
|
|
137
|
+
One fact governs everything below: **only `balance` aggregates lines by dimension tuple.**
|
|
138
|
+
`info` and `turnover` keep no state and the engine skips the aggregation for both. So if a
|
|
139
|
+
document's lines can repeat a key, `balance` is the only kind that will fold them.
|
|
140
|
+
|
|
141
|
+
Work through this flow in order. Lines repeat a key when `Свернуть` over the dimensions and
|
|
142
|
+
the measurements (§1) show that repeated document-line tuples become one register row.
|
|
143
|
+
Quantities add; values replace. A price, status, currency, or percentage share is a value;
|
|
144
|
+
a quantity, amount, or count is a quantity.
|
|
145
|
+
|
|
146
|
+
| Step | Question | Answer | Next step / result |
|
|
147
|
+
|---|---|---|---|
|
|
148
|
+
| 1 | Can the document's lines repeat a key? | No | Go to step 2; every kind remains available. |
|
|
149
|
+
| 1 | Can the document's lines repeat a key? | Yes | Go to step 2. Only `balance` can fold them, but that does not select `balance`: a writer can fold its lines and still replace a snapshot. |
|
|
150
|
+
| 2 | Are the resources quantities or values? | Values; lines do not repeat a key | **`info`**. Done. |
|
|
151
|
+
| 2 | Are the resources quantities or values? | Values; lines repeat a key | **Stop: no kind fits.** The key is incomplete: summing values is meaningless. Find the missing dimension. |
|
|
152
|
+
| 2 | Are the resources quantities or values? | Quantities | Go to step 3. |
|
|
153
|
+
| 3 | Do two documents on one key add or replace? Ask the business and the writer's intent; history (§3) only shows whether the case has occurred. | Add | Go to step 4. |
|
|
154
|
+
| 3 | Do two documents on one key add or replace? | Replace; lines do not repeat a key | **`info`**. Done. |
|
|
155
|
+
| 3 | Do two documents on one key add or replace? | Replace; lines repeat a key | **Stop: no kind fits.** Three options, none free — see the note below. |
|
|
156
|
+
| 4 | Is there a running total to keep? | Yes | **`balance`**. |
|
|
157
|
+
| 4 | Is there a running total to keep? | No; lines do not repeat a key | **`turnover`**: it keeps no total and answers only over an interval. |
|
|
158
|
+
| 4 | Is there a running total to keep? | No; lines repeat a key | **`balance`**: accept the running total because `turnover` would write one row per line. |
|
|
159
|
+
|
|
160
|
+
**The step 3 "replace, lines repeat" note.** Three options, and the choice belongs in the
|
|
161
|
+
declaration's comment along with the reason: declare `balance` and accept that it adds
|
|
162
|
+
across documents; collapse before posting, which nothing expresses declaratively today; or
|
|
163
|
+
leave it unbound and record it as blocked. Taking the first means **enforcing the
|
|
164
|
+
assumption** — a unique constraint over the dimensions on the state entity, so a second
|
|
165
|
+
writer fails loudly instead of summing. Where you cannot enforce it, say in the comment
|
|
166
|
+
that the semantics are "replace" and the kind is a compromise; the next reader will ask,
|
|
167
|
+
and the honest answer is shorter than the reconstruction.
|
|
168
|
+
|
|
169
|
+
Past uniqueness does not make adding safe: a future correction can otherwise double a
|
|
170
|
+
period the first time someone re-states it.
|
|
171
|
+
|
|
172
|
+
One aside that trips people: `info` with a `recorder` is the shape for a 1C register
|
|
173
|
+
subordinate to a регистратор. But subordination is about *who wrote the row*, not about how
|
|
174
|
+
rows combine — do not let it answer step 1.
|
|
175
|
+
|
|
176
|
+
### When the handler cannot be read
|
|
177
|
+
|
|
178
|
+
Extraction can come up short, the source can be missing for an object, and a heavily
|
|
179
|
+
customised configuration can defeat a parser that works everywhere else. None of the four
|
|
180
|
+
steps actually depends on the code: the code was only ever the fastest way to see the
|
|
181
|
+
answer, and the rows carry the same answer more slowly.
|
|
182
|
+
|
|
183
|
+
| step | without the handler |
|
|
184
|
+
|---|---|
|
|
185
|
+
| 1. lines repeat a key? | compare line counts per document against the rows the register holds for that same document — fewer rows than lines **is** the collapse |
|
|
186
|
+
| 2. quantities or values? | the column types and what the values do over time: a figure that repeats unchanged across documents is a value, one that varies per movement is a quantity |
|
|
187
|
+
| 3. add or replace? | history says only whether it has been exercised, so this one still goes to the business — the code would not have answered it either |
|
|
188
|
+
| 4. running total? | whether the register carries a closing or on-hand figure at all, and whether anything reads it as of a date |
|
|
189
|
+
|
|
190
|
+
Two cautions. A missing handler is worth a moment's suspicion before you route around it:
|
|
191
|
+
if the extraction silently dropped it, other objects are missing too, and finding that out
|
|
192
|
+
now is cheaper than discovering it three registers later. And when you infer rather than
|
|
193
|
+
read, say so in the declaration's comment — an inference that later turns out wrong is much
|
|
194
|
+
easier to unpick when it is labelled as one.
|
|
195
|
+
|
|
196
|
+
## Where a posting takes each value
|
|
197
|
+
|
|
198
|
+
A binding that posts from lines reads its values off the line. Any value that belongs to
|
|
199
|
+
the document goes in `headerMap` beside `map`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
"map": { "product_id": "product_id", "quantity": "quantity" },
|
|
203
|
+
"headerMap": { "dealer_id": "dealer_id", "period_id": "period_id" },
|
|
204
|
+
"lockedFields": ["dealer_id", "period_id"]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
It is explicit rather than a fallback, and that is deliberate: "not on the line, try the
|
|
208
|
+
header" turns a typo into a silent read of an unrelated column, and a dimension that reads
|
|
209
|
+
as null is how a line says it does not participate — so the document would post fewer rows
|
|
210
|
+
than it has lines and say nothing.
|
|
211
|
+
|
|
212
|
+
Two rules that bite:
|
|
213
|
+
|
|
214
|
+
- **Every `headerMap` column must be in `lockedFields`.** The posting is keyed by it.
|
|
215
|
+
Change it under a posted document and reversal looks for a tuple that was never posted
|
|
216
|
+
to, leaving the old row carrying the quantity forever.
|
|
217
|
+
- **A resource may come from the document only on an `info` register.** Anywhere else four
|
|
218
|
+
lines would add the document's amount four times.
|
|
219
|
+
|
|
220
|
+
`headerMap` gives the column **as it stands**. If the 1C handler wrote `НачалоДня(Дата)`
|
|
221
|
+
and the header column is a timestamp, the port needs a day-truncated computed column —
|
|
222
|
+
check whether the time is really there rather than assuming either way, and check whether
|
|
223
|
+
the document already carries a date-typed column that says the same thing.
|
|
224
|
+
|
|
225
|
+
## Whether to carry something at all
|
|
226
|
+
|
|
227
|
+
Before porting a column, a link or a mechanism, ask **who reads it**. This is a grep, and
|
|
228
|
+
it settles arguments that otherwise run on aesthetics.
|
|
229
|
+
|
|
230
|
+
A worked case: a register was subordinate to a регистратор, so every one of its 322,352
|
|
231
|
+
rows knew its document. Carrying that into dForge would have meant declaring a journal.
|
|
232
|
+
The grep found that the only object in the entire infobase naming that register was the
|
|
233
|
+
document that writes it, and the only appearance of the recorder was
|
|
234
|
+
`УдалитьДвиженияРегистратора` — which is unposting, which the platform does on its own. No
|
|
235
|
+
reader, no journal.
|
|
236
|
+
|
|
237
|
+
The same question retires whole categories. A column nothing writes and nothing reads is
|
|
238
|
+
worse than absent, because the next reader assumes it means something.
|
|
239
|
+
|
|
240
|
+
## Identify physical columns from metadata, never from cardinality
|
|
241
|
+
|
|
242
|
+
1C's SQL columns are `_FldNNN`. The temptation, when the metadata is awkward to parse, is
|
|
243
|
+
to identify them by how they behave: the dimension with 86,000 distinct values "must be"
|
|
244
|
+
the batch, the one with 40 "must be" the warehouse. **Do not.** The configuration keys
|
|
245
|
+
every attribute by GUID and `DBNames` maps that GUID to its `_FldNNN`; resolve them that
|
|
246
|
+
way, and use cardinality only as a sanity check on the result.
|
|
247
|
+
|
|
248
|
+
This is not a style preference. In the port this skill comes from, a cardinality guess
|
|
249
|
+
swapped `СерияНоменклатуры` for `ДокументОприходования` — two reference dimensions of the
|
|
250
|
+
same shape — and five measurements were then computed confidently against the wrong
|
|
251
|
+
column, published, and had to be withdrawn. The failure is silent by construction: every
|
|
252
|
+
query still runs, every number still looks plausible, and nothing downstream objects.
|
|
253
|
+
|
|
254
|
+
The re-measurement also found what the guess had hidden: the real batch dimension was
|
|
255
|
+
**empty on all 13.8 million rows**, which is a much larger fact about the client's
|
|
256
|
+
accounting than anything the wrong column could have said.
|
|
257
|
+
|
|
258
|
+
## What a strange column type means
|
|
259
|
+
|
|
260
|
+
1C's physical types are not the author's intent, and the port reads the physical type. Two
|
|
261
|
+
failure modes, both real:
|
|
262
|
+
|
|
263
|
+
- **A value parked in the wrong type.** A date in `nvarchar`, a number in a string column.
|
|
264
|
+
1C coerces silently, so it looks fine in 1C and is useless afterwards. Sweep for it once
|
|
265
|
+
rather than meeting it one column at a time:
|
|
266
|
+
|
|
267
|
+
```sql
|
|
268
|
+
-- every text column holding something shaped like a date
|
|
269
|
+
select '<schema>.<table>.<col>', count(*) from <schema>.<table>
|
|
270
|
+
where <col> ~ '^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- **An empty string that is not empty.** 1C writes `''` where a value is absent, so
|
|
274
|
+
`count(column)` counts it and a column the infobase never filled reads as completely
|
|
275
|
+
full. Every emptiness check against 1C-sourced data must test for `''`, not `NULL`.
|
|
276
|
+
|
|
277
|
+
When you retype a column, remember that a date the configuration formatted into a string
|
|
278
|
+
is **not** a 1C date value and must not get the `_YearOffset` correction — that offset is
|
|
279
|
+
how 1C stores a datetime, and nothing stored a datetime there.
|
|
280
|
+
|
|
281
|
+
## Vendor and client
|
|
282
|
+
|
|
283
|
+
Configurations built on a vendor's (УТП, УПП, УТ) carry far more code than the client
|
|
284
|
+
wrote. Separate them early — the integrator's own objects almost always carry a lowercase
|
|
285
|
+
prefix before a capital (`мсбЗаказ`, `рзТранспорт`), and everything else is the vendor's.
|
|
286
|
+
If no such convention exists or it is applied inconsistently, do not fall back to "what the
|
|
287
|
+
client's documents touch" — that is circular, since which documents are the client's is the
|
|
288
|
+
question. Compare against the vendor's stock configuration instead: an object absent from
|
|
289
|
+
it is the integrator's, and an object present but altered is a modification worth reading
|
|
290
|
+
before porting either version of it.
|
|
291
|
+
|
|
292
|
+
**That comparison is often unavailable, and saying so is part of the answer.** The client
|
|
293
|
+
may not hold the distribution, the version they run may not be the version you can get, and
|
|
294
|
+
obtaining it can require exactly the licence this approach is designed not to need. When
|
|
295
|
+
the stock configuration is missing or its version does not match, record the mismatch
|
|
296
|
+
explicitly and treat every conclusion that rests on the comparison as provisional until
|
|
297
|
+
something measured confirms it.
|
|
298
|
+
|
|
299
|
+
Signals that need no stock configuration, in rough order of strength: the naming
|
|
300
|
+
convention; whether an object holds any data at all (a stock object the client never used
|
|
301
|
+
is not a porting question); which objects the extracted code actually writes to; and the
|
|
302
|
+
language and style of the names themselves. None of these is conclusive alone, which is why
|
|
303
|
+
agreement between several of them is what you are looking for.
|
|
304
|
+
|
|
305
|
+
**When they disagree, treat the object as the client's until something settles it.** The
|
|
306
|
+
two mistakes are not symmetric: porting a vendor object you did not need costs some work
|
|
307
|
+
that can be dropped later, while dropping a client object loses business logic silently and
|
|
308
|
+
is found months on, by someone who cannot reconstruct it. An object with no prefix but real
|
|
309
|
+
data and real writers is exactly the case this rule exists for — and it is common, because
|
|
310
|
+
integrators abandon their own conventions under deadline.
|
|
311
|
+
|
|
312
|
+
Two things follow:
|
|
313
|
+
|
|
314
|
+
- **Port the client's requirements, not the vendor's metadata structure.** The stock
|
|
315
|
+
configuration is the vendor's intellectual property; what it encodes about the client's
|
|
316
|
+
business is what you are after.
|
|
317
|
+
- **Do not assume vendor machinery is unused.** Measure which of the client's own objects
|
|
318
|
+
touch it. A register that looks like pure vendor plumbing may be written by twenty of
|
|
319
|
+
the client's documents, and dropping it silently removes what those documents are for.
|
|
320
|
+
|
|
321
|
+
## Order of work
|
|
322
|
+
|
|
323
|
+
1. Extract and inventory: what exists, how many rows, what is the client's.
|
|
324
|
+
2. Generate and load the plain entities. Reconcile counts and control sums against the
|
|
325
|
+
infobase before going further — a port that cannot reproduce a count cannot be trusted
|
|
326
|
+
about anything harder. If counts or control sums do not reconcile, stop and diagnose the
|
|
327
|
+
generator or loader before proceeding to any handler work; do not proceed with partial
|
|
328
|
+
or approximate data.
|
|
329
|
+
3. Read the posting handlers. Triage by size, then order the work by what blocks each one.
|
|
330
|
+
4. Declare registers and bind documents, starting where nothing blocks.
|
|
331
|
+
5. Keep a running list of what blocks the rest, with a count against each. The list should
|
|
332
|
+
shrink in both directions: things the platform must gain, and things that turn out to
|
|
333
|
+
have been modelled wrong. Expect the second to be more common than it feels.
|
|
334
|
+
|
|
335
|
+
Keep the generated half and the project half separated by a check, not by convention. The
|
|
336
|
+
generic half is most of the code and lifts to the next infobase; the project half is the
|
|
337
|
+
judgement above, written down.
|
|
338
|
+
|
|
339
|
+
## The toolkit
|
|
340
|
+
|
|
341
|
+
Do not write the mechanical half again. `scripts/` in this skill's directory holds the
|
|
342
|
+
Python library that did it on a real port:
|
|
343
|
+
|
|
344
|
+
- reading the configuration and DBNames straight from MS SQL;
|
|
345
|
+
- parsing the metadata;
|
|
346
|
+
- inventorying registers and handlers;
|
|
347
|
+
- generating and validating modules;
|
|
348
|
+
- copying rows with every conversion in the loader;
|
|
349
|
+
- reconciling counts, sums and foreign keys;
|
|
350
|
+
- posting checks;
|
|
351
|
+
- reading MXL print templates.
|
|
352
|
+
|
|
353
|
+
`scripts/README.md` gives the setup and the pipeline step by step.
|
|
354
|
+
|
|
355
|
+
Start a port by copying `scripts/project-template/` into the port's own directory. Set
|
|
356
|
+
`DF1C_ROOT` and `DF1C_PROJECT`, then run `python <skill>/scripts/1c.py --list`. Everything
|
|
357
|
+
this infobase needs goes into the project package, never into `lib/`; `1c.py --boundary`
|
|
358
|
+
checks that. When a tool gets something wrong for a new infobase, fix the library and keep
|
|
359
|
+
the fix general. A second infobase is what turns a port's tooling into a library.
|
|
360
|
+
|
|
361
|
+
## References
|
|
362
|
+
|
|
363
|
+
- `references/measurements.md` — the queries behind each decision, ready to adapt
|
|
364
|
+
- `references/loading.md` — copying the rows: the conversions (keys, empty values, the
|
|
365
|
+
year offset, enums, composite references), register state, triggers, dangling
|
|
366
|
+
references, verification, incremental re-runs
|
|
367
|
+
- `references/print-forms.md` — porting 1C print forms: reading MXL templates, what the
|
|
368
|
+
print procedure decides, reproducing 1C's formatting and its slips, datasets as
|
|
369
|
+
queries, verifying a form
|
|
370
|
+
- `references/environment.md` — the environment traps that cost time repeatedly (Windows
|
|
371
|
+
bash, Cyrillic in shell commands, 1C physical naming, the year offset)
|
|
372
|
+
- Platform side, from the dForge MCP server: `dforge://reference/registers`,
|
|
373
|
+
`dforge://reference/data-migration`, `dforge://reference/print-templates`,
|
|
374
|
+
`dforge://reference/queries`
|