mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-06 03:08:57 +03:00
Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71309d2bc2 | |||
| e1f81a0cde | |||
| e8cb5440d8 | |||
| c496047c6c | |||
| df9541470c | |||
| 27ecfb707d | |||
| 35bec4a2a1 | |||
| e635a5be52 | |||
| dfb258dd3f | |||
| 1866c1335a | |||
| 4813ec5cf2 | |||
| 06ad2f8f99 | |||
| c8ac191a01 | |||
| c9d83b1c92 | |||
| b1a7e414d0 | |||
| 7736ad68a0 | |||
| 501bb58ddb | |||
| cd3a242b12 | |||
| 4f9d9aee97 | |||
| f3466e19fd | |||
| ebf92a8780 | |||
| 3d80191ca7 | |||
| 6c60398406 | |||
| 60de083a05 | |||
| 5690c82ab8 | |||
| dc0382cc06 | |||
| b1e29253d5 | |||
| 07b2ec36af | |||
| b4514dc350 | |||
| 8ad5d80f7f | |||
| fc19df604b | |||
| 76800fc92b | |||
| 36cd63d8bb | |||
| e216db5734 | |||
| a59be4b914 | |||
| 4b8d3b67dc | |||
| 6650d2b516 | |||
| 6b63177687 | |||
| 13174b63b1 | |||
| 433a2955b5 | |||
| 42b96bbd21 | |||
| bd3b40852a | |||
| 336dade274 | |||
| 99e9c1e0b8 | |||
| 3c3ed2ff46 | |||
| d75b4d96ca | |||
| 704fbc9f28 | |||
| cd74afa5e0 | |||
| 370873511d | |||
| 9ced410d8f | |||
| 50ba38a9f6 | |||
| d4b105bbe4 | |||
| 204a262746 | |||
| 210bde7b2e | |||
| a932841418 | |||
| 576f6dda8a | |||
| 1a5b788305 | |||
| b39da27d20 | |||
| 1f23afe6f9 | |||
| eda7279de0 | |||
| b3c2602439 | |||
| 853313faed | |||
| b7fbede819 | |||
| bdc38caffa | |||
| 2a86df1c98 | |||
| 2e1487fd17 | |||
| 3729b63b89 | |||
| c3ec51e174 | |||
| fb58a04700 | |||
| a30b720c89 | |||
| 54d47aadad | |||
| 3f23be8219 | |||
| f5dd677ac8 | |||
| 02e9053d00 | |||
| bd462f4cc3 | |||
| be74d224be | |||
| 1b46eb4d85 | |||
| 1cea9e794e | |||
| 00fafd4af5 | |||
| e30b518935 | |||
| afdfc97fb1 | |||
| 82e70d2c30 | |||
| aedf6df674 | |||
| 0d31d17204 | |||
| 2206c4cf3e | |||
| 8a9f285da9 | |||
| e5e3f199f2 | |||
| 3ee715939b | |||
| b8ab791456 | |||
| 97a2ef91d5 | |||
| a41897a966 | |||
| 076bbcb9d5 | |||
| 3c48451704 | |||
| 801ceea2c0 | |||
| 63de8bd27c | |||
| d1e770c843 | |||
| e3069ceb36 | |||
| 0b2c09f8d9 | |||
| 0d5d3451ff | |||
| 7a8f437e77 | |||
| dc4ffa1fc8 | |||
| 6805b9da02 | |||
| aa93031a3f | |||
| 037062c728 | |||
| 9cbda1989a | |||
| d34ddd41ff | |||
| 96d1dea552 | |||
| 83b289de32 | |||
| 384e68cab4 | |||
| 46e065adb9 | |||
| fdfe4ac2f4 | |||
| afacaa5ade | |||
| 73242ee60e | |||
| e2924c4ae0 | |||
| 123fc41b06 | |||
| 338faa253d | |||
| d3520a8945 | |||
| e731bde7f0 | |||
| 321e426f98 | |||
| 54c04cfe76 | |||
| 9727635e5d | |||
| 6404016afb | |||
| 17afd807d2 | |||
| 9bfc431a6a | |||
| d755c41233 | |||
| 87e636f644 | |||
| ae1dcaac07 | |||
| d155086444 | |||
| 940eafb8e4 | |||
| e4dcef8c90 | |||
| 08688f5cab | |||
| 1bc5e8f07a | |||
| 358830c65b | |||
| 3ba6072660 | |||
| 20adf4f463 | |||
| d5aacc9e60 | |||
| 3bd69baae6 | |||
| b0fdc32053 | |||
| 731a652cae | |||
| dd88f78969 | |||
| ff068202e3 | |||
| 91798e3838 | |||
| 9620c3846a | |||
| ffc34904c5 | |||
| b008c820f9 | |||
| e5697d6f5c | |||
| 29ee294de6 | |||
| d72cbacfd6 | |||
| c8a7ba4683 | |||
| ea8b28280d | |||
| eebc2a0679 | |||
| 974e8ff5e4 | |||
| 47c2e5d48f | |||
| e56a932ee2 | |||
| 69f5e0b7ae | |||
| 84462e3dd9 | |||
| d574320849 | |||
| 90ff1d53b6 | |||
| 09bc0d00b8 | |||
| 66c6dc7aa1 | |||
| 1d89b3ec69 | |||
| 43d1d09ee8 | |||
| 404e6c6851 | |||
| 009022d04b | |||
| 70e684d189 | |||
| 72bad1aaaa | |||
| 29a5cbae4c | |||
| be1bbb2d26 | |||
| 5281fd54f2 | |||
| f697ba3ff3 | |||
| 0778cc89ee | |||
| 28b2765f68 | |||
| 0d116863ec | |||
| bc6bc01047 | |||
| 972cd5061d | |||
| 4565808b77 | |||
| 250978c2fd | |||
| 4551525718 | |||
| 9422c66df4 | |||
| a1b3fdd4e2 | |||
| d6d44b8b35 | |||
| b480fa0b49 | |||
| 44a84f8ce7 | |||
| dcacecff7f | |||
| 8b38f8f78d | |||
| 74b3f76a32 | |||
| 671be7c6b5 | |||
| 4a697db47a | |||
| 34f582ddef | |||
| eec626eb6f | |||
| 312d058412 | |||
| 95776a4248 | |||
| 347722ef0d | |||
| 9f6793abae | |||
| 0ddb675502 | |||
| d2dfcfd160 |
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "cc-1c-skills",
|
||||
"interface": {
|
||||
"displayName": "1C Skills"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "1c-skills",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
|
||||
"ref": "port-codex"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE"
|
||||
},
|
||||
"category": "Development"
|
||||
},
|
||||
{
|
||||
"name": "1c-skills-py",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
|
||||
"ref": "port-codex-py"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE"
|
||||
},
|
||||
"category": "Development"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json",
|
||||
"name": "cc-1c-skills",
|
||||
"description": "Маркетплейс навыков для разработки на платформе 1С:Предприятие",
|
||||
"owner": {
|
||||
"name": "Nikolay Shirokov"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "1c-skills",
|
||||
"source": "./",
|
||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент."
|
||||
},
|
||||
{
|
||||
"name": "1c-skills-py",
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "Nikolay-Shirokov/cc-1c-skills",
|
||||
"ref": "port-claude-code-py"
|
||||
},
|
||||
"description": "[Python] То же — для Linux/Mac или когда PowerShell недоступен."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
||||
"name": "1c-skills",
|
||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
||||
"author": {
|
||||
"name": "Nikolay Shirokov"
|
||||
},
|
||||
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
||||
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"1c",
|
||||
"1c-dev",
|
||||
"cf",
|
||||
"cfe",
|
||||
"epf",
|
||||
"erf",
|
||||
"metadata",
|
||||
"configuration",
|
||||
"extension",
|
||||
"form",
|
||||
"report",
|
||||
"skd",
|
||||
"data-processor",
|
||||
"mxl",
|
||||
"web-client",
|
||||
"testing",
|
||||
"test-automation"
|
||||
],
|
||||
"skills": "./.claude/skills/"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: cf-edit
|
||||
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию
|
||||
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу
|
||||
argument-hint: -ConfigPath <path> -Operation <op> -Value <value>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -24,7 +24,7 @@ allowed-tools:
|
||||
| `NoValidate` | Пропустить авто-валидацию |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
||||
```
|
||||
|
||||
## Операции
|
||||
@@ -32,27 +32,29 @@ powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -Conf
|
||||
| Операция | Формат Value | Описание |
|
||||
|----------|-------------|----------|
|
||||
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
|
||||
| `add-childObject` | `Type.Name` (batch `;;`) | Добавить объект в ChildObjects |
|
||||
| `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически |
|
||||
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
|
||||
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
|
||||
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
|
||||
| `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию |
|
||||
| `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) |
|
||||
| `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) |
|
||||
|
||||
Подробнее: `reference.md` в каталоге навыка.
|
||||
Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md)
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Изменить версию и поставщика
|
||||
... -ConfigPath test-tmp/cf -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
|
||||
... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
|
||||
|
||||
# Добавить объекты
|
||||
... -ConfigPath test-tmp/cf -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
|
||||
... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
|
||||
|
||||
# Удалить объект
|
||||
... -ConfigPath test-tmp/cf -Operation remove-childObject -Value "Catalog.Устаревший"
|
||||
... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший"
|
||||
|
||||
# Роли по умолчанию
|
||||
... -ConfigPath test-tmp/cf -Operation add-defaultRole -Value "ПолныеПрава"
|
||||
... -ConfigPath test-tmp/cf -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
|
||||
... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава"
|
||||
... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
### Enum
|
||||
| Свойство | Допустимые значения |
|
||||
|----------|---------------------|
|
||||
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_27`, `DontUse` |
|
||||
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` |
|
||||
| `ConfigurationExtensionCompatibilityMode` | то же |
|
||||
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
|
||||
| `ScriptVariant` | `Russian`, `English` |
|
||||
@@ -21,7 +21,7 @@
|
||||
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
|
||||
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||
| `InterfaceCompatibilityMode` | `Taxi`, `TaxiEnableVersion8_2`, `Version8_2` |
|
||||
| `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` |
|
||||
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
|
||||
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
|
||||
|
||||
@@ -35,10 +35,7 @@
|
||||
|
||||
Формат: `Type.Name` — XML-тип и имя объекта через точку.
|
||||
|
||||
При добавлении объект вставляется в каноническую позицию:
|
||||
1. Находит последний элемент того же типа → вставляет после
|
||||
2. Если тип отсутствует → находит последний элемент предшествующего типа → вставляет после
|
||||
3. Внутри одного типа — алфавитный порядок
|
||||
**Важно про `add-childObject`**: регистрирует в `<ChildObjects>` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов.
|
||||
|
||||
Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"`
|
||||
|
||||
@@ -48,6 +45,99 @@ Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"
|
||||
|
||||
`set-defaultRoles` полностью заменяет список ролей.
|
||||
|
||||
## set-panels
|
||||
|
||||
Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране.
|
||||
|
||||
`value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона).
|
||||
|
||||
**Запись** — одна из:
|
||||
- Строка-алиас (одна панель в этом слоте)
|
||||
- Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом)
|
||||
|
||||
**Алиасы панелей:**
|
||||
|
||||
| Алиас | Панель |
|
||||
|-------|--------|
|
||||
| `sections` | Панель разделов |
|
||||
| `open` | Панель открытых |
|
||||
| `favorites` | Панель избранного |
|
||||
| `history` | Панель истории |
|
||||
| `functions` | Панель функций текущего раздела |
|
||||
|
||||
**Семантика:**
|
||||
- Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов `<top>`/...)
|
||||
- `{"group":[...]}` → один тег с `<group>`-обёрткой, элементы внутри идут стеком
|
||||
|
||||
**Пример** (DefinitionFile):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"operation": "set-panels",
|
||||
"value": {
|
||||
"top": ["open"],
|
||||
"left": ["sections"],
|
||||
"right": [{ "group": ["favorites", "history"] }],
|
||||
"bottom": ["functions"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`.
|
||||
|
||||
## set-home-page
|
||||
|
||||
Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует.
|
||||
|
||||
`value` — объект:
|
||||
|
||||
| Ключ | Канонич. (XML) | Описание |
|
||||
|------|----------------|----------|
|
||||
| `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` |
|
||||
| `left` | `LeftColumn` | массив записей форм |
|
||||
| `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) |
|
||||
|
||||
Принимаются и короткие и канонич. ключи (XML-имена) — оба работают.
|
||||
|
||||
**Запись формы** — одна из:
|
||||
- Строка `"<form>"` — только имя формы, дефолты `height=10`, `visibility=true`
|
||||
- Объект `{form, height?, visibility?, roles?}`
|
||||
|
||||
| Поле | Канонич. | Дефолт | Описание |
|
||||
|------|----------|--------|----------|
|
||||
| `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) |
|
||||
| `height` | `Height` | `10` | Высота |
|
||||
| `visibility` | `Visibility` | `true` | Общая видимость (`<xr:Common>`) |
|
||||
| `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям |
|
||||
|
||||
**Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`.
|
||||
|
||||
**Пример:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"operation": "set-home-page",
|
||||
"value": {
|
||||
"template": "TwoColumnsVariableWidth",
|
||||
"left": [
|
||||
"CommonForm.НачалоРаботы",
|
||||
{ "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false },
|
||||
{ "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 },
|
||||
{
|
||||
"form": "CommonForm.РабочийСтолОператора",
|
||||
"visibility": false,
|
||||
"roles": { "Role.Оператор": true, "Role.ПолныеПрава": false }
|
||||
}
|
||||
],
|
||||
"right": [
|
||||
{ "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## DefinitionFile (JSON)
|
||||
|
||||
```json
|
||||
@@ -58,6 +148,3 @@ Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"
|
||||
]
|
||||
```
|
||||
|
||||
## Авто-валидация
|
||||
|
||||
После сохранения автоматически запускается `cf-validate` (если не указан `-NoValidate`).
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# cf-edit v1.0 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.4 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ConfigPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
|
||||
[string]$DefinitionFile,
|
||||
[ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles")]
|
||||
[ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles","set-panels","set-home-page")]
|
||||
[string]$Operation,
|
||||
[string]$Value,
|
||||
[switch]$NoValidate
|
||||
@@ -27,6 +27,7 @@ if (Test-Path $ConfigPath -PathType Container) {
|
||||
}
|
||||
if (-not (Test-Path $ConfigPath)) { Write-Error "File not found: $ConfigPath"; exit 1 }
|
||||
$resolvedPath = (Resolve-Path $ConfigPath).Path
|
||||
$script:configDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
|
||||
|
||||
# --- Load XML with PreserveWhitespace ---
|
||||
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
||||
@@ -87,6 +88,22 @@ $script:typeOrder = @(
|
||||
"BusinessProcess","Task","IntegrationService"
|
||||
)
|
||||
|
||||
# --- Type → on-disk directory name (plural) ---
|
||||
$script:typeToDir = @{
|
||||
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
|
||||
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"; "CommonTemplate"="CommonTemplates"
|
||||
"FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"; "CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"
|
||||
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
||||
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"; "SettingsStorage"="SettingsStorages"; "FunctionalOption"="FunctionalOptions"
|
||||
"FunctionalOptionsParameter"="FunctionalOptionsParameters"; "DefinedType"="DefinedTypes"; "CommonCommand"="CommonCommands"; "CommandGroup"="CommandGroups"
|
||||
"Constant"="Constants"; "CommonForm"="CommonForms"; "Catalog"="Catalogs"; "Document"="Documents"
|
||||
"DocumentNumerator"="DocumentNumerators"; "Sequence"="Sequences"; "DocumentJournal"="DocumentJournals"; "Enum"="Enums"
|
||||
"Report"="Reports"; "DataProcessor"="DataProcessors"; "InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"; "ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
|
||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
|
||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"; "IntegrationService"="IntegrationServices"
|
||||
}
|
||||
|
||||
# --- XML manipulation helpers (from subsystem-edit pattern) ---
|
||||
function Get-ChildIndent($container) {
|
||||
foreach ($child in $container.ChildNodes) {
|
||||
@@ -247,6 +264,29 @@ function Do-AddChildObject([string]$batchVal) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check that the referenced object actually exists on disk.
|
||||
# cf-edit add-childObject is a low-level operation for rare scenarios
|
||||
# (e.g. restoring a rolled-back Configuration.xml when object files are intact).
|
||||
# For creating NEW objects, meta-compile/role-compile/subsystem-compile already
|
||||
# auto-register in Configuration.xml — calling cf-edit add-childObject there is
|
||||
# unnecessary and error-prone.
|
||||
$typeDir = $script:typeToDir[$typeName]
|
||||
$objFile = Join-Path (Join-Path $script:configDir $typeDir) "$objNameVal.xml"
|
||||
if (-not (Test-Path $objFile)) {
|
||||
$hintSkill = switch ($typeName) {
|
||||
"Subsystem" { "subsystem-compile" }
|
||||
"Role" { "role-compile" }
|
||||
default { "meta-compile" }
|
||||
}
|
||||
Write-Error @"
|
||||
Object file not found: $typeDir/$objNameVal.xml
|
||||
cf-edit add-childObject only references objects that already exist on disk.
|
||||
To create a new $typeName, use $hintSkill (auto-registers in Configuration.xml):
|
||||
/$hintSkill with {"type":"$typeName","name":"$objNameVal"}
|
||||
"@
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Dedup check
|
||||
$existing = $false
|
||||
foreach ($child in $script:childObjsEl.ChildNodes) {
|
||||
@@ -404,6 +444,308 @@ function Do-RemoveDefaultRole([string]$batchVal) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Operation: set-panels ---
|
||||
# Canonical English aliases — preferred form, used in docs and error messages.
|
||||
$script:panelUuids = @{
|
||||
"sections" = "b553047f-c9aa-4157-978d-448ecad24248"
|
||||
"open" = "cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"
|
||||
"favorites" = "13322b22-3960-4d68-93a6-fe2dd7f28ca3"
|
||||
"history" = "c933ac92-92cd-459d-81cc-e0c8a83ced99"
|
||||
"functions" = "b2735bd3-d822-4430-ba59-c9e869693b24"
|
||||
}
|
||||
# Russian synonyms — silently accepted (cf-info displays Russian names; users
|
||||
# may copy them straight into cf-edit value).
|
||||
$script:panelSynonyms = @{
|
||||
"разделов" = "sections"; "разделы" = "sections"
|
||||
"открытых" = "open"; "открытые" = "open"
|
||||
"избранного" = "favorites";"избранное" = "favorites"
|
||||
"истории" = "history"; "история" = "history"
|
||||
"функций" = "functions";"функции" = "functions"
|
||||
}
|
||||
|
||||
function Build-PanelEntryXml($entry, [string]$indent) {
|
||||
# String alias -> <panel><uuid>...</uuid></panel>
|
||||
if ($entry -is [string]) {
|
||||
$key = $entry.ToLowerInvariant()
|
||||
if ($script:panelSynonyms.ContainsKey($key)) { $key = $script:panelSynonyms[$key] }
|
||||
if (-not $script:panelUuids.ContainsKey($key)) {
|
||||
Write-Error "Unknown panel alias '$entry'. Allowed: $(($script:panelUuids.Keys | Sort-Object) -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
$u = $script:panelUuids[$key]
|
||||
$instId = [guid]::NewGuid().ToString()
|
||||
return "$indent<panel id=`"$instId`">`r`n$indent`t<uuid>$u</uuid>`r`n$indent</panel>"
|
||||
}
|
||||
# Object {group: [...]} -> <group id=""><group><panel/></group>...</group> (stack)
|
||||
if ($entry.PSObject.Properties['group']) {
|
||||
$children = $entry.group
|
||||
if (-not $children -or $children.Count -eq 0) {
|
||||
Write-Error "group must contain at least one entry"
|
||||
exit 1
|
||||
}
|
||||
$gid = [guid]::NewGuid().ToString()
|
||||
$inner = ""
|
||||
foreach ($child in $children) {
|
||||
$childXml = Build-PanelEntryXml $child "$indent`t`t"
|
||||
$inner += "$indent`t<group>`r`n$childXml`r`n$indent`t</group>`r`n"
|
||||
}
|
||||
return "$indent<group id=`"$gid`">`r`n$inner$indent</group>"
|
||||
}
|
||||
Write-Error "Panel entry must be a string alias or object {group:[...]}, got: $($entry | ConvertTo-Json -Compress)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Do-SetPanels($valArg) {
|
||||
# Accept string (JSON), PSCustomObject, or hashtable
|
||||
$layout = $valArg
|
||||
if ($layout -is [string]) {
|
||||
try { $layout = $layout | ConvertFrom-Json } catch {
|
||||
Write-Error "set-panels value must be valid JSON object, got: $valArg"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
if (-not $layout) {
|
||||
Write-Error "set-panels value is empty"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sides = @("top","left","right","bottom")
|
||||
$bodyParts = @()
|
||||
foreach ($side in $sides) {
|
||||
$entries = $null
|
||||
if ($layout.PSObject.Properties[$side]) { $entries = $layout.$side }
|
||||
if ($null -eq $entries) { continue }
|
||||
# Normalize to array
|
||||
if ($entries -isnot [System.Array] -and $entries -isnot [System.Collections.IList]) {
|
||||
$entries = @($entries)
|
||||
}
|
||||
foreach ($entry in $entries) {
|
||||
$entryXml = Build-PanelEntryXml $entry "`t`t"
|
||||
$bodyParts += "`t<$side>`r`n$entryXml`r`n`t</$side>"
|
||||
}
|
||||
}
|
||||
|
||||
# Reject unknown side keys (catches typos like "Top" vs "top")
|
||||
foreach ($prop in $layout.PSObject.Properties) {
|
||||
if ($sides -notcontains $prop.Name) {
|
||||
Write-Error "Unknown side '$($prop.Name)'. Allowed: $($sides -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$body = $bodyParts -join "`r`n"
|
||||
$declarations = @"
|
||||
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
|
||||
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
|
||||
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
|
||||
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
|
||||
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
|
||||
"@
|
||||
$bodyBlock = if ($body) { "$body`r`n" } else { "" }
|
||||
$caiXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
$bodyBlock$declarations
|
||||
</ClientApplicationInterface>
|
||||
"@
|
||||
|
||||
$extDir = Join-Path $script:configDir "Ext"
|
||||
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
||||
$caiPath = Join-Path $extDir "ClientApplicationInterface.xml"
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($caiPath, $caiXml, $utf8Bom)
|
||||
$script:modifyCount++
|
||||
Info "Wrote panel layout: $caiPath"
|
||||
}
|
||||
|
||||
# --- Operation: set-home-page ---
|
||||
# Russian → English type aliases for form-ref normalization
|
||||
$script:ruTypeMap = @{
|
||||
"справочник" = "Catalog"
|
||||
"документ" = "Document"
|
||||
"перечисление" = "Enum"
|
||||
"отчёт" = "Report"
|
||||
"отчет" = "Report"
|
||||
"обработка" = "DataProcessor"
|
||||
"общаяформа" = "CommonForm"
|
||||
"журналдокументов" = "DocumentJournal"
|
||||
"планвидовхарактеристик" = "ChartOfCharacteristicTypes"
|
||||
"плансчетов" = "ChartOfAccounts"
|
||||
"планвидоврасчета" = "ChartOfCalculationTypes"
|
||||
"планвидоврасчёта" = "ChartOfCalculationTypes"
|
||||
"регистрсведений" = "InformationRegister"
|
||||
"регистрнакопления" = "AccumulationRegister"
|
||||
"регистрбухгалтерии" = "AccountingRegister"
|
||||
"регистррасчета" = "CalculationRegister"
|
||||
"регистррасчёта" = "CalculationRegister"
|
||||
"бизнеспроцесс" = "BusinessProcess"
|
||||
"задача" = "Task"
|
||||
"планобмена" = "ExchangePlan"
|
||||
"хранилищенастроек" = "SettingsStorage"
|
||||
}
|
||||
# plural folder → singular type
|
||||
$script:dirToType = @{}
|
||||
foreach ($k in $script:typeToDir.Keys) { $script:dirToType[$script:typeToDir[$k].ToLowerInvariant()] = $k }
|
||||
|
||||
function Normalize-FormRef([string]$s) {
|
||||
$s = $s.Trim()
|
||||
if (-not $s) { return $s }
|
||||
# UUID — leave as-is
|
||||
if ($s -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { return $s }
|
||||
# Path form?
|
||||
if ($s.Contains("/") -or $s.Contains("\")) {
|
||||
$parts = $s.Replace("\","/").Split("/") | Where-Object { $_ -ne "" -and $_.ToLowerInvariant() -ne "ext" }
|
||||
# Strip trailing Form.xml
|
||||
if ($parts.Count -gt 0 -and $parts[-1].ToLowerInvariant() -eq "form.xml") {
|
||||
$parts = @($parts[0..($parts.Count - 2)])
|
||||
}
|
||||
if ($parts.Count -ge 2) {
|
||||
$typeDir = $parts[0]
|
||||
$typeSingular = $script:dirToType[$typeDir.ToLowerInvariant()]
|
||||
if ($typeSingular) {
|
||||
if ($typeSingular -eq "CommonForm" -and $parts.Count -ge 2) {
|
||||
return "CommonForm.$($parts[1])"
|
||||
}
|
||||
if ($parts.Count -ge 4 -and $parts[2].ToLowerInvariant() -eq "forms") {
|
||||
return "$typeSingular.$($parts[1]).Form.$($parts[3])"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $s
|
||||
}
|
||||
# Dot form — translate Russian head and 'Форма' segment, auto-insert 'Form'
|
||||
$segs = $s.Split(".")
|
||||
if ($segs.Count -ge 1) {
|
||||
$head = $segs[0].ToLowerInvariant()
|
||||
if ($script:ruTypeMap.ContainsKey($head)) { $segs[0] = $script:ruTypeMap[$head] }
|
||||
for ($i = 1; $i -lt $segs.Count; $i++) {
|
||||
if ($segs[$i] -eq "Форма") { $segs[$i] = "Form" }
|
||||
}
|
||||
# Auto-insert Form: for object types with 3 segments (Type.Object.FormName)
|
||||
if ($segs.Count -eq 3 -and $script:typeOrder -contains $segs[0] -and $segs[0] -ne "CommonForm") {
|
||||
$segs = @($segs[0], $segs[1], "Form", $segs[2])
|
||||
}
|
||||
}
|
||||
return ($segs -join ".")
|
||||
}
|
||||
|
||||
# Accept short DSL or canonical XML keys (silently)
|
||||
function Get-FieldValue($obj, [string[]]$keys) {
|
||||
foreach ($k in $keys) {
|
||||
if ($obj.PSObject.Properties[$k]) { return $obj.PSObject.Properties[$k].Value }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Build-HomePageItemXml($entry, [string]$indent) {
|
||||
# Resolve fields
|
||||
if ($entry -is [string]) {
|
||||
$formRef = Normalize-FormRef $entry
|
||||
$height = 10
|
||||
$common = $true
|
||||
$roles = $null
|
||||
} else {
|
||||
$formRaw = Get-FieldValue $entry @("form","Form")
|
||||
if (-not $formRaw) { Write-Error "Home page item: 'form' is required, got: $($entry | ConvertTo-Json -Compress)"; exit 1 }
|
||||
$formRef = Normalize-FormRef ([string]$formRaw)
|
||||
$h = Get-FieldValue $entry @("height","Height")
|
||||
$height = if ($null -ne $h) { [int]$h } else { 10 }
|
||||
$vis = Get-FieldValue $entry @("visibility","Visibility")
|
||||
$common = if ($null -ne $vis) { [bool]$vis } else { $true }
|
||||
$roles = Get-FieldValue $entry @("roles")
|
||||
}
|
||||
|
||||
$visParts = @()
|
||||
$visParts += "$indent`t`t<xr:Common>$($common.ToString().ToLower())</xr:Common>"
|
||||
if ($roles) {
|
||||
# roles is PSCustomObject {Role.X: bool, ...}
|
||||
foreach ($prop in $roles.PSObject.Properties) {
|
||||
$rname = $prop.Name
|
||||
if (-not $rname.StartsWith("Role.") -and -not ($rname -match '^[0-9a-fA-F]{8}-')) { $rname = "Role.$rname" }
|
||||
$rval = ([bool]$prop.Value).ToString().ToLower()
|
||||
$escName = [System.Security.SecurityElement]::Escape($rname)
|
||||
$visParts += "$indent`t`t<xr:Value name=`"$escName`">$rval</xr:Value>"
|
||||
}
|
||||
}
|
||||
$visBlock = $visParts -join "`r`n"
|
||||
$escForm = [System.Security.SecurityElement]::Escape($formRef)
|
||||
return @"
|
||||
$indent<Item>
|
||||
$indent`t<Form>$escForm</Form>
|
||||
$indent`t<Height>$height</Height>
|
||||
$indent`t<Visibility>
|
||||
$visBlock
|
||||
$indent`t</Visibility>
|
||||
$indent</Item>
|
||||
"@
|
||||
}
|
||||
|
||||
function Do-SetHomePage($valArg) {
|
||||
$layout = $valArg
|
||||
if ($layout -is [string]) {
|
||||
try { $layout = $layout | ConvertFrom-Json } catch {
|
||||
Write-Error "set-home-page value must be valid JSON object"; exit 1
|
||||
}
|
||||
}
|
||||
if (-not $layout) { Write-Error "set-home-page value is empty"; exit 1 }
|
||||
|
||||
$allowedTemplates = @("OneColumn","TwoColumnsEqualWidth","TwoColumnsVariableWidth")
|
||||
$tmpl = Get-FieldValue $layout @("template","WorkingAreaTemplate")
|
||||
if (-not $tmpl) { $tmpl = "TwoColumnsEqualWidth" }
|
||||
if ($allowedTemplates -notcontains $tmpl) {
|
||||
Write-Error "Unknown template '$tmpl'. Allowed: $($allowedTemplates -join ', ')"; exit 1
|
||||
}
|
||||
|
||||
$leftItems = Get-FieldValue $layout @("left","LeftColumn")
|
||||
$rightItems = Get-FieldValue $layout @("right","RightColumn")
|
||||
|
||||
# Reject unknown keys
|
||||
$known = @("template","WorkingAreaTemplate","left","LeftColumn","right","RightColumn")
|
||||
foreach ($prop in $layout.PSObject.Properties) {
|
||||
if ($known -notcontains $prop.Name) {
|
||||
Write-Error "Unknown key '$($prop.Name)'. Allowed: template, left, right"; exit 1
|
||||
}
|
||||
}
|
||||
|
||||
if ($tmpl -eq "OneColumn" -and $rightItems) {
|
||||
Write-Error "Template 'OneColumn' cannot have items in 'right' column"; exit 1
|
||||
}
|
||||
|
||||
function Build-Column([string]$tag, $items) {
|
||||
if (-not $items) { return "`t<$tag/>" }
|
||||
if ($items -isnot [System.Array] -and $items -isnot [System.Collections.IList]) {
|
||||
$items = @($items)
|
||||
}
|
||||
if ($items.Count -eq 0) { return "`t<$tag/>" }
|
||||
$itemBlocks = @()
|
||||
foreach ($it in $items) {
|
||||
$itemBlocks += Build-HomePageItemXml $it "`t`t"
|
||||
}
|
||||
$body = $itemBlocks -join "`r`n"
|
||||
return "`t<$tag>`r`n$body`r`n`t</$tag>"
|
||||
}
|
||||
|
||||
$leftXml = Build-Column "LeftColumn" $leftItems
|
||||
$rightXml = Build-Column "RightColumn" $rightItems
|
||||
|
||||
$hpXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<WorkingAreaTemplate>$tmpl</WorkingAreaTemplate>
|
||||
$leftXml
|
||||
$rightXml
|
||||
</HomePageWorkArea>
|
||||
"@
|
||||
|
||||
$extDir = Join-Path $script:configDir "Ext"
|
||||
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
||||
$hpPath = Join-Path $extDir "HomePageWorkArea.xml"
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($hpPath, $hpXml, $utf8Bom)
|
||||
$script:modifyCount++
|
||||
Info "Wrote home page layout: $hpPath"
|
||||
}
|
||||
|
||||
# --- Operation: set-defaultRoles ---
|
||||
function Do-SetDefaultRoles([string]$batchVal) {
|
||||
$items = Parse-BatchValue $batchVal
|
||||
@@ -468,15 +810,19 @@ if ($DefinitionFile) {
|
||||
|
||||
foreach ($op in $operations) {
|
||||
$opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" }
|
||||
$opValue = if ($op.value) { "$($op.value)" } else { "$Value" }
|
||||
# Pass value through as-is (object or string); set-panels needs object form
|
||||
$opValue = if ($null -ne $op.value) { $op.value } else { $Value }
|
||||
$opValueStr = if ($opValue -is [string]) { $opValue } else { "$opValue" }
|
||||
|
||||
switch ($opName) {
|
||||
"modify-property" { Do-ModifyProperty $opValue }
|
||||
"add-childObject" { Do-AddChildObject $opValue }
|
||||
"remove-childObject" { Do-RemoveChildObject $opValue }
|
||||
"add-defaultRole" { Do-AddDefaultRole $opValue }
|
||||
"remove-defaultRole" { Do-RemoveDefaultRole $opValue }
|
||||
"set-defaultRoles" { Do-SetDefaultRoles $opValue }
|
||||
"modify-property" { Do-ModifyProperty $opValueStr }
|
||||
"add-childObject" { Do-AddChildObject $opValueStr }
|
||||
"remove-childObject" { Do-RemoveChildObject $opValueStr }
|
||||
"add-defaultRole" { Do-AddDefaultRole $opValueStr }
|
||||
"remove-defaultRole" { Do-RemoveDefaultRole $opValueStr }
|
||||
"set-defaultRoles" { Do-SetDefaultRoles $opValueStr }
|
||||
"set-panels" { Do-SetPanels $opValue }
|
||||
"set-home-page" { Do-SetHomePage $opValue }
|
||||
default { Write-Error "Unknown operation: $opName"; exit 1 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-edit v1.0 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.4 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid as _uuid
|
||||
from html import escape as html_escape
|
||||
from lxml import etree
|
||||
|
||||
@@ -32,6 +33,22 @@ TYPE_ORDER = [
|
||||
"BusinessProcess", "Task", "IntegrationService",
|
||||
]
|
||||
|
||||
# Type → on-disk directory name (plural)
|
||||
TYPE_TO_DIR = {
|
||||
"Language": "Languages", "Subsystem": "Subsystems", "StyleItem": "StyleItems", "Style": "Styles",
|
||||
"CommonPicture": "CommonPictures", "SessionParameter": "SessionParameters", "Role": "Roles", "CommonTemplate": "CommonTemplates",
|
||||
"FilterCriterion": "FilterCriteria", "CommonModule": "CommonModules", "CommonAttribute": "CommonAttributes", "ExchangePlan": "ExchangePlans",
|
||||
"XDTOPackage": "XDTOPackages", "WebService": "WebServices", "HTTPService": "HTTPServices", "WSReference": "WSReferences",
|
||||
"EventSubscription": "EventSubscriptions", "ScheduledJob": "ScheduledJobs", "SettingsStorage": "SettingsStorages", "FunctionalOption": "FunctionalOptions",
|
||||
"FunctionalOptionsParameter": "FunctionalOptionsParameters", "DefinedType": "DefinedTypes", "CommonCommand": "CommonCommands", "CommandGroup": "CommandGroups",
|
||||
"Constant": "Constants", "CommonForm": "CommonForms", "Catalog": "Catalogs", "Document": "Documents",
|
||||
"DocumentNumerator": "DocumentNumerators", "Sequence": "Sequences", "DocumentJournal": "DocumentJournals", "Enum": "Enums",
|
||||
"Report": "Reports", "DataProcessor": "DataProcessors", "InformationRegister": "InformationRegisters", "AccumulationRegister": "AccumulationRegisters",
|
||||
"ChartOfCharacteristicTypes": "ChartsOfCharacteristicTypes", "ChartOfAccounts": "ChartsOfAccounts", "AccountingRegister": "AccountingRegisters",
|
||||
"ChartOfCalculationTypes": "ChartsOfCalculationTypes", "CalculationRegister": "CalculationRegisters",
|
||||
"BusinessProcess": "BusinessProcesses", "Task": "Tasks", "IntegrationService": "IntegrationServices",
|
||||
}
|
||||
|
||||
ML_PROPS = ["Synonym", "BriefInformation", "DetailedInformation", "Copyright", "VendorInformationAddress", "ConfigurationInformationAddress"]
|
||||
SCALAR_PROPS = ["Name", "Version", "Vendor", "Comment", "NamePrefix", "UpdateCatalogAddress"]
|
||||
REF_PROPS = ["DefaultLanguage"]
|
||||
@@ -131,7 +148,9 @@ def parse_batch_value(val):
|
||||
|
||||
def save_xml_bom(tree, path):
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
@@ -141,9 +160,9 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Edit 1C configuration root (Configuration.xml)", allow_abbrev=False)
|
||||
parser.add_argument("-ConfigPath", required=True)
|
||||
parser.add_argument("-ConfigPath", "-Path", required=True)
|
||||
parser.add_argument("-DefinitionFile", default=None)
|
||||
parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles"])
|
||||
parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels", "set-home-page"])
|
||||
parser.add_argument("-Value", default=None)
|
||||
parser.add_argument("-NoValidate", action="store_true")
|
||||
args = parser.parse_args()
|
||||
@@ -169,6 +188,7 @@ def main():
|
||||
print(f"File not found: {config_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
resolved_path = os.path.abspath(config_path)
|
||||
config_dir = os.path.dirname(resolved_path)
|
||||
|
||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(resolved_path, xml_parser)
|
||||
@@ -283,6 +303,25 @@ def main():
|
||||
sys.exit(1)
|
||||
type_idx = TYPE_ORDER.index(type_name)
|
||||
|
||||
# Check that the referenced object actually exists on disk.
|
||||
# cf-edit add-childObject is a low-level operation for rare scenarios
|
||||
# (e.g. restoring a rolled-back Configuration.xml when object files are intact).
|
||||
# For creating NEW objects, meta-compile/role-compile/subsystem-compile already
|
||||
# auto-register in Configuration.xml — calling cf-edit add-childObject there is
|
||||
# unnecessary and error-prone.
|
||||
type_dir = TYPE_TO_DIR.get(type_name)
|
||||
obj_file = os.path.join(config_dir, type_dir, f"{obj_name_val}.xml")
|
||||
if not os.path.exists(obj_file):
|
||||
hint_skill = {"Subsystem": "subsystem-compile", "Role": "role-compile"}.get(type_name, "meta-compile")
|
||||
print(
|
||||
f"Object file not found: {type_dir}/{obj_name_val}.xml\n"
|
||||
f"cf-edit add-childObject only references objects that already exist on disk.\n"
|
||||
f"To create a new {type_name}, use {hint_skill} (auto-registers in Configuration.xml):\n"
|
||||
f' /{hint_skill} with {{"type":"{type_name}","name":"{obj_name_val}"}}',
|
||||
file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Dedup
|
||||
exists = False
|
||||
for child in child_objs_el:
|
||||
@@ -455,6 +494,269 @@ def main():
|
||||
modify_count += 1
|
||||
info(f"Set DefaultRoles: {len(items)} roles")
|
||||
|
||||
# --- set-panels (writes Ext/ClientApplicationInterface.xml from scratch) ---
|
||||
# Canonical English aliases — preferred form, used in docs and error messages.
|
||||
PANEL_UUIDS = {
|
||||
"sections": "b553047f-c9aa-4157-978d-448ecad24248",
|
||||
"open": "cbab57f2-a0f3-4f0a-89ea-4cb19570ab75",
|
||||
"favorites": "13322b22-3960-4d68-93a6-fe2dd7f28ca3",
|
||||
"history": "c933ac92-92cd-459d-81cc-e0c8a83ced99",
|
||||
"functions": "b2735bd3-d822-4430-ba59-c9e869693b24",
|
||||
}
|
||||
# Russian synonyms — silently accepted (cf-info displays Russian names;
|
||||
# users may copy them straight into cf-edit value).
|
||||
PANEL_SYNONYMS = {
|
||||
"разделов": "sections", "разделы": "sections",
|
||||
"открытых": "open", "открытые": "open",
|
||||
"избранного": "favorites","избранное": "favorites",
|
||||
"истории": "history", "история": "history",
|
||||
"функций": "functions", "функции": "functions",
|
||||
}
|
||||
|
||||
def build_panel_entry_xml(entry, indent):
|
||||
if isinstance(entry, str):
|
||||
key = entry.lower()
|
||||
key = PANEL_SYNONYMS.get(key, key)
|
||||
if key not in PANEL_UUIDS:
|
||||
allowed = ", ".join(sorted(PANEL_UUIDS.keys()))
|
||||
print(f"Unknown panel alias '{entry}'. Allowed: {allowed}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
inst = str(_uuid.uuid4())
|
||||
return f'{indent}<panel id="{inst}">\r\n{indent}\t<uuid>{PANEL_UUIDS[key]}</uuid>\r\n{indent}</panel>'
|
||||
if isinstance(entry, dict) and "group" in entry:
|
||||
children = entry["group"]
|
||||
if not children:
|
||||
print("group must contain at least one entry", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
gid = str(_uuid.uuid4())
|
||||
inner = ""
|
||||
for child in children:
|
||||
child_xml = build_panel_entry_xml(child, indent + "\t\t")
|
||||
inner += f"{indent}\t<group>\r\n{child_xml}\r\n{indent}\t</group>\r\n"
|
||||
return f'{indent}<group id="{gid}">\r\n{inner}{indent}</group>'
|
||||
print(f"Panel entry must be string alias or {{group:[...]}}, got: {entry!r}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def do_set_panels(value):
|
||||
nonlocal modify_count
|
||||
layout = value
|
||||
if isinstance(layout, str):
|
||||
try:
|
||||
layout = json.loads(layout)
|
||||
except json.JSONDecodeError:
|
||||
print(f"set-panels value must be valid JSON object", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not isinstance(layout, dict) or not layout:
|
||||
print("set-panels value must be non-empty object", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sides = ("top", "left", "right", "bottom")
|
||||
# Reject unknown side keys
|
||||
for k in layout.keys():
|
||||
if k not in sides:
|
||||
print(f"Unknown side '{k}'. Allowed: {', '.join(sides)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
body_parts = []
|
||||
for side in sides:
|
||||
entries = layout.get(side)
|
||||
if entries is None:
|
||||
continue
|
||||
if not isinstance(entries, list):
|
||||
entries = [entries]
|
||||
for entry in entries:
|
||||
entry_xml = build_panel_entry_xml(entry, "\t\t")
|
||||
body_parts.append(f"\t<{side}>\r\n{entry_xml}\r\n\t</{side}>")
|
||||
body = "\r\n".join(body_parts)
|
||||
body_block = body + "\r\n" if body else ""
|
||||
declarations = (
|
||||
'\t<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>\r\n'
|
||||
'\t<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>\r\n'
|
||||
'\t<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>\r\n'
|
||||
'\t<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>\r\n'
|
||||
'\t<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>'
|
||||
)
|
||||
cai_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\r\n'
|
||||
'<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" '
|
||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
||||
'xsi:type="InterfaceLayouter">\r\n'
|
||||
f'{body_block}{declarations}\r\n'
|
||||
'</ClientApplicationInterface>'
|
||||
)
|
||||
ext_dir = os.path.join(config_dir, "Ext")
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
cai_path = os.path.join(ext_dir, "ClientApplicationInterface.xml")
|
||||
with open(cai_path, "w", encoding="utf-8-sig", newline="") as fh:
|
||||
fh.write(cai_xml)
|
||||
modify_count += 1
|
||||
info(f"Wrote panel layout: {cai_path}")
|
||||
|
||||
# --- set-home-page (writes Ext/HomePageWorkArea.xml from scratch) ---
|
||||
RU_TYPE_MAP = {
|
||||
"справочник": "Catalog", "документ": "Document", "перечисление": "Enum",
|
||||
"отчёт": "Report", "отчет": "Report", "обработка": "DataProcessor",
|
||||
"общаяформа": "CommonForm", "журналдокументов": "DocumentJournal",
|
||||
"планвидовхарактеристик": "ChartOfCharacteristicTypes",
|
||||
"плансчетов": "ChartOfAccounts",
|
||||
"планвидоврасчета": "ChartOfCalculationTypes",
|
||||
"планвидоврасчёта": "ChartOfCalculationTypes",
|
||||
"регистрсведений": "InformationRegister",
|
||||
"регистрнакопления": "AccumulationRegister",
|
||||
"регистрбухгалтерии": "AccountingRegister",
|
||||
"регистррасчета": "CalculationRegister",
|
||||
"регистррасчёта": "CalculationRegister",
|
||||
"бизнеспроцесс": "BusinessProcess",
|
||||
"задача": "Task", "планобмена": "ExchangePlan",
|
||||
"хранилищенастроек": "SettingsStorage",
|
||||
}
|
||||
DIR_TO_TYPE = {v.lower(): k for k, v in TYPE_TO_DIR.items()}
|
||||
UUID_RE = __import__("re").compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
||||
|
||||
def normalize_form_ref(s):
|
||||
s = (s or "").strip()
|
||||
if not s:
|
||||
return s
|
||||
if UUID_RE.match(s):
|
||||
return s
|
||||
if "/" in s or "\\" in s:
|
||||
parts = [p for p in s.replace("\\", "/").split("/") if p and p.lower() != "ext"]
|
||||
if parts and parts[-1].lower() == "form.xml":
|
||||
parts = parts[:-1]
|
||||
if len(parts) >= 2:
|
||||
type_dir = parts[0]
|
||||
type_singular = DIR_TO_TYPE.get(type_dir.lower())
|
||||
if type_singular:
|
||||
if type_singular == "CommonForm" and len(parts) >= 2:
|
||||
return f"CommonForm.{parts[1]}"
|
||||
if len(parts) >= 4 and parts[2].lower() == "forms":
|
||||
return f"{type_singular}.{parts[1]}.Form.{parts[3]}"
|
||||
return s
|
||||
segs = s.split(".")
|
||||
if segs:
|
||||
head = segs[0].lower()
|
||||
if head in RU_TYPE_MAP:
|
||||
segs[0] = RU_TYPE_MAP[head]
|
||||
for i in range(1, len(segs)):
|
||||
if segs[i] == "Форма":
|
||||
segs[i] = "Form"
|
||||
if len(segs) == 3 and segs[0] in TYPE_ORDER and segs[0] != "CommonForm":
|
||||
segs = [segs[0], segs[1], "Form", segs[2]]
|
||||
return ".".join(segs)
|
||||
|
||||
def get_field(obj, keys):
|
||||
for k in keys:
|
||||
if isinstance(obj, dict) and k in obj:
|
||||
return obj[k]
|
||||
return None
|
||||
|
||||
def build_home_page_item_xml(entry, indent):
|
||||
if isinstance(entry, str):
|
||||
form_ref = normalize_form_ref(entry)
|
||||
height = 10
|
||||
common = True
|
||||
roles = None
|
||||
elif isinstance(entry, dict):
|
||||
form_raw = get_field(entry, ["form", "Form"])
|
||||
if not form_raw:
|
||||
print(f"Home page item: 'form' is required, got: {entry!r}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
form_ref = normalize_form_ref(str(form_raw))
|
||||
h = get_field(entry, ["height", "Height"])
|
||||
height = int(h) if h is not None else 10
|
||||
vis = get_field(entry, ["visibility", "Visibility"])
|
||||
common = bool(vis) if vis is not None else True
|
||||
roles = get_field(entry, ["roles"])
|
||||
else:
|
||||
print(f"Home page item must be string or object, got: {entry!r}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
vis_parts = [f"{indent}\t\t<xr:Common>{str(common).lower()}</xr:Common>"]
|
||||
if roles and isinstance(roles, dict):
|
||||
for rname, rval in roles.items():
|
||||
if not rname.startswith("Role.") and not UUID_RE.match(rname):
|
||||
rname = f"Role.{rname}"
|
||||
rval_s = str(bool(rval)).lower()
|
||||
vis_parts.append(f'{indent}\t\t<xr:Value name="{html_escape(rname, quote=True)}">{rval_s}</xr:Value>')
|
||||
vis_block = "\r\n".join(vis_parts)
|
||||
esc_form = html_escape(form_ref, quote=True)
|
||||
return (
|
||||
f"{indent}<Item>\r\n"
|
||||
f"{indent}\t<Form>{esc_form}</Form>\r\n"
|
||||
f"{indent}\t<Height>{height}</Height>\r\n"
|
||||
f"{indent}\t<Visibility>\r\n"
|
||||
f"{vis_block}\r\n"
|
||||
f"{indent}\t</Visibility>\r\n"
|
||||
f"{indent}</Item>"
|
||||
)
|
||||
|
||||
def do_set_home_page(value):
|
||||
nonlocal modify_count
|
||||
layout = value
|
||||
if isinstance(layout, str):
|
||||
try:
|
||||
layout = json.loads(layout)
|
||||
except json.JSONDecodeError:
|
||||
print("set-home-page value must be valid JSON object", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not isinstance(layout, dict) or not layout:
|
||||
print("set-home-page value must be non-empty object", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
allowed_templates = ("OneColumn", "TwoColumnsEqualWidth", "TwoColumnsVariableWidth")
|
||||
tmpl = get_field(layout, ["template", "WorkingAreaTemplate"]) or "TwoColumnsEqualWidth"
|
||||
if tmpl not in allowed_templates:
|
||||
print(f"Unknown template '{tmpl}'. Allowed: {', '.join(allowed_templates)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
left_items = get_field(layout, ["left", "LeftColumn"])
|
||||
right_items = get_field(layout, ["right", "RightColumn"])
|
||||
|
||||
known = {"template", "WorkingAreaTemplate", "left", "LeftColumn", "right", "RightColumn"}
|
||||
for k in layout.keys():
|
||||
if k not in known:
|
||||
print(f"Unknown key '{k}'. Allowed: template, left, right", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if tmpl == "OneColumn" and right_items:
|
||||
print("Template 'OneColumn' cannot have items in 'right' column", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def build_column(tag, items):
|
||||
if not items:
|
||||
return f"\t<{tag}/>"
|
||||
if not isinstance(items, list):
|
||||
items = [items]
|
||||
if not items:
|
||||
return f"\t<{tag}/>"
|
||||
blocks = [build_home_page_item_xml(it, "\t\t") for it in items]
|
||||
body = "\r\n".join(blocks)
|
||||
return f"\t<{tag}>\r\n{body}\r\n\t</{tag}>"
|
||||
|
||||
left_xml = build_column("LeftColumn", left_items)
|
||||
right_xml = build_column("RightColumn", right_items)
|
||||
|
||||
hp_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\r\n'
|
||||
'<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" '
|
||||
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">\r\n'
|
||||
f'\t<WorkingAreaTemplate>{tmpl}</WorkingAreaTemplate>\r\n'
|
||||
f'{left_xml}\r\n'
|
||||
f'{right_xml}\r\n'
|
||||
'</HomePageWorkArea>'
|
||||
)
|
||||
|
||||
ext_dir = os.path.join(config_dir, "Ext")
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
hp_path = os.path.join(ext_dir, "HomePageWorkArea.xml")
|
||||
with open(hp_path, "w", encoding="utf-8-sig", newline="") as fh:
|
||||
fh.write(hp_xml)
|
||||
modify_count += 1
|
||||
info(f"Wrote home page layout: {hp_path}")
|
||||
|
||||
# --- Execute operations ---
|
||||
operations = []
|
||||
if args.DefinitionFile:
|
||||
@@ -475,17 +777,21 @@ def main():
|
||||
op_value = op.get("value", args.Value or "")
|
||||
|
||||
if op_name == "modify-property":
|
||||
do_modify_property(op_value)
|
||||
do_modify_property(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "add-childObject":
|
||||
do_add_child_object(op_value)
|
||||
do_add_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "remove-childObject":
|
||||
do_remove_child_object(op_value)
|
||||
do_remove_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "add-defaultRole":
|
||||
do_add_default_role(op_value)
|
||||
do_add_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "remove-defaultRole":
|
||||
do_remove_default_role(op_value)
|
||||
do_remove_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "set-defaultRoles":
|
||||
do_set_default_roles(op_value)
|
||||
do_set_default_roles(op_value if isinstance(op_value, str) else str(op_value))
|
||||
elif op_name == "set-panels":
|
||||
do_set_panels(op_value)
|
||||
elif op_name == "set-home-page":
|
||||
do_set_home_page(op_value)
|
||||
else:
|
||||
print(f"Unknown operation: {op_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -500,7 +806,7 @@ def main():
|
||||
if os.path.isfile(validate_script):
|
||||
print()
|
||||
print("--- Running cf-validate ---")
|
||||
subprocess.run([sys.executable, validate_script, "-ConfigPath", resolved_path])
|
||||
subprocess.run([sys.executable, validate_script, "-ConfigPath", "-Path", resolved_path])
|
||||
|
||||
# --- Summary ---
|
||||
print()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: cf-info
|
||||
description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки
|
||||
argument-hint: <ConfigPath> [-Mode overview|brief|full]
|
||||
argument-hint: <ConfigPath> [-Mode overview|brief|full] [-Section home-page]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -18,11 +18,12 @@ allowed-tools:
|
||||
|----------|----------|
|
||||
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
|
||||
| `Mode` | Режим: `overview` (default), `brief`, `full` |
|
||||
| `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` |
|
||||
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
|
||||
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cf-info/scripts/cf-info.ps1 -ConfigPath "<путь>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-info.ps1" -ConfigPath "<путь>"
|
||||
```
|
||||
|
||||
## Три режима
|
||||
@@ -37,14 +38,17 @@ powershell.exe -NoProfile -File .claude/skills/cf-info/scripts/cf-info.ps1 -Conf
|
||||
|
||||
```powershell
|
||||
# Обзор пустой конфигурации
|
||||
... -ConfigPath upload/cfempty
|
||||
... -ConfigPath src
|
||||
|
||||
# Краткая сводка реальной конфигурации
|
||||
... -ConfigPath upload/acc_8.3.24 -Mode brief
|
||||
... -ConfigPath src -Mode brief
|
||||
|
||||
# Полная информация
|
||||
... -ConfigPath upload/acc_8.3.24 -Mode full
|
||||
... -ConfigPath src -Mode full
|
||||
|
||||
# С пагинацией
|
||||
... -ConfigPath upload/acc_8.3.24 -Mode full -Limit 50 -Offset 100
|
||||
... -ConfigPath src -Mode full -Limit 50 -Offset 100
|
||||
|
||||
# Drill-down: только начальная страница (раскладка форм с ролями)
|
||||
... -ConfigPath src -Section home-page
|
||||
```
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# cf-info v1.0 — Compact summary of 1C configuration root
|
||||
# cf-info v1.2 — Compact summary of 1C configuration root
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ConfigPath,
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
|
||||
[ValidateSet("overview","brief","full")]
|
||||
[string]$Mode = "overview",
|
||||
[Alias('Name')]
|
||||
[ValidateSet("home-page")]
|
||||
[string]$Section,
|
||||
[int]$Limit = 150,
|
||||
[int]$Offset = 0,
|
||||
[string]$OutFile
|
||||
@@ -118,6 +121,115 @@ $typeRuNames = @{
|
||||
"Task"="Задачи"; "IntegrationService"="Сервисы интеграции"
|
||||
}
|
||||
|
||||
# --- Read panel layout (Ext/ClientApplicationInterface.xml) ---
|
||||
$script:panelNames = @{
|
||||
"cbab57f2-a0f3-4f0a-89ea-4cb19570ab75" = "Открытых"
|
||||
"b553047f-c9aa-4157-978d-448ecad24248" = "Разделов"
|
||||
"13322b22-3960-4d68-93a6-fe2dd7f28ca3" = "Избранного"
|
||||
"c933ac92-92cd-459d-81cc-e0c8a83ced99" = "История"
|
||||
"b2735bd3-d822-4430-ba59-c9e869693b24" = "Функций"
|
||||
}
|
||||
|
||||
function Get-PanelsLayout {
|
||||
$configDir = [System.IO.Path]::GetDirectoryName($ConfigPath)
|
||||
$caiPath = Join-Path (Join-Path $configDir "Ext") "ClientApplicationInterface.xml"
|
||||
if (-not (Test-Path $caiPath)) { return $null }
|
||||
try { [xml]$caiDoc = Get-Content -Path $caiPath -Encoding UTF8 } catch { return $null }
|
||||
if (-not $caiDoc.DocumentElement) { return $null }
|
||||
$caiNs = New-Object System.Xml.XmlNamespaceManager($caiDoc.NameTable)
|
||||
$caiNs.AddNamespace("ca", "http://v8.1c.ru/8.2/managed-application/core")
|
||||
$layout = [ordered]@{ top=@(); left=@(); right=@(); bottom=@(); declared=@() }
|
||||
foreach ($side in @("top","left","right","bottom")) {
|
||||
foreach ($sideEl in $caiDoc.DocumentElement.SelectNodes("ca:$side", $caiNs)) {
|
||||
$slot = @()
|
||||
foreach ($u in $sideEl.SelectNodes(".//ca:panel/ca:uuid", $caiNs)) {
|
||||
$key = $u.InnerText.Trim()
|
||||
$nm = if ($script:panelNames.Contains($key)) { $script:panelNames[$key] } else { "?$key" }
|
||||
$slot += $nm
|
||||
}
|
||||
if ($slot.Count -gt 0) { $layout[$side] += ,$slot }
|
||||
}
|
||||
}
|
||||
foreach ($pd in $caiDoc.DocumentElement.SelectNodes("ca:panelDef", $caiNs)) {
|
||||
$key = $pd.GetAttribute("id")
|
||||
$nm = if ($script:panelNames.Contains($key)) { $script:panelNames[$key] } else { "?$key" }
|
||||
$layout.declared += $nm
|
||||
}
|
||||
return $layout
|
||||
}
|
||||
|
||||
function Format-LayoutSlots($slots) {
|
||||
# slots is array of arrays (each inner array = one side-tag's panels, may be 1+)
|
||||
# Single inner array, single panel -> just name
|
||||
# Single inner array, multiple panels -> "Стек(a, b)"
|
||||
# Multiple inner arrays -> separate entries joined by " | "
|
||||
if (-not $slots -or $slots.Count -eq 0) { return "" }
|
||||
$parts = @()
|
||||
foreach ($slot in $slots) {
|
||||
if ($slot.Count -eq 1) { $parts += $slot[0] }
|
||||
else { $parts += ("Стек(" + ($slot -join ", ") + ")") }
|
||||
}
|
||||
return ($parts -join " | ")
|
||||
}
|
||||
|
||||
$script:panelLayout = Get-PanelsLayout
|
||||
|
||||
# --- Read home page layout (Ext/HomePageWorkArea.xml) ---
|
||||
function Get-HomePageLayout {
|
||||
$configDir = [System.IO.Path]::GetDirectoryName($ConfigPath)
|
||||
$hpPath = Join-Path (Join-Path $configDir "Ext") "HomePageWorkArea.xml"
|
||||
if (-not (Test-Path $hpPath)) { return $null }
|
||||
try { [xml]$hpDoc = Get-Content -Path $hpPath -Encoding UTF8 } catch { return $null }
|
||||
if (-not $hpDoc.DocumentElement) { return $null }
|
||||
$hpNs = New-Object System.Xml.XmlNamespaceManager($hpDoc.NameTable)
|
||||
$hpNs.AddNamespace("hp", "http://v8.1c.ru/8.3/xcf/extrnprops")
|
||||
$hpNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
||||
$result = [ordered]@{ template = ""; left = @(); right = @() }
|
||||
$tmplNode = $hpDoc.DocumentElement.SelectSingleNode("hp:WorkingAreaTemplate", $hpNs)
|
||||
if ($tmplNode) { $result.template = $tmplNode.InnerText.Trim() }
|
||||
foreach ($colName in @("LeftColumn","RightColumn")) {
|
||||
$colNode = $hpDoc.DocumentElement.SelectSingleNode("hp:$colName", $hpNs)
|
||||
if (-not $colNode) { continue }
|
||||
$items = @()
|
||||
foreach ($item in $colNode.SelectNodes("hp:Item", $hpNs)) {
|
||||
$f = $item.SelectSingleNode("hp:Form", $hpNs)
|
||||
$h = $item.SelectSingleNode("hp:Height", $hpNs)
|
||||
$visNode = $item.SelectSingleNode("hp:Visibility", $hpNs)
|
||||
$common = $true
|
||||
$roles = @()
|
||||
if ($visNode) {
|
||||
$cn = $visNode.SelectSingleNode("xr:Common", $hpNs)
|
||||
if ($cn) { $common = ($cn.InnerText.Trim() -eq "true") }
|
||||
foreach ($v in $visNode.SelectNodes("xr:Value", $hpNs)) {
|
||||
$roles += @{ name = $v.GetAttribute("name"); value = ($v.InnerText.Trim() -eq "true") }
|
||||
}
|
||||
}
|
||||
$items += [ordered]@{
|
||||
form = if ($f) { $f.InnerText.Trim() } else { "" }
|
||||
height = if ($h) { [int]$h.InnerText.Trim() } else { 10 }
|
||||
common = $common
|
||||
roles = $roles
|
||||
}
|
||||
}
|
||||
if ($colName -eq "LeftColumn") { $result.left = $items } else { $result.right = $items }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
$script:homePage = Get-HomePageLayout
|
||||
|
||||
function Format-HomePageItem($it, [bool]$detailed) {
|
||||
$badges = @()
|
||||
$badges += "h=$($it.height)"
|
||||
if (-not $it.common) { $badges += "скрыта" }
|
||||
if ($it.roles.Count -gt 0) {
|
||||
if ($detailed) { $badges += "роли: $($it.roles.Count)" }
|
||||
else { $badges += "+$($it.roles.Count) ролей" }
|
||||
}
|
||||
$tail = if ($badges.Count -gt 0) { " (" + ($badges -join ", ") + ")" } else { "" }
|
||||
return " $($it.form)$tail"
|
||||
}
|
||||
|
||||
# --- Count objects in ChildObjects ---
|
||||
$objectCounts = [ordered]@{}
|
||||
$totalObjects = 0
|
||||
@@ -154,7 +266,7 @@ $cfgDbSpaces = Get-PropText "DatabaseTablespacesUseMode"
|
||||
$cfgWindowMode = Get-PropText "MainClientApplicationWindowMode"
|
||||
|
||||
# --- BRIEF mode ---
|
||||
if ($Mode -eq "brief") {
|
||||
if ($Mode -eq "brief" -and -not $Section) {
|
||||
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
|
||||
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
|
||||
$compatPart = if ($cfgCompat) { " | $cfgCompat" } else { "" }
|
||||
@@ -162,7 +274,7 @@ if ($Mode -eq "brief") {
|
||||
}
|
||||
|
||||
# --- OVERVIEW mode ---
|
||||
if ($Mode -eq "overview") {
|
||||
if ($Mode -eq "overview" -and -not $Section) {
|
||||
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
|
||||
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
|
||||
Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ==="
|
||||
@@ -181,6 +293,33 @@ if ($Mode -eq "overview") {
|
||||
Out "Интерфейс: $cfgIntfCompat"
|
||||
Out ""
|
||||
|
||||
# Panel layout (if file exists)
|
||||
if ($script:panelLayout) {
|
||||
$hasPlaced = $false
|
||||
foreach ($s in @("top","left","right","bottom")) {
|
||||
if ($script:panelLayout[$s].Count -gt 0) { $hasPlaced = $true; break }
|
||||
}
|
||||
if ($hasPlaced) {
|
||||
Out "--- Раскладка панелей ---"
|
||||
foreach ($s in @("top","left","right","bottom")) {
|
||||
if ($script:panelLayout[$s].Count -gt 0) {
|
||||
Out " $($s.PadRight(7)) $(Format-LayoutSlots $script:panelLayout[$s])"
|
||||
}
|
||||
}
|
||||
Out ""
|
||||
}
|
||||
}
|
||||
|
||||
# Home page layout (brief summary)
|
||||
if ($script:homePage) {
|
||||
$ln = $script:homePage.left.Count
|
||||
$rn = $script:homePage.right.Count
|
||||
Out "--- Начальная страница ---"
|
||||
Out " Шаблон: $($script:homePage.template)"
|
||||
Out " LeftColumn: $ln, RightColumn: $rn (детали: -Section home-page)"
|
||||
Out ""
|
||||
}
|
||||
|
||||
# Object counts table
|
||||
Out "--- Состав ($totalObjects объектов) ---"
|
||||
Out ""
|
||||
@@ -203,8 +342,34 @@ if ($Mode -eq "overview") {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Drill-down: -Section home-page ---
|
||||
if ($Section -eq "home-page") {
|
||||
if (-not $script:homePage) {
|
||||
Out "Файл Ext/HomePageWorkArea.xml не найден"
|
||||
} else {
|
||||
Out "=== Начальная страница: $cfgName ==="
|
||||
Out ""
|
||||
Out "Шаблон: $($script:homePage.template)"
|
||||
Out ""
|
||||
foreach ($side in @(@("LeftColumn","left"), @("RightColumn","right"))) {
|
||||
$items = $script:homePage[$side[1]]
|
||||
$lbl = $side[0]
|
||||
if ($items.Count -eq 0) { Out "${lbl}: —"; Out ""; continue }
|
||||
Out "${lbl} ($($items.Count)):"
|
||||
foreach ($it in $items) {
|
||||
Out (Format-HomePageItem $it $true)
|
||||
foreach ($r in $it.roles) {
|
||||
$rval = if ($r.value) { "true" } else { "false" }
|
||||
Out " $($r.name): $rval"
|
||||
}
|
||||
}
|
||||
Out ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- FULL mode ---
|
||||
if ($Mode -eq "full") {
|
||||
if ($Mode -eq "full" -and -not $Section) {
|
||||
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
|
||||
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
|
||||
Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ==="
|
||||
@@ -275,6 +440,33 @@ if ($Mode -eq "full") {
|
||||
Out "Обычн.формы в управл.: $useOF"
|
||||
Out ""
|
||||
|
||||
# --- Section: Panel layout ---
|
||||
if ($script:panelLayout) {
|
||||
Out "--- Раскладка панелей ---"
|
||||
foreach ($s in @("top","left","right","bottom")) {
|
||||
$slots = $script:panelLayout[$s]
|
||||
if ($slots.Count -gt 0) {
|
||||
Out " $($s.PadRight(7)) $(Format-LayoutSlots $slots)"
|
||||
} else {
|
||||
Out " $($s.PadRight(7)) —"
|
||||
}
|
||||
}
|
||||
if ($script:panelLayout.declared.Count -gt 0) {
|
||||
Out " объявлено: $($script:panelLayout.declared -join ', ')"
|
||||
}
|
||||
Out ""
|
||||
}
|
||||
|
||||
# --- Section: Home page (brief summary) ---
|
||||
if ($script:homePage) {
|
||||
$ln = $script:homePage.left.Count
|
||||
$rn = $script:homePage.right.Count
|
||||
Out "--- Начальная страница ---"
|
||||
Out " Шаблон: $($script:homePage.template)"
|
||||
Out " LeftColumn: $ln, RightColumn: $rn (детали: -Section home-page)"
|
||||
Out ""
|
||||
}
|
||||
|
||||
# --- Section: Storages & default forms ---
|
||||
Out "--- Хранилища и формы по умолчанию ---"
|
||||
$storageProps = @("CommonSettingsStorage","ReportsUserSettingsStorage","ReportsVariantsStorage","FormDataSettingsStorage","DynamicListsUserSettingsStorage","URLExternalDataStorage")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-info v1.0 — Compact summary of 1C configuration root
|
||||
# cf-info v1.2 — Compact summary of 1C configuration root
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -13,8 +13,9 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
# --- Argument parsing ---
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False)
|
||||
parser.add_argument("-ConfigPath", required=True, help="Path to Configuration.xml or directory")
|
||||
parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory")
|
||||
parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview", help="Output mode")
|
||||
parser.add_argument("-Section", "-Name", choices=["home-page"], default=None, help="Drill-down section (alias: -Name)")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||
parser.add_argument("-OutFile", default="", help="Write output to file")
|
||||
@@ -125,6 +126,108 @@ type_ru_names = {
|
||||
"Task": "Задачи", "IntegrationService": "Сервисы интеграции",
|
||||
}
|
||||
|
||||
# --- Read panel layout (Ext/ClientApplicationInterface.xml) ---
|
||||
PANEL_NAMES = {
|
||||
"cbab57f2-a0f3-4f0a-89ea-4cb19570ab75": "Открытых",
|
||||
"b553047f-c9aa-4157-978d-448ecad24248": "Разделов",
|
||||
"13322b22-3960-4d68-93a6-fe2dd7f28ca3": "Избранного",
|
||||
"c933ac92-92cd-459d-81cc-e0c8a83ced99": "История",
|
||||
"b2735bd3-d822-4430-ba59-c9e869693b24": "Функций",
|
||||
}
|
||||
CAI_NS = "http://v8.1c.ru/8.2/managed-application/core"
|
||||
|
||||
def get_panels_layout():
|
||||
cfg_dir = os.path.dirname(config_path)
|
||||
cai_path = os.path.join(cfg_dir, "Ext", "ClientApplicationInterface.xml")
|
||||
if not os.path.isfile(cai_path):
|
||||
return None
|
||||
try:
|
||||
cai_tree = etree.parse(cai_path)
|
||||
except Exception:
|
||||
return None
|
||||
cai_root = cai_tree.getroot()
|
||||
layout = {"top": [], "left": [], "right": [], "bottom": [], "declared": []}
|
||||
for side in ("top", "left", "right", "bottom"):
|
||||
for side_el in cai_root.findall(f"{{{CAI_NS}}}{side}"):
|
||||
slot = []
|
||||
for u in side_el.iter(f"{{{CAI_NS}}}uuid"):
|
||||
key = (u.text or "").strip()
|
||||
slot.append(PANEL_NAMES.get(key, f"?{key}"))
|
||||
if slot:
|
||||
layout[side].append(slot)
|
||||
for pd in cai_root.findall(f"{{{CAI_NS}}}panelDef"):
|
||||
key = pd.get("id", "")
|
||||
layout["declared"].append(PANEL_NAMES.get(key, f"?{key}"))
|
||||
return layout
|
||||
|
||||
def format_layout_slots(slots):
|
||||
if not slots:
|
||||
return ""
|
||||
parts = []
|
||||
for slot in slots:
|
||||
if len(slot) == 1:
|
||||
parts.append(slot[0])
|
||||
else:
|
||||
parts.append("Стек(" + ", ".join(slot) + ")")
|
||||
return " | ".join(parts)
|
||||
|
||||
panel_layout = get_panels_layout()
|
||||
|
||||
# --- Read home page layout (Ext/HomePageWorkArea.xml) ---
|
||||
HP_NS = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
||||
XR_NS_HP = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
|
||||
def get_home_page_layout():
|
||||
cfg_dir = os.path.dirname(config_path)
|
||||
hp_path = os.path.join(cfg_dir, "Ext", "HomePageWorkArea.xml")
|
||||
if not os.path.isfile(hp_path):
|
||||
return None
|
||||
try:
|
||||
hp_tree = etree.parse(hp_path)
|
||||
except Exception:
|
||||
return None
|
||||
hp_root = hp_tree.getroot()
|
||||
result = {"template": "", "left": [], "right": []}
|
||||
tn = hp_root.find(f"{{{HP_NS}}}WorkingAreaTemplate")
|
||||
if tn is not None and tn.text:
|
||||
result["template"] = tn.text.strip()
|
||||
for col_name, key in (("LeftColumn", "left"), ("RightColumn", "right")):
|
||||
col = hp_root.find(f"{{{HP_NS}}}{col_name}")
|
||||
if col is None:
|
||||
continue
|
||||
items = []
|
||||
for it in col.findall(f"{{{HP_NS}}}Item"):
|
||||
f = it.find(f"{{{HP_NS}}}Form")
|
||||
h = it.find(f"{{{HP_NS}}}Height")
|
||||
vis = it.find(f"{{{HP_NS}}}Visibility")
|
||||
common = True
|
||||
roles = []
|
||||
if vis is not None:
|
||||
cn = vis.find(f"{{{XR_NS_HP}}}Common")
|
||||
if cn is not None and cn.text:
|
||||
common = cn.text.strip() == "true"
|
||||
for v in vis.findall(f"{{{XR_NS_HP}}}Value"):
|
||||
roles.append({"name": v.get("name", ""), "value": (v.text or "").strip() == "true"})
|
||||
items.append({
|
||||
"form": (f.text or "").strip() if f is not None else "",
|
||||
"height": int((h.text or "10").strip()) if h is not None else 10,
|
||||
"common": common,
|
||||
"roles": roles,
|
||||
})
|
||||
result[key] = items
|
||||
return result
|
||||
|
||||
home_page = get_home_page_layout()
|
||||
|
||||
def format_home_page_item(it, detailed):
|
||||
badges = [f"h={it['height']}"]
|
||||
if not it["common"]:
|
||||
badges.append("скрыта")
|
||||
if it["roles"]:
|
||||
badges.append(f"роли: {len(it['roles'])}" if detailed else f"+{len(it['roles'])} ролей")
|
||||
tail = f" ({', '.join(badges)})" if badges else ""
|
||||
return f" {it['form']}{tail}"
|
||||
|
||||
# --- Count objects in ChildObjects ---
|
||||
object_counts = OrderedDict()
|
||||
total_objects = 0
|
||||
@@ -159,14 +262,14 @@ cfg_db_spaces = get_prop_text("DatabaseTablespacesUseMode")
|
||||
cfg_window_mode = get_prop_text("MainClientApplicationWindowMode")
|
||||
|
||||
# --- BRIEF mode ---
|
||||
if args.Mode == "brief":
|
||||
if args.Mode == "brief" and not args.Section:
|
||||
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
||||
ver_part = f" v{cfg_version}" if cfg_version else ""
|
||||
compat_part = f" | {cfg_compat}" if cfg_compat else ""
|
||||
out(f"Конфигурация: {cfg_name}{syn_part}{ver_part} | {total_objects} объектов{compat_part}")
|
||||
|
||||
# --- OVERVIEW mode ---
|
||||
if args.Mode == "overview":
|
||||
if args.Mode == "overview" and not args.Section:
|
||||
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
||||
ver_part = f" v{cfg_version}" if cfg_version else ""
|
||||
out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===")
|
||||
@@ -187,6 +290,20 @@ if args.Mode == "overview":
|
||||
out(f"Интерфейс: {cfg_intf_compat}")
|
||||
out()
|
||||
|
||||
if panel_layout and any(panel_layout[s] for s in ("top", "left", "right", "bottom")):
|
||||
out("--- Раскладка панелей ---")
|
||||
for s in ("top", "left", "right", "bottom"):
|
||||
if panel_layout[s]:
|
||||
out(f" {s.ljust(7)} {format_layout_slots(panel_layout[s])}")
|
||||
out()
|
||||
|
||||
# Home page (brief summary)
|
||||
if home_page:
|
||||
out("--- Начальная страница ---")
|
||||
out(f" Шаблон: {home_page['template']}")
|
||||
out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)")
|
||||
out()
|
||||
|
||||
# Object counts table
|
||||
out(f"--- Состав ({total_objects} объектов) ---")
|
||||
out()
|
||||
@@ -207,7 +324,30 @@ if args.Mode == "overview":
|
||||
out(f" {padded} {count}")
|
||||
|
||||
# --- FULL mode ---
|
||||
if args.Mode == "full":
|
||||
# --- Drill-down: -Section home-page ---
|
||||
if args.Section == "home-page":
|
||||
if not home_page:
|
||||
out("Файл Ext/HomePageWorkArea.xml не найден")
|
||||
else:
|
||||
out(f"=== Начальная страница: {cfg_name} ===")
|
||||
out()
|
||||
out(f"Шаблон: {home_page['template']}")
|
||||
out()
|
||||
for col_lbl, col_key in (("LeftColumn", "left"), ("RightColumn", "right")):
|
||||
items = home_page[col_key]
|
||||
if not items:
|
||||
out(f"{col_lbl}: —")
|
||||
out()
|
||||
continue
|
||||
out(f"{col_lbl} ({len(items)}):")
|
||||
for it in items:
|
||||
out(format_home_page_item(it, True))
|
||||
for r in it["roles"]:
|
||||
rval = "true" if r["value"] else "false"
|
||||
out(f" {r['name']}: {rval}")
|
||||
out()
|
||||
|
||||
if args.Mode == "full" and not args.Section:
|
||||
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
||||
ver_part = f" v{cfg_version}" if cfg_version else ""
|
||||
out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===")
|
||||
@@ -283,6 +423,26 @@ if args.Mode == "full":
|
||||
out(f"Обычн.формы в управл.: {use_of}")
|
||||
out()
|
||||
|
||||
# --- Section: Panel layout ---
|
||||
if panel_layout:
|
||||
out("--- Раскладка панелей ---")
|
||||
for s in ("top", "left", "right", "bottom"):
|
||||
slots = panel_layout[s]
|
||||
if slots:
|
||||
out(f" {s.ljust(7)} {format_layout_slots(slots)}")
|
||||
else:
|
||||
out(f" {s.ljust(7)} —")
|
||||
if panel_layout["declared"]:
|
||||
out(f" объявлено: {', '.join(panel_layout['declared'])}")
|
||||
out()
|
||||
|
||||
# --- Section: Home page (brief summary) ---
|
||||
if home_page:
|
||||
out("--- Начальная страница ---")
|
||||
out(f" Шаблон: {home_page['template']}")
|
||||
out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)")
|
||||
out()
|
||||
|
||||
# --- Section: Storages & default forms ---
|
||||
out("--- Хранилища и формы по умолчанию ---")
|
||||
storage_props = [
|
||||
|
||||
@@ -24,16 +24,7 @@ allowed-tools:
|
||||
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cf-init/scripts/cf-init.ps1 -Name "МояКонфигурация"
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<OutputDir>/
|
||||
├── Configuration.xml # Корневой файл — все свойства
|
||||
└── Languages/
|
||||
└── Русский.xml # Язык по умолчанию
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cf-init v1.0 — Create empty 1C configuration scaffold
|
||||
# cf-init v1.2 — Create empty 1C configuration scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -160,7 +160,7 @@ $cfgXml = @"
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
@@ -192,6 +192,33 @@ $langXml = @"
|
||||
</MetaDataObject>
|
||||
"@
|
||||
|
||||
# --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) ---
|
||||
# Open panel on top, Sections panel on left; Functions/Favorites/History declared
|
||||
# via panelDef but not placed by default. Without this file the web client renders
|
||||
# section icons without labels (icon-only mode).
|
||||
$openPanelInst = [guid]::NewGuid().ToString()
|
||||
$sectionsPanelInst = [guid]::NewGuid().ToString()
|
||||
$caiXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="$openPanelInst">
|
||||
<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="$sectionsPanelInst">
|
||||
<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
|
||||
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
|
||||
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
|
||||
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
|
||||
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
|
||||
</ClientApplicationInterface>
|
||||
"@
|
||||
|
||||
# --- Create directories ---
|
||||
if (-not (Test-Path $OutputDir)) {
|
||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||
@@ -200,6 +227,10 @@ $langDir = Join-Path $OutputDir "Languages"
|
||||
if (-not (Test-Path $langDir)) {
|
||||
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
|
||||
}
|
||||
$extDir = Join-Path $OutputDir "Ext"
|
||||
if (-not (Test-Path $extDir)) {
|
||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# --- Write files with UTF-8 BOM ---
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
@@ -207,9 +238,12 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
||||
$langFile = Join-Path $langDir "Русский.xml"
|
||||
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
||||
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
|
||||
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc)
|
||||
|
||||
# --- Output ---
|
||||
Write-Host "[OK] Создана конфигурация: $Name"
|
||||
Write-Host " Каталог: $OutputDir"
|
||||
Write-Host " Configuration.xml: $cfgFile"
|
||||
Write-Host " Languages: $langFile"
|
||||
Write-Host " Ext/CAI: $caiFile"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-init v1.0 — Create empty 1C configuration scaffold
|
||||
# cf-init v1.2 — Create empty 1C configuration scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C configuration."""
|
||||
import sys, os, argparse, uuid
|
||||
@@ -155,7 +155,7 @@ def main():
|
||||
\t\t\t<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
\t\t\t<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
\t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
\t\t\t<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
|
||||
\t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
\t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
\t\t\t<CompatibilityMode>{compat}</CompatibilityMode>
|
||||
\t\t\t<DefaultConstantsForm/>
|
||||
@@ -184,20 +184,50 @@ def main():
|
||||
\t</Language>
|
||||
</MetaDataObject>'''
|
||||
|
||||
# --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) ---
|
||||
# Open panel on top, Sections panel on left; Functions/Favorites/History declared
|
||||
# via panelDef but not placed by default. Without this file the web client renders
|
||||
# section icons without labels (icon-only mode).
|
||||
open_panel_inst = new_uuid()
|
||||
sections_panel_inst = new_uuid()
|
||||
cai_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
\t<top>
|
||||
\t\t<panel id="{open_panel_inst}">
|
||||
\t\t\t<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
|
||||
\t\t</panel>
|
||||
\t</top>
|
||||
\t<left>
|
||||
\t\t<panel id="{sections_panel_inst}">
|
||||
\t\t\t<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
|
||||
\t\t</panel>
|
||||
\t</left>
|
||||
\t<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
|
||||
\t<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
|
||||
\t<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
|
||||
\t<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
|
||||
\t<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
|
||||
</ClientApplicationInterface>'''
|
||||
|
||||
# --- Create directories ---
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
lang_dir = os.path.join(output_dir, "Languages")
|
||||
os.makedirs(lang_dir, exist_ok=True)
|
||||
ext_dir = os.path.join(output_dir, "Ext")
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
|
||||
# --- Write files ---
|
||||
write_utf8_bom(cfg_file, cfg_xml)
|
||||
lang_file = os.path.join(lang_dir, "Русский.xml")
|
||||
write_utf8_bom(lang_file, lang_xml)
|
||||
cai_file = os.path.join(ext_dir, "ClientApplicationInterface.xml")
|
||||
write_utf8_bom(cai_file, cai_xml)
|
||||
|
||||
print(f"[OK] Создана конфигурация: {name}")
|
||||
print(f" Каталог: {output_dir}")
|
||||
print(f" Configuration.xml: {cfg_file}")
|
||||
print(f" Languages: {lang_file}")
|
||||
print(f" Ext/CAI: {cai_file}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -17,28 +17,13 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|------------|:-----:|---------|-------------------------------------------------|
|
||||
| ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.ps1 -ConfigPath "upload/cfempty"
|
||||
powershell.exe -NoProfile -File .claude/skills/cf-validate/scripts/cf-validate.ps1 -ConfigPath "upload/cfempty/Configuration.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml"
|
||||
```
|
||||
|
||||
## Проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|---|----------|-------------|
|
||||
| 1 | XML well-formedness, MetaDataObject/Configuration, version 2.17/2.20 | ERROR |
|
||||
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId, уникальность | ERROR |
|
||||
| 3 | Properties: Name непустой, Synonym, DefaultLanguage, DefaultRunMode | ERROR/WARN |
|
||||
| 4 | Properties: enum-значения (11 свойств) | ERROR |
|
||||
| 5 | ChildObjects: валидные имена типов (44 типа), нет дубликатов, порядок типов | ERROR/WARN |
|
||||
| 6 | DefaultLanguage ссылается на существующий Language в ChildObjects | ERROR |
|
||||
| 7 | Файлы языков Languages/<name>.xml существуют | WARN |
|
||||
| 8 | Каталоги объектов из ChildObjects существуют (spot-check) | WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# cf-validate v1.1 — Validate 1C configuration root structure
|
||||
# cf-validate v1.3 — Validate 1C configuration root structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ConfigPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -145,17 +146,17 @@ $childTypeDirMap = @{
|
||||
|
||||
# Valid enum values for Configuration properties
|
||||
$validEnumValues = @{
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
|
||||
"ScriptVariant" = @("Russian","English")
|
||||
"DataLockControlMode" = @("Automatic","Managed","AutomaticAndManaged")
|
||||
"ObjectAutonumerationMode" = @("NotAutoFree","AutoFree")
|
||||
"ModalityUseMode" = @("DontUse","Use","UseWithWarnings")
|
||||
"SynchronousPlatformExtensionAndAddInCallUseMode" = @("DontUse","Use","UseWithWarnings")
|
||||
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
|
||||
"InterfaceCompatibilityMode" = @("Version8_2","Version8_2EnableTaxi","Taxi","TaxiEnableVersion8_2","TaxiEnableVersion8_5","Version8_5EnableTaxi","Version8_5")
|
||||
"DatabaseTablespacesUseMode" = @("DontUse","Use")
|
||||
"MainClientApplicationWindowMode" = @("Normal","Fullscreen","Kiosk")
|
||||
"CompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"CompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
}
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
@@ -203,8 +204,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
@@ -535,6 +536,72 @@ if ($childObjNode) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 9: Form references (HomePageWorkArea + Properties) ---
|
||||
function Test-FormRef([string]$ref) {
|
||||
if (-not $ref) { return $true }
|
||||
# UUID — cannot verify without scanning all forms; skip
|
||||
if ($ref -match $guidPattern) { return $true }
|
||||
$parts = $ref.Split(".")
|
||||
if ($parts.Count -eq 2 -and $parts[0] -eq "CommonForm") {
|
||||
$p = Join-Path (Join-Path (Join-Path $configDir "CommonForms") $parts[1]) "Form.xml"
|
||||
$pExt = Join-Path (Join-Path (Join-Path (Join-Path $configDir "CommonForms") $parts[1]) "Ext") "Form.xml"
|
||||
return (Test-Path $p) -or (Test-Path $pExt)
|
||||
}
|
||||
if ($parts.Count -eq 4 -and $parts[2] -eq "Form" -and $childTypeDirMap.ContainsKey($parts[0])) {
|
||||
$dir = $childTypeDirMap[$parts[0]]
|
||||
$p = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $configDir $dir) $parts[1]) "Forms") $parts[3]) "Form.xml"
|
||||
$pExt = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $configDir $dir) $parts[1]) "Forms") $parts[3]) "Ext") "Form.xml"
|
||||
return (Test-Path $p) -or (Test-Path $pExt)
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
$formRefsChecked = 0
|
||||
$formRefErrors = @()
|
||||
|
||||
# HomePageWorkArea
|
||||
$hpPath = Join-Path (Join-Path $configDir "Ext") "HomePageWorkArea.xml"
|
||||
if (Test-Path $hpPath) {
|
||||
try {
|
||||
[xml]$hpDoc = Get-Content -Path $hpPath -Encoding UTF8
|
||||
$hpNs = New-Object System.Xml.XmlNamespaceManager($hpDoc.NameTable)
|
||||
$hpNs.AddNamespace("hp", "http://v8.1c.ru/8.3/xcf/extrnprops")
|
||||
foreach ($f in $hpDoc.DocumentElement.SelectNodes("//hp:Item/hp:Form", $hpNs)) {
|
||||
$ref = $f.InnerText.Trim()
|
||||
if (-not $ref) { continue }
|
||||
$formRefsChecked++
|
||||
if (-not (Test-FormRef $ref)) {
|
||||
$formRefErrors += "HomePageWorkArea.Form '$ref' — file not found"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$formRefErrors += "HomePageWorkArea.xml: parse error — $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Properties: DefaultXxxForm refs
|
||||
if ($propsNode) {
|
||||
$formProps = @("DefaultReportForm","DefaultReportVariantForm","DefaultReportSettingsForm","DefaultDynamicListSettingsForm","DefaultSearchForm","DefaultDataHistoryChangeHistoryForm","DefaultDataHistoryVersionDataForm","DefaultDataHistoryVersionDifferencesForm","DefaultCollaborationSystemUsersChoiceForm","DefaultConstantsForm")
|
||||
foreach ($pn in $formProps) {
|
||||
$node = $propsNode.SelectSingleNode("md:$pn", $ns)
|
||||
if ($node -and $node.InnerText.Trim()) {
|
||||
$ref = $node.InnerText.Trim()
|
||||
$formRefsChecked++
|
||||
if (-not (Test-FormRef $ref)) {
|
||||
$formRefErrors += "Properties.$pn '$ref' — form not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($formRefsChecked -eq 0) {
|
||||
Report-OK "9. Form references: none to check"
|
||||
} elseif ($formRefErrors.Count -eq 0) {
|
||||
Report-OK "9. Form references: $formRefsChecked verified"
|
||||
} else {
|
||||
foreach ($err in $formRefErrors) { Report-Error "9. $err" }
|
||||
}
|
||||
|
||||
# --- Final output ---
|
||||
& $finalize
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-validate v1.1 — Validate 1C configuration XML structure
|
||||
# cf-validate v1.3 — Validate 1C configuration XML structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
|
||||
import sys, os, argparse, re
|
||||
@@ -82,7 +82,7 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
'DefaultRunMode': ['ManagedApplication', 'OrdinaryApplication', 'Auto'],
|
||||
'ScriptVariant': ['Russian', 'English'],
|
||||
@@ -90,7 +90,10 @@ VALID_ENUM_VALUES = {
|
||||
'ObjectAutonumerationMode': ['NotAutoFree', 'AutoFree'],
|
||||
'ModalityUseMode': ['DontUse', 'Use', 'UseWithWarnings'],
|
||||
'SynchronousPlatformExtensionAndAddInCallUseMode': ['DontUse', 'Use', 'UseWithWarnings'],
|
||||
'InterfaceCompatibilityMode': ['Taxi', 'TaxiEnableVersion8_2', 'Version8_2'],
|
||||
'InterfaceCompatibilityMode': [
|
||||
'Version8_2', 'Version8_2EnableTaxi', 'Taxi', 'TaxiEnableVersion8_2',
|
||||
'TaxiEnableVersion8_5', 'Version8_5EnableTaxi', 'Version8_5',
|
||||
],
|
||||
'DatabaseTablespacesUseMode': ['DontUse', 'Use'],
|
||||
'MainClientApplicationWindowMode': ['Normal', 'Fullscreen', 'Kiosk'],
|
||||
'CompatibilityMode': [
|
||||
@@ -100,7 +103,7 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -162,7 +165,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C configuration XML structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-ConfigPath', dest='ConfigPath', required=True)
|
||||
parser.add_argument('-ConfigPath', '-Path', dest='ConfigPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
@@ -228,8 +231,8 @@ def main():
|
||||
version = root.get('version', '')
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.20'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ('2.17', '2.20', '2.21'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
@@ -534,6 +537,60 @@ def main():
|
||||
else:
|
||||
pass # no ChildObjects
|
||||
|
||||
# --- Check 9: Form references (HomePageWorkArea + Properties) ---
|
||||
def test_form_ref(ref):
|
||||
if not ref:
|
||||
return True
|
||||
if GUID_PATTERN.match(ref):
|
||||
return True
|
||||
parts = ref.split('.')
|
||||
if len(parts) == 2 and parts[0] == 'CommonForm':
|
||||
p = os.path.join(config_dir, 'CommonForms', parts[1], 'Form.xml')
|
||||
p_ext = os.path.join(config_dir, 'CommonForms', parts[1], 'Ext', 'Form.xml')
|
||||
return os.path.isfile(p) or os.path.isfile(p_ext)
|
||||
if len(parts) == 4 and parts[2] == 'Form' and parts[0] in CHILD_TYPE_DIR_MAP:
|
||||
d = CHILD_TYPE_DIR_MAP[parts[0]]
|
||||
p = os.path.join(config_dir, d, parts[1], 'Forms', parts[3], 'Form.xml')
|
||||
p_ext = os.path.join(config_dir, d, parts[1], 'Forms', parts[3], 'Ext', 'Form.xml')
|
||||
return os.path.isfile(p) or os.path.isfile(p_ext)
|
||||
return False
|
||||
|
||||
form_refs_checked = 0
|
||||
form_ref_errors = []
|
||||
|
||||
hp_path = os.path.join(config_dir, 'Ext', 'HomePageWorkArea.xml')
|
||||
if os.path.isfile(hp_path):
|
||||
try:
|
||||
hp_tree = etree.parse(hp_path)
|
||||
HP_NS = 'http://v8.1c.ru/8.3/xcf/extrnprops'
|
||||
for f in hp_tree.getroot().iter(f'{{{HP_NS}}}Form'):
|
||||
ref = (f.text or '').strip()
|
||||
if not ref:
|
||||
continue
|
||||
form_refs_checked += 1
|
||||
if not test_form_ref(ref):
|
||||
form_ref_errors.append(f"HomePageWorkArea.Form '{ref}' — file not found")
|
||||
except Exception as e:
|
||||
form_ref_errors.append(f'HomePageWorkArea.xml: parse error — {e}')
|
||||
|
||||
if props_node is not None:
|
||||
form_props = ['DefaultReportForm','DefaultReportVariantForm','DefaultReportSettingsForm','DefaultDynamicListSettingsForm','DefaultSearchForm','DefaultDataHistoryChangeHistoryForm','DefaultDataHistoryVersionDataForm','DefaultDataHistoryVersionDifferencesForm','DefaultCollaborationSystemUsersChoiceForm','DefaultConstantsForm']
|
||||
for pn in form_props:
|
||||
node = props_node.find(f'md:{pn}', NS)
|
||||
if node is not None and node.text and node.text.strip():
|
||||
ref = node.text.strip()
|
||||
form_refs_checked += 1
|
||||
if not test_form_ref(ref):
|
||||
form_ref_errors.append(f"Properties.{pn} '{ref}' — form not found")
|
||||
|
||||
if form_refs_checked == 0:
|
||||
r.ok('9. Form references: none to check')
|
||||
elif not form_ref_errors:
|
||||
r.ok(f'9. Form references: {form_refs_checked} verified')
|
||||
else:
|
||||
for err in form_ref_errors:
|
||||
r.error(f'9. {err}')
|
||||
|
||||
# --- Final output ---
|
||||
r.finalize(out_file)
|
||||
sys.exit(1 if r.errors > 0 else 0)
|
||||
|
||||
@@ -31,7 +31,7 @@ allowed-tools:
|
||||
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
|
||||
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
|
||||
| `Object` | Что заимствовать (обязат.), batch через `;;` |
|
||||
| `BorrowMainAttribute` | Используй при добавлении нового реквизита на заимствованную форму. `Form` (по умолч.) — реквизиты с формы, `All` — все реквизиты объекта |
|
||||
| `BorrowMainAttribute` | Заимствовать основной реквизит формы. Без параметра — не заимствует. `Form` — реквизиты, используемые на форме. `All` — все реквизиты объекта. Требует форму в -Object |
|
||||
|
||||
## Формат -Object
|
||||
|
||||
@@ -71,7 +71,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
@@ -316,6 +316,25 @@ function Expand-SelfClosingElement($container, $parentIndent) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- 7b. Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $extDir
|
||||
|
||||
# --- 8. Namespaces declaration for object XML ---
|
||||
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
|
||||
@@ -466,7 +485,7 @@ function Borrow-Form {
|
||||
$newFormUuid = [guid]::NewGuid().ToString()
|
||||
$formMetaSb = New-Object System.Text.StringBuilder
|
||||
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t<Form uuid=`"${newFormUuid}`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<InternalInfo/>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<Properties>") | Out-Null
|
||||
@@ -498,7 +517,7 @@ function Borrow-Form {
|
||||
$srcFormEl = $srcFormDoc.DocumentElement
|
||||
|
||||
$formVersion = $srcFormEl.GetAttribute("version")
|
||||
if (-not $formVersion) { $formVersion = "2.17" }
|
||||
if (-not $formVersion) { $formVersion = $script:formatVersion }
|
||||
|
||||
# Find direct children: form properties, AutoCommandBar, ChildItems
|
||||
$srcAutoCmd = $null
|
||||
@@ -1529,7 +1548,7 @@ function Build-BorrowedObjectXml {
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
||||
$sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null
|
||||
|
||||
# InternalInfo
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -254,6 +254,22 @@ XMLNS_DECL = (
|
||||
)
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
|
||||
def get_child_indent(container):
|
||||
if container.text and "\n" in container.text:
|
||||
after_nl = container.text.rsplit("\n", 1)[-1]
|
||||
@@ -305,7 +321,9 @@ def expand_self_closing(container, parent_indent):
|
||||
|
||||
def save_xml_bom(tree, path):
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
@@ -363,6 +381,8 @@ def main():
|
||||
cfg_resolved = os.path.abspath(cfg_path)
|
||||
cfg_dir = os.path.dirname(cfg_resolved)
|
||||
|
||||
format_version = detect_format_version(ext_dir)
|
||||
|
||||
# --- 2. Load extension Configuration.xml ---
|
||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(ext_resolved, xml_parser)
|
||||
@@ -499,7 +519,7 @@ def main():
|
||||
|
||||
lines = []
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
lines.append(f'<MetaDataObject {XMLNS_DECL} version="2.17">')
|
||||
lines.append(f'<MetaDataObject {XMLNS_DECL} version="{format_version}">')
|
||||
lines.append(f'\t<{type_name} uuid="{new_uuid_val}">')
|
||||
lines.append(internal_info_xml)
|
||||
lines.append("\t\t<Properties>")
|
||||
@@ -1084,7 +1104,7 @@ def main():
|
||||
new_form_uuid = new_guid()
|
||||
form_meta_lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
f'<MetaDataObject {XMLNS_DECL} version="2.17">',
|
||||
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
|
||||
f'\t<Form uuid="{new_form_uuid}">',
|
||||
'\t\t<InternalInfo/>',
|
||||
'\t\t<Properties>',
|
||||
@@ -1111,7 +1131,7 @@ def main():
|
||||
src_form_tree = etree.parse(src_form_xml_path, src_form_parser)
|
||||
src_form_el = src_form_tree.getroot()
|
||||
|
||||
form_version = src_form_el.get("version", "2.17")
|
||||
form_version = src_form_el.get("version", format_version)
|
||||
|
||||
src_auto_cmd = None
|
||||
form_props = []
|
||||
|
||||
@@ -23,7 +23,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-diff/scripts/cfe-diff.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||
```
|
||||
|
||||
## Mode A — обзор расширения
|
||||
@@ -32,19 +32,6 @@ powershell.exe -NoProfile -File .claude/skills/cfe-diff/scripts/cfe-diff.ps1 -Ex
|
||||
- `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы
|
||||
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
|
||||
|
||||
Пример вывода:
|
||||
```
|
||||
[BORROWED] Catalog.Валюты
|
||||
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке") — line 4 in ...
|
||||
&Перед("ЗагрузитьКурсыВалют") — line 13 in ...
|
||||
ChildObjects: 1 own attrs, 1 own TS, 3 own forms
|
||||
Form.ФормаЭлемента (borrowed):
|
||||
Event:OnCreateAtServer [After] -> Расш1_ПриСозданииПосле
|
||||
Command:Подбор [Before] -> Расш1_ПодборПеред
|
||||
Form.Расш1_МояФорма (own)
|
||||
[OWN] Catalog.Расш5_Справочник1
|
||||
```
|
||||
|
||||
Для каждой формы заимствованного объекта показывается:
|
||||
- `(borrowed)` / `(own)` — заимствованная или собственная форма
|
||||
- callType-события формы и элементов
|
||||
|
||||
@@ -44,18 +44,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-init/scripts/cfe-init.ps1 -Name "МоёРасширение"
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<OutputDir>/
|
||||
├── Configuration.xml # Свойства расширения
|
||||
├── Languages/
|
||||
│ └── Русский.xml # Язык (заимствованный)
|
||||
└── Roles/ # Если не -NoRole
|
||||
└── <Prefix>ОсновнаяРоль.xml
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cfe-init v1.0 — Create 1C configuration extension scaffold (CFE)
|
||||
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -69,7 +69,7 @@ if ($ConfigPath) {
|
||||
Write-Host "[WARN] Base config language not found: $baseLangFile"
|
||||
}
|
||||
|
||||
# 3b. Read CompatibilityMode from base config
|
||||
# 3b. Read CompatibilityMode and InterfaceCompatibilityMode from base config
|
||||
$baseCfgDoc = New-Object System.Xml.XmlDocument
|
||||
$baseCfgDoc.PreserveWhitespace = $false
|
||||
$baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
|
||||
@@ -82,7 +82,16 @@ if ($ConfigPath) {
|
||||
} else {
|
||||
Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode"
|
||||
}
|
||||
$ifcNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:InterfaceCompatibilityMode", $baseCfgNs)
|
||||
if ($ifcNode -and $ifcNode.InnerText) {
|
||||
$InterfaceCompatibilityMode = $ifcNode.InnerText.Trim()
|
||||
Write-Host "[INFO] Base config InterfaceCompatibilityMode: $InterfaceCompatibilityMode"
|
||||
} else {
|
||||
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
||||
Write-Host "[WARN] InterfaceCompatibilityMode not found in base config, using default: $InterfaceCompatibilityMode"
|
||||
}
|
||||
} else {
|
||||
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
||||
Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading."
|
||||
}
|
||||
|
||||
@@ -184,7 +193,7 @@ $cfgXml = @"
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<InterfaceCompatibilityMode>$InterfaceCompatibilityMode</InterfaceCompatibilityMode>
|
||||
</Properties>
|
||||
<ChildObjects>$childObjectsXml</ChildObjects>
|
||||
</Configuration>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-init v1.0 — Create 1C configuration extension scaffold (CFE)
|
||||
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C configuration extension."""
|
||||
import sys, os, argparse, uuid
|
||||
@@ -84,7 +84,7 @@ def main():
|
||||
else:
|
||||
print(f"[WARN] Base config language not found: {base_lang_file}")
|
||||
|
||||
# Read CompatibilityMode from base config
|
||||
# Read CompatibilityMode and InterfaceCompatibilityMode from base config
|
||||
try:
|
||||
base_cfg_tree = ET.parse(os.path.abspath(config_path))
|
||||
base_cfg_root = base_cfg_tree.getroot()
|
||||
@@ -95,9 +95,18 @@ def main():
|
||||
print(f"[INFO] Base config CompatibilityMode: {compat}")
|
||||
else:
|
||||
print(f"[WARN] CompatibilityMode not found in base config, using default: {compat}")
|
||||
ifc_node = base_cfg_root.find('.//md:Configuration/md:Properties/md:InterfaceCompatibilityMode', ns)
|
||||
if ifc_node is not None and ifc_node.text:
|
||||
ifc_mode = ifc_node.text.strip()
|
||||
print(f"[INFO] Base config InterfaceCompatibilityMode: {ifc_mode}")
|
||||
else:
|
||||
ifc_mode = "TaxiEnableVersion8_2"
|
||||
print(f"[WARN] InterfaceCompatibilityMode not found in base config, using default: {ifc_mode}")
|
||||
except Exception:
|
||||
print(f"[WARN] Could not parse base config, using default CompatibilityMode: {compat}")
|
||||
ifc_mode = "TaxiEnableVersion8_2"
|
||||
else:
|
||||
ifc_mode = "TaxiEnableVersion8_2"
|
||||
print("[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading.")
|
||||
|
||||
# --- Generate UUIDs ---
|
||||
@@ -173,7 +182,7 @@ def main():
|
||||
\t\t\t<Copyright/>
|
||||
\t\t\t<VendorInformationAddress/>
|
||||
\t\t\t<ConfigurationInformationAddress/>
|
||||
\t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
\t\t\t<InterfaceCompatibilityMode>{ifc_mode}</InterfaceCompatibilityMode>
|
||||
\t\t</Properties>
|
||||
\t\t<ChildObjects>{child_objects_xml}</ChildObjects>
|
||||
\t</Configuration>
|
||||
|
||||
@@ -51,7 +51,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
@@ -1,201 +1,209 @@
|
||||
# cfe-patch-method v1.0 — Generate method interceptor for 1C extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ExtensionPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ModulePath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$MethodName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet("Before","After","ModificationAndControl")]
|
||||
[string]$InterceptorType,
|
||||
|
||||
[string]$Context = "НаСервере",
|
||||
|
||||
[switch]$IsFunction
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Resolve extension path ---
|
||||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||||
}
|
||||
if (Test-Path $ExtensionPath -PathType Leaf) {
|
||||
$ExtensionPath = Split-Path $ExtensionPath -Parent
|
||||
}
|
||||
$cfgFile = Join-Path $ExtensionPath "Configuration.xml"
|
||||
if (-not (Test-Path $cfgFile)) {
|
||||
Write-Error "Configuration.xml not found in: $ExtensionPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Read NamePrefix from Configuration.xml ---
|
||||
$cfgDoc = New-Object System.Xml.XmlDocument
|
||||
$cfgDoc.PreserveWhitespace = $false
|
||||
$cfgDoc.Load($cfgFile)
|
||||
|
||||
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgDoc.NameTable)
|
||||
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
$propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs)
|
||||
$prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null }
|
||||
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" }
|
||||
|
||||
# --- Map ModulePath to file path ---
|
||||
# ModulePath formats:
|
||||
# Catalog.X.ObjectModule -> Catalogs/X/Ext/ObjectModule.bsl
|
||||
# Catalog.X.ManagerModule -> Catalogs/X/Ext/ManagerModule.bsl
|
||||
# Catalog.X.Form.Y -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
|
||||
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
||||
# Document.X.ObjectModule -> Documents/X/Ext/ObjectModule.bsl
|
||||
# Document.X.ManagerModule -> Documents/X/Ext/ManagerModule.bsl
|
||||
# Document.X.Form.Y -> Documents/X/Forms/Y/Ext/Form/Module.bsl
|
||||
|
||||
$typeDirMap = @{
|
||||
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
|
||||
"CommonModule"="CommonModules"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
||||
"ExchangePlan"="ExchangePlans"; "ChartOfAccounts"="ChartsOfAccounts"
|
||||
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
|
||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
|
||||
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
|
||||
}
|
||||
|
||||
$parts = $ModulePath.Split(".")
|
||||
if ($parts.Count -lt 2) {
|
||||
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module or CommonModule.Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$objType = $parts[0]
|
||||
$objName = $parts[1]
|
||||
|
||||
if (-not $typeDirMap.ContainsKey($objType)) {
|
||||
Write-Error "Unknown object type: $objType"
|
||||
exit 1
|
||||
}
|
||||
$dirName = $typeDirMap[$objType]
|
||||
|
||||
$bslFile = $null
|
||||
if ($objType -eq "CommonModule") {
|
||||
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
||||
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Ext") "Module.bsl"
|
||||
} elseif ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||
# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
|
||||
$formName = $parts[3]
|
||||
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext") "Form") "Module.bsl"
|
||||
} elseif ($parts.Count -ge 3) {
|
||||
# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
|
||||
$moduleName = $parts[2]
|
||||
$moduleFileName = switch ($moduleName) {
|
||||
"ObjectModule" { "ObjectModule.bsl" }
|
||||
"ManagerModule" { "ManagerModule.bsl" }
|
||||
"RecordSetModule" { "RecordSetModule.bsl" }
|
||||
"CommandModule" { "CommandModule.bsl" }
|
||||
default { "$moduleName.bsl" }
|
||||
}
|
||||
$bslFile = Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) (Join-Path "Ext" $moduleFileName)
|
||||
} else {
|
||||
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Map InterceptorType to decorator ---
|
||||
$decorator = switch ($InterceptorType) {
|
||||
"Before" { "&Перед" }
|
||||
"After" { "&После" }
|
||||
"ModificationAndControl" { "&ИзменениеИКонтроль" }
|
||||
}
|
||||
|
||||
# --- Map Context to annotation ---
|
||||
$contextAnnotation = switch ($Context) {
|
||||
"НаСервере" { "&НаСервере" }
|
||||
"НаКлиенте" { "&НаКлиенте" }
|
||||
"НаСервереБезКонтекста" { "&НаСервереБезКонтекста" }
|
||||
default { "&$Context" }
|
||||
}
|
||||
|
||||
# --- Procedure name ---
|
||||
$procName = "${namePrefix}${MethodName}"
|
||||
|
||||
# --- Generate BSL code ---
|
||||
$keyword = if ($IsFunction) { "Функция" } else { "Процедура" }
|
||||
$endKeyword = if ($IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
||||
|
||||
$bodyLines = @()
|
||||
switch ($InterceptorType) {
|
||||
"Before" {
|
||||
$bodyLines += "`t// TODO: код перед вызовом оригинального метода"
|
||||
}
|
||||
"After" {
|
||||
$bodyLines += "`t// TODO: код после вызова оригинального метода"
|
||||
}
|
||||
"ModificationAndControl" {
|
||||
$bodyLines += "`t// Скопируйте тело оригинального метода и внесите изменения,"
|
||||
$bodyLines += "`t// используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки"
|
||||
}
|
||||
}
|
||||
|
||||
if ($IsFunction) {
|
||||
$bodyLines += "`t"
|
||||
$bodyLines += "`tВозврат Неопределено; // TODO: заменить на реальное возвращаемое значение"
|
||||
}
|
||||
|
||||
$bslCode = @()
|
||||
$bslCode += "$contextAnnotation"
|
||||
$bslCode += "${decorator}(`"$MethodName`")"
|
||||
$bslCode += "$keyword ${procName}()"
|
||||
$bslCode += $bodyLines
|
||||
$bslCode += "$endKeyword"
|
||||
|
||||
$bslText = ($bslCode -join "`r`n") + "`r`n"
|
||||
|
||||
# --- Check form borrowing for .Form. paths ---
|
||||
if ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||
$formName = $parts[3]
|
||||
$dirName = $typeDirMap[$objType]
|
||||
$formMetaFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") "${formName}.xml"
|
||||
$formXmlFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
|
||||
if (-not (Test-Path $formMetaFile) -or -not (Test-Path $formXmlFile)) {
|
||||
Write-Host "[WARN] Form '$formName' metadata or Form.xml not found in extension."
|
||||
Write-Host " Run /cfe-borrow first:"
|
||||
Write-Host " /cfe-borrow -ExtensionPath $ExtensionPath -ConfigPath <ConfigPath> -Object `"$objType.$objName.Form.$formName`""
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check if file exists and append ---
|
||||
$bslDir = Split-Path $bslFile -Parent
|
||||
if (-not (Test-Path $bslDir)) {
|
||||
New-Item -ItemType Directory -Path $bslDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
if (Test-Path $bslFile) {
|
||||
# Append to existing file
|
||||
$existing = [System.IO.File]::ReadAllText($bslFile, $enc)
|
||||
$separator = "`r`n"
|
||||
if ($existing -and -not $existing.EndsWith("`n")) {
|
||||
$separator = "`r`n`r`n"
|
||||
}
|
||||
$newContent = $existing + $separator + $bslText
|
||||
[System.IO.File]::WriteAllText($bslFile, $newContent, $enc)
|
||||
Write-Host "[OK] Добавлен перехватчик в существующий файл"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($bslFile, $bslText, $enc)
|
||||
Write-Host "[OK] Создан файл модуля"
|
||||
}
|
||||
|
||||
Write-Host " Файл: $bslFile"
|
||||
Write-Host " Декоратор: $decorator(`"$MethodName`")"
|
||||
Write-Host " Процедура: ${procName}()"
|
||||
Write-Host " Контекст: $contextAnnotation"
|
||||
# cfe-patch-method v1.1 — Generate method interceptor for 1C extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ExtensionPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ModulePath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$MethodName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet("Before","After","ModificationAndControl")]
|
||||
[string]$InterceptorType,
|
||||
|
||||
[string]$Context = "НаСервере",
|
||||
|
||||
[switch]$IsFunction
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Resolve extension path ---
|
||||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||||
}
|
||||
if (Test-Path $ExtensionPath -PathType Leaf) {
|
||||
$ExtensionPath = Split-Path $ExtensionPath -Parent
|
||||
}
|
||||
$cfgFile = Join-Path $ExtensionPath "Configuration.xml"
|
||||
if (-not (Test-Path $cfgFile)) {
|
||||
Write-Error "Configuration.xml not found in: $ExtensionPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Read NamePrefix from Configuration.xml ---
|
||||
$cfgDoc = New-Object System.Xml.XmlDocument
|
||||
$cfgDoc.PreserveWhitespace = $false
|
||||
$cfgDoc.Load($cfgFile)
|
||||
|
||||
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgDoc.NameTable)
|
||||
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
$propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs)
|
||||
$prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null }
|
||||
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" }
|
||||
|
||||
# --- Map ModulePath to file path ---
|
||||
# ModulePath formats:
|
||||
# Catalog.X.ObjectModule -> Catalogs/X/Ext/ObjectModule.bsl
|
||||
# Catalog.X.ManagerModule -> Catalogs/X/Ext/ManagerModule.bsl
|
||||
# Catalog.X.Form.Y -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
|
||||
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
||||
# Document.X.ObjectModule -> Documents/X/Ext/ObjectModule.bsl
|
||||
# Document.X.ManagerModule -> Documents/X/Ext/ManagerModule.bsl
|
||||
# Document.X.Form.Y -> Documents/X/Forms/Y/Ext/Form/Module.bsl
|
||||
|
||||
$typeDirMap = @{
|
||||
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
|
||||
"CommonModule"="CommonModules"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
||||
"ExchangePlan"="ExchangePlans"; "ChartOfAccounts"="ChartsOfAccounts"
|
||||
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
|
||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
|
||||
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
|
||||
"Catalogs"="Catalogs"; "Documents"="Documents"; "Enums"="Enums"
|
||||
"CommonModules"="CommonModules"; "Reports"="Reports"; "DataProcessors"="DataProcessors"
|
||||
"ExchangePlans"="ExchangePlans"; "ChartsOfAccounts"="ChartsOfAccounts"
|
||||
"ChartsOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
||||
"ChartsOfCalculationTypes"="ChartsOfCalculationTypes"
|
||||
"BusinessProcesses"="BusinessProcesses"; "Tasks"="Tasks"
|
||||
"InformationRegisters"="InformationRegisters"; "AccumulationRegisters"="AccumulationRegisters"
|
||||
"AccountingRegisters"="AccountingRegisters"; "CalculationRegisters"="CalculationRegisters"
|
||||
}
|
||||
|
||||
$parts = $ModulePath.Split(".")
|
||||
if ($parts.Count -lt 2) {
|
||||
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module or CommonModule.Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$objType = $parts[0]
|
||||
$objName = $parts[1]
|
||||
|
||||
if (-not $typeDirMap.ContainsKey($objType)) {
|
||||
Write-Error "Unknown object type: $objType"
|
||||
exit 1
|
||||
}
|
||||
$dirName = $typeDirMap[$objType]
|
||||
|
||||
$bslFile = $null
|
||||
if ($objType -eq "CommonModule") {
|
||||
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
|
||||
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Ext") "Module.bsl"
|
||||
} elseif ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||
# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
|
||||
$formName = $parts[3]
|
||||
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext") "Form") "Module.bsl"
|
||||
} elseif ($parts.Count -ge 3) {
|
||||
# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
|
||||
$moduleName = $parts[2]
|
||||
$moduleFileName = switch ($moduleName) {
|
||||
"ObjectModule" { "ObjectModule.bsl" }
|
||||
"ManagerModule" { "ManagerModule.bsl" }
|
||||
"RecordSetModule" { "RecordSetModule.bsl" }
|
||||
"CommandModule" { "CommandModule.bsl" }
|
||||
default { "$moduleName.bsl" }
|
||||
}
|
||||
$bslFile = Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) (Join-Path "Ext" $moduleFileName)
|
||||
} else {
|
||||
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Map InterceptorType to decorator ---
|
||||
$decorator = switch ($InterceptorType) {
|
||||
"Before" { "&Перед" }
|
||||
"After" { "&После" }
|
||||
"ModificationAndControl" { "&ИзменениеИКонтроль" }
|
||||
}
|
||||
|
||||
# --- Map Context to annotation ---
|
||||
$contextAnnotation = switch ($Context) {
|
||||
"НаСервере" { "&НаСервере" }
|
||||
"НаКлиенте" { "&НаКлиенте" }
|
||||
"НаСервереБезКонтекста" { "&НаСервереБезКонтекста" }
|
||||
default { "&$Context" }
|
||||
}
|
||||
|
||||
# --- Procedure name ---
|
||||
$procName = "${namePrefix}${MethodName}"
|
||||
|
||||
# --- Generate BSL code ---
|
||||
$keyword = if ($IsFunction) { "Функция" } else { "Процедура" }
|
||||
$endKeyword = if ($IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
|
||||
|
||||
$bodyLines = @()
|
||||
switch ($InterceptorType) {
|
||||
"Before" {
|
||||
$bodyLines += "`t// TODO: код перед вызовом оригинального метода"
|
||||
}
|
||||
"After" {
|
||||
$bodyLines += "`t// TODO: код после вызова оригинального метода"
|
||||
}
|
||||
"ModificationAndControl" {
|
||||
$bodyLines += "`t// Скопируйте тело оригинального метода и внесите изменения,"
|
||||
$bodyLines += "`t// используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки"
|
||||
}
|
||||
}
|
||||
|
||||
if ($IsFunction) {
|
||||
$bodyLines += "`t"
|
||||
$bodyLines += "`tВозврат Неопределено; // TODO: заменить на реальное возвращаемое значение"
|
||||
}
|
||||
|
||||
$bslCode = @()
|
||||
$bslCode += "$contextAnnotation"
|
||||
$bslCode += "${decorator}(`"$MethodName`")"
|
||||
$bslCode += "$keyword ${procName}()"
|
||||
$bslCode += $bodyLines
|
||||
$bslCode += "$endKeyword"
|
||||
|
||||
$bslText = ($bslCode -join "`r`n") + "`r`n"
|
||||
|
||||
# --- Check form borrowing for .Form. paths ---
|
||||
if ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
|
||||
$formName = $parts[3]
|
||||
$dirName = $typeDirMap[$objType]
|
||||
$formMetaFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") "${formName}.xml"
|
||||
$formXmlFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
|
||||
if (-not (Test-Path $formMetaFile) -or -not (Test-Path $formXmlFile)) {
|
||||
Write-Host "[WARN] Form '$formName' metadata or Form.xml not found in extension."
|
||||
Write-Host " Run /cfe-borrow first:"
|
||||
Write-Host " /cfe-borrow -ExtensionPath $ExtensionPath -ConfigPath <ConfigPath> -Object `"$objType.$objName.Form.$formName`""
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check if file exists and append ---
|
||||
$bslDir = Split-Path $bslFile -Parent
|
||||
if (-not (Test-Path $bslDir)) {
|
||||
New-Item -ItemType Directory -Path $bslDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
if (Test-Path $bslFile) {
|
||||
# Append to existing file
|
||||
$existing = [System.IO.File]::ReadAllText($bslFile, $enc)
|
||||
$separator = "`r`n"
|
||||
if ($existing -and -not $existing.EndsWith("`n")) {
|
||||
$separator = "`r`n`r`n"
|
||||
}
|
||||
$newContent = $existing + $separator + $bslText
|
||||
[System.IO.File]::WriteAllText($bslFile, $newContent, $enc)
|
||||
Write-Host "[OK] Добавлен перехватчик в существующий файл"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($bslFile, $bslText, $enc)
|
||||
Write-Host "[OK] Создан файл модуля"
|
||||
}
|
||||
|
||||
Write-Host " Файл: $bslFile"
|
||||
Write-Host " Декоратор: $decorator(`"$MethodName`")"
|
||||
Write-Host " Процедура: ${procName}()"
|
||||
Write-Host " Контекст: $contextAnnotation"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-patch-method v1.0 — Generate method interceptor for 1C extension (CFE)
|
||||
# cfe-patch-method v1.1 — Generate method interceptor for 1C extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -84,6 +84,22 @@ def main():
|
||||
"AccumulationRegister": "AccumulationRegisters",
|
||||
"AccountingRegister": "AccountingRegisters",
|
||||
"CalculationRegister": "CalculationRegisters",
|
||||
"Catalogs": "Catalogs",
|
||||
"Documents": "Documents",
|
||||
"Enums": "Enums",
|
||||
"CommonModules": "CommonModules",
|
||||
"Reports": "Reports",
|
||||
"DataProcessors": "DataProcessors",
|
||||
"ExchangePlans": "ExchangePlans",
|
||||
"ChartsOfAccounts": "ChartsOfAccounts",
|
||||
"ChartsOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
|
||||
"ChartsOfCalculationTypes": "ChartsOfCalculationTypes",
|
||||
"BusinessProcesses": "BusinessProcesses",
|
||||
"Tasks": "Tasks",
|
||||
"InformationRegisters": "InformationRegisters",
|
||||
"AccumulationRegisters": "AccumulationRegisters",
|
||||
"AccountingRegisters": "AccountingRegisters",
|
||||
"CalculationRegisters": "CalculationRegisters",
|
||||
}
|
||||
|
||||
parts = module_path.split(".")
|
||||
|
||||
@@ -17,33 +17,13 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|---------------|:-----:|---------|-------------------------------------------------|
|
||||
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate.ps1 -ExtensionPath "src"
|
||||
powershell.exe -NoProfile -File .claude/skills/cfe-validate/scripts/cfe-validate.ps1 -ExtensionPath "src/Configuration.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml"
|
||||
```
|
||||
|
||||
## Проверки (13 шагов)
|
||||
|
||||
| # | Проверка | Уровень |
|
||||
|---|----------|---------|
|
||||
| 1 | XML well-formedness, MetaDataObject/Configuration, version | ERROR |
|
||||
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId | ERROR |
|
||||
| 3 | Extension properties: ObjectBelonging=Adopted, Name, Purpose, NamePrefix, KeepMapping | ERROR |
|
||||
| 4 | Enum-значения: ConfigurationExtensionCompatibilityMode, DefaultRunMode, ScriptVariant, InterfaceCompatibilityMode | ERROR |
|
||||
| 5 | ChildObjects: валидные типы (44), нет дубликатов, каноничный порядок | ERROR/WARN |
|
||||
| 6 | DefaultLanguage ссылается на Language в ChildObjects | ERROR |
|
||||
| 7 | Файлы языков существуют | WARN |
|
||||
| 8 | Каталоги объектов существуют | WARN |
|
||||
| 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN |
|
||||
| 10 | Sub-items: Attribute, TabularSection (InternalInfo + вложенные), EnumValue, Form-ссылки | ERROR |
|
||||
| 11 | Заимствованные формы: метаданные, Form.xml, Module.bsl, BaseForm version | ERROR/WARN |
|
||||
| 12 | Зависимости форм: CommonPicture, StyleItem (с whitelist платформенных), Enum DesignTimeRef | WARN |
|
||||
| 13 | TypeLink: human-readable Items.* DataPath (должны быть удалены) | WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# cfe-validate v1.3 — Validate 1C configuration extension structure (CFE)
|
||||
# cfe-validate v1.4 — Validate 1C configuration extension structure (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ExtensionPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -145,10 +146,10 @@ $childTypeDirMap = @{
|
||||
|
||||
# Valid enum values for extension properties
|
||||
$validEnumValues = @{
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
|
||||
"ScriptVariant" = @("Russian","English")
|
||||
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
|
||||
"InterfaceCompatibilityMode" = @("Version8_2","Version8_2EnableTaxi","Taxi","TaxiEnableVersion8_2","TaxiEnableVersion8_5","Version8_5EnableTaxi","Version8_5")
|
||||
}
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
@@ -196,8 +197,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-validate v1.3 — Validate 1C configuration extension XML structure (CFE)
|
||||
# cfe-validate v1.4 — Validate 1C configuration extension XML structure (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
||||
import sys, os, argparse, re
|
||||
@@ -82,11 +82,14 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
'DefaultRunMode': ['ManagedApplication', 'OrdinaryApplication', 'Auto'],
|
||||
'ScriptVariant': ['Russian', 'English'],
|
||||
'InterfaceCompatibilityMode': ['Taxi', 'TaxiEnableVersion8_2', 'Version8_2'],
|
||||
'InterfaceCompatibilityMode': [
|
||||
'Version8_2', 'Version8_2EnableTaxi', 'Taxi', 'TaxiEnableVersion8_2',
|
||||
'TaxiEnableVersion8_5', 'Version8_5EnableTaxi', 'Version8_5',
|
||||
],
|
||||
}
|
||||
|
||||
EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||
@@ -147,7 +150,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C configuration extension XML structure (CFE)', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-ExtensionPath', dest='ExtensionPath', required=True)
|
||||
parser.add_argument('-ExtensionPath', '-Path', dest='ExtensionPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
@@ -213,8 +216,8 @@ def main():
|
||||
version = root.get('version', '')
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.20'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ('2.17', '2.20', '2.21'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-create
|
||||
description: Создание информационной базы 1С. Используй когда пользователь просит создать базу, новую ИБ, пустую базу
|
||||
description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу
|
||||
argument-hint: <path|name>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -31,7 +31,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -65,14 +65,14 @@ powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 <
|
||||
|
||||
```powershell
|
||||
# Создать файловую базу
|
||||
powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 -InfoBasePath "C:\Bases\NewDB"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB"
|
||||
|
||||
# Создать серверную базу
|
||||
powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
||||
|
||||
# Создать из шаблона CF
|
||||
powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
||||
|
||||
# Создать и добавить в список баз
|
||||
powershell.exe -NoProfile -File .claude/skills/db-create/scripts/db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-dump-cf
|
||||
description: Выгрузка конфигурации 1С в CF-файл. Используй когда пользователь просит выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
||||
description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
||||
argument-hint: "[database] [output.cf]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -35,7 +35,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -69,11 +69,11 @@ powershell.exe -NoProfile -File .claude/skills/db-dump-cf/scripts/db-dump-cf.ps1
|
||||
|
||||
```powershell
|
||||
# Выгрузка конфигурации (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
||||
|
||||
# Выгрузка расширения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-cf/scripts/db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-dump-xml
|
||||
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда пользователь просит выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
||||
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
||||
argument-hint: "[database] [outputDir]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -37,7 +37,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -81,17 +81,17 @@ powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.p
|
||||
|
||||
```powershell
|
||||
# Полная выгрузка (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
|
||||
# Инкрементальная выгрузка
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
|
||||
|
||||
# Частичная выгрузка
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
|
||||
# Выгрузка расширения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-list
|
||||
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда пользователь говорит про базы данных, список баз, "добавь базу", "какие базы есть"
|
||||
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть
|
||||
argument-hint: "[add|remove|show]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-load-cf
|
||||
description: Загрузка конфигурации 1С из CF-файла. Используй когда пользователь просит загрузить конфигурацию из CF, восстановить из бэкапа CF
|
||||
description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF
|
||||
argument-hint: <input.cf> [database]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -36,7 +36,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-cf/scripts/db-load-cf.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -71,11 +71,11 @@ powershell.exe -NoProfile -File .claude/skills/db-load-cf/scripts/db-load-cf.ps1
|
||||
|
||||
```powershell
|
||||
# Файловая база
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-cf/scripts/db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-cf/scripts/db-load-cf.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
||||
|
||||
# Загрузка расширения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-cf/scripts/db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
||||
```
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
---
|
||||
name: db-load-git
|
||||
description: Загрузка изменений из Git в базу 1С. Используй когда пользователь просит загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
||||
argument-hint: "[database] [source]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
# /db-load-git — Загрузка изменений из Git
|
||||
|
||||
Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/db-load-git [database]
|
||||
/db-load-git dev — все незафиксированные изменения
|
||||
/db-load-git dev -Source Staged — только staged
|
||||
/db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||
/db-load-git dev -DryRun — только показать что будет загружено
|
||||
```
|
||||
|
||||
## Параметры подключения
|
||||
|
||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||
4. Если ветка не совпала — используй `default`
|
||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
||||
Если файла нет — предложи `/db-list add`.
|
||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-git/scripts/db-load-git.ps1 <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|:------------:|----------|
|
||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||
| `-UserName <имя>` | нет | Имя пользователя |
|
||||
| `-Password <пароль>` | нет | Пароль |
|
||||
| `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) |
|
||||
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
|
||||
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
|
||||
| `-Extension <имя>` | нет | Загрузить в расширение |
|
||||
| `-AllExtensions` | нет | Загрузить все расширения |
|
||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
|
||||
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
||||
|
||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||
|
||||
## После выполнения
|
||||
|
||||
1. Показать список загруженных файлов и результат из лога
|
||||
2. Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Все незафиксированные изменения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-git/scripts/db-load-git.ps1 -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
||||
|
||||
# Из диапазона коммитов
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-git/scripts/db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||
```
|
||||
---
|
||||
name: db-load-git
|
||||
description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
||||
argument-hint: "[database] [source]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
# /db-load-git — Загрузка изменений из Git
|
||||
|
||||
Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/db-load-git [database]
|
||||
/db-load-git dev — все незафиксированные изменения
|
||||
/db-load-git dev -Source Staged — только staged
|
||||
/db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||
/db-load-git dev -DryRun — только показать что будет загружено
|
||||
```
|
||||
|
||||
## Параметры подключения
|
||||
|
||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||
4. Если ветка не совпала — используй `default`
|
||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
||||
Если файла нет — предложи `/db-list add`.
|
||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|:------------:|----------|
|
||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||
| `-UserName <имя>` | нет | Имя пользователя |
|
||||
| `-Password <пароль>` | нет | Пароль |
|
||||
| `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) |
|
||||
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
|
||||
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
|
||||
| `-Extension <имя>` | нет | Загрузить в расширение |
|
||||
| `-AllExtensions` | нет | Загрузить все расширения |
|
||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
|
||||
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
||||
|
||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||
|
||||
## После выполнения
|
||||
|
||||
1. Показать список загруженных файлов и результат из лога
|
||||
2. Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Все незафиксированные изменения
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
||||
|
||||
# Из диапазона коммитов
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-load-xml
|
||||
description: Загрузка конфигурации 1С из XML-файлов. Используй когда пользователь просит загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
||||
description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
||||
argument-hint: <configDir> [database]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -38,7 +38,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-xml/scripts/db-load-xml.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -96,14 +96,14 @@ Documents/Заказ/Forms/ФормаДокумента.xml
|
||||
|
||||
```powershell
|
||||
# Полная загрузка
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-xml/scripts/db-load-xml.ps1 -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||
|
||||
# Частичная загрузка конкретных файлов
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-xml/scripts/db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||
|
||||
# Загрузка расширения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-xml/scripts/db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||
|
||||
# Загрузка + обновление БД в одном запуске
|
||||
powershell.exe -NoProfile -File .claude/skills/db-load-xml/scripts/db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-load-xml v1.1 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
<#
|
||||
.SYNOPSIS
|
||||
@@ -98,7 +98,10 @@ param(
|
||||
[string]$Format = "Hierarchical",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$UpdateDB
|
||||
[switch]$UpdateDB,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$StrictLog
|
||||
)
|
||||
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
@@ -213,20 +216,58 @@ try {
|
||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
# --- Read log ---
|
||||
$logContent = $null
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# --- Scan log for silent rejections ---
|
||||
# Platform often writes load-time rejections into /Out but exits with code 0.
|
||||
# These patterns flag cases where metadata was dropped or rejected silently.
|
||||
$fatalLogPatterns = @(
|
||||
'Неверное свойство объекта метаданных',
|
||||
'не входит в состав объекта метаданных',
|
||||
'Неизвестное имя типа',
|
||||
'Неизвестный объект метаданных',
|
||||
'Ни один из документов не является регистратором для регистра',
|
||||
'Неверное значение перечисления',
|
||||
'не может быть приведен к типу'
|
||||
)
|
||||
$silentFailures = @()
|
||||
if ($logContent) {
|
||||
foreach ($line in ($logContent -split "`r?`n")) {
|
||||
foreach ($pat in $fatalLogPatterns) {
|
||||
if ($line -match [regex]::Escape($pat)) {
|
||||
$silentFailures += $line.Trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
||||
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host "Load completed successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($logContent) {
|
||||
Write-Host "--- Log ---"
|
||||
Write-Host $logContent
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
if ($logContent) {
|
||||
Write-Host "--- Log ---"
|
||||
Write-Host $logContent
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
|
||||
if ($silentFailures.Count -gt 0) {
|
||||
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs"
|
||||
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
|
||||
Write-Host $msg -ForegroundColor Yellow
|
||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-load-xml v1.1 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -63,6 +63,11 @@ def main():
|
||||
help="File format (default: Hierarchical)",
|
||||
)
|
||||
parser.add_argument("-UpdateDB", action="store_true", help="Also update database configuration after load")
|
||||
parser.add_argument(
|
||||
"-StrictLog",
|
||||
action="store_true",
|
||||
help="Treat silent rejection warnings in the log as errors (elevate exit code to 1)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- Resolve V8Path ---
|
||||
@@ -157,22 +162,60 @@ def main():
|
||||
)
|
||||
exit_code = result.returncode
|
||||
|
||||
# --- Read log ---
|
||||
log_content = ""
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
log_content = f.read()
|
||||
except Exception:
|
||||
log_content = ""
|
||||
|
||||
# --- Scan log for silent rejections ---
|
||||
# Platform often writes load-time rejections into /Out but exits with code 0.
|
||||
# These patterns flag cases where metadata was dropped or rejected silently.
|
||||
fatal_log_patterns = [
|
||||
"Неверное свойство объекта метаданных",
|
||||
"не входит в состав объекта метаданных",
|
||||
"Неизвестное имя типа",
|
||||
"Неизвестный объект метаданных",
|
||||
"Ни один из документов не является регистратором для регистра",
|
||||
"Неверное значение перечисления",
|
||||
"не может быть приведен к типу",
|
||||
]
|
||||
silent_failures = []
|
||||
if log_content:
|
||||
for line in log_content.splitlines():
|
||||
for pat in fatal_log_patterns:
|
||||
if pat in line:
|
||||
silent_failures.append(line.strip())
|
||||
break
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
||||
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
||||
if exit_code == 0:
|
||||
print("Load completed successfully")
|
||||
else:
|
||||
print(f"Error loading configuration (code: {exit_code})", file=sys.stderr)
|
||||
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
log_content = f.read()
|
||||
if log_content:
|
||||
print("--- Log ---")
|
||||
print(log_content)
|
||||
print("--- End ---")
|
||||
except Exception:
|
||||
pass
|
||||
if log_content:
|
||||
print("--- Log ---")
|
||||
print(log_content)
|
||||
print("--- End ---")
|
||||
|
||||
if silent_failures:
|
||||
suffix = "" if args.StrictLog else " (pass -StrictLog to treat as error)"
|
||||
print(
|
||||
f"[warning] log contains {len(silent_failures)} rejection(s) — "
|
||||
f"platform loaded config but dropped properties/refs{suffix}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
for f in silent_failures:
|
||||
print(f" {f}", file=sys.stderr)
|
||||
if args.StrictLog and exit_code == 0:
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-run
|
||||
description: Запуск 1С:Предприятие. Используй когда пользователь просит запустить 1С, открыть базу, запустить предприятие
|
||||
description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие
|
||||
argument-hint: "[database]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -36,7 +36,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -63,14 +63,14 @@ powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 <пар
|
||||
|
||||
```powershell
|
||||
# Простой запуск
|
||||
powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||
|
||||
# Запуск с обработкой
|
||||
powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
||||
|
||||
# Открыть по навигационной ссылке
|
||||
powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
||||
|
||||
# Серверная база с параметром запуска
|
||||
powershell.exe -NoProfile -File .claude/skills/db-run/scripts/db-run.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-update
|
||||
description: Обновление конфигурации базы данных 1С. Используй когда пользователь просит обновить БД, применить конфигурацию, UpdateDBCfg
|
||||
description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg
|
||||
argument-hint: "[database]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -35,7 +35,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/db-update/scripts/db-update.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -83,11 +83,11 @@ powershell.exe -NoProfile -File .claude/skills/db-update/scripts/db-update.ps1 <
|
||||
|
||||
```powershell
|
||||
# Обычное обновление (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/db-update/scripts/db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||
|
||||
# Динамическое обновление (серверная база)
|
||||
powershell.exe -NoProfile -File .claude/skills/db-update/scripts/db-update.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
|
||||
|
||||
# Обновление расширения
|
||||
powershell.exe -NoProfile -File .claude/skills/db-update/scripts/db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
||||
```
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
name: epf-add-form
|
||||
description: Добавить управляемую форму к внешней обработке 1С
|
||||
argument-hint: <ProcessorName> <FormName> [Synonym]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
---
|
||||
|
||||
# /epf-add-form — Добавление формы
|
||||
|
||||
Создаёт управляемую форму и регистрирует её в корневом XML обработки.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/epf-add-form <ProcessorName> <FormName> [Synonym] [--main]
|
||||
```
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|---------------|:------------:|--------------|-------------------------------------------|
|
||||
| ProcessorName | да | — | Имя обработки (должна существовать) |
|
||||
| FormName | да | — | Имя формы |
|
||||
| Synonym | нет | = FormName | Синоним формы |
|
||||
| --main | нет | авто | Установить как форму по умолчанию (автоматически для первой формы) |
|
||||
| SrcDir | нет | `src` | Каталог исходников |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-add-form/scripts/add-form.ps1 -ProcessorName "<ProcessorName>" -FormName "<FormName>" [-Synonym "<Synonym>"] [-Main] [-SrcDir "<SrcDir>"]
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<SrcDir>/<ProcessorName>/Forms/
|
||||
├── <FormName>.xml # Метаданные формы (1 UUID)
|
||||
└── <FormName>/
|
||||
└── Ext/
|
||||
├── Form.xml # Описание формы (logform namespace)
|
||||
└── Form/
|
||||
└── Module.bsl # BSL-модуль с 4 регионами
|
||||
```
|
||||
|
||||
## Что модифицируется
|
||||
|
||||
- `<SrcDir>/<ProcessorName>.xml` — добавляется `<Form>` в `ChildObjects`, обновляется `DefaultForm` (автоматически если это первая форма, или явно при `--main`)
|
||||
|
||||
## Детали
|
||||
|
||||
- FormType: Managed
|
||||
- UsePurposes: PlatformApplication, MobilePlatformApplication
|
||||
- AutoCommandBar с id=-1
|
||||
- Реквизит "Объект" с MainAttribute=true
|
||||
- BSL-модуль содержит 5 регионов: ОбработчикиСобытийФормы, ОбработчикиСобытийЭлементовФормы, ОбработчикиКомандФормы, ОбработчикиОповещений, СлужебныеПроцедурыИФункции
|
||||
@@ -1,207 +0,0 @@
|
||||
# epf-add-form v1.0 — Add managed form to 1C processor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ProcessorName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FormName,
|
||||
|
||||
[string]$Synonym = $FormName,
|
||||
|
||||
[switch]$Main,
|
||||
|
||||
[string]$SrcDir = "src"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
$rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml"
|
||||
if (-not (Test-Path $rootXmlPath)) {
|
||||
Write-Error "Корневой файл обработки не найден: $rootXmlPath. Сначала выполните epf-init."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$processorDir = Join-Path $SrcDir $ProcessorName
|
||||
$formsDir = Join-Path $processorDir "Forms"
|
||||
$formMetaPath = Join-Path $formsDir "$FormName.xml"
|
||||
|
||||
if (Test-Path $formMetaPath) {
|
||||
Write-Error "Форма уже существует: $formMetaPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Создание каталогов ---
|
||||
|
||||
$formDir = Join-Path $formsDir $FormName
|
||||
$formExtDir = Join-Path $formDir "Ext"
|
||||
$formModuleDir = Join-Path $formExtDir "Form"
|
||||
|
||||
New-Item -ItemType Directory -Path $formModuleDir -Force | Out-Null
|
||||
|
||||
# --- Кодировка ---
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
$encNoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
|
||||
# --- 1. Метаданные формы (Forms/<FormName>.xml) ---
|
||||
|
||||
$formUuid = [guid]::NewGuid().ToString()
|
||||
|
||||
$formMetaXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Form uuid="$formUuid">
|
||||
<Properties>
|
||||
<Name>$FormName</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>$Synonym</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom)
|
||||
|
||||
# --- 2. Описание формы (Forms/<FormName>/Ext/Form.xml) ---
|
||||
|
||||
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:ExternalDataProcessorObject.$ProcessorName</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
|
||||
# --- 3. BSL-модуль (Forms/<FormName>/Ext/Form/Module.bsl) ---
|
||||
|
||||
$modulePath = Join-Path $formModuleDir "Module.bsl"
|
||||
|
||||
$moduleBsl = @"
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
|
||||
# --- 4. Модификация корневого XML ---
|
||||
|
||||
$rootXmlFull = Resolve-Path $rootXmlPath
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($rootXmlFull.Path)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
$childObjects = $xmlDoc.SelectSingleNode("//md:ChildObjects", $nsMgr)
|
||||
if (-not $childObjects) {
|
||||
Write-Error "Не найден элемент ChildObjects в $rootXmlPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Form> перед первым <Template>, или в конец
|
||||
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $FormName
|
||||
|
||||
$firstTemplate = $childObjects.SelectSingleNode("md:Template", $nsMgr)
|
||||
if ($firstTemplate) {
|
||||
# Вставить перед Template, добавив перенос строки + табуляцию
|
||||
$whitespace = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($whitespace, $firstTemplate) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $whitespace) | Out-Null
|
||||
} else {
|
||||
# Добавить в конец ChildObjects
|
||||
# Если ChildObjects пустой (самозакрывающийся), нужно добавить форматирование
|
||||
if ($childObjects.ChildNodes.Count -eq 0) {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
# Вставить перед закрывающим whitespace (если есть), или в конец
|
||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$childObjects.InsertBefore($xmlDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Обновить DefaultForm: явно при -Main, или автоматически если это первая форма
|
||||
$existingForms = $childObjects.SelectNodes("md:Form", $nsMgr)
|
||||
$isFirstForm = ($existingForms.Count -eq 1)
|
||||
|
||||
if ($Main -or $isFirstForm) {
|
||||
$defaultForm = $xmlDoc.SelectSingleNode("//md:DefaultForm", $nsMgr)
|
||||
if ($defaultForm) {
|
||||
$defaultForm.InnerText = "ExternalDataProcessor.$ProcessorName.Form.$FormName"
|
||||
}
|
||||
}
|
||||
|
||||
# Сохранить с BOM
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $encBom
|
||||
$settings.Indent = $false # Preserve original whitespace
|
||||
|
||||
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
||||
$xmlDoc.Save($writer)
|
||||
$writer.Close()
|
||||
$stream.Close()
|
||||
|
||||
Write-Host "[OK] Создана форма: $FormName"
|
||||
Write-Host " Метаданные: $formMetaPath"
|
||||
Write-Host " Описание: $formXmlPath"
|
||||
Write-Host " Модуль: $modulePath"
|
||||
if ($Main -or $isFirstForm) {
|
||||
Write-Host " DefaultForm обновлён"
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-form v1.0 — Add managed form to 1C external data processor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
|
||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||
|
||||
|
||||
def save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
|
||||
|
||||
def write_text_with_bom(path, text):
|
||||
"""Write text to file with UTF-8 BOM."""
|
||||
with open(path, "w", encoding="utf-8-sig") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Add managed form to 1C processor", allow_abbrev=False)
|
||||
parser.add_argument("-ProcessorName", required=True)
|
||||
parser.add_argument("-FormName", required=True)
|
||||
parser.add_argument("-Synonym", default=None)
|
||||
parser.add_argument("-Main", action="store_true")
|
||||
parser.add_argument("-SrcDir", default="src")
|
||||
args = parser.parse_args()
|
||||
|
||||
processor_name = args.ProcessorName
|
||||
form_name = args.FormName
|
||||
synonym = args.Synonym if args.Synonym is not None else form_name
|
||||
is_main = args.Main
|
||||
src_dir = args.SrcDir
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
root_xml_path = os.path.join(src_dir, f"{processor_name}.xml")
|
||||
if not os.path.exists(root_xml_path):
|
||||
print(f"Корневой файл обработки не найден: {root_xml_path}. Сначала выполните epf-init.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
processor_dir = os.path.join(src_dir, processor_name)
|
||||
forms_dir = os.path.join(processor_dir, "Forms")
|
||||
form_meta_path = os.path.join(forms_dir, f"{form_name}.xml")
|
||||
|
||||
if os.path.exists(form_meta_path):
|
||||
print(f"Форма уже существует: {form_meta_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Create directories ---
|
||||
|
||||
form_dir = os.path.join(forms_dir, form_name)
|
||||
form_ext_dir = os.path.join(form_dir, "Ext")
|
||||
form_module_dir = os.path.join(form_ext_dir, "Form")
|
||||
|
||||
os.makedirs(form_module_dir, exist_ok=True)
|
||||
|
||||
# --- 1. Form metadata (Forms/<FormName>.xml) ---
|
||||
|
||||
form_uuid = str(uuid.uuid4())
|
||||
|
||||
form_meta_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
f'\t<Form uuid="{form_uuid}">\n'
|
||||
'\t\t<Properties>\n'
|
||||
f'\t\t\t<Name>{form_name}</Name>\n'
|
||||
'\t\t\t<Synonym>\n'
|
||||
'\t\t\t\t<v8:item>\n'
|
||||
'\t\t\t\t\t<v8:lang>ru</v8:lang>\n'
|
||||
f'\t\t\t\t\t<v8:content>{synonym}</v8:content>\n'
|
||||
'\t\t\t\t</v8:item>\n'
|
||||
'\t\t\t</Synonym>\n'
|
||||
'\t\t\t<Comment/>\n'
|
||||
'\t\t\t<FormType>Managed</FormType>\n'
|
||||
'\t\t\t<IncludeHelpInContents>false</IncludeHelpInContents>\n'
|
||||
'\t\t\t<UsePurposes>\n'
|
||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n'
|
||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n'
|
||||
'\t\t\t</UsePurposes>\n'
|
||||
'\t\t\t<ExtendedPresentation/>\n'
|
||||
'\t\t</Properties>\n'
|
||||
'\t</Form>\n'
|
||||
'</MetaDataObject>'
|
||||
)
|
||||
|
||||
write_text_with_bom(form_meta_path, form_meta_xml)
|
||||
|
||||
# --- 2. Form description (Forms/<FormName>/Ext/Form.xml) ---
|
||||
|
||||
form_xml_path = os.path.join(form_ext_dir, "Form.xml")
|
||||
|
||||
form_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<Form xmlns="http://v8.1c.ru/8.3/xcf/logform"'
|
||||
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||
' xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"'
|
||||
' xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"'
|
||||
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1">\n'
|
||||
'\t\t\t<Type>\n'
|
||||
f'\t\t\t\t<v8:Type>cfg:ExternalDataProcessorObject.{processor_name}</v8:Type>\n'
|
||||
'\t\t\t</Type>\n'
|
||||
'\t\t\t<MainAttribute>true</MainAttribute>\n'
|
||||
'\t\t</Attribute>\n'
|
||||
'\t</Attributes>\n'
|
||||
'</Form>'
|
||||
)
|
||||
|
||||
write_text_with_bom(form_xml_path, form_xml)
|
||||
|
||||
# --- 3. BSL module (Forms/<FormName>/Ext/Form/Module.bsl) ---
|
||||
|
||||
module_path = os.path.join(form_module_dir, "Module.bsl")
|
||||
|
||||
module_bsl = (
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u041a\u043e\u043c\u0430\u043d\u0434\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u041e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0439\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u0421\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0435\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b\u0418\u0424\u0443\u043d\u043a\u0446\u0438\u0438\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438'
|
||||
)
|
||||
|
||||
write_text_with_bom(module_path, module_bsl)
|
||||
|
||||
# --- 4. Modify root XML ---
|
||||
|
||||
root_xml_full = os.path.abspath(root_xml_path)
|
||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(root_xml_full, parser_xml)
|
||||
root = tree.getroot()
|
||||
|
||||
ns = "http://v8.1c.ru/8.3/MDClasses"
|
||||
child_objects = root.find(".//md:ChildObjects", NSMAP)
|
||||
if child_objects is None:
|
||||
print(f"Не найден элемент ChildObjects в {root_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Add <Form> before first <Template>, or at end
|
||||
form_elem = etree.Element(f"{{{ns}}}Form")
|
||||
form_elem.text = form_name
|
||||
|
||||
first_template = child_objects.find("md:Template", NSMAP)
|
||||
if first_template is not None:
|
||||
# Insert before Template, adding newline + indent
|
||||
idx = list(child_objects).index(first_template)
|
||||
child_objects.insert(idx, form_elem)
|
||||
# Set whitespace: form_elem gets same tail pattern
|
||||
form_elem.tail = "\n\t\t\t"
|
||||
else:
|
||||
# Add to end of ChildObjects
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
|
||||
# Update DefaultForm: explicitly with -Main, or automatically if this is the first form
|
||||
existing_forms = child_objects.findall("md:Form", NSMAP)
|
||||
is_first_form = len(existing_forms) == 1
|
||||
|
||||
if is_main or is_first_form:
|
||||
default_form = root.find(".//md:DefaultForm", NSMAP)
|
||||
if default_form is not None:
|
||||
default_form.text = f"ExternalDataProcessor.{processor_name}.Form.{form_name}"
|
||||
|
||||
# Save with BOM
|
||||
save_xml_with_bom(tree, root_xml_full)
|
||||
|
||||
print(f"[OK] Создана форма: {form_name}")
|
||||
print(f" Метаданные: {form_meta_path}")
|
||||
print(f" Описание: {form_xml_path}")
|
||||
print(f" Модуль: {module_path}")
|
||||
if is_main or is_first_form:
|
||||
print(" DefaultForm обновлён")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-bsp-add-command
|
||||
description: Добавить команду в дополнительную обработку БСП
|
||||
description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП
|
||||
argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -191,7 +191,6 @@ allowed-tools:
|
||||
НоваяКоманда.Идентификатор = "ЗаказПокупателя";
|
||||
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
|
||||
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
||||
НоваяКоманда.Модификатор = "ПечатьMXL";
|
||||
```
|
||||
|
||||
И в существующую процедуру `Печать` добавится блок обработки.
|
||||
И в существующую процедуру `ВыполнитьКоманду` добавится блок обработки.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-bsp-init
|
||||
description: Добавить функцию регистрации БСП (СведенияОВнешнейОбработке) в модуль объекта обработки
|
||||
description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки»
|
||||
argument-hint: <ProcessorName> <Вид>
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -203,6 +203,6 @@ allowed-tools:
|
||||
## Дальнейшие шаги
|
||||
|
||||
- Добавить ещё команду: `/epf-bsp-add-command`
|
||||
- Добавить форму: `/epf-add-form`
|
||||
- Добавить форму: `/form-add`
|
||||
- Добавить макет: `/template-add`
|
||||
- Собрать EPF: `/epf-build`
|
||||
|
||||
@@ -40,7 +40,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -62,8 +62,8 @@ powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 <
|
||||
|
||||
```powershell
|
||||
# Сборка обработки (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -62,8 +62,8 @@ powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 <п
|
||||
|
||||
```powershell
|
||||
# Разборка обработки (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-init
|
||||
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников)
|
||||
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля
|
||||
argument-hint: <Name> [Synonym]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -30,26 +30,12 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-init/scripts/init.ps1 -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<SrcDir>/
|
||||
├── <Name>.xml # Корневой файл метаданных (4 UUID)
|
||||
└── <Name>/
|
||||
└── Ext/
|
||||
└── ObjectModule.bsl # Модуль объекта с 3 регионами
|
||||
```
|
||||
|
||||
- Корневой XML содержит `MetaDataObject/ExternalDataProcessor` с пустыми `DefaultForm` и `ChildObjects`
|
||||
- ClassId фиксирован: `c3831ec8-d8d5-4f93-8a22-f9bfae07327f`
|
||||
- Файл создаётся в UTF-8 с BOM
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
- Добавить форму: `/epf-add-form`
|
||||
- Добавить форму: `/form-add`
|
||||
- Добавить макет: `/template-add`
|
||||
- Добавить справку: `/help-add`
|
||||
- Собрать EPF: `/epf-build`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# epf-init v1.0 — Init 1C external data processor scaffold
|
||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -10,6 +10,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-init v1.0 — Init 1C external data processor scaffold
|
||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external data processor."""
|
||||
import sys, os, argparse, uuid
|
||||
|
||||
@@ -17,30 +17,14 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|------------|:-----:|---------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МояОбработка"
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
||||
```
|
||||
|
||||
## Проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|-------------------------------------------------------|--------------|
|
||||
| 1 | Root structure: MetaDataObject/ExternalDataProcessor | ERROR |
|
||||
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
|
||||
| 3 | Properties: Name (identifier), Synonym | ERROR / WARN |
|
||||
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
|
||||
| 5 | Cross-references: DefaultForm → Form, AuxiliaryForm | ERROR / WARN |
|
||||
| 6 | Attributes: UUID, Name, Type | ERROR |
|
||||
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
|
||||
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
|
||||
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
|
||||
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# epf-validate v1.1 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.2 — Validate 1C external data processor / report structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -184,8 +185,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Detect type: ExternalDataProcessor or ExternalReport
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-validate v1.1 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.2 — Validate 1C external data processor / report structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||
|
||||
@@ -46,7 +46,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Validate 1C external data processor/report structure", allow_abbrev=False)
|
||||
parser.add_argument("-ObjectPath", required=True)
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
parser.add_argument("-OutFile", default=None)
|
||||
@@ -165,8 +165,8 @@ def main():
|
||||
version = root.get("version", "")
|
||||
if not version:
|
||||
report_warn("1. Missing version attribute on MetaDataObject")
|
||||
elif version not in ("2.17", "2.20"):
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ("2.17", "2.20", "2.21"):
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Detect type
|
||||
child_elements = []
|
||||
|
||||
@@ -42,7 +42,7 @@ allowed-tools:
|
||||
Используй общий скрипт из epf-build:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -64,8 +64,8 @@ powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 <
|
||||
|
||||
```powershell
|
||||
# Сборка отчёта (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-build/scripts/epf-build.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||
```
|
||||
|
||||
@@ -41,7 +41,7 @@ allowed-tools:
|
||||
Используй общий скрипт из epf-dump:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 <параметры>
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" <параметры>
|
||||
```
|
||||
|
||||
### Параметры скрипта
|
||||
@@ -64,8 +64,8 @@ powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 <п
|
||||
|
||||
```powershell
|
||||
# Разборка отчёта (файловая база)
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||
|
||||
# Серверная база
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-dump/scripts/epf-dump.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: erf-init
|
||||
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников)
|
||||
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля
|
||||
argument-hint: <Name> [Synonym] [--with-skd]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -31,35 +31,9 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/erf-init/scripts/init.ps1 -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<SrcDir>/
|
||||
├── <Name>.xml # Корневой файл метаданных (4 UUID)
|
||||
└── <Name>/
|
||||
└── Ext/
|
||||
└── ObjectModule.bsl # Модуль объекта с 3 регионами
|
||||
```
|
||||
|
||||
При `--WithSKD` дополнительно:
|
||||
|
||||
```
|
||||
<SrcDir>/<Name>/
|
||||
Templates/
|
||||
├── ОсновнаяСхемаКомпоновкиДанных.xml # Метаданные макета
|
||||
└── ОсновнаяСхемаКомпоновкиДанных/
|
||||
└── Ext/
|
||||
└── Template.xml # Пустая СКД
|
||||
```
|
||||
|
||||
- Корневой XML содержит `MetaDataObject/ExternalReport` с пустыми `DefaultForm`, `MainDataCompositionSchema` и `ChildObjects`
|
||||
- При `--WithSKD` — `MainDataCompositionSchema` заполняется ссылкой на макет, `ChildObjects` содержит `<Template>`
|
||||
- ClassId фиксирован: `e41aff26-25cf-4bb6-b6c1-3f478a75f374`
|
||||
- Файл создаётся в UTF-8 с BOM
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
- Добавить форму: `/form-add`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# erf-init v1.0 — Init 1C external report scaffold
|
||||
# erf-init v1.1 — Init 1C external report scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# erf-init v1.0 — Init 1C external report scaffold
|
||||
# erf-init v1.1 — Init 1C external report scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external report."""
|
||||
import sys, os, argparse, uuid
|
||||
|
||||
@@ -19,30 +19,14 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|------------|:-----:|---------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к корневому XML или каталогу отчёта |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МойОтчёт"
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-validate/scripts/epf-validate.ps1 -ObjectPath "src/МойОтчёт/МойОтчёт.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт/МойОтчёт.xml"
|
||||
```
|
||||
|
||||
## Проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|-------------------------------------------------------|--------------|
|
||||
| 1 | Root structure: MetaDataObject/ExternalReport | ERROR |
|
||||
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
|
||||
| 3 | Properties: Name, Synonym, MainDataCompositionSchema | ERROR / WARN |
|
||||
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
|
||||
| 5 | Cross-references: DefaultForm, MainDCS → Template | ERROR / WARN |
|
||||
| 6 | Attributes: UUID, Name, Type | ERROR |
|
||||
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
|
||||
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
|
||||
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
|
||||
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: form-add
|
||||
description: Добавить управляемую форму к объекту конфигурации 1С
|
||||
description: Добавить пустую управляемую форму к объекту 1С. Используй когда нужно создать у объекта новую форму
|
||||
argument-hint: <ObjectPath> <FormName> [Purpose] [--set-default]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -32,7 +32,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-add/scripts/form-add.ps1 -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-add.ps1" -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault]
|
||||
```
|
||||
|
||||
## Purpose — назначение формы
|
||||
@@ -44,26 +44,6 @@ powershell.exe -NoProfile -File .claude/skills/form-add/scripts/form-add.ps1 -Ob
|
||||
| Choice | Document, Catalog, ChartOf*, ExchangePlan, BusinessProcess, Task | Список (DynamicList) | DefaultChoiceForm |
|
||||
| Record | InformationRegister | Запись (InformationRegisterRecordManager) | DefaultRecordForm |
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<ObjectDir>/Forms/
|
||||
├── <FormName>.xml # Метаданные формы (UUID)
|
||||
└── <FormName>/
|
||||
└── Ext/
|
||||
├── Form.xml # Описание формы (logform namespace)
|
||||
└── Form/
|
||||
└── Module.bsl # BSL-модуль с 5 регионами + ПриСозданииНаСервере
|
||||
```
|
||||
|
||||
## Что модифицируется
|
||||
|
||||
- `<ObjectPath>` — добавляется `<Form>` в `ChildObjects` (перед `<Template>` или `<TabularSection>`), обновляется Default*Form (автоматически если пустое, или явно при `--set-default`)
|
||||
|
||||
## Поддерживаемые типы объектов
|
||||
|
||||
Document, Catalog, DataProcessor, Report, ExternalDataProcessor, ExternalReport, InformationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ExchangePlan, BusinessProcess, Task
|
||||
|
||||
## Примеры
|
||||
|
||||
```
|
||||
|
||||
@@ -1,448 +1,478 @@
|
||||
# form-add v1.0 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FormName,
|
||||
|
||||
[string]$Synonym = $FormName,
|
||||
|
||||
[string]$Purpose = "Object",
|
||||
|
||||
[switch]$SetDefault
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Фаза 1: Определение типа объекта ---
|
||||
|
||||
if (-not (Test-Path $ObjectPath)) {
|
||||
Write-Error "Файл объекта не найден: $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$objectXmlFull = Resolve-Path $ObjectPath
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($objectXmlFull.Path)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
# Определяем тип объекта по корневому тегу внутри MetaDataObject
|
||||
$metaDataObject = $xmlDoc.SelectSingleNode("//md:MetaDataObject", $nsMgr)
|
||||
if (-not $metaDataObject) {
|
||||
# Пробуем без namespace (fallback)
|
||||
$metaDataObject = $xmlDoc.DocumentElement
|
||||
}
|
||||
|
||||
$supportedTypes = @(
|
||||
"Document", "Catalog", "DataProcessor", "Report",
|
||||
"ExternalDataProcessor", "ExternalReport",
|
||||
"InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ExchangePlan", "BusinessProcess", "Task"
|
||||
)
|
||||
|
||||
$objectType = $null
|
||||
$objectNode = $null
|
||||
foreach ($t in $supportedTypes) {
|
||||
$node = $xmlDoc.SelectSingleNode("//md:$t", $nsMgr)
|
||||
if ($node) {
|
||||
$objectType = $t
|
||||
$objectNode = $node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $objectType) {
|
||||
Write-Error "Не удалось определить тип объекта. Поддерживаемые типы: $($supportedTypes -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Имя объекта из Properties/Name
|
||||
$objectName = $xmlDoc.SelectSingleNode("//md:${objectType}/md:Properties/md:Name", $nsMgr).InnerText
|
||||
if (-not $objectName) {
|
||||
Write-Error "Не удалось определить имя объекта из Properties/Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== form-add ==="
|
||||
Write-Host ""
|
||||
Write-Host "Object: $objectType.$objectName"
|
||||
|
||||
# --- Фаза 2: Валидация Purpose ---
|
||||
|
||||
$Purpose = $Purpose.Substring(0,1).ToUpper() + $Purpose.Substring(1).ToLower()
|
||||
# Нормализация
|
||||
switch ($Purpose) {
|
||||
"Object" { }
|
||||
"List" { }
|
||||
"Choice" { }
|
||||
"Record" { }
|
||||
default {
|
||||
Write-Error "Недопустимое назначение: $Purpose. Допустимые: Object, List, Choice, Record"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$objectLikeTypes = @("Document", "Catalog", "ChartOfAccounts", "ChartOfCharacteristicTypes", "ExchangePlan", "BusinessProcess", "Task")
|
||||
$processorLikeTypes = @("DataProcessor", "Report", "ExternalDataProcessor", "ExternalReport")
|
||||
|
||||
switch ($Purpose) {
|
||||
"Object" {
|
||||
# допустимо для всех типов
|
||||
}
|
||||
"List" {
|
||||
if ($objectType -eq "DataProcessor") {
|
||||
Write-Error "Purpose=List недопустим для DataProcessor"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
"Choice" {
|
||||
if ($objectType -in $processorLikeTypes -or $objectType -eq "InformationRegister") {
|
||||
Write-Error "Purpose=Choice недопустим для $objectType"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
"Record" {
|
||||
if ($objectType -ne "InformationRegister") {
|
||||
Write-Error "Purpose=Record допустим только для InformationRegister"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Фаза 3: Создание файлов ---
|
||||
|
||||
$objectDir = [System.IO.Path]::ChangeExtension($objectXmlFull.Path, $null).TrimEnd('.')
|
||||
$formsDir = Join-Path $objectDir "Forms"
|
||||
$formMetaPath = Join-Path $formsDir "$FormName.xml"
|
||||
|
||||
if (Test-Path $formMetaPath) {
|
||||
Write-Error "Форма уже существует: $formMetaPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$formDir = Join-Path $formsDir $FormName
|
||||
$formExtDir = Join-Path $formDir "Ext"
|
||||
$formModuleDir = Join-Path $formExtDir "Form"
|
||||
|
||||
New-Item -ItemType Directory -Path $formModuleDir -Force | Out-Null
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# --- 3a. Метаданные формы ---
|
||||
|
||||
$formUuid = [guid]::NewGuid().ToString()
|
||||
|
||||
$formMetaXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Form uuid="$formUuid">
|
||||
<Properties>
|
||||
<Name>$FormName</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>$Synonym</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom)
|
||||
|
||||
# --- 3b. Form.xml ---
|
||||
|
||||
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||
|
||||
$formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
|
||||
if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
# Динамический список
|
||||
# MainTable: тип.имя
|
||||
$mainTable = "$objectType.$objectName"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Список" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DynamicList</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<Settings xsi:type="DynamicList">
|
||||
<MainTable>$mainTable</MainTable>
|
||||
</Settings>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
} elseif ($Purpose -eq "Record") {
|
||||
# Запись регистра сведений
|
||||
$mainAttrName = "Запись"
|
||||
$mainAttrType = "InformationRegisterRecordManager.$objectName"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:$mainAttrType</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
} else {
|
||||
# Object — форма объекта
|
||||
$mainAttrName = "Объект"
|
||||
|
||||
# Маппинг типа объекта на тип реквизита
|
||||
$attrTypeMap = @{
|
||||
"Document" = "DocumentObject"
|
||||
"Catalog" = "CatalogObject"
|
||||
"DataProcessor" = "DataProcessorObject"
|
||||
"Report" = "ReportObject"
|
||||
"ExternalDataProcessor" = "ExternalDataProcessorObject"
|
||||
"ExternalReport" = "ExternalReportObject"
|
||||
"ChartOfAccounts" = "ChartOfAccountsObject"
|
||||
"ChartOfCharacteristicTypes" = "ChartOfCharacteristicTypesObject"
|
||||
"ExchangePlan" = "ExchangePlanObject"
|
||||
"BusinessProcess" = "BusinessProcessObject"
|
||||
"Task" = "TaskObject"
|
||||
"InformationRegister" = "InformationRegisterRecordManager"
|
||||
}
|
||||
|
||||
$mainAttrType = "$($attrTypeMap[$objectType]).$objectName"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:$mainAttrType</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
}
|
||||
|
||||
if (Test-Path $formXmlPath) {
|
||||
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
}
|
||||
|
||||
# --- 3c. Module.bsl ---
|
||||
|
||||
$modulePath = Join-Path $formModuleDir "Module.bsl"
|
||||
|
||||
$moduleBsl = @"
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
&НаСервере
|
||||
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
"@
|
||||
|
||||
if (Test-Path $modulePath) {
|
||||
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
}
|
||||
|
||||
# --- Фаза 4: Регистрация в родительском объекте ---
|
||||
|
||||
$childObjects = $xmlDoc.SelectSingleNode("//md:${objectType}/md:ChildObjects", $nsMgr)
|
||||
if (-not $childObjects) {
|
||||
Write-Error "Не найден элемент ChildObjects в $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Form>$FormName</Form>
|
||||
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $FormName
|
||||
|
||||
# Ищем первый <Template> для вставки перед ним
|
||||
$firstTemplate = $childObjects.SelectSingleNode("md:Template", $nsMgr)
|
||||
# Ищем первую <TabularSection> для вставки перед ней (если нет Template)
|
||||
$firstTabular = $childObjects.SelectSingleNode("md:TabularSection", $nsMgr)
|
||||
|
||||
# Определяем точку вставки: перед Template, перед TabularSection, или в конец
|
||||
$insertBefore = $null
|
||||
if ($firstTemplate) {
|
||||
$insertBefore = $firstTemplate
|
||||
} elseif ($firstTabular) {
|
||||
$insertBefore = $firstTabular
|
||||
}
|
||||
|
||||
if ($insertBefore) {
|
||||
# Вставить перед найденным элементом, с переносом строки
|
||||
$whitespace = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
|
||||
$childObjects.InsertBefore($whitespace, $formElem) | Out-Null
|
||||
# Переставляем: whitespace перед formElem — неправильный порядок
|
||||
# Правильно: formElem, затем whitespace перед insertBefore
|
||||
# InsertBefore возвращает вставленный узел, порядок: ... formElem whitespace insertBefore ...
|
||||
# На самом деле нам нужно: ... \n\t\t\tformElem \n\t\t\tinsertBefore
|
||||
# Удалим и вставим правильно
|
||||
$childObjects.RemoveChild($whitespace) | Out-Null
|
||||
$childObjects.RemoveChild($formElem) | Out-Null
|
||||
|
||||
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
|
||||
# Whitespace нужен ДО formElem (перенос строки + отступ)
|
||||
# Но перед insertBefore уже должен быть whitespace от предыдущего элемента
|
||||
# Нам нужно добавить whitespace ПОСЛЕ formElem (перед insertBefore)
|
||||
$ws = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($ws, $insertBefore) | Out-Null
|
||||
} else {
|
||||
# Добавить в конец ChildObjects
|
||||
if ($childObjects.ChildNodes.Count -eq 0) {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$childObjects.InsertBefore($xmlDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- SetDefault ---
|
||||
|
||||
$existingForms = $childObjects.SelectNodes("md:Form", $nsMgr)
|
||||
$isFirstFormForPurpose = $false
|
||||
$defaultPropName = $null
|
||||
$defaultValue = "$objectType.$objectName.Form.$FormName"
|
||||
|
||||
# Определяем имя свойства для DefaultForm
|
||||
switch ($Purpose) {
|
||||
"Object" {
|
||||
if ($objectType -in $processorLikeTypes) {
|
||||
$defaultPropName = "DefaultForm"
|
||||
} else {
|
||||
$defaultPropName = "DefaultObjectForm"
|
||||
}
|
||||
}
|
||||
"List" { $defaultPropName = "DefaultListForm" }
|
||||
"Choice" { $defaultPropName = "DefaultChoiceForm" }
|
||||
"Record" { $defaultPropName = "DefaultRecordForm" }
|
||||
}
|
||||
|
||||
# Проверяем, установлено ли уже значение
|
||||
$defaultNode = $xmlDoc.SelectSingleNode("//md:${objectType}/md:Properties/md:$defaultPropName", $nsMgr)
|
||||
if ($defaultNode) {
|
||||
$isFirstFormForPurpose = [string]::IsNullOrWhiteSpace($defaultNode.InnerText)
|
||||
}
|
||||
|
||||
$defaultUpdated = $false
|
||||
if ($SetDefault -or $isFirstFormForPurpose) {
|
||||
if ($defaultNode) {
|
||||
$defaultNode.InnerText = $defaultValue
|
||||
$defaultUpdated = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Сохранить с BOM
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $encBom
|
||||
$settings.Indent = $false
|
||||
|
||||
$stream = New-Object System.IO.FileStream($objectXmlFull.Path, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
||||
$xmlDoc.Save($writer)
|
||||
$writer.Close()
|
||||
$stream.Close()
|
||||
|
||||
# --- Фаза 5: Вывод ---
|
||||
|
||||
# Относительные пути для вывода
|
||||
$basePath = Split-Path $objectXmlFull.Path -Parent
|
||||
# Определяем корень (ищем родительский каталог типа Documents, Catalogs и т.д.)
|
||||
$relFormMeta = $formMetaPath.Replace($basePath, "").TrimStart("\", "/")
|
||||
$relFormXml = $formXmlPath.Replace($basePath, "").TrimStart("\", "/")
|
||||
$relModule = $modulePath.Replace($basePath, "").TrimStart("\", "/")
|
||||
|
||||
$objFileName = [System.IO.Path]::GetFileName($ObjectPath)
|
||||
$objDirName = Split-Path $ObjectPath -Parent
|
||||
$objBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ObjectPath)
|
||||
|
||||
Write-Host "Created:"
|
||||
Write-Host " Metadata: $objDirName\$objBaseName\Forms\$FormName.xml"
|
||||
Write-Host " Form: $objDirName\$objBaseName\Forms\$FormName\Ext\Form.xml"
|
||||
Write-Host " Module: $objDirName\$objBaseName\Forms\$FormName\Ext\Form\Module.bsl"
|
||||
Write-Host ""
|
||||
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
|
||||
if ($defaultUpdated) {
|
||||
Write-Host "${defaultPropName}: $defaultValue"
|
||||
}
|
||||
Write-Host ""
|
||||
# form-add v1.5 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FormName,
|
||||
|
||||
[string]$Synonym = $FormName,
|
||||
|
||||
[string]$Purpose = "Object",
|
||||
|
||||
[switch]$SetDefault
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Detect XML format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
# --- Фаза 1: Определение типа объекта ---
|
||||
|
||||
# Resolve ObjectPath (directory → .xml)
|
||||
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
|
||||
$ObjectPath = Join-Path (Get-Location).Path $ObjectPath
|
||||
}
|
||||
if (Test-Path $ObjectPath -PathType Container) {
|
||||
$dirName = Split-Path $ObjectPath -Leaf
|
||||
$candidate = Join-Path $ObjectPath "$dirName.xml"
|
||||
$sibling = Join-Path (Split-Path $ObjectPath) "$dirName.xml"
|
||||
if (Test-Path $candidate) { $ObjectPath = $candidate }
|
||||
elseif (Test-Path $sibling) { $ObjectPath = $sibling }
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ObjectPath)) {
|
||||
Write-Error "Файл объекта не найден: $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$objectXmlFull = Resolve-Path $ObjectPath
|
||||
$script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent)
|
||||
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($objectXmlFull.Path)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
# Определяем тип объекта по корневому тегу внутри MetaDataObject
|
||||
$metaDataObject = $xmlDoc.SelectSingleNode("//md:MetaDataObject", $nsMgr)
|
||||
if (-not $metaDataObject) {
|
||||
# Пробуем без namespace (fallback)
|
||||
$metaDataObject = $xmlDoc.DocumentElement
|
||||
}
|
||||
|
||||
$supportedTypes = @(
|
||||
"Document", "Catalog", "DataProcessor", "Report",
|
||||
"ExternalDataProcessor", "ExternalReport",
|
||||
"InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ExchangePlan", "BusinessProcess", "Task"
|
||||
)
|
||||
|
||||
$objectType = $null
|
||||
$objectNode = $null
|
||||
foreach ($t in $supportedTypes) {
|
||||
$node = $xmlDoc.SelectSingleNode("//md:$t", $nsMgr)
|
||||
if ($node) {
|
||||
$objectType = $t
|
||||
$objectNode = $node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $objectType) {
|
||||
Write-Error "Не удалось определить тип объекта. Поддерживаемые типы: $($supportedTypes -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Имя объекта из Properties/Name
|
||||
$objectName = $xmlDoc.SelectSingleNode("//md:${objectType}/md:Properties/md:Name", $nsMgr).InnerText
|
||||
if (-not $objectName) {
|
||||
Write-Error "Не удалось определить имя объекта из Properties/Name"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== form-add ==="
|
||||
Write-Host ""
|
||||
Write-Host "Object: $objectType.$objectName"
|
||||
|
||||
# --- Фаза 2: Валидация Purpose ---
|
||||
|
||||
$Purpose = $Purpose.Substring(0,1).ToUpper() + $Purpose.Substring(1).ToLower()
|
||||
# Нормализация
|
||||
switch ($Purpose) {
|
||||
"Object" { }
|
||||
"List" { }
|
||||
"Choice" { }
|
||||
"Record" { }
|
||||
default {
|
||||
Write-Error "Недопустимое назначение: $Purpose. Допустимые: Object, List, Choice, Record"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$objectLikeTypes = @("Document", "Catalog", "ChartOfAccounts", "ChartOfCharacteristicTypes", "ExchangePlan", "BusinessProcess", "Task")
|
||||
$processorLikeTypes = @("DataProcessor", "Report", "ExternalDataProcessor", "ExternalReport")
|
||||
|
||||
switch ($Purpose) {
|
||||
"Object" {
|
||||
# допустимо для всех типов
|
||||
}
|
||||
"List" {
|
||||
if ($objectType -eq "DataProcessor") {
|
||||
Write-Error "Purpose=List недопустим для DataProcessor"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
"Choice" {
|
||||
if ($objectType -in $processorLikeTypes -or $objectType -eq "InformationRegister") {
|
||||
Write-Error "Purpose=Choice недопустим для $objectType"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
"Record" {
|
||||
if ($objectType -ne "InformationRegister") {
|
||||
Write-Error "Purpose=Record допустим только для InformationRegister"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Фаза 3: Создание файлов ---
|
||||
|
||||
$objectDir = [System.IO.Path]::ChangeExtension($objectXmlFull.Path, $null).TrimEnd('.')
|
||||
$formsDir = Join-Path $objectDir "Forms"
|
||||
$formMetaPath = Join-Path $formsDir "$FormName.xml"
|
||||
|
||||
if (Test-Path $formMetaPath) {
|
||||
Write-Error "Форма уже существует: $formMetaPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$formDir = Join-Path $formsDir $FormName
|
||||
$formExtDir = Join-Path $formDir "Ext"
|
||||
$formModuleDir = Join-Path $formExtDir "Form"
|
||||
|
||||
New-Item -ItemType Directory -Path $formModuleDir -Force | Out-Null
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# --- 3a. Метаданные формы ---
|
||||
|
||||
$formUuid = [guid]::NewGuid().ToString()
|
||||
|
||||
# ExtendedPresentation — only for DataProcessor, Report, ExternalDataProcessor, ExternalReport forms
|
||||
$extPresentationLine = ""
|
||||
if ($objectType -in $processorLikeTypes) {
|
||||
$extPresentationLine = "`n`t`t`t<ExtendedPresentation/>"
|
||||
}
|
||||
|
||||
$formMetaXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$($script:formatVersion)">
|
||||
<Form uuid="$formUuid">
|
||||
<Properties>
|
||||
<Name>$FormName</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>$Synonym</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>$extPresentationLine
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom)
|
||||
|
||||
# --- 3b. Form.xml ---
|
||||
|
||||
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||
|
||||
$formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
|
||||
if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
# Динамический список
|
||||
# MainTable: тип.имя
|
||||
$mainTable = "$objectType.$objectName"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Список" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DynamicList</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<Settings xsi:type="DynamicList">
|
||||
<MainTable>$mainTable</MainTable>
|
||||
</Settings>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
} elseif ($Purpose -eq "Record") {
|
||||
# Запись регистра сведений
|
||||
$mainAttrName = "Запись"
|
||||
$mainAttrType = "InformationRegisterRecordManager.$objectName"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:$mainAttrType</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
} else {
|
||||
# Object — форма объекта
|
||||
$mainAttrName = "Объект"
|
||||
|
||||
# Маппинг типа объекта на тип реквизита
|
||||
$attrTypeMap = @{
|
||||
"Document" = "DocumentObject"
|
||||
"Catalog" = "CatalogObject"
|
||||
"DataProcessor" = "DataProcessorObject"
|
||||
"Report" = "ReportObject"
|
||||
"ExternalDataProcessor" = "ExternalDataProcessorObject"
|
||||
"ExternalReport" = "ExternalReportObject"
|
||||
"ChartOfAccounts" = "ChartOfAccountsObject"
|
||||
"ChartOfCharacteristicTypes" = "ChartOfCharacteristicTypesObject"
|
||||
"ExchangePlan" = "ExchangePlanObject"
|
||||
"BusinessProcess" = "BusinessProcessObject"
|
||||
"Task" = "TaskObject"
|
||||
"InformationRegister" = "InformationRegisterRecordManager"
|
||||
"AccumulationRegister" = "AccumulationRegisterRecordSet"
|
||||
}
|
||||
|
||||
$mainAttrType = "$($attrTypeMap[$objectType]).$objectName"
|
||||
|
||||
# SavedData: standard for Catalog/Document/etc, but not for processor-like (DataProcessor/Report/External*)
|
||||
$savedDataLine = ""
|
||||
if ($objectType -notin $processorLikeTypes) {
|
||||
$savedDataLine = "`n`t`t`t<SavedData>true</SavedData>"
|
||||
}
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:$mainAttrType</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>$savedDataLine
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
}
|
||||
|
||||
if (Test-Path $formXmlPath) {
|
||||
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
}
|
||||
|
||||
# --- 3c. Module.bsl ---
|
||||
|
||||
$modulePath = Join-Path $formModuleDir "Module.bsl"
|
||||
|
||||
$moduleBsl = @"
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
"@
|
||||
|
||||
if (Test-Path $modulePath) {
|
||||
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
}
|
||||
|
||||
# --- Фаза 4: Регистрация в родительском объекте ---
|
||||
|
||||
$childObjects = $xmlDoc.SelectSingleNode("//md:${objectType}/md:ChildObjects", $nsMgr)
|
||||
if (-not $childObjects) {
|
||||
Write-Error "Не найден элемент ChildObjects в $ObjectPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Form>$FormName</Form>
|
||||
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $FormName
|
||||
|
||||
# Ищем первый <Template> для вставки перед ним
|
||||
$firstTemplate = $childObjects.SelectSingleNode("md:Template", $nsMgr)
|
||||
# Ищем первую <TabularSection> для вставки перед ней (если нет Template)
|
||||
$firstTabular = $childObjects.SelectSingleNode("md:TabularSection", $nsMgr)
|
||||
|
||||
# Определяем точку вставки: перед Template, перед TabularSection, или в конец
|
||||
$insertBefore = $null
|
||||
if ($firstTemplate) {
|
||||
$insertBefore = $firstTemplate
|
||||
} elseif ($firstTabular) {
|
||||
$insertBefore = $firstTabular
|
||||
}
|
||||
|
||||
if ($insertBefore) {
|
||||
# Вставить перед найденным элементом, с переносом строки
|
||||
$whitespace = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
|
||||
$childObjects.InsertBefore($whitespace, $formElem) | Out-Null
|
||||
# Переставляем: whitespace перед formElem — неправильный порядок
|
||||
# Правильно: formElem, затем whitespace перед insertBefore
|
||||
# InsertBefore возвращает вставленный узел, порядок: ... formElem whitespace insertBefore ...
|
||||
# На самом деле нам нужно: ... \n\t\t\tformElem \n\t\t\tinsertBefore
|
||||
# Удалим и вставим правильно
|
||||
$childObjects.RemoveChild($whitespace) | Out-Null
|
||||
$childObjects.RemoveChild($formElem) | Out-Null
|
||||
|
||||
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
|
||||
# Whitespace нужен ДО formElem (перенос строки + отступ)
|
||||
# Но перед insertBefore уже должен быть whitespace от предыдущего элемента
|
||||
# Нам нужно добавить whitespace ПОСЛЕ formElem (перед insertBefore)
|
||||
$ws = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($ws, $insertBefore) | Out-Null
|
||||
} else {
|
||||
# Добавить в конец ChildObjects
|
||||
if ($childObjects.ChildNodes.Count -eq 0) {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$childObjects.InsertBefore($xmlDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- SetDefault ---
|
||||
|
||||
$existingForms = $childObjects.SelectNodes("md:Form", $nsMgr)
|
||||
$isFirstFormForPurpose = $false
|
||||
$defaultPropName = $null
|
||||
$defaultValue = "$objectType.$objectName.Form.$FormName"
|
||||
|
||||
# Определяем имя свойства для DefaultForm
|
||||
switch ($Purpose) {
|
||||
"Object" {
|
||||
if ($objectType -in $processorLikeTypes) {
|
||||
$defaultPropName = "DefaultForm"
|
||||
} else {
|
||||
$defaultPropName = "DefaultObjectForm"
|
||||
}
|
||||
}
|
||||
"List" { $defaultPropName = "DefaultListForm" }
|
||||
"Choice" { $defaultPropName = "DefaultChoiceForm" }
|
||||
"Record" { $defaultPropName = "DefaultRecordForm" }
|
||||
}
|
||||
|
||||
# Проверяем, установлено ли уже значение
|
||||
$defaultNode = $xmlDoc.SelectSingleNode("//md:${objectType}/md:Properties/md:$defaultPropName", $nsMgr)
|
||||
if ($defaultNode) {
|
||||
$isFirstFormForPurpose = [string]::IsNullOrWhiteSpace($defaultNode.InnerText)
|
||||
}
|
||||
|
||||
$defaultUpdated = $false
|
||||
if ($SetDefault -or $isFirstFormForPurpose) {
|
||||
if ($defaultNode) {
|
||||
$defaultNode.InnerText = $defaultValue
|
||||
$defaultUpdated = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Сохранить с BOM
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $encBom
|
||||
$settings.Indent = $false
|
||||
|
||||
$stream = New-Object System.IO.FileStream($objectXmlFull.Path, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
||||
$xmlDoc.Save($writer)
|
||||
$writer.Close()
|
||||
$stream.Close()
|
||||
|
||||
# --- Фаза 5: Вывод ---
|
||||
|
||||
# Относительные пути для вывода
|
||||
$basePath = Split-Path $objectXmlFull.Path -Parent
|
||||
# Определяем корень (ищем родительский каталог типа Documents, Catalogs и т.д.)
|
||||
$relFormMeta = $formMetaPath.Replace($basePath, "").TrimStart("\", "/")
|
||||
$relFormXml = $formXmlPath.Replace($basePath, "").TrimStart("\", "/")
|
||||
$relModule = $modulePath.Replace($basePath, "").TrimStart("\", "/")
|
||||
|
||||
$objFileName = [System.IO.Path]::GetFileName($ObjectPath)
|
||||
$objDirName = Split-Path $ObjectPath -Parent
|
||||
$objBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ObjectPath)
|
||||
|
||||
Write-Host "Created:"
|
||||
Write-Host " Metadata: $objDirName\$objBaseName\Forms\$FormName.xml"
|
||||
Write-Host " Form: $objDirName\$objBaseName\Forms\$FormName\Ext\Form.xml"
|
||||
Write-Host " Module: $objDirName\$objBaseName\Forms\$FormName\Ext\Form\Module.bsl"
|
||||
Write-Host ""
|
||||
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
|
||||
if ($defaultUpdated) {
|
||||
Write-Host "${defaultPropName}: $defaultValue"
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-add v1.0 — Add managed form to 1C config object
|
||||
# form-add v1.5 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
@@ -15,10 +16,28 @@ NSMAP = {
|
||||
}
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
|
||||
def save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
@@ -49,13 +68,24 @@ def main():
|
||||
|
||||
# --- Phase 1: Determine object type ---
|
||||
|
||||
# Resolve ObjectPath (directory → .xml)
|
||||
if not os.path.isabs(object_path):
|
||||
object_path = os.path.join(os.getcwd(), object_path)
|
||||
if os.path.isdir(object_path):
|
||||
object_path = object_path.rstrip("/\\") + ".xml"
|
||||
dir_name = os.path.basename(object_path.rstrip("/\\"))
|
||||
candidate = os.path.join(object_path, dir_name + ".xml")
|
||||
sibling = os.path.join(os.path.dirname(object_path.rstrip("/\\")), dir_name + ".xml")
|
||||
if os.path.isfile(candidate):
|
||||
object_path = candidate
|
||||
elif os.path.isfile(sibling):
|
||||
object_path = sibling
|
||||
if not os.path.isfile(object_path):
|
||||
print(f"Файл объекта не найден: {object_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
object_xml_full = os.path.abspath(object_path)
|
||||
format_version = detect_format_version(os.path.dirname(object_xml_full))
|
||||
|
||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(object_xml_full, parser_xml)
|
||||
root = tree.getroot()
|
||||
@@ -63,7 +93,7 @@ def main():
|
||||
supported_types = [
|
||||
"Document", "Catalog", "DataProcessor", "Report",
|
||||
"ExternalDataProcessor", "ExternalReport",
|
||||
"InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ExchangePlan", "BusinessProcess", "Task",
|
||||
]
|
||||
|
||||
@@ -160,7 +190,7 @@ def main():
|
||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
f' version="{format_version}">\n'
|
||||
f'\t<Form uuid="{form_uuid}">\n'
|
||||
'\t\t<Properties>\n'
|
||||
f'\t\t\t<Name>{form_name}</Name>\n'
|
||||
@@ -177,8 +207,8 @@ def main():
|
||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n'
|
||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n'
|
||||
'\t\t\t</UsePurposes>\n'
|
||||
'\t\t\t<ExtendedPresentation/>\n'
|
||||
'\t\t</Properties>\n'
|
||||
+ ('\t\t\t<ExtendedPresentation/>\n' if object_type in processor_like_types else '')
|
||||
+ '\t\t</Properties>\n'
|
||||
'\t</Form>\n'
|
||||
'</MetaDataObject>'
|
||||
)
|
||||
@@ -214,13 +244,10 @@ def main():
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
'\t\t<Attribute name="\u0421\u043f\u0438\u0441\u043e\u043a" id="1">\n'
|
||||
@@ -243,13 +270,10 @@ def main():
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="{main_attr_name}" id="1">\n'
|
||||
@@ -280,19 +304,22 @@ def main():
|
||||
"BusinessProcess": "BusinessProcessObject",
|
||||
"Task": "TaskObject",
|
||||
"InformationRegister": "InformationRegisterRecordManager",
|
||||
"AccumulationRegister": "AccumulationRegisterRecordSet",
|
||||
}
|
||||
|
||||
main_attr_type = f"{attr_type_map[object_type]}.{object_name}"
|
||||
|
||||
# SavedData: standard for Catalog/Document/etc, but not for processor-like (DataProcessor/Report/External*)
|
||||
saved_data_line = ''
|
||||
if object_type not in processor_like_types:
|
||||
saved_data_line = '\t\t\t<SavedData>true</SavedData>\n'
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="{main_attr_name}" id="1">\n'
|
||||
@@ -300,7 +327,7 @@ def main():
|
||||
f'\t\t\t\t<v8:Type>cfg:{main_attr_type}</v8:Type>\n'
|
||||
'\t\t\t</Type>\n'
|
||||
'\t\t\t<MainAttribute>true</MainAttribute>\n'
|
||||
'\t\t\t<SavedData>true</SavedData>\n'
|
||||
f'{saved_data_line}'
|
||||
'\t\t</Attribute>\n'
|
||||
'\t</Attributes>\n'
|
||||
'</Form>'
|
||||
@@ -318,11 +345,6 @@ def main():
|
||||
module_bsl = (
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'&\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435\n'
|
||||
'\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430 \u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435(\u041e\u0442\u043a\u0430\u0437, \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430)\n'
|
||||
'\n'
|
||||
'\u041a\u043e\u043d\u0435\u0446\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0424\u043e\u0440\u043c\u044b\n'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: form-compile
|
||||
description: Компиляция управляемой формы 1С из компактного JSON-определения. Используй когда нужно создать форму с нуля по описанию элементов
|
||||
argument-hint: <JsonPath> <OutputPath>
|
||||
description: Компиляция управляемой формы 1С из JSON-определения или из метаданных объекта. Используй когда нужно создать форму с нуля по описанию элементов или сгенерировать типовую форму
|
||||
argument-hint: <JsonPath> <OutputPath> | -FromObject <OutputPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -9,29 +9,30 @@ allowed-tools:
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /form-compile — Генерация Form.xml из JSON DSL
|
||||
# /form-compile — Генерация Form.xml
|
||||
|
||||
Принимает компактное JSON-определение формы (20–50 строк) и генерирует полный корректный Form.xml (100–500+ строк) с namespace-декларациями, автогенерированными companion-элементами, последовательными ID.
|
||||
Два режима:
|
||||
1. **JSON DSL** — из JSON-определения формы
|
||||
2. **From object** (`-FromObject`) — автоматически из метаданных объекта 1С по пресету ERP
|
||||
|
||||
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника: архетипы, конвенции именования, продвинутые паттерны. Для простых форм (1–3 поля, пользователь описал что нужно) — не нужно.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/form-compile <JsonPath> <OutputPath>
|
||||
```
|
||||
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника. Для простых форм (1–3 поля) — не нужно.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|-----------------------------------|
|
||||
| JsonPath | да | Путь к JSON-определению формы |
|
||||
| OutputPath | да | Путь к выходному файлу Form.xml |
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|---------------------------------|
|
||||
| JsonPath | режим 1 | Путь к JSON-определению формы |
|
||||
| OutputPath | да | Путь к выходному Form.xml |
|
||||
| FromObject | режим 2 | Флаг (без значения) — генерация по метаданным объекта |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -JsonPath "<json>" -OutputPath "<xml>"
|
||||
# Режим JSON DSL
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-compile.ps1" -JsonPath "<json>" -OutputPath "<Form.xml>"
|
||||
|
||||
# Режим from-object (объект и purpose выводятся из OutputPath; Document и Catalog)
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-compile.ps1" -FromObject -OutputPath "<.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml>"
|
||||
```
|
||||
|
||||
## JSON DSL — справка
|
||||
@@ -61,8 +62,10 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| DSL ключ | XML элемент | Значение ключа |
|
||||
|--------------|-------------------|---------------------------------------------------|
|
||||
| `"group"` | UsualGroup | `"horizontal"` / `"vertical"` / `"alwaysHorizontal"` / `"alwaysVertical"` / `"collapsible"` |
|
||||
| `"columnGroup"` | ColumnGroup | `"horizontal"` / `"vertical"` / `"inCell"` — только внутри `columns` таблицы |
|
||||
| `"input"` | InputField | имя элемента |
|
||||
| `"check"` | CheckBoxField | имя |
|
||||
| `"radio"` | RadioButtonField | имя |
|
||||
| `"label"` | LabelDecoration | имя (текст задаётся через `title`) |
|
||||
| `"labelField"` | LabelField | имя |
|
||||
| `"table"` | Table | имя |
|
||||
@@ -73,6 +76,7 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `"picField"` | PictureField | имя |
|
||||
| `"calendar"` | CalendarField | имя |
|
||||
| `"cmdBar"` | CommandBar | имя |
|
||||
| `"autoCmdBar"` | AutoCommandBar формы | имя — наполняет главную АКП формы (id=-1), не попадает в `<ChildItems>` |
|
||||
| `"popup"` | Popup | имя |
|
||||
|
||||
### Общие свойства (все типы элементов)
|
||||
@@ -95,7 +99,7 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
|
||||
**input / picField**: `OnChange`, `StartChoice`, `ChoiceProcessing`, `AutoComplete`, `TextEditEnd`, `Clearing`, `Creating`, `EditTextChange`
|
||||
|
||||
**check**: `OnChange`
|
||||
**check / radio**: `OnChange`
|
||||
|
||||
**table**: `OnStartEdit`, `OnEditEnd`, `OnChange`, `Selection`, `ValueChoice`, `BeforeAddRow`, `BeforeDeleteRow`, `AfterDeleteRow`, `BeforeRowChange`, `BeforeEditEnd`, `OnActivateRow`, `OnActivateCell`, `Drag`, `DragStart`, `DragCheck`, `DragEnd`
|
||||
|
||||
@@ -123,7 +127,8 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `skipOnInput: true` | Пропускать при обходе Tab | |
|
||||
| `inputHint` | Подсказка в пустом поле | `"Введите наименование..."` |
|
||||
| `width` / `height` | Размер | числа |
|
||||
| `autoMaxWidth: false` | Отключить авто-ширину | для фиксированных полей |
|
||||
| `autoMaxWidth: false` | Снять авто-ограничение ширины (поле растянется) | |
|
||||
| `maxWidth` / `maxHeight` | Жёсткое ограничение размера | числа; обычно вместе с `autoMaxWidth: false` |
|
||||
| `horizontalStretch: true` | Растягивать по ширине | |
|
||||
|
||||
### Чекбокс (check)
|
||||
@@ -133,6 +138,37 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `path` | DataPath |
|
||||
| `titleLocation` | Размещение заголовка |
|
||||
|
||||
### Поле переключателя (radio)
|
||||
|
||||
Радиокнопки или тумблер для выбора одного значения из списка.
|
||||
|
||||
| Ключ | Описание | Пример |
|
||||
|------|----------|--------|
|
||||
| `path` | DataPath — привязка к реквизиту | `"СпособКурса"` |
|
||||
| `radioButtonType` | Вид переключателя | `"Auto"` (по умолчанию), `"RadioButtons"`, `"Tumbler"` |
|
||||
| `columnsCount` | Число колонок раскладки | `1`, `2`, ... |
|
||||
| `titleLocation` | Размещение заголовка | по умолчанию `"none"` |
|
||||
| `choiceList` | Список вариантов: массив `{value, presentation}` | см. ниже |
|
||||
|
||||
`choiceList[*]`:
|
||||
|
||||
| Ключ | Описание |
|
||||
|------|----------|
|
||||
| `value` | Значение варианта. Строка/число/булево; для перечисления — `"Enum.ИмяТипа.EnumValue.ИмяЗначения"` |
|
||||
| `presentation` | Текст рядом с переключателем. Строка (русский) либо объект `{ru, en, ...}` для мультиязычности |
|
||||
|
||||
```json
|
||||
{
|
||||
"radio": "СпособКурса",
|
||||
"path": "Объект.СпособУстановкиКурса",
|
||||
"radioButtonType": "Auto",
|
||||
"choiceList": [
|
||||
{ "value": "Enum.СпособыКурса.EnumValue.Авто", "presentation": { "ru": "Автоматически", "en": "Automatic" } },
|
||||
{ "value": "Enum.СпособыКурса.EnumValue.Ручной", "presentation": "вручную" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Надпись-декорация (label)
|
||||
|
||||
| Ключ | Описание |
|
||||
@@ -148,7 +184,8 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| Ключ | Описание |
|
||||
|------|----------|
|
||||
| `showTitle: true` | Показывать заголовок группы |
|
||||
| `united: false` | Не объединять рамку |
|
||||
| `united: false` | Левый край полей ввода выравнивается только в пределах этой группы (по умолчанию `true` — сквозное выравнивание по самому длинному заголовку, в т.ч. с соседними группами) |
|
||||
| `collapsed: true` | Только для `"group": "collapsible"` — группа создаётся свёрнутой |
|
||||
| `representation` | `"none"`, `"normal"`, `"weak"`, `"strong"` |
|
||||
| `children: [...]` | Вложенные элементы |
|
||||
|
||||
@@ -167,6 +204,41 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `footer: true` | Показать подвал |
|
||||
| `commandBarLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||
| `searchStringLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||
| `choiceMode: true` | Режим выбора (для форм выбора) |
|
||||
| `initialTreeView` | `"ExpandTopLevel"` и др. (иерархические списки) |
|
||||
| `enableDrag: true` | Разрешить перетаскивание |
|
||||
| `enableStartDrag: true` | Разрешить начало перетаскивания |
|
||||
| `rowPictureDataPath` | Путь к картинке строки (напр. `"Список.DefaultPicture"`) |
|
||||
| `tableAutofill: false` | Управление Autofill внутреннего AutoCommandBar |
|
||||
|
||||
Колонки можно группировать через `columnGroup` (см. ниже).
|
||||
|
||||
### Группа колонок (columnGroup)
|
||||
|
||||
Используется только внутри `columns` таблицы. Значение ключа задаёт ориентацию: `"horizontal"`, `"vertical"`, `"inCell"` (склеивает колонки в одну ячейку шапки). Допускается вложение `columnGroup` в `columnGroup`.
|
||||
|
||||
| Ключ | Описание |
|
||||
|------|----------|
|
||||
| `name` | Имя элемента (рекомендуется задавать явно) |
|
||||
| `title` | Заголовок группы |
|
||||
| `showTitle: false` | Скрыть заголовок |
|
||||
| `showInHeader: true/false` | Показывать ли группу в шапке таблицы |
|
||||
| `width` | Ширина |
|
||||
| `horizontalStretch: false` | Растягивание |
|
||||
| `children: [...]` | Колонки внутри группы (`input`, `labelField`, `picField`, вложенный `columnGroup` …) |
|
||||
|
||||
```json
|
||||
{ "table": "Список", "path": "Список", "columns": [
|
||||
{ "columnGroup": "horizontal", "name": "ГруппаДата", "title": "Срок", "children": [
|
||||
{ "input": "СрокИсполнения", "path": "Список.СрокИсполнения" },
|
||||
{ "labelField": "Просрочено", "path": "Список.Просрочено" }
|
||||
]},
|
||||
{ "columnGroup": "inCell", "name": "ГруппаИсполнитель", "showInHeader": true, "children": [
|
||||
{ "input": "Исполнитель", "path": "Список.Исполнитель" }
|
||||
]},
|
||||
{ "input": "Комментарий", "path": "Список.Комментарий" }
|
||||
]}
|
||||
```
|
||||
|
||||
### Страницы (pages + page)
|
||||
|
||||
@@ -188,18 +260,39 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `command` | Имя команды формы → `Form.Command.Имя` |
|
||||
| `stdCommand` | Стандартная команда: `"Close"` → `Form.StandardCommand.Close`; с точкой: `"Товары.Add"` → `Form.Item.Товары.StandardCommand.Add` |
|
||||
| `defaultButton: true` | Кнопка по умолчанию |
|
||||
| `type` | `"usual"`, `"hyperlink"`, `"commandBar"` |
|
||||
| `type` | `"usual"`, `"hyperlink"`. По умолчанию `"usual"`. Конкретный XML-вид (UsualButton/Hyperlink/CommandBarButton/CommandBarHyperlink) подставляется автоматически по контексту |
|
||||
| `picture` | Картинка кнопки |
|
||||
| `representation` | `"Auto"`, `"Text"`, `"Picture"`, `"PictureAndText"` |
|
||||
| `locationInCommandBar` | `"Auto"`, `"InCommandBar"`, `"InAdditionalSubmenu"` |
|
||||
|
||||
### Командная панель (cmdBar)
|
||||
|
||||
Дополнительная пользовательская панель команд, размещается как обычный элемент в layout формы.
|
||||
|
||||
| Ключ | Описание |
|
||||
|------|----------|
|
||||
| `autofill: true` | Автозаполнение стандартными командами |
|
||||
| `children: [...]` | Кнопки панели |
|
||||
|
||||
### Главная автокомандная панель формы (autoCmdBar)
|
||||
|
||||
Наполняет встроенную AutoCommandBar формы (id=-1) кастомными кнопками. Указывать только если нужно добавить свои кнопки на главную панель или явно управлять автозаполнением.
|
||||
|
||||
| Ключ | Описание |
|
||||
|------|----------|
|
||||
| `autofill: true/false` | Автозаполнение стандартными командами |
|
||||
| `horizontalAlign` | `"Left"` / `"Center"` / `"Right"` |
|
||||
| `children: [...]` | Кнопки/popup |
|
||||
|
||||
```json
|
||||
{ "autoCmdBar": "ФормаКоманднаяПанель", "autofill": true, "children": [
|
||||
{ "button": "ИзменитьВыделенные", "command": "ИзменитьВыделенные",
|
||||
"locationInCommandBar": "InAdditionalSubmenu" }
|
||||
]}
|
||||
```
|
||||
|
||||
Кнопки основных действий формы и подменю размещают здесь, а не в отдельной группе на форме. Отдельной кнопкой в layout — только если она логически привязана к конкретному полю или группе.
|
||||
|
||||
### Выпадающее меню (popup)
|
||||
|
||||
| Ключ | Описание |
|
||||
@@ -221,6 +314,9 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
|
||||
```json
|
||||
{ "name": "Объект", "type": "DataProcessorObject.Загрузка", "main": true }
|
||||
{ "name": "Список", "type": "DynamicList", "main": true, "settings": {
|
||||
"mainTable": "Catalog.Номенклатура", "dynamicDataRead": true
|
||||
}}
|
||||
{ "name": "Итого", "type": "decimal(15,2)" }
|
||||
{ "name": "Таблица", "type": "ValueTable", "columns": [
|
||||
{ "name": "Номенклатура", "type": "CatalogRef.Номенклатура" },
|
||||
@@ -229,6 +325,7 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
```
|
||||
|
||||
- `savedData: true` — сохраняемые данные
|
||||
- `main: true` — главный реквизит формы (например, основной `*Object.*`, `DynamicList`, `*RecordSet.*`)
|
||||
|
||||
### Команды (commands)
|
||||
|
||||
@@ -241,6 +338,8 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
|
||||
### Система типов
|
||||
|
||||
**Примитивные:**
|
||||
|
||||
| DSL | XML |
|
||||
|------------------------|----------------------------------------|
|
||||
| `"string"` / `"string(100)"` | `xs:string` + StringQualifiers |
|
||||
@@ -248,11 +347,38 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `"decimal(10,0,nonneg)"` | с AllowedSign=Nonnegative |
|
||||
| `"boolean"` | `xs:boolean` |
|
||||
| `"date"` / `"dateTime"` / `"time"` | `xs:dateTime` + DateFractions |
|
||||
| `"CatalogRef.XXX"` | `cfg:CatalogRef.XXX` |
|
||||
| `"DocumentRef.XXX"` | `cfg:DocumentRef.XXX` |
|
||||
| `"ValueTable"` | `v8:ValueTable` |
|
||||
| `"ValueList"` | `v8:ValueListType` |
|
||||
| `"Type1 \| Type2"` | составной тип |
|
||||
|
||||
**Ссылочные и объектные (`cfg:Prefix.Name`):**
|
||||
|
||||
| DSL | Описание |
|
||||
|-----|----------|
|
||||
| `"CatalogRef.XXX"` / `"CatalogObject.XXX"` | Справочник |
|
||||
| `"DocumentRef.XXX"` / `"DocumentObject.XXX"` | Документ |
|
||||
| `"EnumRef.XXX"` | Перечисление |
|
||||
| `"DataProcessorObject.XXX"` / `"ReportObject.XXX"` | Обработка / Отчёт |
|
||||
| `"InformationRegisterRecordSet.XXX"` | Набор записей регистра сведений |
|
||||
| `"AccumulationRegisterRecordSet.XXX"` | Набор записей регистра накопления |
|
||||
| `"DynamicList"` | Динамический список |
|
||||
|
||||
Также допустимы: `ChartOfAccountsRef/Object`, `ChartOfCharacteristicTypesRef/Object`, `ChartOfCalculationTypesRef/Object`, `ExchangePlanRef/Object`, `BusinessProcessRef/Object`, `TaskRef/Object`, `AccountingRegisterRecordSet`, `InformationRegisterRecordManager`, `ConstantsSet`.
|
||||
|
||||
**Платформенные:**
|
||||
|
||||
| DSL | XML |
|
||||
|-----|-----|
|
||||
| `"ValueTable"` | `v8:ValueTable` |
|
||||
| `"ValueTree"` | `v8:ValueTree` |
|
||||
| `"ValueList"` | `v8:ValueListType` |
|
||||
| `"TypeDescription"` | `v8:TypeDescription` |
|
||||
| `"UUID"` | `v8:UUID` |
|
||||
| `"FormattedString"` | `v8ui:FormattedString` |
|
||||
| `"Picture"` / `"Color"` / `"Font"` | `v8ui:*` |
|
||||
| `"DataCompositionSettings"` | `dcsset:DataCompositionSettings` |
|
||||
| `"Type1 \| Type2"` | составной тип (несколько `<v8:Type>`) |
|
||||
|
||||
**Недопустимые типы (XDTO-ошибка при загрузке):**
|
||||
|
||||
> `FormDataStructure`, `FormDataCollection`, `FormDataTree` — runtime-типы 1С, не существуют в XML-схеме. Вместо них используйте `CatalogObject.XXX`, `DocumentObject.XXX`, `DataProcessorObject.XXX`, `ValueTable`, `ValueTree`.
|
||||
|
||||
## Связки: элемент + реквизит
|
||||
|
||||
@@ -309,9 +435,9 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
{ "check": "ПерваяСтрокаЗаголовок", "path": "ПерваяСтрокаЗаголовок" }
|
||||
]},
|
||||
{ "input": "Результат", "path": "Результат", "multiLine": true, "height": 8, "readOnly": true, "title": "Лог" },
|
||||
{ "group": "horizontal", "name": "ГруппаКнопок", "children": [
|
||||
{ "autoCmdBar": "ФормаКоманднаяПанель", "children": [
|
||||
{ "button": "Загрузить", "command": "Загрузить", "defaultButton": true },
|
||||
{ "button": "Закрыть", "stdCommand": "Close" }
|
||||
{ "button": "Закрыть", "stdCommand": "Close" }
|
||||
]}
|
||||
],
|
||||
"attributes": [
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Form Presets
|
||||
|
||||
Пресеты управляют раскладкой форм, генерируемых в режиме `--from-object`.
|
||||
|
||||
## Как работает
|
||||
|
||||
Цепочка merge (каждый следующий уровень перезаписывает предыдущий через deep merge):
|
||||
|
||||
1. **Hardcoded defaults** -- встроены в скрипт, ориентированы на ERP
|
||||
2. **Built-in preset** -- файл из этой папки (`erp-standard.json` по умолчанию)
|
||||
3. **Project-level preset** -- файл `presets/skills/form/<name>.json`, поиск вверх от OutputPath
|
||||
|
||||
Имя пресета задаётся параметром `--preset` (по умолчанию `erp-standard`).
|
||||
|
||||
## Project-level пресет
|
||||
|
||||
Чтобы переопределить стандартный пресет в своём проекте, создайте файл:
|
||||
|
||||
```
|
||||
<project-root>/presets/skills/form/erp-standard.json
|
||||
```
|
||||
|
||||
Скрипт ищет этот файл, поднимаясь от OutputPath к корню. Первый найденный файл применяется поверх built-in через deep merge -- не нужно копировать весь пресет, достаточно указать только переопределяемые ключи.
|
||||
|
||||
## Секции
|
||||
|
||||
Ключи верхнего уровня в JSON -- секции вида `{тип}.{назначение}`:
|
||||
|
||||
| Секция | Тип объекта | Назначение формы |
|
||||
|--------|-------------|------------------|
|
||||
| `document.item` | Document | Форма документа |
|
||||
| `document.list` | Document | Форма списка |
|
||||
| `document.choice` | Document | Форма выбора |
|
||||
| `catalog.item` | Catalog | Форма элемента |
|
||||
| `catalog.folder` | Catalog | Форма группы |
|
||||
| `catalog.list` | Catalog | Форма списка |
|
||||
| `catalog.choice` | Catalog | Форма выбора |
|
||||
| `informationRegister.record` | InformationRegister | Форма записи |
|
||||
| `informationRegister.list` | InformationRegister | Форма списка |
|
||||
| `accumulationRegister.list` | AccumulationRegister | Форма списка |
|
||||
| `chartOfCharacteristicTypes.*` | ChartOfCharacteristicTypes | item/folder/list/choice |
|
||||
| `exchangePlan.*` | ExchangePlan | item/list/choice |
|
||||
| `chartOfAccounts.*` | ChartOfAccounts | item/folder/list/choice |
|
||||
|
||||
### basedOn
|
||||
|
||||
Секция может наследовать от другой:
|
||||
|
||||
```json
|
||||
{
|
||||
"document.choice": {
|
||||
"basedOn": "document.list",
|
||||
"properties": { "windowOpeningMode": "LockOwnerWindow" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ключи секций
|
||||
|
||||
### Форма объекта (Item/Record)
|
||||
|
||||
| Ключ | Описание | Допустимые значения |
|
||||
|------|----------|---------------------|
|
||||
| `header.position` | Где размещать шапку | `"insidePage"` -- на первой странице, `"abovePages"` -- над страницами |
|
||||
| `header.layout` | Колонки шапки | `"1col"`, `"2col"` |
|
||||
| `header.distribute` | Распределение в 2 колонках | `"even"`, `"left"`, `"right"` |
|
||||
| `header.dateTitle` | Заголовок даты (Document) | строка, напр. `"от"` |
|
||||
| `footer.fields` | Поля в подвале | массив имён реквизитов, напр. `["Комментарий"]` |
|
||||
| `footer.position` | Где размещать подвал | `"insidePage"`, `"belowPages"`, `"none"` |
|
||||
| `tabularSections.container` | Контейнер табчастей | `"pages"` -- на вкладках, `"inline"` -- в корне, `"single-no-pages"` -- одна ТЧ без страниц |
|
||||
| `tabularSections.exclude` | Исключить табчасти | массив имён, напр. `["ДополнительныеРеквизиты"]` |
|
||||
| `tabularSections.lineNumber` | Колонка НомерСтроки | `true` / `false` |
|
||||
| `additional.position` | Блок доп. реквизитов | `"page"` -- отдельная вкладка, `"below"` -- под табчастями, `"none"` -- не создавать |
|
||||
| `additional.layout` | Колонки доп. блока | `"1col"`, `"2col"` |
|
||||
| `additional.bspGroup` | Группа ДополнительныеРеквизиты | `true` / `false` |
|
||||
| `codeDescription.layout` | Код + Наименование | `"horizontal"`, `"vertical"` |
|
||||
| `codeDescription.order` | Порядок Код/Наименование | `"descriptionFirst"`, `"codeFirst"` |
|
||||
| `parent.title` | Заголовок поля Родитель | строка, напр. `"Входит в группу"` |
|
||||
| `parent.position` | Позиция поля Родитель | `"beforeCodeDescription"`, `"afterCodeDescription"`, `"inHeader"` |
|
||||
| `owner.readOnly` | Владелец только для чтения | `true` / `false` |
|
||||
| `owner.position` | Позиция поля Владелец | `"first"` |
|
||||
| `fieldDefaults.ref.choiceButton` | Кнопка выбора для ссылок | `true` / `false` |
|
||||
| `fieldDefaults.boolean.element` | Элемент для Boolean | `"check"` (флажок) |
|
||||
| `commandBar` | Командная панель формы | `"auto"`, `"none"` |
|
||||
| `properties` | Свойства формы | объект: `autoTitle`, `windowOpeningMode` и др. |
|
||||
|
||||
### Форма списка (List/Choice)
|
||||
|
||||
| Ключ | Описание | Допустимые значения |
|
||||
|------|----------|---------------------|
|
||||
| `columns` | Какие колонки показывать | `"all"` -- все реквизиты, или массив имён |
|
||||
| `columnType` | Тип элемента колонки | `"labelField"`, `"input"` |
|
||||
| `hiddenRef` | Скрытая колонка Ref | `true` / `false` |
|
||||
| `tableCommandBar` | Командная панель таблицы | `"auto"`, `"none"` |
|
||||
| `commandBar` | Командная панель формы | `"auto"`, `"none"` |
|
||||
| `choiceMode` | Режим выбора (ChoiceForm) | `true` / `false` |
|
||||
| `properties` | Свойства формы | объект: `windowOpeningMode` и др. |
|
||||
|
||||
## Пример project-level пресета
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "Стиль форм нашего проекта",
|
||||
|
||||
"document.item": {
|
||||
"header": {
|
||||
"layout": "1col"
|
||||
},
|
||||
"tabularSections": {
|
||||
"exclude": ["ДополнительныеРеквизиты", "СведенияОСертификатах"]
|
||||
},
|
||||
"additional": {
|
||||
"position": "none"
|
||||
}
|
||||
},
|
||||
|
||||
"catalog.item": {
|
||||
"codeDescription": {
|
||||
"order": "codeFirst"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Этот файл переопределяет только указанные ключи -- остальное наследуется из built-in пресета.
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "erp-standard",
|
||||
"description": "ERP 8.3.24 standard form layout",
|
||||
|
||||
"document.item": {
|
||||
"header": {
|
||||
"position": "insidePage",
|
||||
"layout": "2col",
|
||||
"distribute": "even",
|
||||
"dateTitle": "от"
|
||||
},
|
||||
"footer": {
|
||||
"fields": ["Комментарий"],
|
||||
"position": "insidePage"
|
||||
},
|
||||
"tabularSections": {
|
||||
"container": "pages",
|
||||
"exclude": ["ДополнительныеРеквизиты"],
|
||||
"lineNumber": true
|
||||
},
|
||||
"additional": {
|
||||
"position": "page",
|
||||
"layout": "2col",
|
||||
"bspGroup": true
|
||||
},
|
||||
"properties": {
|
||||
"autoTitle": false
|
||||
}
|
||||
},
|
||||
|
||||
"catalog.item": {
|
||||
"codeDescription": {
|
||||
"layout": "horizontal",
|
||||
"order": "descriptionFirst"
|
||||
},
|
||||
"parent": {
|
||||
"title": "Входит в группу",
|
||||
"position": "afterCodeDescription"
|
||||
},
|
||||
"tabularSections": {
|
||||
"exclude": ["ДополнительныеРеквизиты", "Представления"]
|
||||
}
|
||||
},
|
||||
|
||||
"informationRegister.record": {
|
||||
"properties": {
|
||||
"windowOpeningMode": "LockOwnerWindow"
|
||||
}
|
||||
},
|
||||
|
||||
"informationRegister.list": {},
|
||||
|
||||
"accumulationRegister.list": {},
|
||||
|
||||
"chartOfCharacteristicTypes.item": {
|
||||
"basedOn": "catalog.item"
|
||||
},
|
||||
|
||||
"exchangePlan.item": {
|
||||
"basedOn": "catalog.item"
|
||||
},
|
||||
|
||||
"chartOfAccounts.item": {
|
||||
"parent": {
|
||||
"title": "Подчинен счету"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -FormPath "<путь>" -JsonPath "<путь>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-edit.ps1" -FormPath "<путь>" -JsonPath "<путь>"
|
||||
```
|
||||
|
||||
## JSON формат
|
||||
@@ -133,33 +133,6 @@ powershell.exe -NoProfile -File .claude/skills/form-edit/scripts/form-edit.ps1 -
|
||||
|
||||
Все extension-секции опциональны — без них навык работает как с обычными формами.
|
||||
|
||||
## Вывод
|
||||
|
||||
```
|
||||
=== form-edit: Форма ===
|
||||
|
||||
[EXTENSION] BaseForm detected — IDs start at 1000000+
|
||||
|
||||
Added form events:
|
||||
+ OnCreateAtServer[After] -> Расш1_ПриСозданииПосле
|
||||
|
||||
Added elements (into ГруппаШапка, after Контрагент):
|
||||
+ [Input] Склад -> Объект.Склад {OnChange}
|
||||
|
||||
Added attributes:
|
||||
+ СуммаИтого: decimal(15,2) (id=1000000)
|
||||
|
||||
---
|
||||
Total: 1 form event(s), 1 element(s) (+2 companions), 1 attribute(s)
|
||||
Run /form-validate to verify.
|
||||
```
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **После `/form-compile`**: добавить элементы, которые не были в исходном JSON
|
||||
- **Модификация существующих форм**: добавить поле, реквизит или команду в форму из конфигурации
|
||||
- **Пакетное добавление**: один JSON может содержать элементы + реквизиты + команды
|
||||
|
||||
## Workflow
|
||||
|
||||
1. `/form-info` — посмотреть текущую структуру формы
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$FormPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
|
||||
@@ -14,7 +14,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
# ── arg parsing ──────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True)
|
||||
parser.add_argument("-FormPath", "-Path", required=True)
|
||||
parser.add_argument("-JsonPath", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -973,8 +973,16 @@ if elements_list:
|
||||
target_ci = root_ci
|
||||
|
||||
if target_ci is None:
|
||||
# Create ChildItems section in form
|
||||
target_ci = etree.SubElement(root, f"{{{FORM_NS}}}ChildItems")
|
||||
# Create ChildItems section in form — insert after Events or AutoCommandBar
|
||||
target_ci = etree.Element(f"{{{FORM_NS}}}ChildItems")
|
||||
insert_after = root.find("f:Events", NS)
|
||||
if insert_after is None:
|
||||
insert_after = root.find("f:AutoCommandBar", NS)
|
||||
if insert_after is not None:
|
||||
idx = list(root).index(insert_after) + 1
|
||||
root.insert(idx, target_ci)
|
||||
else:
|
||||
root.append(target_ci)
|
||||
root_ci = target_ci
|
||||
|
||||
# Detect indent level
|
||||
@@ -1242,8 +1250,10 @@ if elem_events_list:
|
||||
# ── 13. Save ────────────────────────────────────────────────
|
||||
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
# Fix encoding declaration case
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
# Fix XML declaration quotes
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
# Write with BOM
|
||||
with open(resolved_form_path, "wb") as f:
|
||||
f.write(b'\xef\xbb\xbf')
|
||||
|
||||
@@ -15,7 +15,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-info/scripts/form-info.ps1 -FormPath "<путь к Form.xml>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-info.ps1" -FormPath "<путь к Form.xml>"
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-info v1.1 — Analyze 1C managed form structure
|
||||
# form-info v1.3 — Analyze 1C managed form structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -342,7 +342,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C managed form structure", allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True, help="Path to Form.xml")
|
||||
parser.add_argument("-FormPath", "-Path", required=True, help="Path to Form.xml")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
parser.add_argument("-Offset", type=int, default=0, help="Line offset for pagination")
|
||||
parser.add_argument("-Expand", default="", help="Expand collapsed section by name, or * for all")
|
||||
@@ -353,7 +353,27 @@ def main():
|
||||
offset = args.Offset
|
||||
expand = args.Expand
|
||||
|
||||
# --- Validate path ---
|
||||
# --- Resolve FormPath ---
|
||||
if not os.path.isabs(form_path):
|
||||
form_path = os.path.join(os.getcwd(), form_path)
|
||||
# A: Directory → Ext/Form.xml
|
||||
if os.path.isdir(form_path):
|
||||
form_path = os.path.join(form_path, "Ext", "Form.xml")
|
||||
# B1: Missing Ext/ (Forms/Форма/Form.xml → Forms/Форма/Ext/Form.xml)
|
||||
if not os.path.isfile(form_path):
|
||||
fn = os.path.basename(form_path)
|
||||
if fn == "Form.xml":
|
||||
c = os.path.join(os.path.dirname(form_path), "Ext", fn)
|
||||
if os.path.isfile(c):
|
||||
form_path = c
|
||||
# B2: Descriptor (Forms/Форма.xml → Forms/Форма/Ext/Form.xml)
|
||||
if not os.path.isfile(form_path) and form_path.endswith(".xml"):
|
||||
stem = os.path.splitext(os.path.basename(form_path))[0]
|
||||
parent = os.path.dirname(form_path)
|
||||
c = os.path.join(parent, stem, "Ext", "Form.xml")
|
||||
if os.path.isfile(c):
|
||||
form_path = c
|
||||
|
||||
if not os.path.isfile(form_path):
|
||||
print(f"File not found: {form_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -464,6 +484,50 @@ def main():
|
||||
ct_str = f"[{ct}]" if ct else ""
|
||||
lines.append(f" {e_name}{ct_str} -> {e_handler}")
|
||||
|
||||
# --- Main AutoCommandBar (form's id=-1 panel) ---
|
||||
def format_main_acb(acb_node):
|
||||
if acb_node is None:
|
||||
return []
|
||||
autofill_node = acb_node.find("d:Autofill", NSMAP)
|
||||
autofill = not (autofill_node is not None and autofill_node.text == "false")
|
||||
halign_node = acb_node.find("d:HorizontalAlign", NSMAP)
|
||||
flags = ["autofill" if autofill else "no-autofill"]
|
||||
if halign_node is not None and halign_node.text:
|
||||
flags.append(f"align={halign_node.text}")
|
||||
ci_node = acb_node.find("d:ChildItems", NSMAP)
|
||||
buttons = []
|
||||
if ci_node is not None:
|
||||
for btn in ci_node:
|
||||
if not isinstance(btn.tag, str):
|
||||
continue
|
||||
ln = etree.QName(btn).localname
|
||||
if ln in SKIP_ELEMENTS:
|
||||
continue
|
||||
b_name = btn.get("name", "")
|
||||
cmd_node = btn.find("d:CommandName", NSMAP)
|
||||
cmd_ref = cmd_node.text if cmd_node is not None and cmd_node.text else ""
|
||||
loc_node = btn.find("d:LocationInCommandBar", NSMAP)
|
||||
loc_str = f" [{loc_node.text}]" if loc_node is not None and loc_node.text else ""
|
||||
tag = get_element_tag(btn)
|
||||
if cmd_ref:
|
||||
buttons.append(f" {tag} {b_name} -> {cmd_ref}{loc_str}")
|
||||
else:
|
||||
buttons.append(f" {tag} {b_name}{loc_str}")
|
||||
if not buttons and autofill and halign_node is None:
|
||||
return ["AutoCommandBar [autofill]"]
|
||||
return [f"AutoCommandBar [{', '.join(flags)}]"] + buttons
|
||||
|
||||
cb_loc_node = root.find("d:CommandBarLocation", NSMAP)
|
||||
cb_loc = cb_loc_node.text if cb_loc_node is not None and cb_loc_node.text else "Auto"
|
||||
main_acb_node = root.find("d:AutoCommandBar", NSMAP)
|
||||
acb_lines = []
|
||||
if cb_loc != "None" and main_acb_node is not None:
|
||||
acb_lines = format_main_acb(main_acb_node)
|
||||
|
||||
if acb_lines and cb_loc in ("Auto", "Top"):
|
||||
lines.append("")
|
||||
lines.extend(acb_lines)
|
||||
|
||||
# --- Element tree ---
|
||||
tree_state = {"has_collapsed": False}
|
||||
child_items = root.find("d:ChildItems", NSMAP)
|
||||
@@ -474,6 +538,10 @@ def main():
|
||||
build_tree(child_items, " ", tree_lines, expand, tree_state)
|
||||
lines.extend(tree_lines)
|
||||
|
||||
if acb_lines and cb_loc == "Bottom":
|
||||
lines.append("")
|
||||
lines.extend(acb_lines)
|
||||
|
||||
# --- Attributes ---
|
||||
attrs_node = root.find("d:Attributes", NSMAP)
|
||||
if attrs_node is not None:
|
||||
|
||||
@@ -41,9 +41,9 @@ allowed-tools: []
|
||||
├─ Информационные надписи (label, hyperlink)
|
||||
Рабочая область
|
||||
├─ Таблица данных или Pages с вкладками
|
||||
Кнопки действий
|
||||
├─ Выполнить / Применить (defaultButton)
|
||||
├─ Закрыть (stdCommand: Close)
|
||||
Главная АКП формы (autoCmdBar)
|
||||
├─ Выполнить / Применить (defaultButton: true)
|
||||
└─ Закрыть (stdCommand: Close)
|
||||
```
|
||||
|
||||
**События:** OnCreateAtServer, OnOpen, NotificationProcessing
|
||||
@@ -90,12 +90,12 @@ allowed-tools: []
|
||||
├─ Шаг1: описание + параметры
|
||||
├─ Шаг2: основная работа
|
||||
└─ Шаг3: результат
|
||||
Кнопки (horizontal)
|
||||
├─ Назад (command), Далее (command, defaultButton), Выполнить (command)
|
||||
Главная АКП формы (autoCmdBar)
|
||||
├─ Назад, Далее (defaultButton: true), Выполнить
|
||||
└─ Закрыть (stdCommand: Close)
|
||||
```
|
||||
|
||||
**Свойства:** windowOpeningMode=LockOwnerWindow, commandBarLocation=None
|
||||
**Свойства:** windowOpeningMode=LockOwnerWindow
|
||||
|
||||
---
|
||||
|
||||
@@ -111,7 +111,7 @@ allowed-tools: []
|
||||
| Номер+Дата | `ГруппаНомерДата` | horizontal |
|
||||
| Подвал | `ГруппаПодвал` | vertical |
|
||||
| Итоги | `ГруппаИтоги` | horizontal |
|
||||
| Кнопки | `ГруппаКнопок` | horizontal |
|
||||
| Главная АКП формы | `ФормаКоманднаяПанель` | autoCmdBar |
|
||||
| Страницы | `ГруппаСтраницы` / `Страницы` | pages |
|
||||
| Предупреждение | `ГруппаПредупреждение` | horizontal, visible:false |
|
||||
| Доп. секция | `ГруппаДополнительно` / `ГруппаПрочее` | vertical, collapse |
|
||||
@@ -150,7 +150,7 @@ allowed-tools: []
|
||||
|
||||
1. **Порядок чтения.** Сверху вниз, слева направо. Самое важное — вверху.
|
||||
2. **Двухколоночная шапка.** Основные реквизиты слева (контрагент, склад), организационные справа (организация, подразделение).
|
||||
3. **Кнопки действий внизу.** Главная кнопка — `defaultButton: true`. Закрыть — всегда последняя.
|
||||
3. **Кнопки действий — на главной АКП формы** (`autoCmdBar`), не в отдельной группе на форме. Главная кнопка — `defaultButton: true`. Закрыть — всегда последняя.
|
||||
4. **Таблицы — основная область.** Табличные части занимают большую часть формы, обычно на Pages.
|
||||
5. **Итоги рядом с таблицей.** В подвале, горизонтальная группа, все поля readOnly.
|
||||
6. **Фильтры — отдельная зона.** Над списком, alwaysHorizontal, пара «флажок + поле» на каждый фильтр.
|
||||
@@ -166,8 +166,8 @@ allowed-tools: []
|
||||
Для необязательных секций (подписи, дополнительно, прочее):
|
||||
|
||||
```json
|
||||
{ "group": "vertical", "name": "ГруппаПодписи", "title": "Подписи",
|
||||
"behavior": "Collapsible", "collapsed": true, "children": [...] }
|
||||
{ "group": "collapsible", "name": "ГруппаПодписи", "title": "Подписи",
|
||||
"collapsed": true, "children": [...] }
|
||||
```
|
||||
|
||||
### Баннер-предупреждение
|
||||
@@ -233,7 +233,7 @@ allowed-tools: []
|
||||
{ "input": "ДанныеКоличество", "path": "Объект.Данные.Количество", "on": ["OnChange"] },
|
||||
{ "input": "ДанныеСумма", "path": "Объект.Данные.Сумма", "readOnly": true }
|
||||
]},
|
||||
{ "group": "horizontal", "name": "ГруппаКнопок", "children": [
|
||||
{ "autoCmdBar": "ФормаКоманднаяПанель", "children": [
|
||||
{ "button": "Загрузить", "command": "Загрузить", "title": "Загрузить из файла", "defaultButton": true },
|
||||
{ "button": "Очистить", "command": "Очистить", "title": "Очистить таблицу" },
|
||||
{ "button": "Закрыть", "stdCommand": "Close" }
|
||||
|
||||
@@ -31,7 +31,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-remove/scripts/remove-form.ps1 -ObjectName "<ObjectName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/remove-form.ps1" -ObjectName "<ObjectName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"]
|
||||
```
|
||||
|
||||
## Что удаляется
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-remove v1.1 — Remove form from 1C object
|
||||
# form-remove v1.2 — Remove form from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# remove-form v1.0 — Remove form from 1C object
|
||||
# remove-form v1.1 — Remove form from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -16,7 +16,9 @@ NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||
def save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
|
||||
@@ -17,34 +17,13 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|-----------|:-----:|---------|-----------------------------------------|
|
||||
| FormPath | да | — | Путь к файлу Form.xml |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-validate.ps1 -FormPath "Catalogs/Номенклатура/Forms/ФормаЭлемента"
|
||||
powershell.exe -NoProfile -File .claude/skills/form-validate/scripts/form-validate.ps1 -FormPath "src/МояОбработка/Forms/Форма/Ext/Form.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-validate.ps1" -FormPath "Catalogs/Номенклатура/Forms/ФормаЭлемента"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-validate.ps1" -FormPath "src/МояОбработка/Forms/Форма/Ext/Form.xml"
|
||||
```
|
||||
|
||||
## Проверки
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|---|---|---|
|
||||
| 1 | Корневой элемент `<Form>`, version="2.17" | ERROR / WARN |
|
||||
| 2 | `<AutoCommandBar>` присутствует, id="-1" | ERROR |
|
||||
| 3 | Уникальность ID элементов (отдельный пул) | ERROR |
|
||||
| 4 | Уникальность ID реквизитов (отдельный пул) | ERROR |
|
||||
| 5 | Уникальность ID команд (отдельный пул) | ERROR |
|
||||
| 6 | Companion-элементы (ContextMenu, ExtendedTooltip, и др.) | ERROR |
|
||||
| 7 | DataPath → ссылается на существующий реквизит | ERROR |
|
||||
| 8 | CommandName кнопок → ссылается на существующую команду | ERROR |
|
||||
| 9 | События имеют непустые имена обработчиков | ERROR |
|
||||
| 10 | Команды имеют Action (обработчик) | ERROR |
|
||||
| 11 | Не более одного MainAttribute | ERROR |
|
||||
| 12 | BaseForm: наличие и version (при расширении) | OK / WARN |
|
||||
| 13 | callType значения: Before, After, Override | ERROR |
|
||||
| 14 | ID расширения >= 1000000 для добавленных attrs/commands | WARN |
|
||||
| 15 | callType без BaseForm — некорректная структура | WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# form-validate v1.1 — Validate 1C managed form
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$FormPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -58,6 +59,20 @@ $nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- Detect context: config vs EPF/ERF ---
|
||||
# Walk up from FormPath looking for Configuration.xml → config context
|
||||
# No Configuration.xml → external data processor / report (EPF/ERF)
|
||||
$script:isConfigContext = $false
|
||||
$walkDir = Split-Path (Resolve-Path $FormPath) -Parent
|
||||
for ($i = 0; $i -lt 15; $i++) {
|
||||
if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break }
|
||||
if (Test-Path (Join-Path $walkDir "Configuration.xml")) {
|
||||
$script:isConfigContext = $true
|
||||
break
|
||||
}
|
||||
$walkDir = Split-Path $walkDir
|
||||
}
|
||||
|
||||
# --- Counters ---
|
||||
|
||||
$errors = 0
|
||||
@@ -112,10 +127,10 @@ if ($root.LocalName -ne "Form") {
|
||||
Report-Error "Root element is '$($root.LocalName)', expected 'Form'"
|
||||
} else {
|
||||
$version = $root.GetAttribute("version")
|
||||
if ($version -eq "2.17") {
|
||||
if ($version -eq "2.17" -or $version -eq "2.20") {
|
||||
Report-OK "Root element: Form version=$version"
|
||||
} elseif ($version) {
|
||||
Report-Warn "Form version='$version' (expected 2.17)"
|
||||
Report-Warn "Form version='$version' (expected 2.17 or 2.20)"
|
||||
} else {
|
||||
Report-Warn "Form version attribute missing"
|
||||
}
|
||||
@@ -667,6 +682,84 @@ if (-not $stopped -and -not $isExtension) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 12: Type values validation ---
|
||||
|
||||
$knownInvalidTypes = @(
|
||||
"FormDataStructure","FormDataCollection","FormDataTree","FormDataTreeItem","FormDataCollectionItem"
|
||||
"FormGroup","FormField","FormButton","FormDecoration","FormTable"
|
||||
)
|
||||
$validClosedTypes = @(
|
||||
"xs:boolean","xs:string","xs:decimal","xs:dateTime","xs:binary"
|
||||
"v8:FillChecking","v8:Null","v8:StandardPeriod","v8:StandardBeginningDate","v8:Type"
|
||||
"v8:TypeDescription","v8:UUID","v8:ValueListType","v8:ValueTable","v8:ValueTree"
|
||||
"v8:Universal","v8:FixedArray","v8:FixedStructure"
|
||||
"v8ui:Color","v8ui:Font","v8ui:FormattedString","v8ui:HorizontalAlign"
|
||||
"v8ui:Picture","v8ui:SizeChangeMode","v8ui:VerticalAlign"
|
||||
"dcsset:DataCompositionComparisonType","dcsset:DataCompositionFieldPlacement"
|
||||
"dcsset:Filter","dcsset:SettingsComposer","dcsset:DataCompositionSettings"
|
||||
"dcssch:DataCompositionSchema"
|
||||
"dcscor:DataCompositionComparisonType","dcscor:DataCompositionGroupType"
|
||||
"dcscor:DataCompositionPeriodAdditionType","dcscor:DataCompositionSortDirection","dcscor:Field"
|
||||
"ent:AccountType","ent:AccumulationRecordType","ent:AccountingRecordType"
|
||||
)
|
||||
$validCfgPrefixes = @(
|
||||
"AccountingRegisterRecordSet","AccumulationRegisterRecordSet"
|
||||
"BusinessProcessObject","BusinessProcessRef"
|
||||
"CatalogObject","CatalogRef"
|
||||
"ChartOfAccountsObject","ChartOfAccountsRef"
|
||||
"ChartOfCalculationTypesObject","ChartOfCalculationTypesRef"
|
||||
"ChartOfCharacteristicTypesObject","ChartOfCharacteristicTypesRef"
|
||||
"ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef"
|
||||
"DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef"
|
||||
"ExternalDataProcessorObject","ExternalReportObject"
|
||||
"InformationRegisterRecordManager","InformationRegisterRecordSet"
|
||||
"ReportObject","TaskObject","TaskRef"
|
||||
)
|
||||
|
||||
if (-not $stopped) {
|
||||
$typeNodes = $root.SelectNodes("//v8:Type", $nsMgr)
|
||||
$typeOk = $true
|
||||
$typeChecked = 0
|
||||
$typeInvalid = 0
|
||||
foreach ($tn in $typeNodes) {
|
||||
$tv = $tn.InnerText.Trim()
|
||||
if (-not $tv) { continue }
|
||||
$typeChecked++
|
||||
if ($tv -in $knownInvalidTypes) {
|
||||
Report-Error "12. Type '$tv': invalid runtime/UI type (not valid in XDTO schema)"
|
||||
$typeOk = $false; $typeInvalid++
|
||||
continue
|
||||
}
|
||||
if ($tv -in $validClosedTypes) { continue }
|
||||
if ($tv -match '^cfg:(.+)$') {
|
||||
$cfgVal = $Matches[1]
|
||||
if ($cfgVal -eq "DynamicList") { continue }
|
||||
if ($cfgVal -match '^([^.]+)\.') {
|
||||
$pfx = $Matches[1]
|
||||
if ($pfx -in $validCfgPrefixes) {
|
||||
# ExternalDataProcessorObject/ExternalReportObject valid only for EPF/ERF, not config
|
||||
if ($script:isConfigContext -and ($pfx -eq "ExternalDataProcessorObject" -or $pfx -eq "ExternalReportObject")) {
|
||||
Report-Error "12. Type '$tv': External* type in configuration context (use DataProcessorObject/ReportObject instead)"
|
||||
$typeOk = $false; $typeInvalid++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
Report-Warn "12. Type '$tv': unrecognized cfg prefix"
|
||||
$typeOk = $false
|
||||
continue
|
||||
}
|
||||
if ($tv -match ':') { continue }
|
||||
Report-Warn "12. Type '$tv': bare type without namespace prefix"
|
||||
$typeOk = $false
|
||||
}
|
||||
if ($typeChecked -eq 0) {
|
||||
Report-OK "12. Types: no type values to check"
|
||||
} elseif ($typeOk) {
|
||||
Report-OK "12. Types: $typeChecked values, all valid"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
|
||||
$checks = $script:okCount + $errors + $warnings
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.1 — Validate 1C managed form
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -13,6 +13,41 @@ V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
NSMAP = {"f": F_NS, "v8": V8_NS}
|
||||
|
||||
KNOWN_INVALID_TYPES = {
|
||||
'FormDataStructure', 'FormDataCollection', 'FormDataTree',
|
||||
'FormDataTreeItem', 'FormDataCollectionItem',
|
||||
'FormGroup', 'FormField', 'FormButton', 'FormDecoration', 'FormTable',
|
||||
}
|
||||
|
||||
VALID_CLOSED_TYPES = {
|
||||
'xs:boolean', 'xs:string', 'xs:decimal', 'xs:dateTime', 'xs:binary',
|
||||
'v8:FillChecking', 'v8:Null', 'v8:StandardPeriod', 'v8:StandardBeginningDate', 'v8:Type',
|
||||
'v8:TypeDescription', 'v8:UUID', 'v8:ValueListType', 'v8:ValueTable', 'v8:ValueTree',
|
||||
'v8:Universal', 'v8:FixedArray', 'v8:FixedStructure',
|
||||
'v8ui:Color', 'v8ui:Font', 'v8ui:FormattedString', 'v8ui:HorizontalAlign',
|
||||
'v8ui:Picture', 'v8ui:SizeChangeMode', 'v8ui:VerticalAlign',
|
||||
'dcsset:DataCompositionComparisonType', 'dcsset:DataCompositionFieldPlacement',
|
||||
'dcsset:Filter', 'dcsset:SettingsComposer', 'dcsset:DataCompositionSettings',
|
||||
'dcssch:DataCompositionSchema',
|
||||
'dcscor:DataCompositionComparisonType', 'dcscor:DataCompositionGroupType',
|
||||
'dcscor:DataCompositionPeriodAdditionType', 'dcscor:DataCompositionSortDirection', 'dcscor:Field',
|
||||
'ent:AccountType', 'ent:AccumulationRecordType', 'ent:AccountingRecordType',
|
||||
}
|
||||
|
||||
VALID_CFG_PREFIXES = {
|
||||
'AccountingRegisterRecordSet', 'AccumulationRegisterRecordSet',
|
||||
'BusinessProcessObject', 'BusinessProcessRef',
|
||||
'CatalogObject', 'CatalogRef',
|
||||
'ChartOfAccountsObject', 'ChartOfAccountsRef',
|
||||
'ChartOfCalculationTypesObject', 'ChartOfCalculationTypesRef',
|
||||
'ChartOfCharacteristicTypesObject', 'ChartOfCharacteristicTypesRef',
|
||||
'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef',
|
||||
'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef',
|
||||
'ExternalDataProcessorObject', 'ExternalReportObject',
|
||||
'InformationRegisterRecordManager', 'InformationRegisterRecordSet',
|
||||
'ReportObject', 'TaskObject', 'TaskRef',
|
||||
}
|
||||
|
||||
|
||||
def localname(el):
|
||||
return etree.QName(el.tag).localname
|
||||
@@ -22,7 +57,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Validate 1C managed form", allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True)
|
||||
parser.add_argument("-FormPath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
args = parser.parse_args()
|
||||
@@ -69,6 +104,18 @@ def main():
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
# Detect context: config vs EPF/ERF
|
||||
is_config_context = False
|
||||
walk_dir = os.path.dirname(os.path.abspath(form_path))
|
||||
for _ in range(15):
|
||||
parent = os.path.dirname(walk_dir)
|
||||
if parent == walk_dir:
|
||||
break
|
||||
if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')):
|
||||
is_config_context = True
|
||||
break
|
||||
walk_dir = parent
|
||||
|
||||
errors = 0
|
||||
warnings = 0
|
||||
ok_count = 0
|
||||
@@ -114,10 +161,10 @@ def main():
|
||||
report_error(f"Root element is '{localname(root)}', expected 'Form'")
|
||||
else:
|
||||
version = root.get("version", "")
|
||||
if version == "2.17":
|
||||
if version in ("2.17", "2.20"):
|
||||
report_ok(f"Root element: Form version={version}")
|
||||
elif version:
|
||||
report_warn(f"Form version='{version}' (expected 2.17)")
|
||||
report_warn(f"Form version='{version}' (expected 2.17 or 2.20)")
|
||||
else:
|
||||
report_warn("Form version attribute missing")
|
||||
|
||||
@@ -588,6 +635,48 @@ def main():
|
||||
if call_type_without_base:
|
||||
report_warn("callType attributes found but no BaseForm \u2014 possible incorrect structure")
|
||||
|
||||
# --- Check 12: Type validation ---
|
||||
if not stopped:
|
||||
type_nodes = root.xpath('//v8:Type', namespaces={'v8': V8_NS})
|
||||
type_error_count = 0
|
||||
type_warn_count = 0
|
||||
type_count = len(type_nodes)
|
||||
|
||||
for tn in type_nodes:
|
||||
if stopped:
|
||||
break
|
||||
tv = (tn.text or "").strip()
|
||||
if not tv:
|
||||
continue
|
||||
|
||||
if tv in KNOWN_INVALID_TYPES:
|
||||
report_error(f'12. Type "{tv}": invalid runtime/UI type (not valid in XDTO schema)')
|
||||
type_error_count += 1
|
||||
elif tv in VALID_CLOSED_TYPES:
|
||||
pass # OK
|
||||
elif tv.startswith("cfg:"):
|
||||
suffix = tv[4:] # after "cfg:"
|
||||
prefix = suffix.split(".")[0]
|
||||
if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList":
|
||||
# ExternalDataProcessorObject/ExternalReportObject valid only in EPF/ERF context
|
||||
if is_config_context and prefix in ('ExternalDataProcessorObject', 'ExternalReportObject'):
|
||||
report_error(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)')
|
||||
type_invalid += 1
|
||||
else:
|
||||
report_warn(f'12. Type "{tv}": unrecognized cfg prefix')
|
||||
type_warn_count += 1
|
||||
elif ":" in tv:
|
||||
pass # unknown namespace, pass through
|
||||
else:
|
||||
report_warn(f'12. Type "{tv}": bare type without namespace prefix')
|
||||
type_warn_count += 1
|
||||
|
||||
if type_error_count == 0 and type_warn_count == 0:
|
||||
if type_count > 0:
|
||||
report_ok(f'12. Types: {type_count} values, all valid')
|
||||
else:
|
||||
report_ok('12. Types: no type values to check')
|
||||
|
||||
# --- Finalize ---
|
||||
checks = ok_count + errors + warnings
|
||||
if errors == 0 and warnings == 0 and not detailed:
|
||||
|
||||
@@ -30,7 +30,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/help-add/scripts/add-help.ps1 -ObjectName "<ObjectName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-help.ps1" -ObjectName "<ObjectName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"]
|
||||
```
|
||||
|
||||
## Что делает скрипт
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# help-add v1.2 — Add built-in help to 1C object
|
||||
# help-add v1.4 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -10,6 +10,27 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
@@ -35,7 +56,7 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
$helpXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
||||
<Page>$Lang</Page>
|
||||
</Help>
|
||||
"@
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-help v1.2 — Add built-in help to 1C object
|
||||
# add-help v1.4 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
@@ -11,10 +12,28 @@ from lxml import etree
|
||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
|
||||
def save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
@@ -39,6 +58,8 @@ def main():
|
||||
lang = args.Lang
|
||||
src_dir = args.SrcDir
|
||||
|
||||
format_version = detect_format_version(os.path.abspath(src_dir))
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
object_dir = os.path.join(src_dir, object_name)
|
||||
@@ -60,7 +81,7 @@ def main():
|
||||
'<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
f' version="{format_version}">\n'
|
||||
f'\t<Page>{lang}</Page>\n'
|
||||
'</Help>'
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: img-grid
|
||||
description: Наложить пронумерованную сетку на изображение для определения пропорций колонок
|
||||
description: Наложить пронумерованную сетку на изображение. Используй при анализе скриншота макета или печатной формы — измерить пропорции колонок перед генерацией табличного документа
|
||||
argument-hint: <ImagePath> [-c COLS]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -29,7 +29,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```bash
|
||||
python .claude/skills/img-grid/scripts/overlay-grid.py "<ImagePath>" [-c 50] [-o "<OutputPath>"]
|
||||
python "${CLAUDE_SKILL_DIR}/scripts/overlay-grid.py" "<ImagePath>" [-c 50] [-o "<OutputPath>"]
|
||||
```
|
||||
|
||||
Требуется Python 3 с библиотекой Pillow (`pip install Pillow`).
|
||||
|
||||
@@ -11,8 +11,65 @@ allowed-tools:
|
||||
|
||||
# /interface-edit — редактирование CommandInterface.xml
|
||||
|
||||
Операции: hide, show, place, order, subsystem-order, group-order. Подробнее: `.claude/skills/interface-edit/reference.md`
|
||||
Точечное редактирование файла командного интерфейса подсистемы 1С.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обяз. | Описание |
|
||||
|----------|:-----:|----------|
|
||||
| CIPath | да | Путь к CommandInterface.xml |
|
||||
| Operation | нет | Операция: hide, show, place, order, subsystem-order, group-order |
|
||||
| Value | нет | Значение для операции |
|
||||
| DefinitionFile | нет | JSON-файл с массивом операций (альтернатива Operation) |
|
||||
| CreateIfMissing | нет | Создать файл если не существует |
|
||||
| NoValidate | нет | Пропустить авто-валидацию |
|
||||
|
||||
## Команда
|
||||
|
||||
### Inline mode
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File '.claude/skills/interface-edit/scripts/interface-edit.ps1' -CIPath '<path>' -Operation hide -Value '<cmd>'
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-edit.ps1" -CIPath '<path>' -Operation hide -Value '<cmd>'
|
||||
```
|
||||
|
||||
### JSON mode
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-edit.ps1" -CIPath '<path>' -DefinitionFile '<json>'
|
||||
```
|
||||
|
||||
## Операции
|
||||
|
||||
| Операция | Значение | Описание |
|
||||
|----------|----------|----------|
|
||||
| hide | Cmd.Name или массив | Скрыть команду (CommandsVisibility, false) |
|
||||
| show | Cmd.Name или массив | Показать команду (visibility, true) |
|
||||
| place | {"command":"...","group":"CommandGroup.X"} | Разместить команду в группе |
|
||||
| order | {"group":"...","commands":[...]} | Задать порядок команд в группе |
|
||||
| subsystem-order | ["Subsystem.X.Subsystem.A",...] | Порядок дочерних подсистем |
|
||||
| group-order | ["NavigationPanelOrdinary",...] | Порядок групп |
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Скрыть команду
|
||||
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
|
||||
|
||||
# Показать команду
|
||||
... -Operation show -Value "Report.Продажи.Command.Отчёт"
|
||||
|
||||
# Разместить в группе
|
||||
... -Operation place -Value '{"command":"Report.X.Command.Y","group":"CommandGroup.Отчеты"}'
|
||||
|
||||
# Задать порядок подсистем
|
||||
... -Operation subsystem-order -Value '["Subsystem.X.Subsystem.A","Subsystem.X.Subsystem.B"]'
|
||||
|
||||
# Создать новый CI
|
||||
... -CIPath <new-path> -Operation subsystem-order -Value '[...]' -CreateIfMissing
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/interface-validate <CIPath>
|
||||
```
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# /interface-edit — редактирование CommandInterface.xml
|
||||
|
||||
Точечное редактирование файла командного интерфейса подсистемы 1С.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| CIPath | Путь к CommandInterface.xml |
|
||||
| DefinitionFile | JSON-файл с массивом операций |
|
||||
| Operation | Одна операция: hide, show, place, order, subsystem-order, group-order |
|
||||
| Value | Значение для операции |
|
||||
| CreateIfMissing | Создать файл если не существует |
|
||||
| NoValidate | Пропустить авто-валидацию |
|
||||
|
||||
## Операции
|
||||
|
||||
| Операция | Значение | Описание |
|
||||
|----------|----------|----------|
|
||||
| hide | Cmd.Name или массив | Скрыть команду (CommandsVisibility, false) |
|
||||
| show | Cmd.Name или массив | Показать команду (visibility, true) |
|
||||
| place | {"command":"...","group":"CommandGroup.X"} | Разместить команду в группе |
|
||||
| order | {"group":"...","commands":[...]} | Задать порядок команд в группе |
|
||||
| subsystem-order | ["Subsystem.X.Subsystem.A",...] | Порядок дочерних подсистем |
|
||||
| group-order | ["NavigationPanelOrdinary",...] | Порядок групп |
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Скрыть команду
|
||||
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
|
||||
|
||||
# Показать команду
|
||||
... -Operation show -Value "Report.Продажи.Command.Отчёт"
|
||||
|
||||
# Разместить в группе
|
||||
... -Operation place -Value '{"command":"Report.X.Command.Y","group":"CommandGroup.Отчеты"}'
|
||||
|
||||
# Задать порядок подсистем
|
||||
... -Operation subsystem-order -Value '["Subsystem.X.Subsystem.A","Subsystem.X.Subsystem.B"]'
|
||||
|
||||
# Создать новый CI
|
||||
... -CIPath <new-path> -Operation subsystem-order -Value '[...]' -CreateIfMissing
|
||||
```
|
||||
|
||||
## Авто-валидация
|
||||
|
||||
После каждой операции автоматически запускается `/interface-validate`. Подавить: `-NoValidate`.
|
||||
@@ -1,7 +1,7 @@
|
||||
# interface-edit v1.0 — Edit 1C CommandInterface.xml
|
||||
# interface-edit v1.3 — Edit 1C CommandInterface.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$CIPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||
[string]$DefinitionFile,
|
||||
[ValidateSet("hide","show","place","order","subsystem-order","group-order")]
|
||||
[string]$Operation,
|
||||
@@ -23,6 +23,25 @@ if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
|
||||
}
|
||||
$resolvedPath = $CIPath
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($CIPath))
|
||||
|
||||
# --- Namespaces ---
|
||||
$script:ciNs = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
||||
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
@@ -42,7 +61,7 @@ if (-not (Test-Path $CIPath)) {
|
||||
xmlns:xr="$($script:xrNs)"
|
||||
xmlns:xs="$($script:xsNs)"
|
||||
xmlns:xsi="$($script:xsiNs)"
|
||||
version="2.17">
|
||||
version="$formatVersion">
|
||||
</CommandInterface>
|
||||
"@
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
@@ -202,9 +221,59 @@ function Find-CommandByName($section, [string]$cmdName) {
|
||||
return $null
|
||||
}
|
||||
|
||||
# --- Command name normalization (plural/Russian type prefix → singular English) ---
|
||||
$script:typeNormMap = @{
|
||||
"Catalogs"="Catalog"; "Documents"="Document"; "Enums"="Enum"; "Constants"="Constant"
|
||||
"Reports"="Report"; "DataProcessors"="DataProcessor"
|
||||
"InformationRegisters"="InformationRegister"; "AccumulationRegisters"="AccumulationRegister"
|
||||
"AccountingRegisters"="AccountingRegister"; "CalculationRegisters"="CalculationRegister"
|
||||
"ChartsOfAccounts"="ChartOfAccounts"; "ChartsOfCharacteristicTypes"="ChartOfCharacteristicTypes"
|
||||
"ChartsOfCalculationTypes"="ChartOfCalculationTypes"
|
||||
"BusinessProcesses"="BusinessProcess"; "Tasks"="Task"
|
||||
"ExchangePlans"="ExchangePlan"; "DocumentJournals"="DocumentJournal"
|
||||
"CommonModules"="CommonModule"; "CommonCommands"="CommonCommand"
|
||||
"CommonForms"="CommonForm"; "CommonPictures"="CommonPicture"
|
||||
"CommonTemplates"="CommonTemplate"; "CommonAttributes"="CommonAttribute"
|
||||
"CommandGroups"="CommandGroup"; "Roles"="Role"
|
||||
"Subsystems"="Subsystem"; "StyleItems"="StyleItem"
|
||||
# Russian singular
|
||||
"Справочник"="Catalog"; "Документ"="Document"; "Перечисление"="Enum"
|
||||
"Константа"="Constant"; "Отчёт"="Report"; "Отчет"="Report"; "Обработка"="DataProcessor"
|
||||
"РегистрСведений"="InformationRegister"; "РегистрНакопления"="AccumulationRegister"
|
||||
"РегистрБухгалтерии"="AccountingRegister"
|
||||
"ПланСчетов"="ChartOfAccounts"; "ПланВидовХарактеристик"="ChartOfCharacteristicTypes"
|
||||
"БизнесПроцесс"="BusinessProcess"; "Задача"="Task"
|
||||
"ПланОбмена"="ExchangePlan"; "ЖурналДокументов"="DocumentJournal"
|
||||
"ОбщийМодуль"="CommonModule"; "ОбщаяКоманда"="CommonCommand"
|
||||
"ОбщаяФорма"="CommonForm"; "Подсистема"="Subsystem"
|
||||
# Russian plural
|
||||
"Справочники"="Catalog"; "Документы"="Document"; "Перечисления"="Enum"
|
||||
"Константы"="Constant"; "Отчёты"="Report"; "Отчеты"="Report"; "Обработки"="DataProcessor"
|
||||
"РегистрыСведений"="InformationRegister"; "РегистрыНакопления"="AccumulationRegister"
|
||||
"РегистрыБухгалтерии"="AccountingRegister"
|
||||
"ПланыСчетов"="ChartOfAccounts"; "ПланыВидовХарактеристик"="ChartOfCharacteristicTypes"
|
||||
"БизнесПроцессы"="BusinessProcess"; "Задачи"="Task"
|
||||
"ПланыОбмена"="ExchangePlan"; "ЖурналыДокументов"="DocumentJournal"
|
||||
"Подсистемы"="Subsystem"
|
||||
}
|
||||
|
||||
function Normalize-CmdName([string]$name) {
|
||||
if (-not $name -or -not $name.Contains('.')) { return $name }
|
||||
$dotIdx = $name.IndexOf('.')
|
||||
$first = $name.Substring(0, $dotIdx)
|
||||
$rest = $name.Substring($dotIdx)
|
||||
if ($script:typeNormMap.ContainsKey($first)) {
|
||||
$normalized = "$($script:typeNormMap[$first])$rest"
|
||||
if ($normalized -ne $name) { Write-Host "[NORM] Command: $name -> $normalized" }
|
||||
return $normalized
|
||||
}
|
||||
return $name
|
||||
}
|
||||
|
||||
# --- Operations ---
|
||||
|
||||
function Do-Hide([string[]]$commands) {
|
||||
$commands = @($commands | ForEach-Object { Normalize-CmdName $_ })
|
||||
$section = Ensure-Section "CommandsVisibility"
|
||||
$sectionIndent = Get-ChildIndent $section
|
||||
|
||||
@@ -244,6 +313,7 @@ function Do-Hide([string[]]$commands) {
|
||||
}
|
||||
|
||||
function Do-Show([string[]]$commands) {
|
||||
$commands = @($commands | ForEach-Object { Normalize-CmdName $_ })
|
||||
$section = $null
|
||||
foreach ($child in $root.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "CommandsVisibility") {
|
||||
@@ -292,7 +362,7 @@ function Do-Show([string[]]$commands) {
|
||||
|
||||
function Do-Place([string]$jsonVal) {
|
||||
$def = $jsonVal | ConvertFrom-Json
|
||||
$cmdName = "$($def.command)"
|
||||
$cmdName = Normalize-CmdName "$($def.command)"
|
||||
$groupName = "$($def.group)"
|
||||
if (-not $cmdName -or -not $groupName) { Write-Error "place requires {command, group}"; exit 1 }
|
||||
|
||||
@@ -326,7 +396,7 @@ function Do-Place([string]$jsonVal) {
|
||||
function Do-Order([string]$jsonVal) {
|
||||
$def = $jsonVal | ConvertFrom-Json
|
||||
$groupName = "$($def.group)"
|
||||
$commands = @($def.commands | ForEach-Object { "$_" })
|
||||
$commands = @($def.commands | ForEach-Object { Normalize-CmdName "$_" })
|
||||
if (-not $groupName -or $commands.Count -eq 0) { Write-Error "order requires {group, commands:[...]}"; exit 1 }
|
||||
|
||||
$section = Ensure-Section "CommandsOrder"
|
||||
@@ -436,7 +506,9 @@ if ($DefinitionFile) {
|
||||
|
||||
foreach ($op in $operations) {
|
||||
$opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" }
|
||||
$opValue = if ($op.value) { "$($op.value)" } else { "$Value" }
|
||||
$opValueRaw = if ($op.value) { $op.value } else { "$Value" }
|
||||
# For operations expecting JSON (place, order, etc.): accept object or string
|
||||
$opValue = if ($opValueRaw -is [string]) { $opValueRaw } else { $opValueRaw | ConvertTo-Json -Compress }
|
||||
|
||||
switch ($opName) {
|
||||
"hide" { Do-Hide (Parse-ValueList $opValue) }
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# interface-edit v1.0 — Edit 1C CommandInterface.xml
|
||||
# interface-edit v1.3 — Edit 1C CommandInterface.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from lxml import etree
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
|
||||
CI_NS = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
@@ -95,12 +112,64 @@ def parse_value_list(val):
|
||||
|
||||
def save_xml_bom(tree, path):
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
|
||||
|
||||
TYPE_NORM_MAP = {
|
||||
'Catalogs': 'Catalog', 'Documents': 'Document', 'Enums': 'Enum',
|
||||
'Constants': 'Constant', 'Reports': 'Report', 'DataProcessors': 'DataProcessor',
|
||||
'InformationRegisters': 'InformationRegister', 'AccumulationRegisters': 'AccumulationRegister',
|
||||
'AccountingRegisters': 'AccountingRegister', 'CalculationRegisters': 'CalculationRegister',
|
||||
'ChartsOfAccounts': 'ChartOfAccounts', 'ChartsOfCharacteristicTypes': 'ChartOfCharacteristicTypes',
|
||||
'ChartsOfCalculationTypes': 'ChartOfCalculationTypes',
|
||||
'BusinessProcesses': 'BusinessProcess', 'Tasks': 'Task',
|
||||
'ExchangePlans': 'ExchangePlan', 'DocumentJournals': 'DocumentJournal',
|
||||
'CommonModules': 'CommonModule', 'CommonCommands': 'CommonCommand',
|
||||
'CommonForms': 'CommonForm', 'CommonPictures': 'CommonPicture',
|
||||
'CommonTemplates': 'CommonTemplate', 'CommonAttributes': 'CommonAttribute',
|
||||
'CommandGroups': 'CommandGroup', 'Roles': 'Role',
|
||||
'Subsystems': 'Subsystem', 'StyleItems': 'StyleItem',
|
||||
# Russian singular
|
||||
'Справочник': 'Catalog', 'Документ': 'Document', 'Перечисление': 'Enum',
|
||||
'Константа': 'Constant', 'Отчёт': 'Report', 'Отчет': 'Report', 'Обработка': 'DataProcessor',
|
||||
'РегистрСведений': 'InformationRegister', 'РегистрНакопления': 'AccumulationRegister',
|
||||
'РегистрБухгалтерии': 'AccountingRegister',
|
||||
'ПланСчетов': 'ChartOfAccounts', 'ПланВидовХарактеристик': 'ChartOfCharacteristicTypes',
|
||||
'БизнесПроцесс': 'BusinessProcess', 'Задача': 'Task',
|
||||
'ПланОбмена': 'ExchangePlan', 'ЖурналДокументов': 'DocumentJournal',
|
||||
'ОбщийМодуль': 'CommonModule', 'ОбщаяКоманда': 'CommonCommand',
|
||||
'ОбщаяФорма': 'CommonForm', 'Подсистема': 'Subsystem',
|
||||
# Russian plural
|
||||
'Справочники': 'Catalog', 'Документы': 'Document', 'Перечисления': 'Enum',
|
||||
'Константы': 'Constant', 'Отчёты': 'Report', 'Отчеты': 'Report', 'Обработки': 'DataProcessor',
|
||||
'РегистрыСведений': 'InformationRegister', 'РегистрыНакопления': 'AccumulationRegister',
|
||||
'РегистрыБухгалтерии': 'AccountingRegister',
|
||||
'ПланыСчетов': 'ChartOfAccounts', 'ПланыВидовХарактеристик': 'ChartOfCharacteristicTypes',
|
||||
'БизнесПроцессы': 'BusinessProcess', 'Задачи': 'Task',
|
||||
'ПланыОбмена': 'ExchangePlan', 'ЖурналыДокументов': 'DocumentJournal',
|
||||
'Подсистемы': 'Subsystem',
|
||||
}
|
||||
|
||||
|
||||
def normalize_cmd_name(name):
|
||||
if not name or '.' not in name:
|
||||
return name
|
||||
dot_idx = name.index('.')
|
||||
first = name[:dot_idx]
|
||||
rest = name[dot_idx:]
|
||||
if first in TYPE_NORM_MAP:
|
||||
normalized = TYPE_NORM_MAP[first] + rest
|
||||
if normalized != name:
|
||||
print(f'[NORM] Command: {name} -> {normalized}')
|
||||
return normalized
|
||||
return name
|
||||
|
||||
|
||||
def find_command_by_name(section, cmd_name):
|
||||
for child in section:
|
||||
if isinstance(child.tag, str) and localname(child) == "Command":
|
||||
@@ -113,7 +182,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Edit 1C CommandInterface.xml", allow_abbrev=False)
|
||||
parser.add_argument("-CIPath", required=True)
|
||||
parser.add_argument("-CIPath", "-Path", required=True)
|
||||
parser.add_argument("-DefinitionFile", default=None)
|
||||
parser.add_argument("-Operation", default=None, choices=["hide", "show", "place", "order", "subsystem-order", "group-order"])
|
||||
parser.add_argument("-Value", default=None)
|
||||
@@ -129,6 +198,10 @@ def main():
|
||||
print("Either -DefinitionFile or -Operation is required", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Detect format version ---
|
||||
ci_dir = os.path.dirname(os.path.abspath(args.CIPath))
|
||||
format_version = detect_format_version(ci_dir)
|
||||
|
||||
# --- Resolve path ---
|
||||
ci_path = args.CIPath
|
||||
if not os.path.isabs(ci_path):
|
||||
@@ -147,7 +220,7 @@ def main():
|
||||
f'\txmlns:xr="{XR_NS}"\n'
|
||||
f'\txmlns:xs="{XS_NS}"\n'
|
||||
f'\txmlns:xsi="{XSI_NS}"\n'
|
||||
f'\tversion="2.17">\n'
|
||||
f'\tversion="{format_version}">\n'
|
||||
f'</CommandInterface>'
|
||||
)
|
||||
with open(ci_path, "w", encoding="utf-8-sig") as fh:
|
||||
@@ -205,6 +278,7 @@ def main():
|
||||
|
||||
def do_hide(commands):
|
||||
nonlocal add_count, modify_count
|
||||
commands = [normalize_cmd_name(c) for c in commands]
|
||||
section = ensure_section("CommandsVisibility")
|
||||
section_indent = get_child_indent(section)
|
||||
|
||||
@@ -236,6 +310,7 @@ def main():
|
||||
|
||||
def do_show(commands):
|
||||
nonlocal add_count, modify_count
|
||||
commands = [normalize_cmd_name(c) for c in commands]
|
||||
section = None
|
||||
for child in root:
|
||||
if isinstance(child.tag, str) and localname(child) == "CommandsVisibility":
|
||||
@@ -274,8 +349,8 @@ def main():
|
||||
|
||||
def do_place(json_val):
|
||||
nonlocal add_count, modify_count
|
||||
defn = json.loads(json_val)
|
||||
cmd_name = str(defn["command"])
|
||||
defn = json_val if isinstance(json_val, dict) else json.loads(json_val)
|
||||
cmd_name = normalize_cmd_name(str(defn["command"]))
|
||||
group_name = str(defn["group"])
|
||||
if not cmd_name or not group_name:
|
||||
print("place requires {command, group}", file=sys.stderr)
|
||||
@@ -302,9 +377,9 @@ def main():
|
||||
|
||||
def do_order(json_val):
|
||||
nonlocal add_count, remove_count
|
||||
defn = json.loads(json_val)
|
||||
defn = json_val if isinstance(json_val, dict) else json.loads(json_val)
|
||||
group_name = str(defn["group"])
|
||||
commands = [str(c) for c in defn["commands"]]
|
||||
commands = [normalize_cmd_name(str(c)) for c in defn["commands"]]
|
||||
if not group_name or not commands:
|
||||
print("order requires {group, commands:[...]}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -336,7 +411,7 @@ def main():
|
||||
|
||||
def do_subsystem_order(json_val):
|
||||
nonlocal add_count, remove_count
|
||||
parsed = json.loads(json_val)
|
||||
parsed = json_val if isinstance(json_val, list) else json.loads(json_val)
|
||||
subsystems = [str(s) for s in parsed]
|
||||
if not subsystems:
|
||||
print("subsystem-order requires array of subsystem paths", file=sys.stderr)
|
||||
@@ -361,7 +436,7 @@ def main():
|
||||
|
||||
def do_group_order(json_val):
|
||||
nonlocal add_count, remove_count
|
||||
parsed = json.loads(json_val)
|
||||
parsed = json_val if isinstance(json_val, list) else json.loads(json_val)
|
||||
groups = [str(g) for g in parsed]
|
||||
if not groups:
|
||||
print("group-order requires array of group names", file=sys.stderr)
|
||||
@@ -429,7 +504,7 @@ def main():
|
||||
if os.path.isfile(validate_script):
|
||||
print()
|
||||
print("--- Running interface-validate ---")
|
||||
subprocess.run([sys.executable, validate_script, "-CIPath", resolved_path])
|
||||
subprocess.run([sys.executable, validate_script, "-CIPath", "-Path", resolved_path])
|
||||
|
||||
# --- Summary ---
|
||||
print()
|
||||
|
||||
@@ -17,33 +17,13 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|-----------|:-----:|---------|-----------------------------------------|
|
||||
| CIPath | да | — | Путь к CommandInterface.xml |
|
||||
| Detailed | нет | — | Показывать [OK] для каждой проверки |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File ".claude/skills/interface-validate/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи"
|
||||
powershell.exe -NoProfile -File ".claude/skills/interface-validate/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи/Ext/CommandInterface.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи/Ext/CommandInterface.xml"
|
||||
```
|
||||
|
||||
## Проверки (13)
|
||||
|
||||
| # | Проверка | Серьёзность |
|
||||
|----|--------------------------------------------------------------|-------------|
|
||||
| 1 | XML well-formedness + root element (CommandInterface, version, namespace) | ERROR |
|
||||
| 2 | Допустимые дочерние элементы (только 5 секций) | ERROR |
|
||||
| 3 | Порядок секций корректен | ERROR |
|
||||
| 4 | Нет дублирующихся секций | ERROR |
|
||||
| 5 | CommandsVisibility — Command.name + Visibility/xr:Common | ERROR |
|
||||
| 6 | CommandsVisibility — нет дубликатов по name | WARN |
|
||||
| 7 | CommandsPlacement — Command.name + CommandGroup + Placement | ERROR |
|
||||
| 8 | CommandsOrder — Command.name + CommandGroup | ERROR |
|
||||
| 9 | SubsystemsOrder — Subsystem непустой, формат Subsystem.X | ERROR |
|
||||
| 10 | SubsystemsOrder — нет дубликатов | WARN |
|
||||
| 11 | GroupsOrder — Group непустой | ERROR |
|
||||
| 12 | GroupsOrder — нет дубликатов | WARN |
|
||||
| 13 | Формат ссылок на команды | WARN |
|
||||
|
||||
Exit code: 0 = OK, 1 = есть ошибки. По умолчанию краткий вывод. `-Detailed` для поштучной детализации.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# interface-validate v1.1 — Validate 1C CommandInterface.xml structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$CIPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||
[switch]$Detailed,
|
||||
[int]$MaxErrors = 30,
|
||||
[string]$OutFile
|
||||
|
||||
@@ -79,7 +79,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C CommandInterface.xml structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-CIPath', dest='CIPath', required=True)
|
||||
parser.add_argument('-CIPath', '-Path', dest='CIPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: meta-compile
|
||||
description: Создать объект метаданных 1С. Используй когда пользователь просит создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
|
||||
description: Создать объект метаданных 1С. Используй когда нужно создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
|
||||
argument-hint: <JsonPath> <OutputDir>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -23,7 +23,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/meta-compile/scripts/meta-compile.ps1 -JsonPath "<json>" -OutputDir "<ConfigDir>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-compile.ps1" -JsonPath "<json>" -OutputDir "<ConfigDir>"
|
||||
```
|
||||
|
||||
| Параметр | Описание |
|
||||
@@ -117,9 +117,3 @@ powershell.exe -NoProfile -File .claude/skills/meta-compile/scripts/meta-compile
|
||||
]
|
||||
```
|
||||
|
||||
## Что генерируется
|
||||
|
||||
- `{TypePlural}/{Name}.xml` — метаданные объекта
|
||||
- `{TypePlural}/{Name}/Ext/*.bsl` — модули (ObjectModule, RecordSetModule, Module — зависит от типа)
|
||||
- `Configuration.xml` — автоматическая регистрация в `<ChildObjects>`
|
||||
|
||||
|
||||
@@ -6,13 +6,20 @@
|
||||
|-----------|----------|-------------|
|
||||
| `hierarchical` | `false` | Hierarchical |
|
||||
| `hierarchyType` | `HierarchyFoldersAndItems` | HierarchyType |
|
||||
| `limitLevelCount` | `false` | LimitLevelCount |
|
||||
| `levelCount` | `2` | LevelCount |
|
||||
| `foldersOnTop` | `true` | FoldersOnTop |
|
||||
| `codeLength` | `9` | CodeLength |
|
||||
| `codeType` | `String` | CodeType |
|
||||
| `codeAllowedLength` | `Variable` | CodeAllowedLength |
|
||||
| `codeSeries` | `WholeCatalog` | CodeSeries |
|
||||
| `descriptionLength` | `25` | DescriptionLength |
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `checkUnique` | `false` | CheckUnique |
|
||||
| `defaultPresentation` | `AsDescription` | DefaultPresentation |
|
||||
| `subordinationUse` | `ToItems` | SubordinationUse |
|
||||
| `quickChoice` | `false` | QuickChoice |
|
||||
| `choiceMode` | `BothWays` | ChoiceMode |
|
||||
| `owners` | `[]` | Owners |
|
||||
| `attributes` | `[]` | → Attribute в ChildObjects |
|
||||
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
|
||||
|
||||
@@ -156,15 +156,15 @@
|
||||
| `descriptionLength` | `25` | DescriptionLength |
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `checkUnique` | `false` | CheckUnique |
|
||||
| `dependenceOnCalculationTypes` | `NotUsed` | DependenceOnCalculationTypes |
|
||||
| `dependenceOnCalculationTypes` | `DontUse` | DependenceOnCalculationTypes |
|
||||
| `actionPeriodUse` | `false` | ActionPeriodUse |
|
||||
| `attributes` | `[]` | → Attribute |
|
||||
| `tabularSections` | `{}` | → TabularSection |
|
||||
|
||||
`dependenceOnCalculationTypes`: `NotUsed`, `ExclusionAndDependence`, `ExclusionOnly`.
|
||||
`dependenceOnCalculationTypes`: `DontUse`, `OnActionPeriod`.
|
||||
|
||||
```json
|
||||
{ "type": "ChartOfCalculationTypes", "name": "Начисления", "dependenceOnCalculationTypes": "ExclusionAndDependence" }
|
||||
{ "type": "ChartOfCalculationTypes", "name": "Начисления", "dependenceOnCalculationTypes": "OnActionPeriod" }
|
||||
```
|
||||
|
||||
## Зависимости
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.3 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.11 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -79,6 +79,96 @@ $script:objectTypeSynonyms = @{
|
||||
"ОпределяемыйТип" = "DefinedType"
|
||||
}
|
||||
|
||||
# Enum property value synonyms — model often gets these slightly wrong
|
||||
$script:enumValueAliases = @{
|
||||
# RegisterType (AccumulationRegister)
|
||||
"Balances" = "Balance"; "Остатки" = "Balance"; "Обороты" = "Turnovers"
|
||||
# WriteMode (InformationRegister)
|
||||
"RecordSubordinate" = "RecorderSubordinate"; "Subordinate" = "RecorderSubordinate"
|
||||
"ПодчинениеРегистратору" = "RecorderSubordinate"; "Независимый" = "Independent"
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"; "NotUsed" = "DontUse"
|
||||
"Depend" = "OnActionPeriod"; "ПоПериодуДействия" = "OnActionPeriod"
|
||||
# InformationRegisterPeriodicity
|
||||
"None" = "Nonperiodical"; "Daily" = "Day"; "Monthly" = "Month"
|
||||
"Quarterly" = "Quarter"; "Yearly" = "Year"
|
||||
"Непериодический" = "Nonperiodical"; "Секунда" = "Second"; "День" = "Day"
|
||||
"Месяц" = "Month"; "Квартал" = "Quarter"; "Год" = "Year"
|
||||
"ПозицияРегистратора" = "RecorderPosition"
|
||||
# DataLockControlMode
|
||||
"Автоматический" = "Automatic"; "Управляемый" = "Managed"
|
||||
# FullTextSearch
|
||||
"Использовать" = "Use"; "НеИспользовать" = "DontUse"
|
||||
# Posting
|
||||
"Разрешить" = "Allow"; "Запретить" = "Deny"
|
||||
# EditType
|
||||
"ВДиалоге" = "InDialog"; "ВСписке" = "InList"; "ОбаСпособа" = "BothWays"
|
||||
# DefaultPresentation
|
||||
"ВВидеНаименования" = "AsDescription"; "ВВидеКода" = "AsCode"
|
||||
# FillChecking
|
||||
"НеПроверять" = "DontCheck"; "Ошибка" = "ShowError"; "Предупреждение" = "ShowWarning"
|
||||
# Indexing
|
||||
"НеИндексировать" = "DontIndex"; "Индексировать" = "Index"
|
||||
"ИндексироватьСДопУпорядочиванием" = "IndexWithAdditionalOrder"
|
||||
}
|
||||
|
||||
# Valid enum values per property (from meta-validate)
|
||||
$script:validEnumValues = @{
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","OnActionPeriod")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
"DefaultPresentation" = @("AsDescription","AsCode")
|
||||
"Posting" = @("Allow","Deny")
|
||||
"RealTimePosting" = @("Allow","Deny")
|
||||
"EditType" = @("InDialog","InList","BothWays")
|
||||
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
|
||||
"CodeType" = @("String","Number")
|
||||
"CodeAllowedLength" = @("Variable","Fixed")
|
||||
"NumberType" = @("String","Number")
|
||||
"NumberAllowedLength" = @("Variable","Fixed")
|
||||
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
||||
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||
"ReuseSessions" = @("DontUse","AutoUse")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"SubordinationUse" = @("ToItems","ToFolders","ToFoldersAndItems")
|
||||
"CodeSeries" = @("WholeCatalog","WithinSubordination")
|
||||
"ChoiceMode" = @("BothWays","QuickChoice","FromForm")
|
||||
}
|
||||
|
||||
function Normalize-EnumValue {
|
||||
param([string]$propName, [string]$value)
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if ($script:enumValueAliases.ContainsKey($value)) {
|
||||
return $script:enumValueAliases[$value]
|
||||
}
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
$valid = $script:validEnumValues[$propName]
|
||||
if ($valid) {
|
||||
foreach ($v in $valid) {
|
||||
if ($v -ieq $value) { return $v }
|
||||
}
|
||||
# 3. Known property, unknown value — error with hint
|
||||
Write-Error "Invalid value '$value' for property '$propName'. Valid values: $($valid -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return $value
|
||||
}
|
||||
|
||||
# Helper: read enum property from $def with default and normalization
|
||||
function Get-EnumProp {
|
||||
param([string]$propName, [string]$fieldName, [string]$default)
|
||||
$val = $def.$fieldName
|
||||
$raw = if ($val) { "$val" } else { $default }
|
||||
return (Normalize-EnumValue $propName $raw)
|
||||
}
|
||||
|
||||
if (-not $def.type) {
|
||||
Write-Error "JSON must have 'type' field"
|
||||
exit 1
|
||||
@@ -411,6 +501,7 @@ function Parse-AttributeShorthand {
|
||||
flags = @(if ($val.flags) { $val.flags } else { @() })
|
||||
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
|
||||
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
|
||||
multiLine = if ($val.multiLine -eq $true) { $true } else { $false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,7 +764,8 @@ function Emit-Attribute {
|
||||
param([string]$indent, $parsed, [string]$context)
|
||||
# $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register"
|
||||
$attrName = $parsed.name
|
||||
if ($script:reservedAttrNames.ContainsKey($attrName) -or $script:reservedAttrNames.ContainsValue($attrName)) {
|
||||
if ($context -notin @("tabular", "processor-tabular") -and
|
||||
($script:reservedAttrNames.ContainsKey($attrName) -or $script:reservedAttrNames.ContainsValue($attrName))) {
|
||||
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C."
|
||||
}
|
||||
$uuid = New-Guid-String
|
||||
@@ -700,18 +792,20 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
|
||||
# FillFromFillingValue — not for tabular/processor (non-stored objects don't have these)
|
||||
if ($context -notin @("tabular", "processor")) {
|
||||
# FillFromFillingValue — not for tabular/processor/chart/register-other
|
||||
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
|
||||
if ($context -notin @("tabular", "processor", "chart", "register-other")) {
|
||||
X "$indent`t`t<FillFromFillingValue>false</FillFromFillingValue>"
|
||||
}
|
||||
|
||||
# FillValue — not for tabular/processor
|
||||
if ($context -notin @("tabular", "processor")) {
|
||||
# FillValue — same restriction
|
||||
if ($context -notin @("tabular", "processor", "chart", "register-other")) {
|
||||
Emit-FillValue "$indent`t`t" $typeStr
|
||||
}
|
||||
|
||||
@@ -744,7 +838,10 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<Indexing>$indexing</Indexing>"
|
||||
|
||||
X "$indent`t`t<FullTextSearch>Use</FullTextSearch>"
|
||||
X "$indent`t`t<DataHistory>Use</DataHistory>"
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if ($context -notin @("chart", "register-other")) {
|
||||
X "$indent`t`t<DataHistory>Use</DataHistory>"
|
||||
}
|
||||
}
|
||||
|
||||
X "$indent`t</Properties>"
|
||||
@@ -840,7 +937,8 @@ function Emit-Dimension {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
@@ -933,7 +1031,8 @@ function Emit-Resource {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
@@ -984,20 +1083,33 @@ function Emit-CatalogProperties {
|
||||
X "$i<Comment/>"
|
||||
|
||||
$hierarchical = if ($def.hierarchical -eq $true) { "true" } else { "false" }
|
||||
$hierarchyType = if ($def.hierarchyType) { "$($def.hierarchyType)" } else { "HierarchyFoldersAndItems" }
|
||||
$hierarchyType = Get-EnumProp "HierarchyType" "hierarchyType" "HierarchyFoldersAndItems"
|
||||
X "$i<Hierarchical>$hierarchical</Hierarchical>"
|
||||
X "$i<HierarchyType>$hierarchyType</HierarchyType>"
|
||||
X "$i<LimitLevelCount>false</LimitLevelCount>"
|
||||
X "$i<LevelCount>2</LevelCount>"
|
||||
X "$i<FoldersOnTop>true</FoldersOnTop>"
|
||||
$limitLevelCount = if ($def.limitLevelCount -eq $true) { "true" } else { "false" }
|
||||
$levelCount = if ($null -ne $def.levelCount) { "$($def.levelCount)" } else { "2" }
|
||||
$foldersOnTop = if ($def.foldersOnTop -eq $false) { "false" } else { "true" }
|
||||
X "$i<LimitLevelCount>$limitLevelCount</LimitLevelCount>"
|
||||
X "$i<LevelCount>$levelCount</LevelCount>"
|
||||
X "$i<FoldersOnTop>$foldersOnTop</FoldersOnTop>"
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
X "$i<Owners/>"
|
||||
X "$i<SubordinationUse>ToItems</SubordinationUse>"
|
||||
if ($def.owners -and $def.owners.Count -gt 0) {
|
||||
X "$i<Owners>"
|
||||
foreach ($ownerRef in $def.owners) {
|
||||
$fullRef = if ("$ownerRef" -match '\.') { "$ownerRef" } else { "Catalog.$ownerRef" }
|
||||
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$fullRef</xr:Item>"
|
||||
}
|
||||
X "$i</Owners>"
|
||||
} else {
|
||||
X "$i<Owners/>"
|
||||
}
|
||||
$subordinationUse = Get-EnumProp "SubordinationUse" "subordinationUse" "ToItems"
|
||||
X "$i<SubordinationUse>$subordinationUse</SubordinationUse>"
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
|
||||
$codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" }
|
||||
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
|
||||
$codeType = Get-EnumProp "CodeType" "codeType" "String"
|
||||
$codeAllowedLength = Get-EnumProp "CodeAllowedLength" "codeAllowedLength" "Variable"
|
||||
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
|
||||
$checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" }
|
||||
|
||||
@@ -1005,19 +1117,22 @@ function Emit-CatalogProperties {
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
X "$i<CodeType>$codeType</CodeType>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
X "$i<CodeSeries>WholeCatalog</CodeSeries>"
|
||||
$codeSeries = Get-EnumProp "CodeSeries" "codeSeries" "WholeCatalog"
|
||||
X "$i<CodeSeries>$codeSeries</CodeSeries>"
|
||||
X "$i<CheckUnique>$checkUnique</CheckUnique>"
|
||||
X "$i<Autonumbering>$autonumbering</Autonumbering>"
|
||||
|
||||
$defaultPresentation = if ($def.defaultPresentation) { "$($def.defaultPresentation)" } else { "AsDescription" }
|
||||
$defaultPresentation = Get-EnumProp "DefaultPresentation" "defaultPresentation" "AsDescription"
|
||||
X "$i<DefaultPresentation>$defaultPresentation</DefaultPresentation>"
|
||||
|
||||
Emit-StandardAttributes $i "Catalog"
|
||||
X "$i<Characteristics/>"
|
||||
X "$i<PredefinedDataUpdate>Auto</PredefinedDataUpdate>"
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
|
||||
$choiceMode = Get-EnumProp "ChoiceMode" "choiceMode" "BothWays"
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>$choiceMode</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>Catalog.$objName.StandardAttribute.Description</xr:Field>"
|
||||
X "$i`t<xr:Field>Catalog.$objName.StandardAttribute.Code</xr:Field>"
|
||||
@@ -1039,10 +1154,10 @@ function Emit-CatalogProperties {
|
||||
X "$i<BasedOn/>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1067,9 +1182,9 @@ function Emit-DocumentProperties {
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
X "$i<Numerator/>"
|
||||
|
||||
$numberType = if ($def.numberType) { "$($def.numberType)" } else { "String" }
|
||||
$numberType = Get-EnumProp "NumberType" "numberType" "String"
|
||||
$numberLength = if ($null -ne $def.numberLength) { "$($def.numberLength)" } else { "11" }
|
||||
$numberAllowedLength = if ($def.numberAllowedLength) { "$($def.numberAllowedLength)" } else { "Variable" }
|
||||
$numberAllowedLength = Get-EnumProp "NumberAllowedLength" "numberAllowedLength" "Variable"
|
||||
$numberPeriodicity = if ($def.numberPeriodicity) { "$($def.numberPeriodicity)" } else { "Year" }
|
||||
$checkUnique = if ($def.checkUnique -eq $false) { "false" } else { "true" }
|
||||
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
|
||||
@@ -1099,10 +1214,10 @@ function Emit-DocumentProperties {
|
||||
X "$i<AuxiliaryListForm/>"
|
||||
X "$i<AuxiliaryChoiceForm/>"
|
||||
|
||||
$posting = if ($def.posting) { "$($def.posting)" } else { "Allow" }
|
||||
$realTimePosting = if ($def.realTimePosting) { "$($def.realTimePosting)" } else { "Deny" }
|
||||
$registerRecordsDeletion = if ($def.registerRecordsDeletion) { "$($def.registerRecordsDeletion)" } else { "AutoDelete" }
|
||||
$registerRecordsWritingOnPost = if ($def.registerRecordsWritingOnPost) { "$($def.registerRecordsWritingOnPost)" } else { "WriteModified" }
|
||||
$posting = Get-EnumProp "Posting" "posting" "Allow"
|
||||
$realTimePosting = Get-EnumProp "RealTimePosting" "realTimePosting" "Deny"
|
||||
$registerRecordsDeletion = Get-EnumProp "RegisterRecordsDeletion" "registerRecordsDeletion" "AutoDelete"
|
||||
$registerRecordsWritingOnPost = Get-EnumProp "RegisterRecordsWritingOnPost" "registerRecordsWritingOnPost" "WriteModified"
|
||||
$sequenceFilling = if ($def.sequenceFilling) { "$($def.sequenceFilling)" } else { "AutoFill" }
|
||||
$postInPrivilegedMode = if ($def.postInPrivilegedMode -eq $false) { "false" } else { "true" }
|
||||
$unpostInPrivilegedMode = if ($def.unpostInPrivilegedMode -eq $false) { "false" } else { "true" }
|
||||
@@ -1148,10 +1263,10 @@ function Emit-DocumentProperties {
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1177,7 +1292,8 @@ function Emit-EnumProperties {
|
||||
Emit-StandardAttributes $i "Enum"
|
||||
X "$i<Characteristics/>"
|
||||
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
$quickChoice = if ($def.quickChoice -eq $false) { "false" } else { "true" }
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
X "$i<DefaultListForm/>"
|
||||
X "$i<DefaultChoiceForm/>"
|
||||
@@ -1225,7 +1341,7 @@ function Emit-ConstantProperties {
|
||||
X "$i<LinkByType/>"
|
||||
X "$i<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
X "$i<DataHistory>DontUse</DataHistory>"
|
||||
X "$i<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>"
|
||||
@@ -1248,8 +1364,8 @@ function Emit-InformationRegisterProperties {
|
||||
|
||||
Emit-StandardAttributes $i "InformationRegister"
|
||||
|
||||
$periodicity = if ($def.periodicity) { "$($def.periodicity)" } else { "Nonperiodical" }
|
||||
$writeMode = if ($def.writeMode) { "$($def.writeMode)" } else { "Independent" }
|
||||
$periodicity = Get-EnumProp "InformationRegisterPeriodicity" "periodicity" "Nonperiodical"
|
||||
$writeMode = Get-EnumProp "WriteMode" "writeMode" "Independent"
|
||||
|
||||
# MainFilterOnPeriod: auto based on periodicity unless explicitly set
|
||||
$mainFilterOnPeriod = "false"
|
||||
@@ -1264,10 +1380,10 @@ function Emit-InformationRegisterProperties {
|
||||
X "$i<MainFilterOnPeriod>$mainFilterOnPeriod</MainFilterOnPeriod>"
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<EnableTotalsSliceFirst>false</EnableTotalsSliceFirst>"
|
||||
@@ -1293,17 +1409,17 @@ function Emit-AccumulationRegisterProperties {
|
||||
X "$i<DefaultListForm/>"
|
||||
X "$i<AuxiliaryListForm/>"
|
||||
|
||||
$registerType = if ($def.registerType) { "$($def.registerType)" } else { "Balance" }
|
||||
$registerType = Get-EnumProp "RegisterType" "registerType" "Balance"
|
||||
X "$i<RegisterType>$registerType</RegisterType>"
|
||||
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
|
||||
Emit-StandardAttributes $i "AccumulationRegister"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
$enableTotalsSplitting = if ($def.enableTotalsSplitting -eq $false) { "false" } else { "true" }
|
||||
@@ -1393,7 +1509,7 @@ function Emit-CommonModuleProperties {
|
||||
X "$i<ServerCall>$serverCall</ServerCall>"
|
||||
X "$i<Privileged>$privileged</Privileged>"
|
||||
|
||||
$returnValuesReuse = if ($def.returnValuesReuse) { "$($def.returnValuesReuse)" } else { "DontUse" }
|
||||
$returnValuesReuse = Get-EnumProp "ReturnValuesReuse" "returnValuesReuse" "DontUse"
|
||||
X "$i<ReturnValuesReuse>$returnValuesReuse</ReturnValuesReuse>"
|
||||
}
|
||||
|
||||
@@ -1532,7 +1648,7 @@ function Emit-ExchangePlanProperties {
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "100" }
|
||||
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
|
||||
$codeAllowedLength = Get-EnumProp "CodeAllowedLength" "codeAllowedLength" "Variable"
|
||||
|
||||
X "$i<CodeLength>$codeLength</CodeLength>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
@@ -1548,7 +1664,8 @@ function Emit-ExchangePlanProperties {
|
||||
X "$i<IncludeConfigurationExtensions>$includeExt</IncludeConfigurationExtensions>"
|
||||
|
||||
X "$i<BasedOn/>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>ExchangePlan.$objName.StandardAttribute.Description</xr:Field>"
|
||||
@@ -1566,10 +1683,10 @@ function Emit-ExchangePlanProperties {
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1595,7 +1712,7 @@ function Emit-ChartOfCharacteristicTypesProperties {
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
|
||||
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
|
||||
$codeAllowedLength = Get-EnumProp "CodeAllowedLength" "codeAllowedLength" "Variable"
|
||||
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
|
||||
$checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" }
|
||||
|
||||
@@ -1649,7 +1766,8 @@ function Emit-ChartOfCharacteristicTypesProperties {
|
||||
X "$i<Characteristics/>"
|
||||
X "$i<PredefinedDataUpdate>Auto</PredefinedDataUpdate>"
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>ChartOfCharacteristicTypes.$objName.StandardAttribute.Description</xr:Field>"
|
||||
@@ -1672,10 +1790,10 @@ function Emit-ChartOfCharacteristicTypesProperties {
|
||||
X "$i<BasedOn/>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1790,7 +1908,8 @@ function Emit-ChartOfAccountsProperties {
|
||||
|
||||
X "$i<Characteristics/>"
|
||||
X "$i<PredefinedDataUpdate>Auto</PredefinedDataUpdate>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>ChartOfAccounts.$objName.StandardAttribute.Description</xr:Field>"
|
||||
@@ -1809,10 +1928,10 @@ function Emit-ChartOfAccountsProperties {
|
||||
X "$i<BasedOn/>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1852,10 +1971,10 @@ function Emit-AccountingRegisterProperties {
|
||||
|
||||
Emit-StandardAttributes $i "AccountingRegister"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ListPresentation/>"
|
||||
@@ -1874,8 +1993,8 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
|
||||
$codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" }
|
||||
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
|
||||
$codeType = Get-EnumProp "CodeType" "codeType" "String"
|
||||
$codeAllowedLength = Get-EnumProp "CodeAllowedLength" "codeAllowedLength" "Variable"
|
||||
|
||||
X "$i<CodeLength>$codeLength</CodeLength>"
|
||||
X "$i<CodeType>$codeType</CodeType>"
|
||||
@@ -1883,7 +2002,7 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
|
||||
|
||||
$dependence = if ($def.dependenceOnCalculationTypes) { "$($def.dependenceOnCalculationTypes)" } else { "DontUse" }
|
||||
$dependence = Get-EnumProp "DependenceOnCalculationTypes" "dependenceOnCalculationTypes" "DontUse"
|
||||
X "$i<DependenceOnCalculationTypes>$dependence</DependenceOnCalculationTypes>"
|
||||
|
||||
# BaseCalculationTypes
|
||||
@@ -1906,7 +2025,8 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
X "$i<Characteristics/>"
|
||||
X "$i<PredefinedDataUpdate>Auto</PredefinedDataUpdate>"
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>ChartOfCalculationTypes.$objName.StandardAttribute.Description</xr:Field>"
|
||||
@@ -1925,10 +2045,10 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
X "$i<BasedOn/>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -1955,7 +2075,7 @@ function Emit-CalculationRegisterProperties {
|
||||
if ($chartOfCalcTypes) { X "$i<ChartOfCalculationTypes>$chartOfCalcTypes</ChartOfCalculationTypes>" }
|
||||
else { X "$i<ChartOfCalculationTypes/>" }
|
||||
|
||||
$periodicity = if ($def.periodicity) { "$($def.periodicity)" } else { "Month" }
|
||||
$periodicity = Get-EnumProp "InformationRegisterPeriodicity" "periodicity" "Month"
|
||||
X "$i<Periodicity>$periodicity</Periodicity>"
|
||||
|
||||
$actionPeriod = if ($def.actionPeriod -eq $true) { "true" } else { "false" }
|
||||
@@ -1977,10 +2097,10 @@ function Emit-CalculationRegisterProperties {
|
||||
|
||||
Emit-StandardAttributes $i "CalculationRegister"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ListPresentation/>"
|
||||
@@ -1999,12 +2119,12 @@ function Emit-BusinessProcessProperties {
|
||||
X "$i<Comment/>"
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
|
||||
$editType = if ($def.editType) { "$($def.editType)" } else { "InDialog" }
|
||||
$editType = Get-EnumProp "EditType" "editType" "InDialog"
|
||||
X "$i<EditType>$editType</EditType>"
|
||||
|
||||
$numberType = if ($def.numberType) { "$($def.numberType)" } else { "String" }
|
||||
$numberType = Get-EnumProp "NumberType" "numberType" "String"
|
||||
$numberLength = if ($null -ne $def.numberLength) { "$($def.numberLength)" } else { "11" }
|
||||
$numberAllowedLength = if ($def.numberAllowedLength) { "$($def.numberAllowedLength)" } else { "Variable" }
|
||||
$numberAllowedLength = Get-EnumProp "NumberAllowedLength" "numberAllowedLength" "Variable"
|
||||
$checkUnique = if ($def.checkUnique -eq $false) { "false" } else { "true" }
|
||||
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
|
||||
|
||||
@@ -2041,10 +2161,10 @@ function Emit-BusinessProcessProperties {
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -2067,9 +2187,9 @@ function Emit-TaskProperties {
|
||||
X "$i<Comment/>"
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
|
||||
$numberType = if ($def.numberType) { "$($def.numberType)" } else { "String" }
|
||||
$numberType = Get-EnumProp "NumberType" "numberType" "String"
|
||||
$numberLength = if ($null -ne $def.numberLength) { "$($def.numberLength)" } else { "14" }
|
||||
$numberAllowedLength = if ($def.numberAllowedLength) { "$($def.numberAllowedLength)" } else { "Variable" }
|
||||
$numberAllowedLength = Get-EnumProp "NumberAllowedLength" "numberAllowedLength" "Variable"
|
||||
$checkUnique = if ($def.checkUnique -eq $false) { "false" } else { "true" }
|
||||
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
|
||||
|
||||
@@ -2114,10 +2234,10 @@ function Emit-TaskProperties {
|
||||
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
|
||||
X "$i<DataLockFields/>"
|
||||
|
||||
$dataLockControlMode = if ($def.dataLockControlMode) { "$($def.dataLockControlMode)" } else { "Automatic" }
|
||||
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
|
||||
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
|
||||
|
||||
$fullTextSearch = if ($def.fullTextSearch) { "$($def.fullTextSearch)" } else { "Use" }
|
||||
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
|
||||
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
|
||||
|
||||
X "$i<ObjectPresentation/>"
|
||||
@@ -2144,7 +2264,7 @@ function Emit-HTTPServiceProperties {
|
||||
$rootURL = if ($def.rootURL) { "$($def.rootURL)" } else { $objName.ToLower() }
|
||||
X "$i<RootURL>$(Esc-Xml $rootURL)</RootURL>"
|
||||
|
||||
$reuseSessions = if ($def.reuseSessions) { "$($def.reuseSessions)" } else { "DontUse" }
|
||||
$reuseSessions = Get-EnumProp "ReuseSessions" "reuseSessions" "DontUse"
|
||||
X "$i<ReuseSessions>$reuseSessions</ReuseSessions>"
|
||||
|
||||
$sessionMaxAge = if ($null -ne $def.sessionMaxAge) { "$($def.sessionMaxAge)" } else { "20" }
|
||||
@@ -2165,7 +2285,7 @@ function Emit-WebServiceProperties {
|
||||
$xdtoPackages = if ($def.xdtoPackages) { "$($def.xdtoPackages)" } else { "" }
|
||||
if ($xdtoPackages) { X "$i<XDTOPackages>$xdtoPackages</XDTOPackages>" } else { X "$i<XDTOPackages/>" }
|
||||
|
||||
$reuseSessions = if ($def.reuseSessions) { "$($def.reuseSessions)" } else { "DontUse" }
|
||||
$reuseSessions = Get-EnumProp "ReuseSessions" "reuseSessions" "DontUse"
|
||||
X "$i<ReuseSessions>$reuseSessions</ReuseSessions>"
|
||||
|
||||
$sessionMaxAge = if ($null -ne $def.sessionMaxAge) { "$($def.sessionMaxAge)" } else { "20" }
|
||||
@@ -2453,13 +2573,32 @@ function Emit-AddressingAttribute {
|
||||
|
||||
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
|
||||
# --- 14a. Detect format version from existing Configuration.xml ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $OutputDir
|
||||
|
||||
# --- 15. Main assembler ---
|
||||
|
||||
$uuid = New-Guid-String
|
||||
|
||||
# XML declaration
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X "<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">"
|
||||
X "<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">"
|
||||
X "`t<$objType uuid=`"$uuid`">"
|
||||
|
||||
# InternalInfo
|
||||
@@ -2549,6 +2688,7 @@ if ($objType -in $typesWithAttrTS) {
|
||||
"Catalog" { "catalog" }
|
||||
"Document" { "document" }
|
||||
{ $_ -in @("DataProcessor","Report") } { "processor" }
|
||||
{ $_ -in @("ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes") } { "chart" }
|
||||
default { "object" }
|
||||
}
|
||||
foreach ($a in $attrs) {
|
||||
@@ -2559,10 +2699,12 @@ if ($objType -in $typesWithAttrTS) {
|
||||
Emit-TabularSection "`t`t`t" $tsName $columns $objType $objName
|
||||
}
|
||||
foreach ($af in $acctFlags) {
|
||||
Emit-AccountingFlag "`t`t`t" "$af"
|
||||
$afName = if ($af.name) { $af.name } else { "$af" }
|
||||
Emit-AccountingFlag "`t`t`t" $afName
|
||||
}
|
||||
foreach ($edf in $extDimFlags) {
|
||||
Emit-ExtDimensionAccountingFlag "`t`t`t" "$edf"
|
||||
$edfName = if ($edf.name) { $edf.name } else { "$edf" }
|
||||
Emit-ExtDimensionAccountingFlag "`t`t`t" $edfName
|
||||
}
|
||||
foreach ($aa in $addrAttrs) {
|
||||
Emit-AddressingAttribute "`t`t`t" $aa
|
||||
@@ -2625,8 +2767,11 @@ if ($objType -in @("InformationRegister","AccumulationRegister","AccountingRegis
|
||||
foreach ($d in $dims) {
|
||||
Emit-Dimension "`t`t`t" $d $objType
|
||||
}
|
||||
# InformationRegister.Attribute supports FillFromFillingValue/FillValue/DataHistory;
|
||||
# AccumulationRegister/AccountingRegister/CalculationRegister.Attribute do NOT.
|
||||
$regCtx = if ($objType -eq "InformationRegister") { "register-info" } else { "register-other" }
|
||||
foreach ($a in $regAttrs) {
|
||||
Emit-Attribute "`t`t`t" $a "register"
|
||||
Emit-Attribute "`t`t`t" $a $regCtx
|
||||
}
|
||||
X "`t`t</ChildObjects>"
|
||||
} else {
|
||||
@@ -2743,8 +2888,8 @@ if (-not (Test-Path $typeDir)) {
|
||||
New-Item -ItemType Directory -Path $typeDir -Force | Out-Null
|
||||
}
|
||||
if ($objType -notin $typesNoSubDir) {
|
||||
if (-not (Test-Path $extDir)) {
|
||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||
if (-not (Test-Path $objSubDir)) {
|
||||
New-Item -ItemType Directory -Path $objSubDir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2754,6 +2899,13 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
||||
# Module files
|
||||
$modulesCreated = @()
|
||||
|
||||
# Helper: create Ext/ only when needed (avoids empty Ext/ for Constant, Enum, etc.)
|
||||
function Ensure-ExtDir {
|
||||
if (-not (Test-Path $extDir)) {
|
||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Types with ObjectModule.bsl
|
||||
$typesWithObjectModule = @("Catalog","Document","Report","DataProcessor","ExchangePlan",
|
||||
"ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes",
|
||||
@@ -2761,13 +2913,16 @@ $typesWithObjectModule = @("Catalog","Document","Report","DataProcessor","Exchan
|
||||
# Types with RecordSetModule.bsl
|
||||
$typesWithRecordSetModule = @("InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister")
|
||||
# Types with ManagerModule.bsl
|
||||
$typesWithManagerModule = @("Report","DataProcessor")
|
||||
$typesWithManagerModule = @("Report","DataProcessor","Constant","Enum")
|
||||
# Types with ValueManagerModule.bsl
|
||||
$typesWithValueManagerModule = @("Constant")
|
||||
# Types with Module.bsl (general)
|
||||
$typesWithModule = @("CommonModule","HTTPService","WebService")
|
||||
|
||||
if ($objType -in $typesWithObjectModule) {
|
||||
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Ensure-ExtDir
|
||||
[System.IO.File]::WriteAllText($modulePath, "", $enc)
|
||||
$modulesCreated += $modulePath
|
||||
}
|
||||
@@ -2775,6 +2930,15 @@ if ($objType -in $typesWithObjectModule) {
|
||||
if ($objType -in $typesWithManagerModule) {
|
||||
$modulePath = Join-Path $extDir "ManagerModule.bsl"
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Ensure-ExtDir
|
||||
[System.IO.File]::WriteAllText($modulePath, "", $enc)
|
||||
$modulesCreated += $modulePath
|
||||
}
|
||||
}
|
||||
if ($objType -in $typesWithValueManagerModule) {
|
||||
$modulePath = Join-Path $extDir "ValueManagerModule.bsl"
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Ensure-ExtDir
|
||||
[System.IO.File]::WriteAllText($modulePath, "", $enc)
|
||||
$modulesCreated += $modulePath
|
||||
}
|
||||
@@ -2782,6 +2946,7 @@ if ($objType -in $typesWithManagerModule) {
|
||||
if ($objType -in $typesWithRecordSetModule) {
|
||||
$modulePath = Join-Path $extDir "RecordSetModule.bsl"
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Ensure-ExtDir
|
||||
[System.IO.File]::WriteAllText($modulePath, "", $enc)
|
||||
$modulesCreated += $modulePath
|
||||
}
|
||||
@@ -2789,6 +2954,7 @@ if ($objType -in $typesWithRecordSetModule) {
|
||||
if ($objType -in $typesWithModule) {
|
||||
$modulePath = Join-Path $extDir "Module.bsl"
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Ensure-ExtDir
|
||||
[System.IO.File]::WriteAllText($modulePath, "", $enc)
|
||||
$modulesCreated += $modulePath
|
||||
}
|
||||
@@ -2798,7 +2964,8 @@ if ($objType -in $typesWithModule) {
|
||||
if ($objType -eq "ExchangePlan") {
|
||||
$contentPath = Join-Path $extDir "Content.xml"
|
||||
if (-not (Test-Path $contentPath)) {
|
||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/xcf/extrnprops`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" version=`"2.17`"/>`r`n"
|
||||
Ensure-ExtDir
|
||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/xcf/extrnprops`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" version=`"$($script:formatVersion)`"/>`r`n"
|
||||
[System.IO.File]::WriteAllText($contentPath, $contentXml, $enc)
|
||||
$modulesCreated += $contentPath
|
||||
}
|
||||
@@ -2806,7 +2973,8 @@ if ($objType -eq "ExchangePlan") {
|
||||
if ($objType -eq "BusinessProcess") {
|
||||
$flowchartPath = Join-Path $extDir "Flowchart.xml"
|
||||
if (-not (Test-Path $flowchartPath)) {
|
||||
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"2.17`"/>`r`n"
|
||||
Ensure-ExtDir
|
||||
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"$($script:formatVersion)`"/>`r`n"
|
||||
[System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc)
|
||||
$modulesCreated += $flowchartPath
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.3 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.11 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -139,6 +139,89 @@ object_type_synonyms = {
|
||||
'ОпределяемыйТип': 'DefinedType',
|
||||
}
|
||||
|
||||
# Enum property value synonyms — model often gets these slightly wrong
|
||||
enum_value_aliases = {
|
||||
# RegisterType (AccumulationRegister)
|
||||
'Balances': 'Balance', 'Остатки': 'Balance', 'Обороты': 'Turnovers',
|
||||
# WriteMode (InformationRegister)
|
||||
'RecordSubordinate': 'RecorderSubordinate', 'Subordinate': 'RecorderSubordinate',
|
||||
'ПодчинениеРегистратору': 'RecorderSubordinate', 'Независимый': 'Independent',
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse', 'NotUsed': 'DontUse',
|
||||
'Depend': 'OnActionPeriod', 'ПоПериодуДействия': 'OnActionPeriod',
|
||||
# InformationRegisterPeriodicity
|
||||
'None': 'Nonperiodical', 'Daily': 'Day', 'Monthly': 'Month',
|
||||
'Quarterly': 'Quarter', 'Yearly': 'Year',
|
||||
'Непериодический': 'Nonperiodical', 'Секунда': 'Second', 'День': 'Day',
|
||||
'Месяц': 'Month', 'Квартал': 'Quarter', 'Год': 'Year',
|
||||
'ПозицияРегистратора': 'RecorderPosition',
|
||||
# DataLockControlMode
|
||||
'Автоматический': 'Automatic', 'Управляемый': 'Managed',
|
||||
# FullTextSearch
|
||||
'Использовать': 'Use', 'НеИспользовать': 'DontUse',
|
||||
# Posting
|
||||
'Разрешить': 'Allow', 'Запретить': 'Deny',
|
||||
# EditType
|
||||
'ВДиалоге': 'InDialog', 'ВСписке': 'InList', 'ОбаСпособа': 'BothWays',
|
||||
# DefaultPresentation
|
||||
'ВВидеНаименования': 'AsDescription', 'ВВидеКода': 'AsCode',
|
||||
# FillChecking
|
||||
'НеПроверять': 'DontCheck', 'Ошибка': 'ShowError', 'Предупреждение': 'ShowWarning',
|
||||
# Indexing
|
||||
'НеИндексировать': 'DontIndex', 'Индексировать': 'Index',
|
||||
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
||||
}
|
||||
|
||||
# Valid enum values per property (from meta-validate)
|
||||
valid_enum_values = {
|
||||
'RegisterType': ['Balance', 'Turnovers'],
|
||||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||||
'InformationRegisterPeriodicity': ['Nonperiodical', 'Second', 'Day', 'Month', 'Quarter', 'Year', 'RecorderPosition'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'OnActionPeriod'],
|
||||
'DataLockControlMode': ['Automatic', 'Managed'],
|
||||
'FullTextSearch': ['Use', 'DontUse'],
|
||||
'DataHistory': ['Use', 'DontUse'],
|
||||
'DefaultPresentation': ['AsDescription', 'AsCode'],
|
||||
'Posting': ['Allow', 'Deny'],
|
||||
'RealTimePosting': ['Allow', 'Deny'],
|
||||
'EditType': ['InDialog', 'InList', 'BothWays'],
|
||||
'HierarchyType': ['HierarchyFoldersAndItems', 'HierarchyItemsOnly'],
|
||||
'CodeType': ['String', 'Number'],
|
||||
'CodeAllowedLength': ['Variable', 'Fixed'],
|
||||
'NumberType': ['String', 'Number'],
|
||||
'NumberAllowedLength': ['Variable', 'Fixed'],
|
||||
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
|
||||
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
|
||||
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
|
||||
'ReuseSessions': ['DontUse', 'AutoUse'],
|
||||
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
||||
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
||||
'SubordinationUse': ['ToItems', 'ToFolders', 'ToFoldersAndItems'],
|
||||
'CodeSeries': ['WholeCatalog', 'WithinSubordination'],
|
||||
'ChoiceMode': ['BothWays', 'QuickChoice', 'FromForm'],
|
||||
}
|
||||
|
||||
def normalize_enum_value(prop_name, value):
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if value in enum_value_aliases:
|
||||
return enum_value_aliases[value]
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
valid = valid_enum_values.get(prop_name)
|
||||
if valid:
|
||||
for v in valid:
|
||||
if v.lower() == value.lower():
|
||||
return v
|
||||
# 3. Known property, unknown value — error with hint
|
||||
print(f"Invalid value '{value}' for property '{prop_name}'. Valid values: {', '.join(valid)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return value
|
||||
|
||||
def get_enum_prop(prop_name, field_name, default):
|
||||
val = defn.get(field_name)
|
||||
raw = str(val) if val else default
|
||||
return normalize_enum_value(prop_name, raw)
|
||||
|
||||
if not defn.get('type'):
|
||||
print("JSON must have 'type' field", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -381,6 +464,7 @@ def parse_attribute_shorthand(val):
|
||||
'flags': list(val.get('flags', [])),
|
||||
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
|
||||
'indexing': str(val['indexing']) if val.get('indexing') else '',
|
||||
'multiLine': True if val.get('multiLine') is True else False,
|
||||
}
|
||||
|
||||
def parse_enum_value_shorthand(val):
|
||||
@@ -645,7 +729,7 @@ RESERVED_ATTR_NAMES_RU = {
|
||||
|
||||
def emit_attribute(indent, parsed, context):
|
||||
attr_name = parsed['name']
|
||||
if attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU:
|
||||
if context not in ('tabular', 'processor-tabular') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU):
|
||||
print(f"WARNING: Attribute '{attr_name}' conflicts with a standard attribute name. This may cause errors when loading into 1C.", file=sys.stderr)
|
||||
uid = new_uuid()
|
||||
X(f'{indent}<Attribute uuid="{uid}">')
|
||||
@@ -666,13 +750,16 @@ def emit_attribute(indent, parsed, context):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
if context not in ('tabular', 'processor'):
|
||||
# FillFromFillingValue / FillValue — not for tabular/processor/chart/register-other
|
||||
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
|
||||
if context not in ('tabular', 'processor', 'chart', 'register-other'):
|
||||
X(f'{indent}\t\t<FillFromFillingValue>false</FillFromFillingValue>')
|
||||
if context not in ('tabular', 'processor'):
|
||||
if context not in ('tabular', 'processor', 'chart', 'register-other'):
|
||||
emit_fill_value(f'{indent}\t\t', type_str)
|
||||
fill_checking = 'DontCheck'
|
||||
if 'req' in parsed.get('flags', []):
|
||||
@@ -700,7 +787,9 @@ def emit_attribute(indent, parsed, context):
|
||||
indexing = parsed['indexing']
|
||||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||||
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
|
||||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if context not in ('chart', 'register-other'):
|
||||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</Attribute>')
|
||||
|
||||
@@ -780,7 +869,8 @@ def emit_dimension(indent, parsed, register_type):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
@@ -853,7 +943,8 @@ def emit_resource(indent, parsed, register_type):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
@@ -892,36 +983,51 @@ def emit_catalog_properties(indent):
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
X(f'{i}<Comment/>')
|
||||
hierarchical = 'true' if defn.get('hierarchical') is True else 'false'
|
||||
hierarchy_type = str(defn['hierarchyType']) if defn.get('hierarchyType') else 'HierarchyFoldersAndItems'
|
||||
hierarchy_type = get_enum_prop('HierarchyType', 'hierarchyType', 'HierarchyFoldersAndItems')
|
||||
X(f'{i}<Hierarchical>{hierarchical}</Hierarchical>')
|
||||
X(f'{i}<HierarchyType>{hierarchy_type}</HierarchyType>')
|
||||
X(f'{i}<LimitLevelCount>false</LimitLevelCount>')
|
||||
X(f'{i}<LevelCount>2</LevelCount>')
|
||||
X(f'{i}<FoldersOnTop>true</FoldersOnTop>')
|
||||
limit_level_count = 'true' if defn.get('limitLevelCount') is True else 'false'
|
||||
level_count = str(defn['levelCount']) if defn.get('levelCount') is not None else '2'
|
||||
folders_on_top = 'false' if defn.get('foldersOnTop') is False else 'true'
|
||||
X(f'{i}<LimitLevelCount>{limit_level_count}</LimitLevelCount>')
|
||||
X(f'{i}<LevelCount>{level_count}</LevelCount>')
|
||||
X(f'{i}<FoldersOnTop>{folders_on_top}</FoldersOnTop>')
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
X(f'{i}<Owners/>')
|
||||
X(f'{i}<SubordinationUse>ToItems</SubordinationUse>')
|
||||
owners = defn.get('owners', [])
|
||||
if owners:
|
||||
X(f'{i}<Owners>')
|
||||
for owner_ref in owners:
|
||||
full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}'
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{full_ref}</xr:Item>')
|
||||
X(f'{i}</Owners>')
|
||||
else:
|
||||
X(f'{i}<Owners/>')
|
||||
subordination_use = get_enum_prop('SubordinationUse', 'subordinationUse', 'ToItems')
|
||||
X(f'{i}<SubordinationUse>{subordination_use}</SubordinationUse>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||||
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
|
||||
code_type = get_enum_prop('CodeType', 'codeType', 'String')
|
||||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||||
check_unique = 'true' if defn.get('checkUnique') is True else 'false'
|
||||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
X(f'{i}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<CodeSeries>WholeCatalog</CodeSeries>')
|
||||
code_series = get_enum_prop('CodeSeries', 'codeSeries', 'WholeCatalog')
|
||||
X(f'{i}<CodeSeries>{code_series}</CodeSeries>')
|
||||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||||
default_presentation = str(defn['defaultPresentation']) if defn.get('defaultPresentation') else 'AsDescription'
|
||||
default_presentation = get_enum_prop('DefaultPresentation', 'defaultPresentation', 'AsDescription')
|
||||
X(f'{i}<DefaultPresentation>{default_presentation}</DefaultPresentation>')
|
||||
emit_standard_attributes(i, 'Catalog')
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
|
||||
X(f'{i}<EditType>InDialog</EditType>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||||
choice_mode = get_enum_prop('ChoiceMode', 'choiceMode', 'BothWays')
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>{choice_mode}</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Code</xr:Field>')
|
||||
@@ -942,9 +1048,9 @@ def emit_catalog_properties(indent):
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<BasedOn/>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -964,10 +1070,10 @@ def emit_document_properties(indent):
|
||||
X(f'{i}<Comment/>')
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
X(f'{i}<Numerator/>')
|
||||
number_type = str(defn['numberType']) if defn.get('numberType') else 'String'
|
||||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '11'
|
||||
number_allowed_length = str(defn['numberAllowedLength']) if defn.get('numberAllowedLength') else 'Variable'
|
||||
number_periodicity = str(defn['numberPeriodicity']) if defn.get('numberPeriodicity') else 'Year'
|
||||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||||
number_periodicity = get_enum_prop('InformationRegisterPeriodicity', 'numberPeriodicity', 'Year')
|
||||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||||
X(f'{i}<NumberType>{number_type}</NumberType>')
|
||||
@@ -992,10 +1098,10 @@ def emit_document_properties(indent):
|
||||
X(f'{i}<AuxiliaryObjectForm/>')
|
||||
X(f'{i}<AuxiliaryListForm/>')
|
||||
X(f'{i}<AuxiliaryChoiceForm/>')
|
||||
posting = str(defn['posting']) if defn.get('posting') else 'Allow'
|
||||
real_time_posting = str(defn['realTimePosting']) if defn.get('realTimePosting') else 'Deny'
|
||||
reg_records_deletion = str(defn['registerRecordsDeletion']) if defn.get('registerRecordsDeletion') else 'AutoDelete'
|
||||
reg_records_writing = str(defn['registerRecordsWritingOnPost']) if defn.get('registerRecordsWritingOnPost') else 'WriteModified'
|
||||
posting = get_enum_prop('Posting', 'posting', 'Allow')
|
||||
real_time_posting = get_enum_prop('RealTimePosting', 'realTimePosting', 'Deny')
|
||||
reg_records_deletion = get_enum_prop('RegisterRecordsDeletion', 'registerRecordsDeletion', 'AutoDelete')
|
||||
reg_records_writing = get_enum_prop('RegisterRecordsWritingOnPost', 'registerRecordsWritingOnPost', 'WriteModified')
|
||||
sequence_filling = str(defn['sequenceFilling']) if defn.get('sequenceFilling') else 'AutoFill'
|
||||
post_in_priv = 'false' if defn.get('postInPrivilegedMode') is False else 'true'
|
||||
unpost_in_priv = 'false' if defn.get('unpostInPrivilegedMode') is False else 'true'
|
||||
@@ -1029,9 +1135,9 @@ def emit_document_properties(indent):
|
||||
X(f'{i}<UnpostInPrivilegedMode>{unpost_in_priv}</UnpostInPrivilegedMode>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1051,7 +1157,8 @@ def emit_enum_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>false</UseStandardCommands>')
|
||||
emit_standard_attributes(i, 'Enum')
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
quick_choice = 'false' if defn.get('quickChoice') is False else 'true'
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
X(f'{i}<DefaultListForm/>')
|
||||
X(f'{i}<DefaultChoiceForm/>')
|
||||
@@ -1092,7 +1199,7 @@ def emit_constant_properties(indent):
|
||||
X(f'{i}<ChoiceForm/>')
|
||||
X(f'{i}<LinkByType/>')
|
||||
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
X(f'{i}<DataHistory>DontUse</DataHistory>')
|
||||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
|
||||
@@ -1110,8 +1217,8 @@ def emit_information_register_properties(indent):
|
||||
X(f'{i}<AuxiliaryRecordForm/>')
|
||||
X(f'{i}<AuxiliaryListForm/>')
|
||||
emit_standard_attributes(i, 'InformationRegister')
|
||||
periodicity = str(defn['periodicity']) if defn.get('periodicity') else 'Nonperiodical'
|
||||
write_mode = str(defn['writeMode']) if defn.get('writeMode') else 'Independent'
|
||||
periodicity = get_enum_prop('InformationRegisterPeriodicity', 'periodicity', 'Nonperiodical')
|
||||
write_mode = get_enum_prop('WriteMode', 'writeMode', 'Independent')
|
||||
main_filter_on_period = 'false'
|
||||
if defn.get('mainFilterOnPeriod') is not None:
|
||||
main_filter_on_period = 'true' if defn['mainFilterOnPeriod'] is True else 'false'
|
||||
@@ -1121,9 +1228,9 @@ def emit_information_register_properties(indent):
|
||||
X(f'{i}<WriteMode>{write_mode}</WriteMode>')
|
||||
X(f'{i}<MainFilterOnPeriod>{main_filter_on_period}</MainFilterOnPeriod>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<EnableTotalsSliceFirst>false</EnableTotalsSliceFirst>')
|
||||
X(f'{i}<EnableTotalsSliceLast>false</EnableTotalsSliceLast>')
|
||||
@@ -1144,13 +1251,13 @@ def emit_accumulation_register_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
X(f'{i}<DefaultListForm/>')
|
||||
X(f'{i}<AuxiliaryListForm/>')
|
||||
register_type = str(defn['registerType']) if defn.get('registerType') else 'Balance'
|
||||
register_type = get_enum_prop('RegisterType', 'registerType', 'Balance')
|
||||
X(f'{i}<RegisterType>{register_type}</RegisterType>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
emit_standard_attributes(i, 'AccumulationRegister')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
enable_totals_splitting = 'false' if defn.get('enableTotalsSplitting') is False else 'true'
|
||||
X(f'{i}<EnableTotalsSplitting>{enable_totals_splitting}</EnableTotalsSplitting>')
|
||||
@@ -1231,7 +1338,7 @@ def emit_common_module_properties(indent):
|
||||
X(f'{i}<ClientOrdinaryApplication>{client_ordinary}</ClientOrdinaryApplication>')
|
||||
X(f'{i}<ServerCall>{server_call}</ServerCall>')
|
||||
X(f'{i}<Privileged>{privileged}</Privileged>')
|
||||
return_values_reuse = str(defn['returnValuesReuse']) if defn.get('returnValuesReuse') else 'DontUse'
|
||||
return_values_reuse = get_enum_prop('ReturnValuesReuse', 'returnValuesReuse', 'DontUse')
|
||||
X(f'{i}<ReturnValuesReuse>{return_values_reuse}</ReturnValuesReuse>')
|
||||
|
||||
def emit_scheduled_job_properties(indent):
|
||||
@@ -1353,7 +1460,7 @@ def emit_exchange_plan_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
|
||||
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
|
||||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
@@ -1365,7 +1472,8 @@ def emit_exchange_plan_properties(indent):
|
||||
X(f'{i}<DistributedInfoBase>{distributed}</DistributedInfoBase>')
|
||||
X(f'{i}<IncludeConfigurationExtensions>{include_ext}</IncludeConfigurationExtensions>')
|
||||
X(f'{i}<BasedOn/>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>ExchangePlan.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
@@ -1382,9 +1490,9 @@ def emit_exchange_plan_properties(indent):
|
||||
X(f'{i}<AuxiliaryChoiceForm/>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1405,7 +1513,7 @@ def emit_chart_of_characteristic_types_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||||
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
|
||||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||||
check_unique = 'true' if defn.get('checkUnique') is True else 'false'
|
||||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||||
@@ -1451,7 +1559,8 @@ def emit_chart_of_characteristic_types_properties(indent):
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
|
||||
X(f'{i}<EditType>InDialog</EditType>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>ChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
@@ -1473,9 +1582,9 @@ def emit_chart_of_characteristic_types_properties(indent):
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<BasedOn/>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1567,7 +1676,8 @@ def emit_chart_of_accounts_properties(indent):
|
||||
X(f'{i}</StandardTabularSections>')
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>ChartOfAccounts.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
@@ -1585,9 +1695,9 @@ def emit_chart_of_accounts_properties(indent):
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<BasedOn/>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1619,9 +1729,9 @@ def emit_accounting_register_properties(indent):
|
||||
X(f'{i}<PeriodAdjustmentLength>{period_adj_len}</PeriodAdjustmentLength>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
emit_standard_attributes(i, 'AccountingRegister')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ListPresentation/>')
|
||||
X(f'{i}<ExtendedListPresentation/>')
|
||||
@@ -1635,14 +1745,14 @@ def emit_chart_of_calculation_types_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||||
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
|
||||
code_type = get_enum_prop('CodeType', 'codeType', 'String')
|
||||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||||
X(f'{i}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
|
||||
dependence = str(defn['dependenceOnCalculationTypes']) if defn.get('dependenceOnCalculationTypes') else 'DontUse'
|
||||
dependence = get_enum_prop('DependenceOnCalculationTypes', 'dependenceOnCalculationTypes', 'DontUse')
|
||||
X(f'{i}<DependenceOnCalculationTypes>{dependence}</DependenceOnCalculationTypes>')
|
||||
base_types = list(defn.get('baseCalculationTypes', []))
|
||||
if base_types:
|
||||
@@ -1658,7 +1768,8 @@ def emit_chart_of_calculation_types_properties(indent):
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
|
||||
X(f'{i}<EditType>InDialog</EditType>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>ChartOfCalculationTypes.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
@@ -1676,9 +1787,9 @@ def emit_chart_of_calculation_types_properties(indent):
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<BasedOn/>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1701,7 +1812,7 @@ def emit_calculation_register_properties(indent):
|
||||
X(f'{i}<ChartOfCalculationTypes>{chart_of_calc_types}</ChartOfCalculationTypes>')
|
||||
else:
|
||||
X(f'{i}<ChartOfCalculationTypes/>')
|
||||
periodicity = str(defn['periodicity']) if defn.get('periodicity') else 'Month'
|
||||
periodicity = get_enum_prop('InformationRegisterPeriodicity', 'periodicity', 'Month')
|
||||
X(f'{i}<Periodicity>{periodicity}</Periodicity>')
|
||||
action_period = 'true' if defn.get('actionPeriod') is True else 'false'
|
||||
X(f'{i}<ActionPeriod>{action_period}</ActionPeriod>')
|
||||
@@ -1724,9 +1835,9 @@ def emit_calculation_register_properties(indent):
|
||||
X(f'{i}<ScheduleDate/>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
emit_standard_attributes(i, 'CalculationRegister')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ListPresentation/>')
|
||||
X(f'{i}<ExtendedListPresentation/>')
|
||||
@@ -1738,11 +1849,11 @@ def emit_business_process_properties(indent):
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
X(f'{i}<Comment/>')
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
edit_type = str(defn['editType']) if defn.get('editType') else 'InDialog'
|
||||
edit_type = get_enum_prop('EditType', 'editType', 'InDialog')
|
||||
X(f'{i}<EditType>{edit_type}</EditType>')
|
||||
number_type = str(defn['numberType']) if defn.get('numberType') else 'String'
|
||||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '11'
|
||||
number_allowed_length = str(defn['numberAllowedLength']) if defn.get('numberAllowedLength') else 'Variable'
|
||||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||||
X(f'{i}<NumberType>{number_type}</NumberType>')
|
||||
@@ -1773,9 +1884,9 @@ def emit_business_process_properties(indent):
|
||||
X(f'{i}<AuxiliaryChoiceForm/>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1793,9 +1904,9 @@ def emit_task_properties(indent):
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
X(f'{i}<Comment/>')
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
number_type = str(defn['numberType']) if defn.get('numberType') else 'String'
|
||||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '14'
|
||||
number_allowed_length = str(defn['numberAllowedLength']) if defn.get('numberAllowedLength') else 'Variable'
|
||||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||||
task_number_auto_prefix = str(defn['taskNumberAutoPrefix']) if defn.get('taskNumberAutoPrefix') else 'BusinessProcessNumber'
|
||||
@@ -1840,9 +1951,9 @@ def emit_task_properties(indent):
|
||||
X(f'{i}<AuxiliaryChoiceForm/>')
|
||||
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
|
||||
X(f'{i}<DataLockFields/>')
|
||||
data_lock_control_mode = str(defn['dataLockControlMode']) if defn.get('dataLockControlMode') else 'Automatic'
|
||||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
|
||||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||||
full_text_search = str(defn['fullTextSearch']) if defn.get('fullTextSearch') else 'Use'
|
||||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||||
X(f'{i}<ObjectPresentation/>')
|
||||
X(f'{i}<ExtendedObjectPresentation/>')
|
||||
@@ -1861,7 +1972,7 @@ def emit_http_service_properties(indent):
|
||||
X(f'{i}<Comment/>')
|
||||
root_url = str(defn['rootURL']) if defn.get('rootURL') else obj_name.lower()
|
||||
X(f'{i}<RootURL>{esc_xml(root_url)}</RootURL>')
|
||||
reuse_sessions = str(defn['reuseSessions']) if defn.get('reuseSessions') else 'DontUse'
|
||||
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
|
||||
X(f'{i}<ReuseSessions>{reuse_sessions}</ReuseSessions>')
|
||||
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
|
||||
X(f'{i}<SessionMaxAge>{session_max_age}</SessionMaxAge>')
|
||||
@@ -1878,7 +1989,7 @@ def emit_web_service_properties(indent):
|
||||
X(f'{i}<XDTOPackages>{xdto_packages}</XDTOPackages>')
|
||||
else:
|
||||
X(f'{i}<XDTOPackages/>')
|
||||
reuse_sessions = str(defn['reuseSessions']) if defn.get('reuseSessions') else 'DontUse'
|
||||
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
|
||||
X(f'{i}<ReuseSessions>{reuse_sessions}</ReuseSessions>')
|
||||
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
|
||||
X(f'{i}<SessionMaxAge>{session_max_age}</SessionMaxAge>')
|
||||
@@ -2000,7 +2111,7 @@ def emit_url_template(indent, tmpl_name, tmpl_def):
|
||||
X(f'{indent}\t</Properties>')
|
||||
if methods:
|
||||
X(f'{indent}\t<ChildObjects>')
|
||||
for method_name, http_method in methods.items():
|
||||
for method_name, http_method in sorted(methods.items()):
|
||||
method_uuid = new_uuid()
|
||||
method_synonym = split_camel_case(method_name)
|
||||
handler = f'{tmpl_name}{method_name}'
|
||||
@@ -2051,7 +2162,7 @@ def emit_operation(indent, op_name, op_def):
|
||||
X(f'{indent}\t</Properties>')
|
||||
if params:
|
||||
X(f'{indent}\t<ChildObjects>')
|
||||
for param_name, param_def in params.items():
|
||||
for param_name, param_def in sorted(params.items()):
|
||||
param_uuid = new_uuid()
|
||||
param_synonym = split_camel_case(param_name)
|
||||
param_type = 'xs:string'
|
||||
@@ -2123,6 +2234,27 @@ def emit_addressing_attribute(indent, addr_def):
|
||||
|
||||
xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 14a. Detect format version from existing Configuration.xml
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
format_version = detect_format_version(output_dir)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 15. Main assembler
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -2130,7 +2262,7 @@ xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8
|
||||
obj_uuid = new_uuid()
|
||||
|
||||
X('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
X(f'<MetaDataObject {xmlns_decl} version="2.17">')
|
||||
X(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
|
||||
X(f'\t<{obj_type} uuid="{obj_uuid}">')
|
||||
|
||||
# InternalInfo
|
||||
@@ -2228,6 +2360,8 @@ if obj_type in types_with_attr_ts:
|
||||
context = 'document'
|
||||
elif obj_type in ('DataProcessor', 'Report'):
|
||||
context = 'processor'
|
||||
elif obj_type in ('ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes'):
|
||||
context = 'chart'
|
||||
else:
|
||||
context = 'object'
|
||||
for a in attrs:
|
||||
@@ -2236,9 +2370,11 @@ if obj_type in types_with_attr_ts:
|
||||
columns = ts_sections[ts_name]
|
||||
emit_tabular_section('\t\t\t', ts_name, columns, obj_type, obj_name)
|
||||
for af in acct_flags:
|
||||
emit_accounting_flag('\t\t\t', str(af))
|
||||
af_name = af['name'] if isinstance(af, dict) else str(af)
|
||||
emit_accounting_flag('\t\t\t', af_name)
|
||||
for edf in ext_dim_flags:
|
||||
emit_ext_dimension_accounting_flag('\t\t\t', str(edf))
|
||||
edf_name = edf['name'] if isinstance(edf, dict) else str(edf)
|
||||
emit_ext_dimension_accounting_flag('\t\t\t', edf_name)
|
||||
for aa in addr_attrs:
|
||||
emit_addressing_attribute('\t\t\t', aa)
|
||||
X('\t\t</ChildObjects>')
|
||||
@@ -2283,8 +2419,11 @@ if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegist
|
||||
emit_resource('\t\t\t', r, obj_type)
|
||||
for d in dims:
|
||||
emit_dimension('\t\t\t', d, obj_type)
|
||||
# InformationRegister.Attribute supports FillFromFillingValue/FillValue/DataHistory;
|
||||
# AccumulationRegister/AccountingRegister/CalculationRegister.Attribute do NOT.
|
||||
reg_ctx = 'register-info' if obj_type == 'InformationRegister' else 'register-other'
|
||||
for a in reg_attrs:
|
||||
emit_attribute('\t\t\t', a, 'register')
|
||||
emit_attribute('\t\t\t', a, reg_ctx)
|
||||
X('\t\t</ChildObjects>')
|
||||
else:
|
||||
X('\t\t<ChildObjects/>')
|
||||
@@ -2312,7 +2451,7 @@ if obj_type == 'HTTPService':
|
||||
if url_templates:
|
||||
has_children = True
|
||||
X('\t\t<ChildObjects>')
|
||||
for tmpl_name in url_tmpl_order:
|
||||
for tmpl_name in sorted(url_tmpl_order):
|
||||
emit_url_template('\t\t\t', tmpl_name, url_templates[tmpl_name])
|
||||
X('\t\t</ChildObjects>')
|
||||
else:
|
||||
@@ -2329,7 +2468,7 @@ if obj_type == 'WebService':
|
||||
if operations:
|
||||
has_children = True
|
||||
X('\t\t<ChildObjects>')
|
||||
for op_name in op_order:
|
||||
for op_name in sorted(op_order):
|
||||
emit_operation('\t\t\t', op_name, operations[op_name])
|
||||
X('\t\t</ChildObjects>')
|
||||
else:
|
||||
@@ -2340,7 +2479,7 @@ if obj_type == 'WebService':
|
||||
X(f'\t</{obj_type}>')
|
||||
X('</MetaDataObject>')
|
||||
|
||||
metadata_xml = '\n'.join(lines)
|
||||
metadata_xml = '\n'.join(lines) + '\n'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 16. Write files
|
||||
@@ -2386,7 +2525,7 @@ ext_dir = os.path.join(obj_sub_dir, 'Ext')
|
||||
|
||||
os.makedirs(type_dir, exist_ok=True)
|
||||
if obj_type not in types_no_sub_dir:
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
os.makedirs(obj_sub_dir, exist_ok=True)
|
||||
|
||||
write_utf8_bom(main_xml_path, metadata_xml)
|
||||
|
||||
@@ -2401,30 +2540,45 @@ types_with_object_module = [
|
||||
types_with_record_set_module = [
|
||||
'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister',
|
||||
]
|
||||
types_with_manager_module = ['Report', 'DataProcessor']
|
||||
types_with_manager_module = ['Report', 'DataProcessor', 'Constant', 'Enum']
|
||||
types_with_value_manager_module = ['Constant']
|
||||
types_with_module = ['CommonModule', 'HTTPService', 'WebService']
|
||||
|
||||
def ensure_ext_dir():
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
|
||||
if obj_type in types_with_object_module:
|
||||
module_path = os.path.join(ext_dir, 'ObjectModule.bsl')
|
||||
if not os.path.isfile(module_path):
|
||||
ensure_ext_dir()
|
||||
write_utf8_bom(module_path, '')
|
||||
modules_created.append(module_path)
|
||||
|
||||
if obj_type in types_with_manager_module:
|
||||
module_path = os.path.join(ext_dir, 'ManagerModule.bsl')
|
||||
if not os.path.isfile(module_path):
|
||||
ensure_ext_dir()
|
||||
write_utf8_bom(module_path, '')
|
||||
modules_created.append(module_path)
|
||||
|
||||
if obj_type in types_with_value_manager_module:
|
||||
module_path = os.path.join(ext_dir, 'ValueManagerModule.bsl')
|
||||
if not os.path.isfile(module_path):
|
||||
ensure_ext_dir()
|
||||
write_utf8_bom(module_path, '')
|
||||
modules_created.append(module_path)
|
||||
|
||||
if obj_type in types_with_record_set_module:
|
||||
module_path = os.path.join(ext_dir, 'RecordSetModule.bsl')
|
||||
if not os.path.isfile(module_path):
|
||||
ensure_ext_dir()
|
||||
write_utf8_bom(module_path, '')
|
||||
modules_created.append(module_path)
|
||||
|
||||
if obj_type in types_with_module:
|
||||
module_path = os.path.join(ext_dir, 'Module.bsl')
|
||||
if not os.path.isfile(module_path):
|
||||
ensure_ext_dir()
|
||||
write_utf8_bom(module_path, '')
|
||||
modules_created.append(module_path)
|
||||
|
||||
@@ -2432,14 +2586,16 @@ if obj_type in types_with_module:
|
||||
if obj_type == 'ExchangePlan':
|
||||
content_path = os.path.join(ext_dir, 'Content.xml')
|
||||
if not os.path.isfile(content_path):
|
||||
content_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" version="2.17"/>\r\n'
|
||||
ensure_ext_dir()
|
||||
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" version="{format_version}"/>\r\n'
|
||||
write_utf8_bom(content_path, content_xml)
|
||||
modules_created.append(content_path)
|
||||
|
||||
if obj_type == 'BusinessProcess':
|
||||
flowchart_path = os.path.join(ext_dir, 'Flowchart.xml')
|
||||
if not os.path.isfile(flowchart_path):
|
||||
flowchart_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="2.17"/>\r\n'
|
||||
ensure_ext_dir()
|
||||
flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n'
|
||||
write_utf8_bom(flowchart_path, flowchart_xml)
|
||||
modules_created.append(flowchart_path)
|
||||
|
||||
@@ -2502,9 +2658,13 @@ if os.path.isfile(config_xml_path):
|
||||
|
||||
# Write back preserving BOM
|
||||
tree.write(config_xml_path, encoding='utf-8', xml_declaration=True)
|
||||
# Re-read to add BOM
|
||||
# Re-read to add BOM, fix declaration quotes, ensure trailing newline
|
||||
with open(config_xml_path, 'r', encoding='utf-8') as f:
|
||||
raw = f.read()
|
||||
if raw.startswith("<?xml version='1.0' encoding='utf-8'?>"):
|
||||
raw = raw.replace("<?xml version='1.0' encoding='utf-8'?>", '<?xml version="1.0" encoding="UTF-8"?>', 1)
|
||||
if not raw.endswith('\n'):
|
||||
raw += '\n'
|
||||
write_utf8_bom(config_xml_path, raw)
|
||||
reg_result = 'added'
|
||||
else:
|
||||
|
||||
@@ -18,13 +18,13 @@ allowed-tools:
|
||||
### Inline mode (простые операции)
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/meta-edit/scripts/meta-edit.ps1 -ObjectPath "<path>" -Operation <op> -Value "<val>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-edit.ps1" -ObjectPath "<path>" -Operation <op> -Value "<val>"
|
||||
```
|
||||
|
||||
### JSON mode (сложные/комбинированные)
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/meta-edit/scripts/meta-edit.ps1 -DefinitionFile "<json>" -ObjectPath "<path>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-edit.ps1" -DefinitionFile "<json>" -ObjectPath "<path>"
|
||||
```
|
||||
|
||||
| Параметр | Описание |
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# meta-edit v1.4 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# meta-edit v1.6 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ObjectPath,
|
||||
|
||||
# Inline mode (alternative to DefinitionFile)
|
||||
@@ -43,6 +44,73 @@ if (-not $DefinitionFile -and -not $Operation) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Enum value normalization (same as meta-compile) ---
|
||||
$script:enumValueAliases = @{
|
||||
"Balances" = "Balance"; "Остатки" = "Balance"; "Обороты" = "Turnovers"
|
||||
"RecordSubordinate" = "RecorderSubordinate"; "Subordinate" = "RecorderSubordinate"
|
||||
"ПодчинениеРегистратору" = "RecorderSubordinate"; "Независимый" = "Independent"
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"; "NotUsed" = "DontUse"
|
||||
"Depend" = "OnActionPeriod"; "ПоПериодуДействия" = "OnActionPeriod"
|
||||
"None" = "Nonperiodical"; "Daily" = "Day"; "Monthly" = "Month"
|
||||
"Quarterly" = "Quarter"; "Yearly" = "Year"
|
||||
"Непериодический" = "Nonperiodical"; "Секунда" = "Second"; "День" = "Day"; "Месяц" = "Month"
|
||||
"Квартал" = "Quarter"; "Год" = "Year"
|
||||
"ПозицияРегистратора" = "RecorderPosition"
|
||||
"Автоматический" = "Automatic"; "Управляемый" = "Managed"
|
||||
"Использовать" = "Use"; "НеИспользовать" = "DontUse"
|
||||
"Разрешить" = "Allow"; "Запретить" = "Deny"
|
||||
"ВДиалоге" = "InDialog"; "ВСписке" = "InList"; "ОбаСпособа" = "BothWays"
|
||||
"ВВидеНаименования" = "AsDescription"; "ВВидеКода" = "AsCode"
|
||||
"НеПроверять" = "DontCheck"; "Ошибка" = "ShowError"; "Предупреждение" = "ShowWarning"
|
||||
"НеИндексировать" = "DontIndex"; "Индексировать" = "Index"
|
||||
"ИндексироватьСДопУпорядочиванием" = "IndexWithAdditionalOrder"
|
||||
}
|
||||
|
||||
$script:validEnumValues = @{
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","OnActionPeriod")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
"DefaultPresentation" = @("AsDescription","AsCode")
|
||||
"Posting" = @("Allow","Deny")
|
||||
"RealTimePosting" = @("Allow","Deny")
|
||||
"EditType" = @("InDialog","InList","BothWays")
|
||||
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
|
||||
"CodeType" = @("String","Number")
|
||||
"CodeAllowedLength" = @("Variable","Fixed")
|
||||
"NumberType" = @("String","Number")
|
||||
"NumberAllowedLength" = @("Variable","Fixed")
|
||||
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
||||
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||
"ReuseSessions" = @("DontUse","AutoUse")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
}
|
||||
|
||||
function Normalize-EnumValue {
|
||||
param([string]$propName, [string]$value)
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if ($script:enumValueAliases.ContainsKey($value)) {
|
||||
return $script:enumValueAliases[$value]
|
||||
}
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
$valid = $script:validEnumValues[$propName]
|
||||
if ($valid) {
|
||||
foreach ($v in $valid) {
|
||||
if ($v -ieq $value) { return $v }
|
||||
}
|
||||
# 3. Known property, unknown value — error with hint
|
||||
Write-Error "Invalid value '$value' for property '$propName'. Valid values: $($valid -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return $value
|
||||
}
|
||||
|
||||
# --- Load JSON definition (DefinitionFile mode) ---
|
||||
$def = $null
|
||||
if ($DefinitionFile) {
|
||||
@@ -756,7 +824,7 @@ function Build-AttributeFragment {
|
||||
# FillChecking
|
||||
$fillChecking = "DontCheck"
|
||||
if ($parsed.flags -contains "req") { $fillChecking = "ShowError" }
|
||||
if ($parsed.fillChecking) { $fillChecking = $parsed.fillChecking }
|
||||
if ($parsed.fillChecking) { $fillChecking = Normalize-EnumValue "FillChecking" $parsed.fillChecking }
|
||||
$sb.AppendLine("$indent`t`t<FillChecking>$fillChecking</FillChecking>") | Out-Null
|
||||
|
||||
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
|
||||
@@ -778,7 +846,7 @@ function Build-AttributeFragment {
|
||||
$indexing = "DontIndex"
|
||||
if ($parsed.flags -contains "index") { $indexing = "Index" }
|
||||
if ($parsed.flags -contains "indexadditional") { $indexing = "IndexWithAdditionalOrder" }
|
||||
if ($parsed.indexing) { $indexing = $parsed.indexing }
|
||||
if ($parsed.indexing) { $indexing = Normalize-EnumValue "Indexing" $parsed.indexing }
|
||||
$sb.AppendLine("$indent`t`t<Indexing>$indexing</Indexing>") | Out-Null
|
||||
|
||||
$sb.AppendLine("$indent`t`t<FullTextSearch>Use</FullTextSearch>") | Out-Null
|
||||
@@ -2040,6 +2108,8 @@ function Modify-ChildElements($modifyDef, [string]$childType) {
|
||||
$valueStr = "$changeValue"
|
||||
if ($changeValue -is [bool]) {
|
||||
$valueStr = if ($changeValue) { "true" } else { "false" }
|
||||
} else {
|
||||
$valueStr = Normalize-EnumValue $changeProp $valueStr
|
||||
}
|
||||
$scalarEl.InnerText = $valueStr
|
||||
Info "Modified $xmlTag '$elemName'.$changeProp = $valueStr"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-edit v1.4 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# meta-edit v1.6 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -77,6 +77,85 @@ def esc_xml(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Enum value normalization (same as meta-compile)
|
||||
# ============================================================
|
||||
|
||||
enum_value_aliases = {
|
||||
# RegisterType (AccumulationRegister)
|
||||
'Balances': 'Balance', 'Остатки': 'Balance', 'Обороты': 'Turnovers',
|
||||
# WriteMode (InformationRegister)
|
||||
'RecordSubordinate': 'RecorderSubordinate', 'Subordinate': 'RecorderSubordinate',
|
||||
'ПодчинениеРегистратору': 'RecorderSubordinate', 'Независимый': 'Independent',
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse', 'NotUsed': 'DontUse',
|
||||
'Depend': 'OnActionPeriod', 'ПоПериодуДействия': 'OnActionPeriod',
|
||||
# InformationRegisterPeriodicity
|
||||
'None': 'Nonperiodical', 'Daily': 'Day', 'Monthly': 'Month',
|
||||
'Quarterly': 'Quarter', 'Yearly': 'Year',
|
||||
'Непериодический': 'Nonperiodical', 'Секунда': 'Second', 'День': 'Day',
|
||||
'Месяц': 'Month', 'Квартал': 'Quarter', 'Год': 'Year',
|
||||
'ПозицияРегистратора': 'RecorderPosition',
|
||||
# DataLockControlMode
|
||||
'Автоматический': 'Automatic', 'Управляемый': 'Managed',
|
||||
# FullTextSearch
|
||||
'Использовать': 'Use', 'НеИспользовать': 'DontUse',
|
||||
# Posting
|
||||
'Разрешить': 'Allow', 'Запретить': 'Deny',
|
||||
# EditType
|
||||
'ВДиалоге': 'InDialog', 'ВСписке': 'InList', 'ОбаСпособа': 'BothWays',
|
||||
# DefaultPresentation
|
||||
'ВВидеНаименования': 'AsDescription', 'ВВидеКода': 'AsCode',
|
||||
# FillChecking
|
||||
'НеПроверять': 'DontCheck', 'Ошибка': 'ShowError', 'Предупреждение': 'ShowWarning',
|
||||
# Indexing
|
||||
'НеИндексировать': 'DontIndex', 'Индексировать': 'Index',
|
||||
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
||||
}
|
||||
|
||||
valid_enum_values = {
|
||||
'RegisterType': ['Balance', 'Turnovers'],
|
||||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||||
'InformationRegisterPeriodicity': ['Nonperiodical', 'Second', 'Day', 'Month', 'Quarter', 'Year', 'RecorderPosition'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'OnActionPeriod'],
|
||||
'DataLockControlMode': ['Automatic', 'Managed'],
|
||||
'FullTextSearch': ['Use', 'DontUse'],
|
||||
'DataHistory': ['Use', 'DontUse'],
|
||||
'DefaultPresentation': ['AsDescription', 'AsCode'],
|
||||
'Posting': ['Allow', 'Deny'],
|
||||
'RealTimePosting': ['Allow', 'Deny'],
|
||||
'EditType': ['InDialog', 'InList', 'BothWays'],
|
||||
'HierarchyType': ['HierarchyFoldersAndItems', 'HierarchyItemsOnly'],
|
||||
'CodeType': ['String', 'Number'],
|
||||
'CodeAllowedLength': ['Variable', 'Fixed'],
|
||||
'NumberType': ['String', 'Number'],
|
||||
'NumberAllowedLength': ['Variable', 'Fixed'],
|
||||
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
|
||||
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
|
||||
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
|
||||
'ReuseSessions': ['DontUse', 'AutoUse'],
|
||||
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
||||
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
||||
}
|
||||
|
||||
|
||||
def normalize_enum_value(prop_name, value):
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if value in enum_value_aliases:
|
||||
return enum_value_aliases[value]
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
valid = valid_enum_values.get(prop_name)
|
||||
if valid:
|
||||
for v in valid:
|
||||
if v.lower() == value.lower():
|
||||
return v
|
||||
# 3. Known property, unknown value — error with hint
|
||||
print(f"Invalid value '{value}' for property '{prop_name}'. Valid values: {', '.join(valid)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return value
|
||||
|
||||
|
||||
def new_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@@ -552,8 +631,8 @@ def parse_attribute_shorthand(val):
|
||||
"synonym": str(val.get("synonym", "")) if val.get("synonym") else split_camel_case(name),
|
||||
"comment": str(val.get("comment", "")),
|
||||
"flags": list(val.get("flags", [])),
|
||||
"fillChecking": str(val.get("fillChecking", "")),
|
||||
"indexing": str(val.get("indexing", "")),
|
||||
"fillChecking": normalize_enum_value("FillChecking", str(val.get("fillChecking", ""))) if val.get("fillChecking") else "",
|
||||
"indexing": normalize_enum_value("Indexing", str(val.get("indexing", ""))) if val.get("indexing") else "",
|
||||
"after": str(val.get("after", "")),
|
||||
"before": str(val.get("before", "")),
|
||||
}
|
||||
@@ -986,7 +1065,7 @@ def build_column_fragment(col_def, indent):
|
||||
name = str(col_def.get("name", ""))
|
||||
synonym = str(col_def.get("synonym", "")) if col_def.get("synonym") else split_camel_case(name)
|
||||
if col_def.get("indexing"):
|
||||
indexing = str(col_def["indexing"])
|
||||
indexing = normalize_enum_value("Indexing", str(col_def["indexing"]))
|
||||
if col_def.get("references"):
|
||||
references = list(col_def["references"])
|
||||
|
||||
@@ -1823,6 +1902,8 @@ def modify_child_elements(modify_def, child_type):
|
||||
value_str = str(change_value)
|
||||
if isinstance(change_value, bool):
|
||||
value_str = "true" if change_value else "false"
|
||||
else:
|
||||
value_str = normalize_enum_value(change_prop, value_str)
|
||||
# Clear children and set text
|
||||
for ch in list(scalar_el):
|
||||
scalar_el.remove(ch)
|
||||
@@ -1997,8 +2078,8 @@ def set_complex_property(property_name, values):
|
||||
def save_xml(tree, path):
|
||||
"""Save XML tree with BOM and proper encoding declaration."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
# Fix encoding quotes: encoding='UTF-8' -> encoding="UTF-8"
|
||||
xml_bytes = xml_bytes.replace(b"encoding='UTF-8'", b'encoding="UTF-8"')
|
||||
# Fix XML declaration quotes
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
# Fix d5p1 namespace declarations stripped by lxml (it treats them as unused
|
||||
# because d5p1: appears only in text content, not in element/attribute names)
|
||||
xml_bytes = re.sub(
|
||||
@@ -2006,6 +2087,8 @@ def save_xml(tree, path):
|
||||
b'\\1 xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config"\\2',
|
||||
xml_bytes
|
||||
)
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
@@ -2038,7 +2121,7 @@ def main():
|
||||
|
||||
parser = argparse.ArgumentParser(description="Edit existing 1C metadata object XML", allow_abbrev=False)
|
||||
parser.add_argument("-DefinitionFile", default=None, help="JSON definition file")
|
||||
parser.add_argument("-ObjectPath", required=True, help="Path to object XML or directory")
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True, help="Path to object XML or directory")
|
||||
parser.add_argument("-Operation", default=None, choices=valid_operations, help="Inline operation")
|
||||
parser.add_argument("-Value", default=None, help="Inline value")
|
||||
parser.add_argument("-NoValidate", action="store_true", help="Skip auto-validation")
|
||||
@@ -2174,7 +2257,7 @@ def main():
|
||||
print()
|
||||
print("--- Running meta-validate ---")
|
||||
python_exe = sys.executable
|
||||
subprocess.run([python_exe, validate_script, "-ObjectPath", resolved_path])
|
||||
subprocess.run([python_exe, validate_script, "-ObjectPath", "-Path", resolved_path])
|
||||
else:
|
||||
print()
|
||||
print(f"[SKIP] meta-validate not found at: {validate_script}")
|
||||
|
||||
@@ -23,7 +23,7 @@ allowed-tools:
|
||||
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/meta-info/scripts/meta-info.ps1 -ObjectPath "<путь>"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-info.ps1" -ObjectPath "<путь>"
|
||||
```
|
||||
|
||||
## Три режима
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user