merge dev: meta-compile v1.2, meta-edit v1.4, batch mode, validation improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-08 18:46:52 +03:00
co-authored by Claude Opus 4.6
13 changed files with 1526 additions and 212 deletions
+60 -121
View File
@@ -1,6 +1,6 @@
--- ---
name: meta-compile name: meta-compile
description: Создать исходники объекта метаданных 1С (справочник, документ, регистр, перечисление, константа, общий модуль, обработка, HTTP-сервис и др.) в выгрузке конфигурации. Используй когда пользователь просит добавить или создать объект конфигурации description: Создать объект метаданных 1С. Используй когда пользователь просит создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
argument-hint: <JsonPath> <OutputDir> argument-hint: <JsonPath> <OutputDir>
allowed-tools: allowed-tools:
- Bash - Bash
@@ -13,101 +13,91 @@ allowed-tools:
Принимает JSON-определение объекта метаданных → генерирует XML + модули в структуре выгрузки конфигурации + регистрирует в Configuration.xml. Принимает JSON-определение объекта метаданных → генерирует XML + модули в структуре выгрузки конфигурации + регистрирует в Configuration.xml.
## Параметры и команда ## Порядок работы
| Параметр | Описание | 1. Составь JSON по синтаксису и примерам ниже → запиши во временный файл
|----------|----------| 2. Запусти скрипт meta-compile
| `JsonPath` | Путь к JSON-определению объекта | 3. Если нужно изменить созданный объект — `/meta-edit`
| `OutputDir` | Корневая директория выгрузки конфигурации (где `Catalogs/`, `Documents/` и т.д.) | 4. Если нужно проверить — `/meta-validate`
## Команда
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/meta-compile/scripts/meta-compile.ps1 -JsonPath "<json>" -OutputDir "<ConfigDir>" powershell.exe -NoProfile -File .claude/skills/meta-compile/scripts/meta-compile.ps1 -JsonPath "<json>" -OutputDir "<ConfigDir>"
``` ```
`OutputDir` — директория, содержащая подпапки типов (`Catalogs/`, `Documents/`, ...) и `Configuration.xml`. | Параметр | Описание |
|----------|----------|
| `JsonPath` | Путь к JSON-файлу (один объект `{...}` или массив `[{...}, ...]`) |
| `OutputDir` | Корень выгрузки конфигурации (где `Configuration.xml`, `Catalogs/`, `Documents/` и т.д.) |
## Поддерживаемые типы (23) ## JSON DSL
### Ссылочные ### Общая структура
Catalog (Справочник), Document (Документ), Enum (Перечисление), ExchangePlan (ПланОбмена), ChartOfAccounts (ПланСчетов), ChartOfCharacteristicTypes (ПВХ), ChartOfCalculationTypes (ПВР), BusinessProcess (БизнесПроцесс), Task (Задача)
### Регистры
InformationRegister (РегистрСведений), AccumulationRegister (РегистрНакопления), AccountingRegister (РегистрБухгалтерии), CalculationRegister (РегистрРасчёта)
### Отчёты/Обработки
Report (Отчёт), DataProcessor (Обработка)
### Сервисные
Constant (Константа), DefinedType (ОпределяемыйТип), CommonModule (ОбщийМодуль), ScheduledJob (РегламентноеЗадание), EventSubscription (ПодпискаНаСобытие), DocumentJournal (ЖурналДокументов), HTTPService (HTTPСервис), WebService (ВебСервис)
## JSON DSL — краткий справочник
Полная спецификация: `docs/meta-dsl-spec.md`.
### Корневая структура
```json ```json
{ { "type": "Catalog", "name": "Номенклатура", ...свойства типа... }
"type": "Catalog",
"name": "Номенклатура",
"synonym": "авто из name",
...type-specific...,
"attributes": [...],
"tabularSections": {...}
}
``` ```
### Реквизиты — shorthand `type` и `name` — обязательные. `synonym` генерируется из `name` автоматически (CamelCase → слова через пробел). Можно задать явно: `"synonym": "Мой синоним"`.
### Shorthand реквизитов
Используется в `attributes`, `dimensions`, `resources`, `tabularSections`:
``` ```
"ИмяРеквизита" String без квалификаторов "ИмяРеквизита" String(10) по умолчанию
"ИмяРеквизита: Тип" с типом "ИмяРеквизита: Тип" с типом
"ИмяРеквизита: Тип | req, index" с флагами "ИмяРеквизита: Тип | req, index" с флагами
``` ```
Типы: `String(100)`, `Number(15,2)`, `Boolean`, `Date`, `DateTime`, `CatalogRef.Xxx`, `DocumentRef.Xxx`, `EnumRef.Xxx`, `ChartOfAccountsRef.Xxx`, `ChartOfCharacteristicTypesRef.Xxx`, `ChartOfCalculationTypesRef.Xxx`, `ExchangePlanRef.Xxx`, `BusinessProcessRef.Xxx`, `TaskRef.Xxx`, `DefinedType.Xxx`. Типы: `String(100)`, `Number(15,2)`, `Boolean`, `Date`, `DateTime`, `CatalogRef.Xxx`, `DocumentRef.Xxx`, `EnumRef.Xxx`, `DefinedType.Xxx` и др. ссылочные.
Русские синонимы типов: `Строка`, `Число`, `Булево`, `Дата`, `СправочникСсылка.Xxx`, `ДокументСсылка.Xxx`, `ПланСчетовСсылка.Xxx`. Составной тип: `"Значение: String + Number(15,2) + CatalogRef.Контрагенты"`.
Составной тип (несколько допустимых типов через `+`): `"Значение: Строка + Число(15,2) + Дата + CatalogRef.Контрагенты"`.
Флаги: `req`, `index`, `indexAdditional`, `nonneg`, `master`, `mainFilter`, `denyIncomplete`, `useInTotals`. Флаги: `req`, `index`, `indexAdditional`, `nonneg`, `master`, `mainFilter`, `denyIncomplete`, `useInTotals`.
## Примеры ### Свойства по типам
### Справочник Примеров и shorthand-синтаксиса выше достаточно для типовых задач. Если нужны свойства типа, не показанные в примерах, и их допустимые значения — см. reference-файл:
- `reference/types-basic.md` — Catalog, Document, Enum, Constant, DefinedType, Report, DataProcessor
- `reference/types-registers.md` — InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes
- `reference/types-process.md` — BusinessProcess, Task, ExchangePlan, CommonModule, ScheduledJob, EventSubscription, DocumentJournal
- `reference/types-web.md` — HTTPService, WebService
Эта инструкция и reference-файлы — полная документация для генерации. Не ищи примеры XML в выгрузках конфигураций.
## Примеры паттернов DSL
### Минимальный объект
```json ```json
{ "type": "Catalog", "name": "Валюты" } { "type": "Catalog", "name": "Валюты" }
``` ```
### Перечисление ### С реквизитами
```json ```json
{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] } {
"type": "Catalog", "name": "Организации",
"descriptionLength": 100,
"attributes": ["ИНН: String(12)", "КПП: String(9)", "Директор: CatalogRef.ФизическиеЛица"]
}
``` ```
### Константа ### С табличной частью
```json ```json
{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" } {
"type": "Document", "name": "ПриходнаяНакладная",
"registerRecords": ["AccumulationRegister.ОстаткиТоваров"],
"attributes": ["Организация: CatalogRef.Организации", "Контрагент: CatalogRef.Контрагенты"],
"tabularSections": { "Товары": ["Номенклатура: CatalogRef.Номенклатура", "Количество: Number(15,3)", "Цена: Number(15,2)"] }
}
``` ```
### Определяемый тип ### Регистровый паттерн (измерения + ресурсы)
```json
{ "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] }
```
### Общий модуль
```json
{ "type": "CommonModule", "name": "ОбменДаннымиСервер", "context": "server", "returnValuesReuse": "DuringRequest" }
```
Шорткаты context: `"server"` → Server+ServerCall, `"client"` → ClientManagedApplication, `"serverClient"` → Server+ClientManagedApplication.
### Регистр сведений
```json ```json
{ {
@@ -117,70 +107,19 @@ Constant (Константа), DefinedType (ОпределяемыйТип), Com
} }
``` ```
### План обмена ### Batch — несколько объектов в одном файле
```json ```json
{ "type": "ExchangePlan", "name": "ОбменССайтом", "attributes": ["АдресСервера: String(200)"] } [
``` { "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] },
{ "type": "Catalog", "name": "Валюты" },
### Журнал документов { "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" }
]
```json
{
"type": "DocumentJournal", "name": "Взаимодействия",
"registeredDocuments": ["Document.Встреча", "Document.ТелефонныйЗвонок"],
"columns": [{ "name": "Организация", "indexing": "Index", "references": ["Document.Встреча.Attribute.Организация"] }]
}
```
### HTTP-сервис
```json
{
"type": "HTTPService", "name": "API", "rootURL": "api",
"urlTemplates": { "Users": { "template": "/v1/users", "methods": { "Get": "GET", "Create": "POST" } } }
}
```
### Веб-сервис
```json
{
"type": "WebService", "name": "DataExchange", "namespace": "http://www.1c.ru/DataExchange",
"operations": { "TestConnection": { "returnType": "xs:boolean", "handler": "ПроверкаПодключения", "parameters": { "ErrorMessage": { "type": "xs:string", "direction": "Out" } } } }
}
```
### План счетов
```json
{
"type": "ChartOfAccounts", "name": "Хозрасчетный",
"extDimensionTypes": "ChartOfCharacteristicTypes.ВидыСубконто", "maxExtDimensionCount": 3,
"codeMask": "@@@.@@.@", "codeLength": 8,
"accountingFlags": ["Валютный", "Количественный"],
"extDimensionAccountingFlags": ["Суммовой", "Валютный"]
}
```
### Бизнес-процесс
```json
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] }
``` ```
## Что генерируется ## Что генерируется
- `{OutputDir}/{TypePlural}/{Name}.xml` — метаданные объекта - `{TypePlural}/{Name}.xml` — метаданные объекта
- `{OutputDir}/{TypePlural}/{Name}/Ext/ObjectModule.bsl` — модуль объекта (Catalog, Document, Report, DataProcessor, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task) - `{TypePlural}/{Name}/Ext/*.bsl` — модули (ObjectModule, RecordSetModule, Module — зависит от типа)
- `{OutputDir}/{TypePlural}/{Name}/Ext/RecordSetModule.bsl` — модуль набора записей (4 типа регистров)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Module.bsl` — модуль (CommonModule, HTTPService, WebService)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Content.xml` — состав плана обмена (ExchangePlan)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Flowchart.xml` — карта маршрута (BusinessProcess)
- `Configuration.xml` — автоматическая регистрация в `<ChildObjects>` - `Configuration.xml` — автоматическая регистрация в `<ChildObjects>`
## Верификация
```
/meta-info <OutputDir>/<TypePlural>/<Name>.xml — проверка структуры
```
@@ -0,0 +1,109 @@
# Базовые типы: Catalog, Document, Enum, Constant, DefinedType, Report, DataProcessor
## Catalog
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `hierarchical` | `false` | Hierarchical |
| `hierarchyType` | `HierarchyFoldersAndItems` | HierarchyType |
| `codeLength` | `9` | CodeLength |
| `codeType` | `String` | CodeType |
| `codeAllowedLength` | `Variable` | CodeAllowedLength |
| `descriptionLength` | `25` | DescriptionLength |
| `autonumbering` | `true` | Autonumbering |
| `checkUnique` | `false` | CheckUnique |
| `defaultPresentation` | `AsDescription` | DefaultPresentation |
| `owners` | `[]` | Owners |
| `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
```json
{ "type": "Catalog", "name": "Организации", "attributes": ["ИНН: String(12)", "КПП: String(9)"] }
```
## Document
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `numberType` | `String` | NumberType |
| `numberLength` | `11` | NumberLength |
| `numberAllowedLength` | `Variable` | NumberAllowedLength |
| `numberPeriodicity` | `Year` | NumberPeriodicity |
| `checkUnique` | `true` | CheckUnique |
| `autonumbering` | `true` | Autonumbering |
| `posting` | `Allow` | Posting |
| `realTimePosting` | `Deny` | RealTimePosting |
| `registerRecordsDeletion` | `AutoDelete` | RegisterRecordsDeletion |
| `registerRecordsWritingOnPost` | `WriteModified` | RegisterRecordsWritingOnPost |
| `postInPrivilegedMode` | `true` | PostInPrivilegedMode |
| `unpostInPrivilegedMode` | `true` | UnpostInPrivilegedMode |
| `registerRecords` | `[]` | RegisterRecords |
| `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
RegisterRecords — массив строк: `"AccumulationRegister.Продажи"`, `"InformationRegister.Цены"`.
```json
{
"type": "Document", "name": "ПриходнаяНакладная",
"registerRecords": ["AccumulationRegister.ОстаткиТоваров"],
"attributes": ["Организация: CatalogRef.Организации", "Контрагент: CatalogRef.Контрагенты"],
"tabularSections": { "Товары": ["Номенклатура: CatalogRef.Номенклатура", "Количество: Number(15,3)"] }
}
```
## Enum
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `values` | `[]` | → EnumValue в ChildObjects |
```json
{ "type": "Enum", "name": "Статусы", "values": ["Новый", "ВРаботе", "Закрыт"] }
```
## Constant
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `valueType` | `String` | Type |
`valueType` принимает shorthand типа: `"String(100)"`, `"Number(15,2)"`, `"Boolean"`, `"CatalogRef.Валюты"`.
```json
{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" }
```
## DefinedType
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `valueTypes` | `[]` | Type (составной тип) |
| `valueType` | — | Алиас для `valueTypes` (строка или массив) |
```json
{ "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] }
{ "type": "DefinedType", "name": "ФлагАктивности", "valueType": "Boolean" }
```
## Report
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
```json
{ "type": "Report", "name": "ОстаткиТоваров" }
```
## DataProcessor
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
```json
{ "type": "DataProcessor", "name": "ЗагрузкаДанных", "attributes": ["ПутьКФайлу: String(500)"] }
```
@@ -0,0 +1,136 @@
# Процессы и сервисные: BusinessProcess, Task, ExchangePlan, CommonModule, ScheduledJob, EventSubscription, DocumentJournal
## BusinessProcess
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `task` | `""` | Task (ссылка `Task.XXX`) |
| `numberType` | `String` | NumberType |
| `numberLength` | `11` | NumberLength |
| `checkUnique` | `true` | CheckUnique |
| `autonumbering` | `true` | Autonumbering |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
Модули: `Ext/ObjectModule.bsl`, `Ext/Flowchart.xml`.
```json
{ "type": "BusinessProcess", "name": "Задание", "task": "Task.ЗадачаИсполнителя", "attributes": ["Описание: String(200)"] }
```
## Task
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `numberType` | `String` | NumberType |
| `numberLength` | `14` | NumberLength |
| `checkUnique` | `true` | CheckUnique |
| `autonumbering` | `true` | Autonumbering |
| `descriptionLength` | `150` | DescriptionLength |
| `addressing` | `""` | Addressing (ссылка на РС адресации) |
| `mainAddressingAttribute` | `""` | MainAddressingAttribute |
| `currentPerformer` | `""` | CurrentPerformer |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
| `addressingAttributes` | `[]` | → AddressingAttribute (shorthand или объект) |
AddressingAttribute — shorthand `"Имя: Тип"` или объект `{ "name", "type", "addressingDimension" }`.
```json
{
"type": "Task", "name": "ЗадачаИсполнителя",
"addressingAttributes": ["Исполнитель: CatalogRef.Пользователи"]
}
```
## ExchangePlan
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `codeLength` | `9` | CodeLength |
| `descriptionLength` | `100` | DescriptionLength |
| `distributedInfoBase` | `false` | DistributedInfoBase |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
Модули: `Ext/ObjectModule.bsl`, `Ext/Content.xml`.
```json
{ "type": "ExchangePlan", "name": "ОбменССайтом", "attributes": ["АдресСервера: String(200)"] }
```
## CommonModule
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `context` | — | Шорткат (см. ниже) |
| `global` | `false` | Global |
| `server` | `false` | Server |
| `serverCall` | `false` | ServerCall |
| `clientManagedApplication` | `false` | ClientManagedApplication |
| `externalConnection` | `false` | ExternalConnection |
| `privileged` | `false` | Privileged |
| `returnValuesReuse` | `DontUse` | ReturnValuesReuse |
Шорткаты `context`: `"server"` → Server+ServerCall, `"client"` → ClientManagedApplication, `"serverClient"` → Server+ClientManagedApplication.
```json
{ "type": "CommonModule", "name": "ОбщиеФункции", "context": "serverClient" }
```
## ScheduledJob
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `methodName` | `""` | MethodName |
| `description` | = synonym | Description |
| `use` | `false` | Use |
| `predefined` | `false` | Predefined |
| `restartCountOnFailure` | `3` | RestartCountOnFailure |
| `restartIntervalOnFailure` | `10` | RestartIntervalOnFailure |
Формат `methodName`: `"МодульСервер.Процедура"` — авто-дополняется до `CommonModule.МодульСервер.Процедура`.
```json
{ "type": "ScheduledJob", "name": "ОбменДанными", "methodName": "ОбменДаннымиСервер.Выполнить" }
```
## EventSubscription
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `source` | `[]` | Source (массив, формат `XxxObject.Name`) |
| `event` | `BeforeWrite` | Event |
| `handler` | `""` | Handler |
Формат `handler`: `"МодульСервер.Процедура"` — авто-дополняется до `CommonModule.МодульСервер.Процедура`.
Значения `event`: `BeforeWrite`, `OnWrite`, `BeforeDelete`, `OnReadAtServer`, `FillCheckProcessing`.
Формат `source`: `"CatalogObject.Xxx"`, `"DocumentObject.Xxx"`.
```json
{ "type": "EventSubscription", "name": "ПередЗаписью", "source": ["CatalogObject.Контрагенты"], "event": "BeforeWrite", "handler": "ОбщиеФункции.ПередЗаписью" }
```
## DocumentJournal
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `registeredDocuments` | `[]` | RegisteredDocuments (массив `"Document.Xxx"`) |
| `columns` | `[]` | → Column |
Колонки — строка `"Имя"` или объект `{ "name", "synonym", "indexing": "Index"/"DontIndex", "references": ["Document.Xxx.Attribute.Yyy"] }`.
```json
{
"type": "DocumentJournal", "name": "Взаимодействия",
"registeredDocuments": ["Document.Встреча", "Document.Звонок"],
"columns": [{ "name": "Организация", "indexing": "Index", "references": ["Document.Встреча.Attribute.Организация"] }]
}
```
## Зависимости
- **ScheduledJob/EventSubscription** — процедура-обработчик должна существовать в модуле (экспортная)
- **BusinessProcess** → `Task` (задача должна существовать)
@@ -0,0 +1,174 @@
# Регистры и планы: InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes
## Измерения и ресурсы (общее)
Синтаксис аналогичен реквизитам (shorthand `"Имя: Тип | флаги"`).
Флаги измерений: `master`, `mainFilter`, `denyIncomplete`, `useInTotals` (AccumulationRegister only, default `true`).
```json
"dimensions": ["Организация: CatalogRef.Организации | master, mainFilter, denyIncomplete"],
"resources": ["Сумма: Number(15,2)"]
```
---
## InformationRegister
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `writeMode` | `Independent` | WriteMode |
| `periodicity` | `Nonperiodical` | InformationRegisterPeriodicity |
| `mainFilterOnPeriod` | авто* | MainFilterOnPeriod |
| `dimensions` | `[]` | → Dimension |
| `resources` | `[]` | → Resource |
| `attributes` | `[]` | → Attribute |
\* `mainFilterOnPeriod` = `true` если `periodicity` != `Nonperiodical`.
```json
{
"type": "InformationRegister", "name": "КурсыВалют", "periodicity": "Day",
"dimensions": ["Валюта: CatalogRef.Валюты | master, mainFilter, denyIncomplete"],
"resources": ["Курс: Number(15,4)", "Кратность: Number(10,0)"]
}
```
## AccumulationRegister
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `registerType` | `Balance` | RegisterType (`Balance` / `Turnovers`) |
| `enableTotalsSplitting` | `true` | EnableTotalsSplitting |
| `dimensions` | `[]` | → Dimension |
| `resources` | `[]` | → Resource |
| `attributes` | `[]` | → Attribute |
```json
{
"type": "AccumulationRegister", "name": "ОстаткиТоваров", "registerType": "Balance",
"dimensions": ["Номенклатура: CatalogRef.Номенклатура"],
"resources": ["Количество: Number(15,3)"]
}
```
## AccountingRegister
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `chartOfAccounts` | `""` | ChartOfAccounts (**обязательная** ссылка на план счетов) |
| `correspondence` | `false` | Correspondence |
| `periodAdjustmentLength` | `0` | PeriodAdjustmentLength |
| `dimensions` | `[]` | → Dimension |
| `resources` | `[]` | → Resource |
| `attributes` | `[]` | → Attribute |
```json
{
"type": "AccountingRegister", "name": "Хозрасчетный",
"chartOfAccounts": "ChartOfAccounts.Хозрасчетный",
"dimensions": ["Организация: CatalogRef.Организации"],
"resources": ["Сумма: Number(15,2)"]
}
```
## CalculationRegister
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `chartOfCalculationTypes` | `""` | ChartOfCalculationTypes (**обязательная** ссылка на ПВР) |
| `periodicity` | `Month` | Periodicity |
| `actionPeriod` | `false` | ActionPeriod |
| `basePeriod` | `false` | BasePeriod |
| `schedule` | `""` | Schedule (ссылка на РС графиков) |
| `dimensions` | `[]` | → Dimension |
| `resources` | `[]` | → Resource |
| `attributes` | `[]` | → Attribute |
```json
{
"type": "CalculationRegister", "name": "Начисления",
"chartOfCalculationTypes": "ChartOfCalculationTypes.Начисления",
"periodicity": "Month",
"dimensions": ["Сотрудник: CatalogRef.Сотрудники"],
"resources": ["Сумма: Number(15,2)"]
}
```
---
## ChartOfCharacteristicTypes
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `codeLength` | `9` | CodeLength |
| `descriptionLength` | `25` | DescriptionLength |
| `autonumbering` | `true` | Autonumbering |
| `checkUnique` | `false` | CheckUnique |
| `characteristicExtValues` | `""` | CharacteristicExtValues |
| `valueTypes` | авто* | Type (составной тип значений характеристик) |
| `hierarchical` | `false` | Hierarchical |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
\* По умолчанию: Boolean, String(100), Number(15,2), DateTime.
```json
{
"type": "ChartOfCharacteristicTypes", "name": "ВидыСубконто",
"valueTypes": ["CatalogRef.Номенклатура", "CatalogRef.Контрагенты", "Boolean", "String", "Number(15,2)"]
}
```
## ChartOfAccounts
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `extDimensionTypes` | `""` | ExtDimensionTypes (ссылка на ПВХ) |
| `maxExtDimensionCount` | `3` | MaxExtDimensionCount |
| `codeMask` | `""` | CodeMask |
| `codeLength` | `8` | CodeLength |
| `descriptionLength` | `120` | DescriptionLength |
| `codeSeries` | `WholeChartOfAccounts` | CodeSeries |
| `autoOrderByCode` | `true` | AutoOrderByCode |
| `orderLength` | `5` | OrderLength |
| `hierarchical` | `false` | Hierarchical |
| `accountingFlags` | `[]` | → AccountingFlag (Boolean-тип, массив имён) |
| `extDimensionAccountingFlags` | `[]` | → ExtDimensionAccountingFlag (Boolean-тип, массив имён) |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
```json
{
"type": "ChartOfAccounts", "name": "Хозрасчетный",
"extDimensionTypes": "ChartOfCharacteristicTypes.ВидыСубконто", "maxExtDimensionCount": 3,
"codeLength": 8, "codeMask": "@@@.@@.@",
"accountingFlags": ["Валютный", "Количественный"],
"extDimensionAccountingFlags": ["Суммовой", "Валютный"]
}
```
## ChartOfCalculationTypes
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `codeLength` | `9` | CodeLength |
| `descriptionLength` | `25` | DescriptionLength |
| `autonumbering` | `true` | Autonumbering |
| `checkUnique` | `false` | CheckUnique |
| `dependenceOnCalculationTypes` | `NotUsed` | DependenceOnCalculationTypes |
| `actionPeriodUse` | `false` | ActionPeriodUse |
| `attributes` | `[]` | → Attribute |
| `tabularSections` | `{}` | → TabularSection |
`dependenceOnCalculationTypes`: `NotUsed`, `ExclusionAndDependence`, `ExclusionOnly`.
```json
{ "type": "ChartOfCalculationTypes", "name": "Начисления", "dependenceOnCalculationTypes": "ExclusionAndDependence" }
```
## Зависимости
- **AccountingRegister** требует `ChartOfAccounts` (и документ-регистратор)
- **CalculationRegister** требует `ChartOfCalculationTypes` (и документ-регистратор)
- **ChartOfAccounts** ссылается на `ChartOfCharacteristicTypes` через `extDimensionTypes`
@@ -0,0 +1,103 @@
# Веб-сервисы: HTTPService, WebService
## HTTPService
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `rootURL` | `= name.toLower()` | RootURL |
| `reuseSessions` | `DontUse` | ReuseSessions |
| `sessionMaxAge` | `20` | SessionMaxAge |
| `urlTemplates` | `{}` | → URLTemplate |
Модули: `Ext/Module.bsl`.
### urlTemplates — вложенная структура
`urlTemplates` — объект `{ "TemplateName": templateDef, ... }`.
Каждый `templateDef`:
- Строка — URL-шаблон: `"/v1/users"` (без методов)
- Объект:
| Поле | Умолчание | Описание |
|------|----------|----------|
| `template` | `"/templatename"` | URL-путь (с параметрами `{id}`) |
| `methods` | `{}` | Методы: `{ "MethodName": "HTTPMethod" }` |
Допустимые HTTPMethod: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `CONNECT`, `TRACE`, `MERGE`.
Обработчик метода генерируется автоматически: `{TemplateName}{MethodName}` — должен быть реализован в `Ext/Module.bsl`.
```json
{
"type": "HTTPService", "name": "API", "rootURL": "api",
"urlTemplates": {
"Users": {
"template": "/v1/users/{id}",
"methods": { "Get": "GET", "Create": "POST", "Update": "PUT", "Delete": "DELETE" }
},
"Health": "/health"
}
}
```
## WebService
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `namespace` | `""` | Namespace (URI пространства имён WSDL) |
| `xdtoPackages` | `""` | XDTOPackages |
| `reuseSessions` | `DontUse` | ReuseSessions |
| `sessionMaxAge` | `20` | SessionMaxAge |
| `operations` | `{}` | → Operation |
Модули: `Ext/Module.bsl`.
### operations — вложенная структура
`operations` — объект `{ "OperationName": operationDef, ... }`.
Каждый `operationDef`:
- Строка — тип возврата: `"xs:boolean"` (параметров нет, обработчик = имя операции)
- Объект:
| Поле | Умолчание | Описание |
|------|----------|----------|
| `returnType` | `xs:string` | XDTO-тип возврата |
| `nillable` | `false` | Может ли вернуть null |
| `transactioned` | `false` | Выполнять в транзакции |
| `handler` | `= operationName` | Имя процедуры в модуле |
| `parameters` | `{}` | Параметры операции |
### parameters — параметры операции
`parameters` — объект `{ "ParamName": paramDef, ... }`.
Каждый `paramDef`:
- Строка — XDTO-тип: `"xs:string"` (direction = In, nillable = true)
- Объект:
| Поле | Умолчание | Описание |
|------|----------|----------|
| `type` | `xs:string` | XDTO-тип параметра |
| `nillable` | `true` | Может ли быть null |
| `direction` | `In` | Направление: `In`, `Out`, `InOut` |
Стандартные XDTO-типы: `xs:string`, `xs:boolean`, `xs:int`, `xs:long`, `xs:decimal`, `xs:dateTime`, `xs:base64Binary`.
```json
{
"type": "WebService", "name": "DataExchange",
"namespace": "http://www.1c.ru/DataExchange",
"operations": {
"TestConnection": {
"returnType": "xs:boolean",
"handler": "ПроверкаПодключения",
"parameters": {
"ErrorMessage": { "type": "xs:string", "direction": "Out" }
}
},
"GetVersion": "xs:string"
}
}
```
@@ -1,4 +1,4 @@
# meta-compile v1.0 — Compile 1C metadata object from JSON # meta-compile v1.2 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -21,6 +21,28 @@ if (-not (Test-Path $JsonPath)) {
$json = Get-Content -Raw -Encoding UTF8 $JsonPath $json = Get-Content -Raw -Encoding UTF8 $JsonPath
$def = $json | ConvertFrom-Json $def = $json | ConvertFrom-Json
# --- Batch mode: JSON array of objects ---
if ($def -is [array] -or ($null -ne $def -and $def.GetType().BaseType.Name -eq 'Array')) {
$batchOk = 0
$batchFail = 0
$idx = 0
foreach ($item in $def) {
$idx++
$tmpJson = Join-Path ([System.IO.Path]::GetTempPath()) "meta-compile-batch-$idx.json"
try {
$item | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpJson
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$PSCommandPath`" -JsonPath `"$tmpJson`" -OutputDir `"$OutputDir`"" -NoNewWindow -Wait -PassThru
if ($proc.ExitCode -eq 0) { $batchOk++ } else { $batchFail++ }
} finally {
Remove-Item $tmpJson -Force -ErrorAction SilentlyContinue
}
}
Write-Host ""
Write-Host "=== Batch: $idx objects, $batchOk compiled, $batchFail failed ==="
if ($batchFail -gt 0) { exit 1 }
exit 0
}
# Normalize field synonyms: accept "objectType" as alias for "type" # Normalize field synonyms: accept "objectType" as alias for "type"
if (-not $def.type -and $def.objectType) { if (-not $def.type -and $def.objectType) {
$def | Add-Member -NotePropertyName "type" -NotePropertyValue $def.objectType $def | Add-Member -NotePropertyName "type" -NotePropertyValue $def.objectType
@@ -163,6 +185,10 @@ $script:typeSynonyms["бизнеспроцессссылка"] = "Bus
$script:typeSynonyms["задачассылка"] = "TaskRef" $script:typeSynonyms["задачассылка"] = "TaskRef"
$script:typeSynonyms["определяемыйтип"] = "DefinedType" $script:typeSynonyms["определяемыйтип"] = "DefinedType"
$script:typeSynonyms["definedtype"] = "DefinedType" $script:typeSynonyms["definedtype"] = "DefinedType"
# English lowercase ref synonyms
$script:typeSynonyms["catalogref"] = "CatalogRef"
$script:typeSynonyms["documentref"] = "DocumentRef"
$script:typeSynonyms["enumref"] = "EnumRef"
function Resolve-TypeStr { function Resolve-TypeStr {
param([string]$typeStr) param([string]$typeStr)
@@ -217,7 +243,7 @@ function Emit-TypeContent {
# String or String(N) # String or String(N)
if ($typeStr -match '^String(\((\d+)\))?$') { if ($typeStr -match '^String(\((\d+)\))?$') {
$len = if ($Matches[2]) { $Matches[2] } else { "0" } $len = if ($Matches[2]) { $Matches[2] } else { "10" }
X "$indent<v8:Type>xs:string</v8:Type>" X "$indent<v8:Type>xs:string</v8:Type>"
X "$indent<v8:StringQualifiers>" X "$indent<v8:StringQualifiers>"
X "$indent`t<v8:Length>$len</v8:Length>" X "$indent`t<v8:Length>$len</v8:Length>"
@@ -226,6 +252,17 @@ function Emit-TypeContent {
return return
} }
# Number without params → Number(10,0)
if ($typeStr -eq "Number") {
X "$indent<v8:Type>xs:decimal</v8:Type>"
X "$indent<v8:NumberQualifiers>"
X "$indent`t<v8:Digits>10</v8:Digits>"
X "$indent`t<v8:FractionDigits>0</v8:FractionDigits>"
X "$indent`t<v8:AllowedSign>Any</v8:AllowedSign>"
X "$indent</v8:NumberQualifiers>"
return
}
# Number(D,F) or Number(D,F,nonneg) # Number(D,F) or Number(D,F,nonneg)
if ($typeStr -match '^Number\((\d+),(\d+)(,nonneg)?\)$') { if ($typeStr -match '^Number\((\d+),(\d+)(,nonneg)?\)$') {
$digits = $Matches[1] $digits = $Matches[1]
@@ -263,6 +300,12 @@ function Emit-TypeContent {
return return
} }
# ValueStorage
if ($typeStr -eq "ValueStorage") {
X "$indent<v8:Type>xs:base64Binary</v8:Type>"
return
}
# Reference types — use local xmlns declaration for 1C compatibility # Reference types — use local xmlns declaration for 1C compatibility
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') { if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') {
X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>" X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>"
@@ -311,6 +354,21 @@ function Emit-FillValue {
# --- 5. Attribute shorthand parser --- # --- 5. Attribute shorthand parser ---
function Build-TypeStr {
param($obj)
$t = if ($obj.valueType) { "$($obj.valueType)" } elseif ($obj.type) { "$($obj.type)" } else { "" }
if ($t -and -not $t.Contains('(')) {
if ($t -eq "String" -and $obj.length) {
$t = "String($($obj.length))"
} elseif ($t -eq "Number" -and $obj.length) {
$p = if ($obj.precision) { $obj.precision } else { 0 }
$nn = if ($obj.nonneg -or $obj.nonnegative) { ",nonneg" } else { "" }
$t = "Number($($obj.length),$p$nn)"
}
}
return $t
}
function Parse-AttributeShorthand { function Parse-AttributeShorthand {
param($val) param($val)
@@ -347,7 +405,7 @@ function Parse-AttributeShorthand {
$name = "$($val.name)" $name = "$($val.name)"
return @{ return @{
name = $name name = $name
type = if ($val.type) { "$($val.type)" } else { "" } type = Build-TypeStr $val
synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name } synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name }
comment = if ($val.comment) { "$($val.comment)" } else { "" } comment = if ($val.comment) { "$($val.comment)" } else { "" }
flags = @(if ($val.flags) { $val.flags } else { @() }) flags = @(if ($val.flags) { $val.flags } else { @() })
@@ -436,13 +494,16 @@ $script:generatedTypes = @{
@{ prefix = "CalculationRegisterList"; category = "List" } @{ prefix = "CalculationRegisterList"; category = "List" }
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" } @{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" } @{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
@{ prefix = "RecalculationsManager"; category = "Recalcs" }
) )
"ChartOfAccounts" = @( "ChartOfAccounts" = @(
@{ prefix = "ChartOfAccountsObject"; category = "Object" } @{ prefix = "ChartOfAccountsObject"; category = "Object" }
@{ prefix = "ChartOfAccountsRef"; category = "Ref" } @{ prefix = "ChartOfAccountsRef"; category = "Ref" }
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" } @{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
@{ prefix = "ChartOfAccountsList"; category = "List" } @{ prefix = "ChartOfAccountsList"; category = "List" }
@{ prefix = "ChartOfAccountsManager"; category = "Manager" } @{ prefix = "ChartOfAccountsManager"; category = "Manager" }
@{ prefix = "ChartOfAccountsExtDimensionTypes"; category = "ExtDimensionTypes" }
@{ prefix = "ChartOfAccountsExtDimensionTypesRow"; category = "ExtDimensionTypesRow" }
) )
"ChartOfCharacteristicTypes" = @( "ChartOfCharacteristicTypes" = @(
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" } @{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
@@ -487,6 +548,9 @@ $script:generatedTypes = @{
@{ prefix = "ExchangePlanList"; category = "List" } @{ prefix = "ExchangePlanList"; category = "List" }
@{ prefix = "ExchangePlanManager"; category = "Manager" } @{ prefix = "ExchangePlanManager"; category = "Manager" }
) )
"DefinedType" = @(
@{ prefix = "DefinedType"; category = "DefinedType" }
)
"DocumentJournal" = @( "DocumentJournal" = @(
@{ prefix = "DocumentJournalSelection"; category = "Selection" } @{ prefix = "DocumentJournalSelection"; category = "Selection" }
@{ prefix = "DocumentJournalList"; category = "List" } @{ prefix = "DocumentJournalList"; category = "List" }
@@ -592,9 +656,26 @@ function Emit-TabularStandardAttributes {
# --- 8. Attribute emitter --- # --- 8. Attribute emitter ---
$script:reservedAttrNames = @{
"Ref"="Ссылка"; "DeletionMark"="ПометкаУдаления"; "Code"="Код"; "Description"="Наименование"
"Date"="Дата"; "Number"="Номер"; "Posted"="Проведен"; "Parent"="Родитель"; "Owner"="Владелец"
"IsFolder"="ЭтоГруппа"; "Predefined"="Предопределенный"; "PredefinedDataName"="ИмяПредопределенныхДанных"
"Recorder"="Регистратор"; "Period"="Период"; "LineNumber"="НомерСтроки"; "Active"="Активность"
"Order"="Порядок"; "Type"="Тип"; "OffBalance"="Забалансовый"
"Started"="Стартован"; "Completed"="Завершен"; "HeadTask"="ВедущаяЗадача"
"Executed"="Выполнена"; "RoutePoint"="ТочкаМаршрута"; "BusinessProcess"="БизнесПроцесс"
"ThisNode"="ЭтотУзел"; "SentNo"="НомерОтправленного"; "ReceivedNo"="НомерПринятого"
"CalculationType"="ВидРасчета"; "RegistrationPeriod"="ПериодРегистрации"; "ReversingEntry"="СторноЗапись"
"Account"="Счет"; "ValueType"="ТипЗначения"; "ActionPeriodIsBasic"="ПериодДействияБазовый"
}
function Emit-Attribute { function Emit-Attribute {
param([string]$indent, $parsed, [string]$context) param([string]$indent, $parsed, [string]$context)
# $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register" # $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register"
$attrName = $parsed.name
if ($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 $uuid = New-Guid-String
X "$indent<Attribute uuid=`"$uuid`">" X "$indent<Attribute uuid=`"$uuid`">"
X "$indent`t<Properties>" X "$indent`t<Properties>"
@@ -1055,7 +1136,7 @@ function Emit-DocumentProperties {
if ($regRecords.Count -gt 0) { if ($regRecords.Count -gt 0) {
X "$i<RegisterRecords>" X "$i<RegisterRecords>"
foreach ($rr in $regRecords) { foreach ($rr in $regRecords) {
X "$i`t<xr:Record>$rr</xr:Record>" X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$rr</xr:Item>"
} }
X "$i</RegisterRecords>" X "$i</RegisterRecords>"
} else { } else {
@@ -1117,7 +1198,8 @@ function Emit-ConstantProperties {
X "$i<Comment/>" X "$i<Comment/>"
# Type # Type
$valueType = if ($def.valueType) { "$($def.valueType)" } else { "String" } $valueType = Build-TypeStr $def
if (-not $valueType) { $valueType = "String" }
Emit-ValueType $i $valueType Emit-ValueType $i $valueType
X "$i<UseStandardCommands>true</UseStandardCommands>" X "$i<UseStandardCommands>true</UseStandardCommands>"
@@ -1242,10 +1324,12 @@ function Emit-DefinedTypeProperties {
Emit-MLText $i "Synonym" $synonym Emit-MLText $i "Synonym" $synonym
X "$i<Comment/>" X "$i<Comment/>"
# Type — composite type with multiple v8:Type entries # Type — composite type with multiple v8:Type entries (accept both valueType and valueTypes)
$valueTypes = @() $valueTypes = @()
if ($def.valueTypes) { if ($def.valueTypes) {
$valueTypes = @($def.valueTypes) $valueTypes = @($def.valueTypes)
} elseif ($def.valueType) {
$valueTypes = @($def.valueType)
} }
if ($valueTypes.Count -gt 0) { if ($valueTypes.Count -gt 0) {
X "$i<Type>" X "$i<Type>"
@@ -1322,6 +1406,10 @@ function Emit-ScheduledJobProperties {
X "$i<Comment/>" X "$i<Comment/>"
$methodName = if ($def.methodName) { "$($def.methodName)" } else { "" } $methodName = if ($def.methodName) { "$($def.methodName)" } else { "" }
# Ensure CommonModule. prefix
if ($methodName -and -not $methodName.StartsWith("CommonModule.")) {
$methodName = "CommonModule.$methodName"
}
X "$i<MethodName>$(Esc-Xml $methodName)</MethodName>" X "$i<MethodName>$(Esc-Xml $methodName)</MethodName>"
$description = if ($def.description) { "$($def.description)" } else { $synonym } $description = if ($def.description) { "$($def.description)" } else { $synonym }
@@ -1368,6 +1456,10 @@ function Emit-EventSubscriptionProperties {
X "$i<Event>$event</Event>" X "$i<Event>$event</Event>"
$handler = if ($def.handler) { "$($def.handler)" } else { "" } $handler = if ($def.handler) { "$($def.handler)" } else { "" }
# Ensure CommonModule. prefix
if ($handler -and -not $handler.StartsWith("CommonModule.")) {
$handler = "CommonModule.$handler"
}
X "$i<Handler>$(Esc-Xml $handler)</Handler>" X "$i<Handler>$(Esc-Xml $handler)</Handler>"
} }
@@ -1440,19 +1532,13 @@ function Emit-ExchangePlanProperties {
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" } $codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "100" } $descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "100" }
$codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" }
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" } $codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
$checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" }
X "$i<CodeLength>$codeLength</CodeLength>" X "$i<CodeLength>$codeLength</CodeLength>"
X "$i<CodeType>$codeType</CodeType>"
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>" X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>" X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>" X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
X "$i<EditType>InDialog</EditType>" X "$i<EditType>InDialog</EditType>"
X "$i<CheckUnique>$checkUnique</CheckUnique>"
X "$i<Autonumbering>$autonumbering</Autonumbering>"
Emit-StandardAttributes $i "ExchangePlan" Emit-StandardAttributes $i "ExchangePlan"
@@ -1509,13 +1595,11 @@ function Emit-ChartOfCharacteristicTypesProperties {
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" } $codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" } $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" } $codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" } $autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
$checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" } $checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" }
X "$i<CodeLength>$codeLength</CodeLength>" X "$i<CodeLength>$codeLength</CodeLength>"
X "$i<CodeType>$codeType</CodeType>"
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>" X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>" X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
X "$i<CheckUnique>$checkUnique</CheckUnique>" X "$i<CheckUnique>$checkUnique</CheckUnique>"
@@ -1541,7 +1625,7 @@ function Emit-ChartOfCharacteristicTypesProperties {
X "$i`t<v8:Type>xs:boolean</v8:Type>" X "$i`t<v8:Type>xs:boolean</v8:Type>"
X "$i`t<v8:Type>xs:string</v8:Type>" X "$i`t<v8:Type>xs:string</v8:Type>"
X "$i`t<v8:StringQualifiers>" X "$i`t<v8:StringQualifiers>"
X "$i`t`t<v8:Length>0</v8:Length>" X "$i`t`t<v8:Length>100</v8:Length>"
X "$i`t`t<v8:AllowedLength>Variable</v8:AllowedLength>" X "$i`t`t<v8:AllowedLength>Variable</v8:AllowedLength>"
X "$i`t</v8:StringQualifiers>" X "$i`t</v8:StringQualifiers>"
X "$i`t<v8:Type>xs:decimal</v8:Type>" X "$i`t<v8:Type>xs:decimal</v8:Type>"
@@ -1685,14 +1769,10 @@ function Emit-ChartOfAccountsProperties {
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>" X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
X "$i<CodeSeries>$codeSeries</CodeSeries>" X "$i<CodeSeries>$codeSeries</CodeSeries>"
X "$i<CheckUnique>false</CheckUnique>" X "$i<CheckUnique>false</CheckUnique>"
X "$i<Autonumbering>true</Autonumbering>"
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>" X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
X "$i<AutoOrderByCode>$autoOrder</AutoOrderByCode>" X "$i<AutoOrderByCode>$autoOrder</AutoOrderByCode>"
X "$i<OrderLength>$orderLength</OrderLength>" X "$i<OrderLength>$orderLength</OrderLength>"
$hierarchical = if ($def.hierarchical -eq $true) { "true" } else { "false" }
X "$i<Hierarchical>$hierarchical</Hierarchical>"
X "$i<EditType>InDialog</EditType>" X "$i<EditType>InDialog</EditType>"
Emit-StandardAttributes $i "ChartOfAccounts" Emit-StandardAttributes $i "ChartOfAccounts"
@@ -1796,18 +1876,14 @@ function Emit-ChartOfCalculationTypesProperties {
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" } $descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
$codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" } $codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" }
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" } $codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
$autonumbering = if ($def.autonumbering -eq $false) { "false" } else { "true" }
$checkUnique = if ($def.checkUnique -eq $true) { "true" } else { "false" }
X "$i<CodeLength>$codeLength</CodeLength>" X "$i<CodeLength>$codeLength</CodeLength>"
X "$i<CodeType>$codeType</CodeType>" X "$i<CodeType>$codeType</CodeType>"
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>" X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>" X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>" X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
X "$i<CheckUnique>$checkUnique</CheckUnique>"
X "$i<Autonumbering>$autonumbering</Autonumbering>"
$dependence = if ($def.dependenceOnCalculationTypes) { "$($def.dependenceOnCalculationTypes)" } else { "NotUsed" } $dependence = if ($def.dependenceOnCalculationTypes) { "$($def.dependenceOnCalculationTypes)" } else { "DontUse" }
X "$i<DependenceOnCalculationTypes>$dependence</DependenceOnCalculationTypes>" X "$i<DependenceOnCalculationTypes>$dependence</DependenceOnCalculationTypes>"
# BaseCalculationTypes # BaseCalculationTypes
@@ -1941,6 +2017,13 @@ function Emit-BusinessProcessProperties {
Emit-StandardAttributes $i "BusinessProcess" Emit-StandardAttributes $i "BusinessProcess"
X "$i<Characteristics/>" X "$i<Characteristics/>"
$task = if ($def.task) { "$($def.task)" } else { "" }
if ($task) {
X "$i<Task>$task</Task>"
} else {
X "$i<Task/>"
}
X "$i<BasedOn/>" X "$i<BasedOn/>"
X "$i<InputByString>" X "$i<InputByString>"
X "$i`t<xr:Field>BusinessProcess.$objName.StandardAttribute.Number</xr:Field>" X "$i`t<xr:Field>BusinessProcess.$objName.StandardAttribute.Number</xr:Field>"
@@ -2330,13 +2413,11 @@ function Emit-AddressingAttribute {
$addressingDimension = "" $addressingDimension = ""
$indexing = "Index" $indexing = "Index"
if ($addrDef -is [string]) { $parsed = Parse-AttributeShorthand $addrDef
$name = "$addrDef" $name = $parsed.name
$attrSynonym = Split-CamelCase $name $attrSynonym = $parsed.synonym
} else { $typeStr = $parsed.type
$name = "$($addrDef.name)" if ($addrDef -isnot [string]) {
$attrSynonym = if ($addrDef.synonym) { "$($addrDef.synonym)" } else { Split-CamelCase $name }
if ($addrDef.type) { $typeStr = "$($addrDef.type)" }
if ($addrDef.addressingDimension) { $addressingDimension = "$($addrDef.addressingDimension)" } if ($addrDef.addressingDimension) { $addressingDimension = "$($addrDef.addressingDimension)" }
if ($addrDef.indexing) { $indexing = "$($addrDef.indexing)" } if ($addrDef.indexing) { $indexing = "$($addrDef.indexing)" }
} }
@@ -2708,7 +2789,7 @@ if ($objType -in $typesWithModule) {
if ($objType -eq "ExchangePlan") { if ($objType -eq "ExchangePlan") {
$contentPath = Join-Path $extDir "Content.xml" $contentPath = Join-Path $extDir "Content.xml"
if (-not (Test-Path $contentPath)) { if (-not (Test-Path $contentPath)) {
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/MDClasses`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"/>`r`n" $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"
[System.IO.File]::WriteAllText($contentPath, $contentXml, $enc) [System.IO.File]::WriteAllText($contentPath, $contentXml, $enc)
$modulesCreated += $contentPath $modulesCreated += $contentPath
} }
@@ -2716,7 +2797,7 @@ if ($objType -eq "ExchangePlan") {
if ($objType -eq "BusinessProcess") { if ($objType -eq "BusinessProcess") {
$flowchartPath = Join-Path $extDir "Flowchart.xml" $flowchartPath = Join-Path $extDir "Flowchart.xml"
if (-not (Test-Path $flowchartPath)) { if (-not (Test-Path $flowchartPath)) {
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`"/>`r`n" $flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"2.17`"/>`r`n"
[System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc) [System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc)
$modulesCreated += $flowchartPath $modulesCreated += $flowchartPath
} }
@@ -2842,3 +2923,24 @@ switch ($regResult) {
"no-childobj" { Write-Warning "Configuration.xml found but <ChildObjects> not found" } "no-childobj" { Write-Warning "Configuration.xml found but <ChildObjects> not found" }
"no-config" { Write-Host " Configuration.xml: not found at $configXmlPath (register manually)" } "no-config" { Write-Host " Configuration.xml: not found at $configXmlPath (register manually)" }
} }
# Cross-reference hints
if ($objType -eq "AccountingRegister" -and -not $def.chartOfAccounts) {
Write-Host "[HINT] AccountingRegister requires ChartOfAccounts reference:"
Write-Host " /meta-edit -Operation modify-property -Value `"ChartOfAccounts=ChartOfAccounts.XXX`""
}
if ($objType -eq "CalculationRegister" -and -not $def.chartOfCalculationTypes) {
Write-Host "[HINT] CalculationRegister requires ChartOfCalculationTypes reference:"
Write-Host " /meta-edit -Operation modify-property -Value `"ChartOfCalculationTypes=ChartOfCalculationTypes.XXX`""
}
if ($objType -eq "BusinessProcess" -and -not $def.task) {
Write-Host "[HINT] BusinessProcess requires Task reference:"
Write-Host " /meta-edit -Operation modify-property -Value `"Task=Task.XXX`""
}
if ($objType -eq "ChartOfAccounts") {
$maxExtDim = if ($null -ne $def.maxExtDimensionCount) { [int]$def.maxExtDimensionCount } else { 0 }
if ($maxExtDim -gt 0 -and -not $def.extDimensionTypes) {
Write-Host "[HINT] ChartOfAccounts with MaxExtDimensionCount>0 requires ExtDimensionTypes:"
Write-Host " /meta-edit -Operation modify-property -Value `"ExtDimensionTypes=ChartOfCharacteristicTypes.XXX`""
}
}
@@ -1,12 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-compile v1.0 — Compile 1C metadata object from JSON # meta-compile v1.2 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
import os import os
import re import re
import subprocess
import sys import sys
import tempfile
import uuid import uuid
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -81,6 +83,27 @@ with open(json_path, 'r', encoding='utf-8-sig') as f:
defn = json.loads(json_text) defn = json.loads(json_text)
# --- Batch mode: JSON array of objects ---
if isinstance(defn, list):
batch_ok = 0
batch_fail = 0
for idx, item in enumerate(defn, 1):
tmp_fd, tmp_path = tempfile.mkstemp(suffix='.json', prefix=f'meta-compile-batch-{idx}-')
try:
with os.fdopen(tmp_fd, 'w', encoding='utf-8') as f:
json.dump(item, f, ensure_ascii=False, indent=2)
rc = subprocess.call([sys.executable, __file__, '-JsonPath', tmp_path, '-OutputDir', output_dir])
if rc == 0:
batch_ok += 1
else:
batch_fail += 1
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
print()
print(f"=== Batch: {len(defn)} objects, {batch_ok} compiled, {batch_fail} failed ===")
sys.exit(1 if batch_fail > 0 else 0)
# Normalize field synonyms: accept "objectType" as alias for "type" # Normalize field synonyms: accept "objectType" as alias for "type"
if not defn.get('type') and defn.get('objectType'): if not defn.get('type') and defn.get('objectType'):
defn['type'] = defn['objectType'] defn['type'] = defn['objectType']
@@ -175,6 +198,10 @@ type_synonyms = {
'задачассылка': 'TaskRef', 'задачассылка': 'TaskRef',
'определяемыйтип': 'DefinedType', 'определяемыйтип': 'DefinedType',
'definedtype': 'DefinedType', 'definedtype': 'DefinedType',
# English lowercase ref synonyms
'catalogref': 'CatalogRef',
'documentref': 'DocumentRef',
'enumref': 'EnumRef',
} }
def resolve_type_str(type_str): def resolve_type_str(type_str):
@@ -221,13 +248,23 @@ def emit_type_content(indent, type_str):
# String or String(N) # String or String(N)
m = re.match(r'^String(\((\d+)\))?$', type_str) m = re.match(r'^String(\((\d+)\))?$', type_str)
if m: if m:
length = m.group(2) if m.group(2) else '0' length = m.group(2) if m.group(2) else '10'
X(f'{indent}<v8:Type>xs:string</v8:Type>') X(f'{indent}<v8:Type>xs:string</v8:Type>')
X(f'{indent}<v8:StringQualifiers>') X(f'{indent}<v8:StringQualifiers>')
X(f'{indent}\t<v8:Length>{length}</v8:Length>') X(f'{indent}\t<v8:Length>{length}</v8:Length>')
X(f'{indent}\t<v8:AllowedLength>Variable</v8:AllowedLength>') X(f'{indent}\t<v8:AllowedLength>Variable</v8:AllowedLength>')
X(f'{indent}</v8:StringQualifiers>') X(f'{indent}</v8:StringQualifiers>')
return return
# Number without params -> Number(10,0)
if type_str == 'Number':
X(f'{indent}<v8:Type>xs:decimal</v8:Type>')
X(f'{indent}<v8:NumberQualifiers>')
X(f'{indent}\t<v8:Digits>10</v8:Digits>')
X(f'{indent}\t<v8:FractionDigits>0</v8:FractionDigits>')
X(f'{indent}\t<v8:AllowedSign>Any</v8:AllowedSign>')
X(f'{indent}</v8:NumberQualifiers>')
return
# Number(D,F) or Number(D,F,nonneg) # Number(D,F) or Number(D,F,nonneg)
m = re.match(r'^Number\((\d+),(\d+)(,nonneg)?\)$', type_str) m = re.match(r'^Number\((\d+),(\d+)(,nonneg)?\)$', type_str)
if m: if m:
@@ -260,6 +297,11 @@ def emit_type_content(indent, type_str):
dt_name = m.group(1) dt_name = m.group(1)
X(f'{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>') X(f'{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>')
return return
# ValueStorage
if type_str == 'ValueStorage':
X(f'{indent}<v8:Type>xs:base64Binary</v8:Type>')
return
# Reference types — use local xmlns declaration for 1C compatibility # Reference types — use local xmlns declaration for 1C compatibility
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$', type_str) m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$', type_str)
if m: if m:
@@ -296,6 +338,17 @@ def emit_fill_value(indent, type_str):
# 5. Attribute shorthand parser # 5. Attribute shorthand parser
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def build_type_str(obj):
t = str(obj.get('valueType') or obj.get('type') or '')
if t and '(' not in t:
if t == 'String' and obj.get('length'):
t = f"String({obj['length']})"
elif t == 'Number' and obj.get('length'):
prec = obj.get('precision', 0)
nn = ',nonneg' if obj.get('nonneg') or obj.get('nonnegative') else ''
t = f"Number({obj['length']},{prec}{nn})"
return t
def parse_attribute_shorthand(val): def parse_attribute_shorthand(val):
if isinstance(val, str): if isinstance(val, str):
parsed = { parsed = {
@@ -322,7 +375,7 @@ def parse_attribute_shorthand(val):
name = str(val.get('name', '')) name = str(val.get('name', ''))
return { return {
'name': name, 'name': name,
'type': str(val['type']) if val.get('type') else '', 'type': build_type_str(val),
'synonym': str(val['synonym']) if val.get('synonym') else split_camel_case(name), 'synonym': str(val['synonym']) if val.get('synonym') else split_camel_case(name),
'comment': str(val['comment']) if val.get('comment') else '', 'comment': str(val['comment']) if val.get('comment') else '',
'flags': list(val.get('flags', [])), 'flags': list(val.get('flags', [])),
@@ -406,6 +459,7 @@ generated_types = {
{'prefix': 'CalculationRegisterList', 'category': 'List'}, {'prefix': 'CalculationRegisterList', 'category': 'List'},
{'prefix': 'CalculationRegisterRecordSet', 'category': 'RecordSet'}, {'prefix': 'CalculationRegisterRecordSet', 'category': 'RecordSet'},
{'prefix': 'CalculationRegisterRecordKey', 'category': 'RecordKey'}, {'prefix': 'CalculationRegisterRecordKey', 'category': 'RecordKey'},
{'prefix': 'RecalculationsManager', 'category': 'Recalcs'},
], ],
'ChartOfAccounts': [ 'ChartOfAccounts': [
{'prefix': 'ChartOfAccountsObject', 'category': 'Object'}, {'prefix': 'ChartOfAccountsObject', 'category': 'Object'},
@@ -413,6 +467,8 @@ generated_types = {
{'prefix': 'ChartOfAccountsSelection', 'category': 'Selection'}, {'prefix': 'ChartOfAccountsSelection', 'category': 'Selection'},
{'prefix': 'ChartOfAccountsList', 'category': 'List'}, {'prefix': 'ChartOfAccountsList', 'category': 'List'},
{'prefix': 'ChartOfAccountsManager', 'category': 'Manager'}, {'prefix': 'ChartOfAccountsManager', 'category': 'Manager'},
{'prefix': 'ChartOfAccountsExtDimensionTypes', 'category': 'ExtDimensionTypes'},
{'prefix': 'ChartOfAccountsExtDimensionTypesRow', 'category': 'ExtDimensionTypesRow'},
], ],
'ChartOfCharacteristicTypes': [ 'ChartOfCharacteristicTypes': [
{'prefix': 'ChartOfCharacteristicTypesObject', 'category': 'Object'}, {'prefix': 'ChartOfCharacteristicTypesObject', 'category': 'Object'},
@@ -457,6 +513,9 @@ generated_types = {
{'prefix': 'ExchangePlanList', 'category': 'List'}, {'prefix': 'ExchangePlanList', 'category': 'List'},
{'prefix': 'ExchangePlanManager', 'category': 'Manager'}, {'prefix': 'ExchangePlanManager', 'category': 'Manager'},
], ],
'DefinedType': [
{'prefix': 'DefinedType', 'category': 'DefinedType'},
],
'DocumentJournal': [ 'DocumentJournal': [
{'prefix': 'DocumentJournalSelection', 'category': 'Selection'}, {'prefix': 'DocumentJournalSelection', 'category': 'Selection'},
{'prefix': 'DocumentJournalList', 'category': 'List'}, {'prefix': 'DocumentJournalList', 'category': 'List'},
@@ -554,7 +613,40 @@ def emit_tabular_standard_attributes(indent):
# 8. Attribute emitter # 8. Attribute emitter
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
RESERVED_ATTR_NAMES = {
'Ref', 'DeletionMark', 'Code', 'Description', 'Date', 'Number', 'Posted',
'Parent', 'Owner', 'IsFolder', 'Predefined', 'PredefinedDataName',
'Recorder', 'Period', 'LineNumber', 'Active', 'Order', 'Type', 'OffBalance',
'Started', 'Completed', 'HeadTask', 'Executed', 'RoutePoint', 'BusinessProcess',
'ThisNode', 'SentNo', 'ReceivedNo', 'CalculationType', 'RegistrationPeriod',
'ReversingEntry', 'Account', 'ValueType', 'ActionPeriodIsBasic',
}
RESERVED_ATTR_NAMES_RU = {
'\u0421\u0441\u044b\u043b\u043a\u0430', '\u041f\u043e\u043c\u0435\u0442\u043a\u0430\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u044f',
'\u041a\u043e\u0434', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435',
'\u0414\u0430\u0442\u0430', '\u041d\u043e\u043c\u0435\u0440', '\u041f\u0440\u043e\u0432\u0435\u0434\u0435\u043d',
'\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c', '\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446',
'\u042d\u0442\u043e\u0413\u0440\u0443\u043f\u043f\u0430', '\u041f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439',
'\u0418\u043c\u044f\u041f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0414\u0430\u043d\u043d\u044b\u0445',
'\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440', '\u041f\u0435\u0440\u0438\u043e\u0434',
'\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438', '\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c',
'\u041f\u043e\u0440\u044f\u0434\u043e\u043a', '\u0422\u0438\u043f', '\u0417\u0430\u0431\u0430\u043b\u0430\u043d\u0441\u043e\u0432\u044b\u0439',
'\u0421\u0442\u0430\u0440\u0442\u043e\u0432\u0430\u043d', '\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d',
'\u0412\u0435\u0434\u0443\u0449\u0430\u044f\u0417\u0430\u0434\u0430\u0447\u0430',
'\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430', '\u0422\u043e\u0447\u043a\u0430\u041c\u0430\u0440\u0448\u0440\u0443\u0442\u0430',
'\u0411\u0438\u0437\u043d\u0435\u0441\u041f\u0440\u043e\u0446\u0435\u0441\u0441',
'\u042d\u0442\u043e\u0442\u0423\u0437\u0435\u043b', '\u041d\u043e\u043c\u0435\u0440\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e',
'\u041d\u043e\u043c\u0435\u0440\u041f\u0440\u0438\u043d\u044f\u0442\u043e\u0433\u043e',
'\u0412\u0438\u0434\u0420\u0430\u0441\u0447\u0435\u0442\u0430', '\u041f\u0435\u0440\u0438\u043e\u0434\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438',
'\u0421\u0442\u043e\u0440\u043d\u043e\u0417\u0430\u043f\u0438\u0441\u044c',
'\u0421\u0447\u0435\u0442', '\u0422\u0438\u043f\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f',
'\u041f\u0435\u0440\u0438\u043e\u0434\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u0411\u0430\u0437\u043e\u0432\u044b\u0439',
}
def emit_attribute(indent, parsed, context): 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:
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() uid = new_uuid()
X(f'{indent}<Attribute uuid="{uid}">') X(f'{indent}<Attribute uuid="{uid}">')
X(f'{indent}\t<Properties>') X(f'{indent}\t<Properties>')
@@ -929,7 +1021,7 @@ def emit_document_properties(indent):
if reg_records: if reg_records:
X(f'{i}<RegisterRecords>') X(f'{i}<RegisterRecords>')
for rr in reg_records: for rr in reg_records:
X(f'{i}\t<xr:Record>{rr}</xr:Record>') X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{rr}</xr:Item>')
X(f'{i}</RegisterRecords>') X(f'{i}</RegisterRecords>')
else: else:
X(f'{i}<RegisterRecords/>') X(f'{i}<RegisterRecords/>')
@@ -975,7 +1067,8 @@ def emit_constant_properties(indent):
X(f'{i}<Name>{esc_xml(obj_name)}</Name>') X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym) emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>') X(f'{i}<Comment/>')
value_type = str(defn['valueType']) if defn.get('valueType') else 'String' # Type
value_type = build_type_str(defn) or 'String'
emit_value_type(i, value_type) emit_value_type(i, value_type)
X(f'{i}<UseStandardCommands>true</UseStandardCommands>') X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<DefaultForm/>') X(f'{i}<DefaultForm/>')
@@ -1072,7 +1165,11 @@ def emit_defined_type_properties(indent):
X(f'{i}<Name>{esc_xml(obj_name)}</Name>') X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym) emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>') X(f'{i}<Comment/>')
# Accept both valueType and valueTypes
value_types = list(defn.get('valueTypes', [])) value_types = list(defn.get('valueTypes', []))
if not value_types and defn.get('valueType'):
vt_raw = defn['valueType']
value_types = list(vt_raw) if isinstance(vt_raw, list) else [vt_raw]
if value_types: if value_types:
X(f'{i}<Type>') X(f'{i}<Type>')
for vt in value_types: for vt in value_types:
@@ -1143,6 +1240,9 @@ def emit_scheduled_job_properties(indent):
emit_mltext(i, 'Synonym', synonym) emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>') X(f'{i}<Comment/>')
method_name = str(defn['methodName']) if defn.get('methodName') else '' method_name = str(defn['methodName']) if defn.get('methodName') else ''
# Ensure CommonModule. prefix
if method_name and not method_name.startswith('CommonModule.'):
method_name = f'CommonModule.{method_name}'
X(f'{i}<MethodName>{esc_xml(method_name)}</MethodName>') X(f'{i}<MethodName>{esc_xml(method_name)}</MethodName>')
description = str(defn['description']) if defn.get('description') else synonym description = str(defn['description']) if defn.get('description') else synonym
X(f'{i}<Description>{esc_xml(description)}</Description>') X(f'{i}<Description>{esc_xml(description)}</Description>')
@@ -1174,6 +1274,9 @@ def emit_event_subscription_properties(indent):
event = str(defn['event']) if defn.get('event') else 'BeforeWrite' event = str(defn['event']) if defn.get('event') else 'BeforeWrite'
X(f'{i}<Event>{event}</Event>') X(f'{i}<Event>{event}</Event>')
handler = str(defn['handler']) if defn.get('handler') else '' handler = str(defn['handler']) if defn.get('handler') else ''
# Ensure CommonModule. prefix
if handler and not handler.startswith('CommonModule.'):
handler = f'CommonModule.{handler}'
X(f'{i}<Handler>{esc_xml(handler)}</Handler>') X(f'{i}<Handler>{esc_xml(handler)}</Handler>')
# --- 13b. Report, DataProcessor --- # --- 13b. Report, DataProcessor ---
@@ -1250,18 +1353,12 @@ def emit_exchange_plan_properties(indent):
X(f'{i}<UseStandardCommands>true</UseStandardCommands>') X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9' 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' description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable' code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else '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}<CodeLength>{code_length}</CodeLength>')
X(f'{i}<CodeType>{code_type}</CodeType>')
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>') X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>') X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>') X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
X(f'{i}<EditType>InDialog</EditType>') X(f'{i}<EditType>InDialog</EditType>')
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
emit_standard_attributes(i, 'ExchangePlan') emit_standard_attributes(i, 'ExchangePlan')
distributed = 'true' if defn.get('distributedInfoBase') is True else 'false' distributed = 'true' if defn.get('distributedInfoBase') is True else 'false'
include_ext = 'true' if defn.get('includeConfigurationExtensions') is True else 'false' include_ext = 'true' if defn.get('includeConfigurationExtensions') is True else 'false'
@@ -1308,12 +1405,10 @@ def emit_chart_of_characteristic_types_properties(indent):
X(f'{i}<UseStandardCommands>true</UseStandardCommands>') X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9' 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' 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_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
autonumbering = 'false' if defn.get('autonumbering') is False else 'true' autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
check_unique = 'true' if defn.get('checkUnique') is True else 'false' check_unique = 'true' if defn.get('checkUnique') is True else 'false'
X(f'{i}<CodeLength>{code_length}</CodeLength>') 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}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>') X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>') X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
@@ -1335,7 +1430,7 @@ def emit_chart_of_characteristic_types_properties(indent):
X(f'{i}\t<v8:Type>xs:boolean</v8:Type>') X(f'{i}\t<v8:Type>xs:boolean</v8:Type>')
X(f'{i}\t<v8:Type>xs:string</v8:Type>') X(f'{i}\t<v8:Type>xs:string</v8:Type>')
X(f'{i}\t<v8:StringQualifiers>') X(f'{i}\t<v8:StringQualifiers>')
X(f'{i}\t\t<v8:Length>0</v8:Length>') X(f'{i}\t\t<v8:Length>100</v8:Length>')
X(f'{i}\t\t<v8:AllowedLength>Variable</v8:AllowedLength>') X(f'{i}\t\t<v8:AllowedLength>Variable</v8:AllowedLength>')
X(f'{i}\t</v8:StringQualifiers>') X(f'{i}\t</v8:StringQualifiers>')
X(f'{i}\t<v8:Type>xs:decimal</v8:Type>') X(f'{i}\t<v8:Type>xs:decimal</v8:Type>')
@@ -1457,12 +1552,9 @@ def emit_chart_of_accounts_properties(indent):
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>') X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<CodeSeries>{code_series}</CodeSeries>') X(f'{i}<CodeSeries>{code_series}</CodeSeries>')
X(f'{i}<CheckUnique>false</CheckUnique>') X(f'{i}<CheckUnique>false</CheckUnique>')
X(f'{i}<Autonumbering>true</Autonumbering>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>') X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
X(f'{i}<AutoOrderByCode>{auto_order}</AutoOrderByCode>') X(f'{i}<AutoOrderByCode>{auto_order}</AutoOrderByCode>')
X(f'{i}<OrderLength>{order_length}</OrderLength>') X(f'{i}<OrderLength>{order_length}</OrderLength>')
hierarchical = 'true' if defn.get('hierarchical') is True else 'false'
X(f'{i}<Hierarchical>{hierarchical}</Hierarchical>')
X(f'{i}<EditType>InDialog</EditType>') X(f'{i}<EditType>InDialog</EditType>')
emit_standard_attributes(i, 'ChartOfAccounts') emit_standard_attributes(i, 'ChartOfAccounts')
X(f'{i}<StandardTabularSections>') X(f'{i}<StandardTabularSections>')
@@ -1545,16 +1637,12 @@ def emit_chart_of_calculation_types_properties(indent):
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25' 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_type = str(defn['codeType']) if defn.get('codeType') else 'String'
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable' code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else '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}<CodeLength>{code_length}</CodeLength>')
X(f'{i}<CodeType>{code_type}</CodeType>') X(f'{i}<CodeType>{code_type}</CodeType>')
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>') X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>') X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>') X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>') dependence = str(defn['dependenceOnCalculationTypes']) if defn.get('dependenceOnCalculationTypes') else 'DontUse'
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
dependence = str(defn['dependenceOnCalculationTypes']) if defn.get('dependenceOnCalculationTypes') else 'NotUsed'
X(f'{i}<DependenceOnCalculationTypes>{dependence}</DependenceOnCalculationTypes>') X(f'{i}<DependenceOnCalculationTypes>{dependence}</DependenceOnCalculationTypes>')
base_types = list(defn.get('baseCalculationTypes', [])) base_types = list(defn.get('baseCalculationTypes', []))
if base_types: if base_types:
@@ -1664,6 +1752,11 @@ def emit_business_process_properties(indent):
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>') X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
emit_standard_attributes(i, 'BusinessProcess') emit_standard_attributes(i, 'BusinessProcess')
X(f'{i}<Characteristics/>') X(f'{i}<Characteristics/>')
task_ref = str(defn['task']) if defn.get('task') else ''
if task_ref:
X(f'{i}<Task>{task_ref}</Task>')
else:
X(f'{i}<Task/>')
X(f'{i}<BasedOn/>') X(f'{i}<BasedOn/>')
X(f'{i}<InputByString>') X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>BusinessProcess.{obj_name}.StandardAttribute.Number</xr:Field>') X(f'{i}\t<xr:Field>BusinessProcess.{obj_name}.StandardAttribute.Number</xr:Field>')
@@ -1994,14 +2087,11 @@ def emit_addressing_attribute(indent, addr_def):
type_str = '' type_str = ''
addressing_dimension = '' addressing_dimension = ''
indexing = 'Index' indexing = 'Index'
if isinstance(addr_def, str): parsed = parse_attribute_shorthand(addr_def)
name = addr_def name = parsed['name']
attr_synonym = split_camel_case(name) attr_synonym = parsed['synonym']
else: type_str = parsed['type']
name = str(addr_def.get('name', '')) if not isinstance(addr_def, str):
attr_synonym = str(addr_def['synonym']) if addr_def.get('synonym') else split_camel_case(name)
if addr_def.get('type'):
type_str = str(addr_def['type'])
if addr_def.get('addressingDimension'): if addr_def.get('addressingDimension'):
addressing_dimension = str(addr_def['addressingDimension']) addressing_dimension = str(addr_def['addressingDimension'])
if addr_def.get('indexing'): if addr_def.get('indexing'):
@@ -2335,14 +2425,14 @@ if obj_type in types_with_module:
if obj_type == 'ExchangePlan': if obj_type == 'ExchangePlan':
content_path = os.path.join(ext_dir, 'Content.xml') content_path = os.path.join(ext_dir, 'Content.xml')
if not os.path.isfile(content_path): 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/MDClasses" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>\r\n' 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'
write_utf8_bom(content_path, content_xml) write_utf8_bom(content_path, content_xml)
modules_created.append(content_path) modules_created.append(content_path)
if obj_type == 'BusinessProcess': if obj_type == 'BusinessProcess':
flowchart_path = os.path.join(ext_dir, 'Flowchart.xml') flowchart_path = os.path.join(ext_dir, 'Flowchart.xml')
if not os.path.isfile(flowchart_path): 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"/>\r\n' 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'
write_utf8_bom(flowchart_path, flowchart_xml) write_utf8_bom(flowchart_path, flowchart_xml)
modules_created.append(flowchart_path) modules_created.append(flowchart_path)
@@ -2464,3 +2554,19 @@ elif reg_result == 'no-childobj':
print('WARNING: Configuration.xml found but <ChildObjects> not found', file=sys.stderr) print('WARNING: Configuration.xml found but <ChildObjects> not found', file=sys.stderr)
elif reg_result == 'no-config': elif reg_result == 'no-config':
print(f' Configuration.xml: not found at {config_xml_path} (register manually)') print(f' Configuration.xml: not found at {config_xml_path} (register manually)')
# Cross-reference hints
if obj_type == 'AccountingRegister' and not defn.get('chartOfAccounts'):
print('[HINT] AccountingRegister requires ChartOfAccounts reference:')
print(' /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')
if obj_type == 'CalculationRegister' and not defn.get('chartOfCalculationTypes'):
print('[HINT] CalculationRegister requires ChartOfCalculationTypes reference:')
print(' /meta-edit -Operation modify-property -Value "ChartOfCalculationTypes=ChartOfCalculationTypes.XXX"')
if obj_type == 'BusinessProcess' and not defn.get('task'):
print('[HINT] BusinessProcess requires Task reference:')
print(' /meta-edit -Operation modify-property -Value "Task=Task.XXX"')
if obj_type == 'ChartOfAccounts':
max_ext_dim = int(defn['maxExtDimensionCount']) if defn.get('maxExtDimensionCount') is not None else 0
if max_ext_dim > 0 and not defn.get('extDimensionTypes'):
print('[HINT] ChartOfAccounts with MaxExtDimensionCount>0 requires ExtDimensionTypes:')
print(' /meta-edit -Operation modify-property -Value "ExtDimensionTypes=ChartOfCharacteristicTypes.XXX"')
+26 -2
View File
@@ -1,4 +1,4 @@
# meta-edit v1.3 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) # meta-edit v1.4 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -297,7 +297,7 @@ function Build-TypeContentXml {
# String or String(N) # String or String(N)
if ($typeStr -match '^String(\((\d+)\))?$') { if ($typeStr -match '^String(\((\d+)\))?$') {
$len = if ($Matches[2]) { $Matches[2] } else { "0" } $len = if ($Matches[2]) { $Matches[2] } else { "10" }
$sb.AppendLine("$indent<v8:Type>xs:string</v8:Type>") | Out-Null $sb.AppendLine("$indent<v8:Type>xs:string</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:StringQualifiers>") | Out-Null $sb.AppendLine("$indent<v8:StringQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:Length>$len</v8:Length>") | Out-Null $sb.AppendLine("$indent`t<v8:Length>$len</v8:Length>") | Out-Null
@@ -689,10 +689,34 @@ function Get-AttributeContext {
} }
} }
$script:reservedAttrNames = @{
"Ref"="Ссылка"; "DeletionMark"="ПометкаУдаления"; "Code"="Код"; "Description"="Наименование"
"Date"="Дата"; "Number"="Номер"; "Posted"="Проведен"; "Parent"="Родитель"; "Owner"="Владелец"
"IsFolder"="ЭтоГруппа"; "Predefined"="Предопределенный"; "PredefinedDataName"="ИмяПредопределенныхДанных"
"Recorder"="Регистратор"; "Period"="Период"; "LineNumber"="НомерСтроки"; "Active"="Активность"
"Order"="Порядок"; "Type"="Тип"; "OffBalance"="Забалансовый"
"Started"="Стартован"; "Completed"="Завершен"; "HeadTask"="ВедущаяЗадача"
"Executed"="Выполнена"; "RoutePoint"="ТочкаМаршрута"; "BusinessProcess"="БизнесПроцесс"
"ThisNode"="ЭтотУзел"; "SentNo"="НомерОтправленного"; "ReceivedNo"="НомерПринятого"
"CalculationType"="ВидРасчета"; "RegistrationPeriod"="ПериодРегистрации"; "ReversingEntry"="СторноЗапись"
"Account"="Счет"; "ValueType"="ТипЗначения"; "ActionPeriodIsBasic"="ПериодДействияБазовый"
}
function Build-AttributeFragment { function Build-AttributeFragment {
param($parsed, [string]$context, [string]$indent) param($parsed, [string]$context, [string]$indent)
if (-not $context) { $context = Get-AttributeContext } if (-not $context) { $context = Get-AttributeContext }
# Check reserved attribute names
$attrName = $parsed.name
if ($script:reservedAttrNames.ContainsKey($attrName)) {
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C."
}
$ruValues = $script:reservedAttrNames.Values
if ($ruValues -contains $attrName) {
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name (Russian). This may cause errors when loading into 1C."
}
$uuid = New-Guid-String $uuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder $sb = New-Object System.Text.StringBuilder
+30 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-edit v1.0 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) # meta-edit v1.4 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -214,7 +214,7 @@ def build_type_content_xml(indent, type_str):
# String or String(N) # String or String(N)
m = re.match(r"^String(\((\d+)\))?$", type_str) m = re.match(r"^String(\((\d+)\))?$", type_str)
if m: if m:
length = m.group(2) if m.group(2) else "0" length = m.group(2) if m.group(2) else "10"
lines.append(f"{indent}<v8:Type>xs:string</v8:Type>") lines.append(f"{indent}<v8:Type>xs:string</v8:Type>")
lines.append(f"{indent}<v8:StringQualifiers>") lines.append(f"{indent}<v8:StringQualifiers>")
lines.append(f"{indent}\t<v8:Length>{length}</v8:Length>") lines.append(f"{indent}\t<v8:Length>{length}</v8:Length>")
@@ -600,10 +600,38 @@ def get_attribute_context():
return "object" return "object"
RESERVED_ATTR_NAMES = {
'Ref', 'DeletionMark', 'Code', 'Description', 'Date', 'Number', 'Posted',
'Parent', 'Owner', 'IsFolder', 'Predefined', 'PredefinedDataName',
'Recorder', 'Period', 'LineNumber', 'Active', 'Order', 'Type', 'OffBalance',
'Started', 'Completed', 'HeadTask', 'Executed', 'RoutePoint', 'BusinessProcess',
'ThisNode', 'SentNo', 'ReceivedNo', 'CalculationType', 'RegistrationPeriod',
'ReversingEntry', 'Account', 'ValueType', 'ActionPeriodIsBasic',
}
RESERVED_ATTR_NAMES_RU = {
'Ссылка', 'ПометкаУдаления', 'Код', 'Наименование',
'Дата', 'Номер', 'Проведен', 'Родитель', 'Владелец',
'ЭтоГруппа', 'Предопределенный', 'ИмяПредопределенныхДанных',
'Регистратор', 'Период', 'НомерСтроки', 'Активность',
'Порядок', 'Тип', 'Забалансовый',
'Стартован', 'Завершен', 'ВедущаяЗадача',
'Выполнена', 'ТочкаМаршрута', 'БизнесПроцесс',
'ЭтотУзел', 'НомерОтправленного', 'НомерПринятого',
'ВидРасчета', 'ПериодРегистрации', 'СторноЗапись',
'Счет', 'ТипЗначения', 'ПериодДействияБазовый',
}
def build_attribute_fragment(parsed, context, indent): def build_attribute_fragment(parsed, context, indent):
"""Build XML fragment string for an Attribute element.""" """Build XML fragment string for an Attribute element."""
if not context: if not context:
context = get_attribute_context() context = get_attribute_context()
# Check reserved attribute names
attr_name = parsed['name']
if 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() uid = new_uuid()
lines = [] lines = []
+9 -3
View File
@@ -1,7 +1,7 @@
--- ---
name: meta-validate name: meta-validate
description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30] argument-hint: <ObjectPath> [-MaxErrors 30] — pipe-separated paths for batch
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -16,18 +16,21 @@ allowed-tools:
``` ```
/meta-validate <ObjectPath> /meta-validate <ObjectPath>
/meta-validate path1.xml|path2.xml|path3.xml — batch mode
``` ```
## Параметры ## Параметры
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------------------| |------------|:------------:|--------------|-------------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта | | ObjectPath | да | — | Путь к XML-файлу или каталогу объекта. Несколько путей через `\|` для batch |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок (per object) |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`. `ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
**Batch mode**: при нескольких путях через `|` каждый объект валидируется отдельно, в конце выводится сводка `=== Batch: N objects, X passed, Y failed ===`.
## Команда ## Команда
```powershell ```powershell
@@ -94,6 +97,9 @@ Errors: 1, Warnings: 1
# С записью в файл # С записью в файл
... -ObjectPath Catalogs/Номенклатура.xml -OutFile result.txt ... -ObjectPath Catalogs/Номенклатура.xml -OutFile result.txt
# Batch: несколько объектов через |
... -ObjectPath "Catalogs/Банки.xml|Documents/Заказ.xml|Enums/ВидДоговора.xml"
``` ```
## Верификация ## Верификация
@@ -1,4 +1,4 @@
# meta-validate v1.0 — Validate 1C metadata object structure # meta-validate v1.1 — Validate 1C metadata object structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -12,6 +12,31 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Batch mode: pipe-separated paths (comma reserved by PowerShell) ---
$pathList = @($ObjectPath -split '\|' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
if ($pathList.Count -gt 1) {
$batchOk = 0
$batchFail = 0
foreach ($singlePath in $pathList) {
$callArgs = @{ ObjectPath = $singlePath; MaxErrors = $MaxErrors }
if ($OutFile) {
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($OutFile)
$ext = [System.IO.Path]::GetExtension($OutFile)
$dir = Split-Path $OutFile
if (-not $dir) { $dir = "." }
$objLeaf = [System.IO.Path]::GetFileNameWithoutExtension($singlePath)
$callArgs.OutFile = Join-Path $dir "$baseName`_$objLeaf$ext"
}
& $PSCommandPath @callArgs
if ($LASTEXITCODE -eq 0) { $batchOk++ } else { $batchFail++ }
}
Write-Host ""
Write-Host "=== Batch: $($pathList.Count) objects, $batchOk passed, $batchFail failed ==="
if ($batchFail -gt 0) { exit 1 }
exit 0
}
# --- Resolve path --- # --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) { if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
@@ -54,6 +79,19 @@ if (-not (Test-Path $ObjectPath)) {
$resolvedPath = (Resolve-Path $ObjectPath).Path $resolvedPath = (Resolve-Path $ObjectPath).Path
# --- Detect config directory (for cross-object checks) ---
$script:configDir = $null
$probe = Split-Path $resolvedPath
for ($depth = 0; $depth -lt 4; $depth++) {
if (-not $probe) { break }
if (Test-Path (Join-Path $probe "Configuration.xml")) {
$script:configDir = $probe
break
}
$probe = Split-Path $probe
}
# --- Output infrastructure --- # --- Output infrastructure ---
$script:errors = 0 $script:errors = 0
@@ -216,6 +254,15 @@ $validPropertyValues = @{
"FillChecking" = @("DontCheck","ShowError","ShowWarning") "FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder") "Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
"DataHistory" = @("Use","DontUse") "DataHistory" = @("Use","DontUse")
"DependenceOnCalculationTypes" = @("DontUse","RequireCalculationTypes")
}
# Properties forbidden per type (would cause LoadConfigFromFiles error)
$forbiddenProperties = @{
"ChartOfCharacteristicTypes" = @("CodeType")
"ChartOfAccounts" = @("Autonumbering","Hierarchical")
"ChartOfCalculationTypes" = @("CheckUnique","Autonumbering")
"ExchangePlan" = @("CodeType","CheckUnique","Autonumbering")
} }
# --- 1. Parse XML --- # --- 1. Parse XML ---
@@ -649,6 +696,41 @@ if ($childObjNode) {
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7b: Reserved attribute names ---
$reservedAttrNames = @(
"Ref","DeletionMark","Code","Description","Date","Number","Posted","Parent","Owner",
"IsFolder","Predefined","PredefinedDataName","Recorder","Period","LineNumber","Active",
"Order","Type","OffBalance","Started","Completed","HeadTask","Executed","RoutePoint",
"BusinessProcess","ThisNode","SentNo","ReceivedNo","CalculationType","RegistrationPeriod",
"ReversingEntry","Account","ValueType","ActionPeriodIsBasic"
)
if ($childObjNode) {
$check7bOk = $true
$attrNodes = $childObjNode.SelectNodes("md:Attribute", $ns)
foreach ($attrNode in $attrNodes) {
$attrProps = $attrNode.SelectSingleNode("md:Properties", $ns)
if ($attrProps) {
$attrNameNode = $attrProps.SelectSingleNode("md:Name", $ns)
if ($attrNameNode -and $attrNameNode.InnerText) {
$an = $attrNameNode.InnerText
if ($reservedAttrNames -contains $an) {
Report-Warn "7b. Attribute '$an' conflicts with a standard attribute name"
$check7bOk = $false
}
}
}
}
if ($check7bOk) {
Report-OK "7b. Reserved attribute names: no conflicts"
}
} else {
Report-OK "7b. Reserved attribute names: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Name uniqueness --- # --- Check 8: Name uniqueness ---
function Check-Uniqueness { function Check-Uniqueness {
@@ -887,6 +969,97 @@ if ($propsNode) {
$check10Issues++ $check10Issues++
} }
} }
# AccountingRegister: ChartOfAccounts must not be empty
if ($mdType -eq "AccountingRegister") {
$coa = $propsNode.SelectSingleNode("md:ChartOfAccounts", $ns)
if (-not $coa -or -not $coa.InnerText.Trim()) {
Report-Error "10. AccountingRegister: empty ChartOfAccounts"
$check10Ok = $false
$check10Issues++
Write-Host "[HINT] /meta-edit -Operation modify-property -Value `"ChartOfAccounts=ChartOfAccounts.XXX`""
}
}
# CalculationRegister: ChartOfCalculationTypes must not be empty
if ($mdType -eq "CalculationRegister") {
$coct = $propsNode.SelectSingleNode("md:ChartOfCalculationTypes", $ns)
if (-not $coct -or -not $coct.InnerText.Trim()) {
Report-Error "10. CalculationRegister: empty ChartOfCalculationTypes"
$check10Ok = $false
$check10Issues++
Write-Host "[HINT] /meta-edit -Operation modify-property -Value `"ChartOfCalculationTypes=ChartOfCalculationTypes.XXX`""
}
}
# BusinessProcess: Task should not be empty
if ($mdType -eq "BusinessProcess") {
$taskProp = $propsNode.SelectSingleNode("md:Task", $ns)
if (-not $taskProp -or -not $taskProp.InnerText.Trim()) {
Report-Warn "10. BusinessProcess: empty Task reference"
$check10Issues++
Write-Host "[HINT] /meta-edit -Operation modify-property -Value `"Task=Task.XXX`""
}
}
# DocumentJournal: RegisteredDocuments should not be empty
if ($mdType -eq "DocumentJournal") {
$regDocs = $propsNode.SelectSingleNode("md:RegisteredDocuments", $ns)
$hasRegDocs = $false
if ($regDocs) {
$items = $regDocs.SelectNodes("v8:Type", $ns)
if ($items.Count -gt 0) { $hasRegDocs = $true }
}
if (-not $hasRegDocs) {
Report-Warn "10. DocumentJournal: no RegisteredDocuments specified"
$check10Issues++
}
}
# ChartOfAccounts: ExtDimensionTypes should be set if MaxExtDimensionCount > 0
if ($mdType -eq "ChartOfAccounts") {
$maxExtDim = $propsNode.SelectSingleNode("md:MaxExtDimensionCount", $ns)
if ($maxExtDim -and [int]$maxExtDim.InnerText -gt 0) {
$edt = $propsNode.SelectSingleNode("md:ExtDimensionTypes", $ns)
if (-not $edt -or -not $edt.InnerText.Trim()) {
Report-Warn "10. ChartOfAccounts: MaxExtDimensionCount>0 but ExtDimensionTypes is empty"
$check10Issues++
Write-Host "[HINT] /meta-edit -Operation modify-property -Value `"ExtDimensionTypes=ChartOfCharacteristicTypes.XXX`""
}
}
}
# Register: must have at least one registrar document
$registerTypes = @("AccumulationRegister","AccountingRegister","CalculationRegister","InformationRegister")
if ($registerTypes -contains $mdType -and $script:configDir -and $objName -ne "(unknown)") {
$needsRegistrar = $true
# InformationRegister with WriteMode=Independent does not need a registrar
if ($mdType -eq "InformationRegister") {
$writeMode = $propsNode.SelectSingleNode("md:WriteMode", $ns)
if (-not $writeMode -or $writeMode.InnerText -ne "RecorderSubordinate") {
$needsRegistrar = $false
}
}
if ($needsRegistrar) {
$regRef = "$mdType.$objName"
$docsDir = Join-Path $script:configDir "Documents"
$hasRegistrar = $false
if (Test-Path $docsDir) {
$docXmls = Get-ChildItem $docsDir -Filter "*.xml" -File -ErrorAction SilentlyContinue
foreach ($docXml in $docXmls) {
$content = [System.IO.File]::ReadAllText($docXml.FullName, [System.Text.Encoding]::UTF8)
if ($content.Contains($regRef)) {
$hasRegistrar = $true
break
}
}
}
if (-not $hasRegistrar) {
Report-Warn "10. $mdType`: no registrar document found (none references '$regRef' in RegisterRecords)"
$check10Issues++
}
}
}
} }
if ($check10Ok -and $check10Issues -eq 0) { if ($check10Ok -and $check10Issues -eq 0) {
@@ -991,6 +1164,129 @@ if ($mdType -eq "HTTPService" -and $childObjNode) {
Report-OK "11. HTTPService/WebService: N/A" Report-OK "11. HTTPService/WebService: N/A"
} }
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 12: Forbidden properties per type ---
if ($propsNode -and $forbiddenProperties.ContainsKey($mdType)) {
$forbidden = $forbiddenProperties[$mdType]
$check12Ok = $true
foreach ($fp in $forbidden) {
$fpNode = $propsNode.SelectSingleNode("md:$fp", $ns)
if ($fpNode) {
Report-Error "12. Forbidden property '$fp' present in $mdType (will fail on LoadConfigFromFiles)"
$check12Ok = $false
}
}
if ($check12Ok) {
Report-OK "12. Forbidden properties: none found"
}
} else {
Report-OK "12. Forbidden properties: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 13: Method reference validation (EventSubscription.Handler, ScheduledJob.MethodName) ---
if ($propsNode -and $mdType -in @("EventSubscription","ScheduledJob") -and $script:configDir) {
$check13Ok = $true
$methodRef = $null
$propLabel = $null
if ($mdType -eq "EventSubscription") {
$hNode = $propsNode.SelectSingleNode("md:Handler", $ns)
if ($hNode) { $methodRef = $hNode.InnerText.Trim() }
$propLabel = "Handler"
} elseif ($mdType -eq "ScheduledJob") {
$mNode = $propsNode.SelectSingleNode("md:MethodName", $ns)
if ($mNode) { $methodRef = $mNode.InnerText.Trim() }
$propLabel = "MethodName"
}
if ($methodRef) {
$parts = $methodRef.Split('.')
# Format: CommonModule.ModuleName.ProcedureName (3 parts) or ModuleName.ProcedureName (2 parts, legacy)
if ($parts.Count -eq 3 -and $parts[0] -eq "CommonModule") {
$cmName = $parts[1]
$procName = $parts[2]
} elseif ($parts.Count -eq 2) {
$cmName = $parts[0]
$procName = $parts[1]
} else {
Report-Error "13. ${mdType}.${propLabel} = '$methodRef': expected format 'CommonModule.ModuleName.ProcedureName'"
$check13Ok = $false
$cmName = $null
$procName = $null
}
if ($cmName) {
$cmXml = Join-Path (Join-Path $script:configDir "CommonModules") "$cmName.xml"
if (-not (Test-Path $cmXml)) {
Report-Error "13. ${mdType}.${propLabel}: CommonModule '$cmName' not found (expected $cmXml)"
$check13Ok = $false
} else {
# Check BSL file for exported procedure
$bslPath = Join-Path (Join-Path (Join-Path $script:configDir "CommonModules") $cmName) "Ext/Module.bsl"
if (Test-Path $bslPath) {
$bslContent = [System.IO.File]::ReadAllText($bslPath, [System.Text.Encoding]::UTF8)
# Match: Procedure/Function ProcName(...) Export or Процедура/Функция ProcName(...) Экспорт
$exportPattern = "(?mi)^[\s]*(Procedure|Function|Процедура|Функция)\s+$([regex]::Escape($procName))\s*\(.*\)\s+(Export|Экспорт)"
if (-not [regex]::IsMatch($bslContent, $exportPattern)) {
Report-Warn "13. ${mdType}.${propLabel}: procedure '$procName' not found as exported in CommonModule '$cmName'"
$check13Ok = $false
}
} else {
Report-Warn "13. ${mdType}.${propLabel}: BSL file not found ($bslPath), cannot verify procedure"
}
}
}
}
if ($check13Ok) {
Report-OK "13. Method reference: $propLabel = '$methodRef'"
}
} else {
Report-OK "13. Method reference: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 14: DocumentJournal Column content ---
if ($mdType -eq "DocumentJournal" -and $childObjNode) {
$columns = $childObjNode.SelectNodes("md:Column", $ns)
$check14Ok = $true
$colCount = 0
$emptyRefCount = 0
foreach ($col in $columns) {
$colCount++
$colProps = $col.SelectSingleNode("md:Properties", $ns)
$colNameNode = if ($colProps) { $colProps.SelectSingleNode("md:Name", $ns) } else { $null }
$colName = if ($colNameNode) { $colNameNode.InnerText } else { "(unnamed)" }
$refs = if ($colProps) { $colProps.SelectSingleNode("md:References", $ns) } else { $null }
$hasItems = $false
if ($refs) {
$items = $refs.SelectNodes("xr:Item", $ns)
if ($items.Count -gt 0) { $hasItems = $true }
}
if (-not $hasItems) {
Report-Error "14. DocumentJournal Column '$colName': empty References (will fail on LoadConfigFromFiles)"
$check14Ok = $false
$emptyRefCount++
}
}
if ($check14Ok -and $colCount -gt 0) {
Report-OK "14. DocumentJournal Columns: $colCount column(s), all have References"
} elseif ($colCount -eq 0) {
Report-OK "14. DocumentJournal Columns: none"
}
} else {
Report-OK "14. DocumentJournal Columns: N/A"
}
# --- Final output --- # --- Final output ---
& $finalize & $finalize
@@ -1,8 +1,9 @@
# meta-validate v1.0 — Validate 1C metadata object structure (Python port) # meta-validate v1.1 — Validate 1C metadata object structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
import re import re
import subprocess
import sys import sys
from lxml import etree from lxml import etree
@@ -18,10 +19,32 @@ parser.add_argument("-MaxErrors", type=int, default=30)
parser.add_argument("-OutFile", default="") parser.add_argument("-OutFile", default="")
args = parser.parse_args() args = parser.parse_args()
object_path = args.ObjectPath
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
# ── batch mode: pipe-separated paths ─────────────────────────
path_list = [p.strip() for p in args.ObjectPath.split('|') if p.strip()]
if len(path_list) > 1:
batch_ok = 0
batch_fail = 0
for single_path in path_list:
cmd = [sys.executable, __file__, "-ObjectPath", single_path, "-MaxErrors", str(max_errors)]
if out_file:
base, ext = os.path.splitext(out_file)
obj_leaf = os.path.splitext(os.path.basename(single_path))[0]
cmd += ["-OutFile", f"{base}_{obj_leaf}{ext}"]
rc = subprocess.call(cmd)
if rc == 0:
batch_ok += 1
else:
batch_fail += 1
print()
print(f"=== Batch: {len(path_list)} objects, {batch_ok} passed, {batch_fail} failed ===")
sys.exit(1 if batch_fail > 0 else 0)
object_path = path_list[0]
# ── resolve path ───────────────────────────────────────────── # ── resolve path ─────────────────────────────────────────────
if not os.path.isabs(object_path): if not os.path.isabs(object_path):
@@ -59,6 +82,18 @@ if not os.path.exists(object_path):
resolved_path = os.path.abspath(object_path) resolved_path = os.path.abspath(object_path)
# ── detect config directory (for cross-object checks) ────────
config_dir = None
probe = os.path.dirname(resolved_path)
for _ in range(4):
if not probe:
break
if os.path.exists(os.path.join(probe, "Configuration.xml")):
config_dir = probe
break
probe = os.path.dirname(probe)
# ── output infrastructure ──────────────────────────────────── # ── output infrastructure ────────────────────────────────────
errors = 0 errors = 0
@@ -216,6 +251,15 @@ valid_property_values = {
"FillChecking": ["DontCheck", "ShowError", "ShowWarning"], "FillChecking": ["DontCheck", "ShowError", "ShowWarning"],
"Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"], "Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"],
"DataHistory": ["Use", "DontUse"], "DataHistory": ["Use", "DontUse"],
"DependenceOnCalculationTypes": ["DontUse", "RequireCalculationTypes"],
}
# Properties forbidden per type (would cause LoadConfigFromFiles error)
forbidden_properties = {
"ChartOfCharacteristicTypes": ["CodeType"],
"ChartOfAccounts": ["Autonumbering", "Hierarchical"],
"ChartOfCalculationTypes": ["CheckUnique", "Autonumbering"],
"ExchangePlan": ["CodeType", "CheckUnique", "Autonumbering"],
} }
# ── Namespaces ─────────────────────────────────────────────── # ── Namespaces ───────────────────────────────────────────────
@@ -626,6 +670,37 @@ if stopped:
finalize() finalize()
sys.exit(1) sys.exit(1)
# ── Check 7b: Reserved attribute names ───────────────────────
RESERVED_ATTR_NAMES = {
'Ref', 'DeletionMark', 'Code', 'Description', 'Date', 'Number', 'Posted',
'Parent', 'Owner', 'IsFolder', 'Predefined', 'PredefinedDataName',
'Recorder', 'Period', 'LineNumber', 'Active', 'Order', 'Type', 'OffBalance',
'Started', 'Completed', 'HeadTask', 'Executed', 'RoutePoint', 'BusinessProcess',
'ThisNode', 'SentNo', 'ReceivedNo', 'CalculationType', 'RegistrationPeriod',
'ReversingEntry', 'Account', 'ValueType', 'ActionPeriodIsBasic',
}
if child_obj_node is not None:
check7b_ok = True
for attr_node in find_all(child_obj_node, 'md:Attribute'):
attr_props = find(attr_node, 'md:Properties')
if attr_props is not None:
attr_name_node = find(attr_props, 'md:Name')
if attr_name_node is not None and inner_text(attr_name_node):
an = inner_text(attr_name_node)
if an in RESERVED_ATTR_NAMES:
report_warn(f"7b. Attribute '{an}' conflicts with a standard attribute name")
check7b_ok = False
if check7b_ok:
report_ok("7b. Reserved attribute names: no conflicts")
else:
report_ok("7b. Reserved attribute names: N/A")
if stopped:
finalize()
sys.exit(1)
# ── Check 8: Name uniqueness ───────────────────────────────── # ── Check 8: Name uniqueness ─────────────────────────────────
@@ -841,6 +916,88 @@ if props_node is not None:
check10_ok = False check10_ok = False
check10_issues += 1 check10_issues += 1
# AccountingRegister: ChartOfAccounts must not be empty
if md_type == 'AccountingRegister':
coa = find(props_node, 'md:ChartOfAccounts')
if coa is None or not text_of(coa):
report_error('10. AccountingRegister: empty ChartOfAccounts')
check10_ok = False
check10_issues += 1
print('[HINT] /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')
# CalculationRegister: ChartOfCalculationTypes must not be empty
if md_type == 'CalculationRegister':
coct = find(props_node, 'md:ChartOfCalculationTypes')
if coct is None or not text_of(coct):
report_error('10. CalculationRegister: empty ChartOfCalculationTypes')
check10_ok = False
check10_issues += 1
print('[HINT] /meta-edit -Operation modify-property -Value "ChartOfCalculationTypes=ChartOfCalculationTypes.XXX"')
# BusinessProcess: Task should not be empty
if md_type == 'BusinessProcess':
task_prop = find(props_node, 'md:Task')
if task_prop is None or not text_of(task_prop):
report_warn('10. BusinessProcess: empty Task reference')
check10_issues += 1
print('[HINT] /meta-edit -Operation modify-property -Value "Task=Task.XXX"')
# DocumentJournal: RegisteredDocuments should not be empty
if md_type == 'DocumentJournal':
reg_docs = find(props_node, 'md:RegisteredDocuments')
has_reg_docs = False
if reg_docs is not None:
items = find_all(reg_docs, 'v8:Type')
if len(items) > 0:
has_reg_docs = True
if not has_reg_docs:
report_warn('10. DocumentJournal: no RegisteredDocuments specified')
check10_issues += 1
# ChartOfAccounts: ExtDimensionTypes should be set if MaxExtDimensionCount > 0
if md_type == 'ChartOfAccounts':
max_ext_dim = find(props_node, 'md:MaxExtDimensionCount')
if max_ext_dim is not None:
try:
med_val = int(inner_text(max_ext_dim) or '0')
except ValueError:
med_val = 0
if med_val > 0:
edt = find(props_node, 'md:ExtDimensionTypes')
if edt is None or not text_of(edt):
report_warn('10. ChartOfAccounts: MaxExtDimensionCount>0 but ExtDimensionTypes is empty')
check10_issues += 1
print('[HINT] /meta-edit -Operation modify-property -Value "ExtDimensionTypes=ChartOfCharacteristicTypes.XXX"')
# Register: must have at least one registrar document
register_types = ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'InformationRegister')
if md_type in register_types and config_dir and obj_name != '(unknown)':
needs_registrar = True
# InformationRegister with WriteMode=Independent does not need a registrar
if md_type == 'InformationRegister':
write_mode = find(props_node, 'md:WriteMode')
if write_mode is None or inner_text(write_mode) != 'RecorderSubordinate':
needs_registrar = False
if needs_registrar:
reg_ref = f'{md_type}.{obj_name}'
docs_dir = os.path.join(config_dir, 'Documents')
has_registrar = False
if os.path.isdir(docs_dir):
for fname in os.listdir(docs_dir):
if not fname.endswith('.xml'):
continue
fpath = os.path.join(docs_dir, fname)
if not os.path.isfile(fpath):
continue
with open(fpath, 'r', encoding='utf-8-sig') as f:
content = f.read()
if reg_ref in content:
has_registrar = True
break
if not has_registrar:
report_warn(f"10. {md_type}: no registrar document found (none references '{reg_ref}' in RegisterRecords)")
check10_issues += 1
if check10_ok and check10_issues == 0: if check10_ok and check10_issues == 0:
report_ok("10. Cross-property consistency") report_ok("10. Cross-property consistency")
@@ -929,6 +1086,120 @@ elif md_type == "WebService" and child_obj_node is not None:
else: else:
report_ok("11. HTTPService/WebService: N/A") report_ok("11. HTTPService/WebService: N/A")
if stopped:
finalize()
sys.exit(1)
# ── Check 12: Forbidden properties per type ──────────────────
if props_node is not None and md_type in forbidden_properties:
forbidden = forbidden_properties[md_type]
check12_ok = True
for fp in forbidden:
fp_node = find(props_node, f"md:{fp}")
if fp_node is not None:
report_error(f"12. Forbidden property '{fp}' present in {md_type} (will fail on LoadConfigFromFiles)")
check12_ok = False
if check12_ok:
report_ok("12. Forbidden properties: none found")
else:
report_ok("12. Forbidden properties: N/A")
if stopped:
finalize()
sys.exit(1)
# ── Check 13: Method reference validation ─────────────────────
if props_node is not None and md_type in ("EventSubscription", "ScheduledJob") and config_dir:
check13_ok = True
method_ref = None
prop_label = None
if md_type == "EventSubscription":
h_node = find(props_node, "md:Handler")
if h_node is not None:
method_ref = text_of(h_node)
prop_label = "Handler"
elif md_type == "ScheduledJob":
m_node = find(props_node, "md:MethodName")
if m_node is not None:
method_ref = text_of(m_node)
prop_label = "MethodName"
if method_ref:
parts = method_ref.split(".")
# Format: CommonModule.ModuleName.ProcedureName (3 parts) or ModuleName.ProcedureName (2 parts, legacy)
if len(parts) == 3 and parts[0] == "CommonModule":
cm_name = parts[1]
proc_name = parts[2]
elif len(parts) == 2:
cm_name = parts[0]
proc_name = parts[1]
else:
report_error(f"13. {md_type}.{prop_label} = '{method_ref}': expected format 'CommonModule.ModuleName.ProcedureName'")
check13_ok = False
cm_name = None
proc_name = None
if cm_name:
cm_xml = os.path.join(config_dir, "CommonModules", f"{cm_name}.xml")
if not os.path.exists(cm_xml):
report_error(f"13. {md_type}.{prop_label}: CommonModule '{cm_name}' not found (expected {cm_xml})")
check13_ok = False
else:
# Check BSL file for exported procedure
bsl_path = os.path.join(config_dir, "CommonModules", cm_name, "Ext", "Module.bsl")
if os.path.exists(bsl_path):
with open(bsl_path, "r", encoding="utf-8-sig") as f:
bsl_content = f.read()
export_pattern = rf"(?mi)^\s*(Procedure|Function|Процедура|Функция)\s+{re.escape(proc_name)}\s*\(.*\)\s+(Export|Экспорт)"
if not re.search(export_pattern, bsl_content):
report_warn(f"13. {md_type}.{prop_label}: procedure '{proc_name}' not found as exported in CommonModule '{cm_name}'")
check13_ok = False
else:
report_warn(f"13. {md_type}.{prop_label}: BSL file not found ({bsl_path}), cannot verify procedure")
if check13_ok:
report_ok(f"13. Method reference: {prop_label} = '{method_ref}'")
else:
report_ok("13. Method reference: N/A")
if stopped:
finalize()
sys.exit(1)
# ── Check 14: DocumentJournal Column content ──────────────────
if md_type == "DocumentJournal" and child_obj_node is not None:
columns = find_all(child_obj_node, "md:Column")
check14_ok = True
col_count = 0
empty_ref_count = 0
for col in columns:
col_count += 1
col_props = find(col, "md:Properties")
col_name_node = find(col_props, "md:Name") if col_props is not None else None
col_name = inner_text(col_name_node) if col_name_node is not None else "(unnamed)"
refs = find(col_props, "md:References") if col_props is not None else None
has_items = False
if refs is not None:
items = find_all(refs, "xr:Item")
if len(items) > 0:
has_items = True
if not has_items:
report_error(f"14. DocumentJournal Column '{col_name}': empty References (will fail on LoadConfigFromFiles)")
check14_ok = False
empty_ref_count += 1
if check14_ok and col_count > 0:
report_ok(f"14. DocumentJournal Columns: {col_count} column(s), all have References")
elif col_count == 0:
report_ok("14. DocumentJournal Columns: none")
else:
report_ok("14. DocumentJournal Columns: N/A")
# ── Final output ────────────────────────────────────────────── # ── Final output ──────────────────────────────────────────────
finalize() finalize()
+31 -11
View File
@@ -1,6 +1,6 @@
# Meta DSL — спецификация JSON-формата для объектов метаданных 1С # Meta DSL — спецификация JSON-формата для объектов метаданных 1С
Версия: 2.0 Версия: 2.1
## Обзор ## Обзор
@@ -112,7 +112,7 @@ JSON DSL для описания объектов метаданных конф
### 4.1 Строковая форма ### 4.1 Строковая форма
``` ```
"ИмяРеквизита" → String (без квалификаторов) "ИмяРеквизита" → String(10) по умолчанию
"ИмяРеквизита: Тип" → с типом "ИмяРеквизита: Тип" → с типом
"ИмяРеквизита: Тип | req, index" → с флагами "ИмяРеквизита: Тип | req, index" → с флагами
``` ```
@@ -130,6 +130,17 @@ JSON DSL для описания объектов метаданных конф
} }
``` ```
Тип можно задать единой строкой (`"type": "String(100)"`) или раздельными полями:
```json
{ "name": "Имя", "type": "String", "length": 100 }
{ "name": "Сумма", "type": "Number", "length": 15, "precision": 2 }
{ "name": "Остаток", "type": "Number", "length": 15, "precision": 2, "nonneg": true }
```
Раздельная форма эквивалентна `String(100)`, `Number(15,2)`, `Number(15,2,nonneg)`.
Если `type` уже содержит скобки — `length`/`precision` игнорируются.
### 4.3 Флаги ### 4.3 Флаги
| Флаг | Действие | Применимость | | Флаг | Действие | Применимость |
@@ -243,8 +254,13 @@ JSON DSL для описания объектов метаданных конф
| Поле JSON | Умолчание | XML элемент | | Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------| |-----------|----------|-------------|
| `valueType` | `String` | Type | | `valueType` | `String` | Type |
| `length` | — | Длина строки (если valueType=String) |
| `precision` | — | Точность числа (если valueType=Number) |
| `dataLockControlMode` | `Automatic` | DataLockControlMode | | `dataLockControlMode` | `Automatic` | DataLockControlMode |
`valueType` + `length`/`precision` работают аналогично раздельной форме типа (§4.2):
`"valueType": "String", "length": 100``String(100)`.
### 7.5 InformationRegister ### 7.5 InformationRegister
| Поле JSON | Умолчание | XML элемент | | Поле JSON | Умолчание | XML элемент |
@@ -277,11 +293,13 @@ JSON DSL для описания объектов метаданных конф
| Поле JSON | Умолчание | XML элемент | | Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------| |-----------|----------|-------------|
| `valueTypes` | `[]` | Type (составной — массив `v8:Type`) | | `valueTypes` | `[]` | Type (составной — массив `v8:Type`) |
| `valueType` | — | Алиас для `valueTypes` (принимает строку или массив) |
Без ChildObjects и модулей. Без ChildObjects и модулей. Принимается как `valueTypes` (мн.ч.), так и `valueType` (ед.ч.).
```json ```json
{ "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] } { "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] }
{ "type": "DefinedType", "name": "ФлагАктивности", "valueType": "Boolean" }
``` ```
### 7.8 CommonModule ### 7.8 CommonModule
@@ -313,7 +331,7 @@ JSON DSL для описания объектов метаданных конф
| Поле JSON | Умолчание | XML элемент | | Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------| |-----------|----------|-------------|
| `methodName` | `""` | MethodName | | `methodName` | `""` | MethodName (авто-префикс `CommonModule.`) |
| `description` | = synonym | Description | | `description` | = synonym | Description |
| `key` | `""` | Key | | `key` | `""` | Key |
| `use` | `false` | Use | | `use` | `false` | Use |
@@ -323,6 +341,8 @@ JSON DSL для описания объектов метаданных конф
Без ChildObjects и модулей. Без ChildObjects и модулей.
Формат `methodName`: `"МодульСервер.Процедура"` — при компиляции авто-дополняется до `CommonModule.МодульСервер.Процедура`. Если уже содержит `CommonModule.` — оставляется как есть.
```json ```json
{ "type": "ScheduledJob", "name": "ОбменДанными", "methodName": "ОбменДаннымиСервер.Выполнить", "use": true } { "type": "ScheduledJob", "name": "ОбменДанными", "methodName": "ОбменДаннымиСервер.Выполнить", "use": true }
``` ```
@@ -333,12 +353,14 @@ JSON DSL для описания объектов метаданных конф
|-----------|----------|-------------| |-----------|----------|-------------|
| `source` | `[]` | Source (массив `v8:Type`, формат `cfg:XxxObject.Name`) | | `source` | `[]` | Source (массив `v8:Type`, формат `cfg:XxxObject.Name`) |
| `event` | `BeforeWrite` | Event | | `event` | `BeforeWrite` | Event |
| `handler` | `""` | Handler | | `handler` | `""` | Handler (авто-префикс `CommonModule.`) |
Без ChildObjects и модулей. Без ChildObjects и модулей.
Значения `event`: `BeforeWrite`, `OnWrite`, `BeforeDelete`, `OnReadAtServer`, `FillCheckProcessing` и др. Значения `event`: `BeforeWrite`, `OnWrite`, `BeforeDelete`, `OnReadAtServer`, `FillCheckProcessing` и др.
Формат `handler`: `"МодульСервер.Процедура"` — при компиляции авто-дополняется до `CommonModule.МодульСервер.Процедура`. Если уже содержит `CommonModule.` — оставляется как есть.
```json ```json
{ "type": "EventSubscription", "name": "ПередЗаписьюКонтрагента", "source": ["CatalogObject.Контрагенты"], "event": "BeforeWrite", "handler": "ОбщегоНазначенияСервер.ПередЗаписьюКонтрагента" } { "type": "EventSubscription", "name": "ПередЗаписьюКонтрагента", "source": ["CatalogObject.Контрагенты"], "event": "BeforeWrite", "handler": "ОбщегоНазначенияСервер.ПередЗаписьюКонтрагента" }
``` ```
@@ -382,11 +404,8 @@ JSON DSL для описания объектов метаданных конф
| Поле JSON | Умолчание | XML элемент | | Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------| |-----------|----------|-------------|
| `codeLength` | `9` | CodeLength | | `codeLength` | `9` | CodeLength |
| `codeType` | `String` | CodeType |
| `codeAllowedLength` | `Variable` | CodeAllowedLength | | `codeAllowedLength` | `Variable` | CodeAllowedLength |
| `descriptionLength` | `100` | DescriptionLength | | `descriptionLength` | `100` | DescriptionLength |
| `autonumbering` | `true` | Autonumbering |
| `checkUnique` | `false` | CheckUnique |
| `distributedInfoBase` | `false` | DistributedInfoBase | | `distributedInfoBase` | `false` | DistributedInfoBase |
| `includeConfigurationExtensions` | `false` | IncludeConfigurationExtensions | | `includeConfigurationExtensions` | `false` | IncludeConfigurationExtensions |
| `dataLockControlMode` | `Automatic` | DataLockControlMode | | `dataLockControlMode` | `Automatic` | DataLockControlMode |
@@ -419,7 +438,7 @@ JSON DSL для описания объектов метаданных конф
| `attributes` | `[]` | → Attribute в ChildObjects | | `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects | | `tabularSections` | `{}` | → TabularSection в ChildObjects |
\* Если `valueTypes` не указан, по умолчанию: Boolean, String, Number(15,2), DateTime. \* Если `valueTypes` не указан, по умолчанию: Boolean, String(100), Number(15,2), DateTime.
Модули: `Ext/ObjectModule.bsl` (пустой). Модули: `Ext/ObjectModule.bsl` (пустой).
@@ -580,6 +599,7 @@ DSL для `columns` (§12).
| `autonumbering` | `true` | Autonumbering | | `autonumbering` | `true` | Autonumbering |
| `dataLockControlMode` | `Automatic` | DataLockControlMode | | `dataLockControlMode` | `Automatic` | DataLockControlMode |
| `fullTextSearch` | `Use` | FullTextSearch | | `fullTextSearch` | `Use` | FullTextSearch |
| `task` | `""` | Task (ссылка на Task.XXX) |
| `attributes` | `[]` | → Attribute в ChildObjects | | `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects | | `tabularSections` | `{}` | → TabularSection в ChildObjects |
@@ -587,7 +607,7 @@ DSL для `columns` (§12).
Дополнительно: `Ext/Flowchart.xml` (заглушка карты маршрута). Дополнительно: `Ext/Flowchart.xml` (заглушка карты маршрута).
```json ```json
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] } { "type": "BusinessProcess", "name": "Задание", "task": "Task.ЗадачаИсполнителя", "attributes": ["Описание: String(200)"] }
``` ```
### 7.21 Task ### 7.21 Task
@@ -904,7 +924,7 @@ DSL для `columns` (§12).
### Бизнес-процесс ### Бизнес-процесс
```json ```json
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] } { "type": "BusinessProcess", "name": "Задание", "task": "Task.ЗадачаИсполнителя", "attributes": ["Описание: String(200)"] }
``` ```
### Задача ### Задача