mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-13 01:14:56 +03:00
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:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: meta-compile
|
||||
description: Создать исходники объекта метаданных 1С (справочник, документ, регистр, перечисление, константа, общий модуль, обработка, HTTP-сервис и др.) в выгрузке конфигурации. Используй когда пользователь просит добавить или создать объект конфигурации
|
||||
description: Создать объект метаданных 1С. Используй когда пользователь просит создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
|
||||
argument-hint: <JsonPath> <OutputDir>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -13,101 +13,91 @@ allowed-tools:
|
||||
|
||||
Принимает JSON-определение объекта метаданных → генерирует XML + модули в структуре выгрузки конфигурации + регистрирует в Configuration.xml.
|
||||
|
||||
## Параметры и команда
|
||||
## Порядок работы
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `JsonPath` | Путь к JSON-определению объекта |
|
||||
| `OutputDir` | Корневая директория выгрузки конфигурации (где `Catalogs/`, `Documents/` и т.д.) |
|
||||
1. Составь JSON по синтаксису и примерам ниже → запиши во временный файл
|
||||
2. Запусти скрипт meta-compile
|
||||
3. Если нужно изменить созданный объект — `/meta-edit`
|
||||
4. Если нужно проверить — `/meta-validate`
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
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
|
||||
{
|
||||
"type": "Catalog",
|
||||
"name": "Номенклатура",
|
||||
"synonym": "авто из name",
|
||||
...type-specific...,
|
||||
"attributes": [...],
|
||||
"tabularSections": {...}
|
||||
}
|
||||
{ "type": "Catalog", "name": "Номенклатура", ...свойства типа... }
|
||||
```
|
||||
|
||||
### Реквизиты — shorthand
|
||||
`type` и `name` — обязательные. `synonym` генерируется из `name` автоматически (CamelCase → слова через пробел). Можно задать явно: `"synonym": "Мой синоним"`.
|
||||
|
||||
### Shorthand реквизитов
|
||||
|
||||
Используется в `attributes`, `dimensions`, `resources`, `tabularSections`:
|
||||
|
||||
```
|
||||
"ИмяРеквизита" — String без квалификаторов
|
||||
"ИмяРеквизита: Тип" — с типом
|
||||
"ИмяРеквизита: Тип | req, index" — с флагами
|
||||
"ИмяРеквизита" → String(10) по умолчанию
|
||||
"ИмяРеквизита: Тип" → с типом
|
||||
"ИмяРеквизита: Тип | 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`.
|
||||
|
||||
Составной тип (несколько допустимых типов через `+`): `"Значение: Строка + Число(15,2) + Дата + CatalogRef.Контрагенты"`.
|
||||
Составной тип: `"Значение: String + Number(15,2) + CatalogRef.Контрагенты"`.
|
||||
|
||||
Флаги: `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
|
||||
{ "type": "Catalog", "name": "Валюты" }
|
||||
```
|
||||
|
||||
### Перечисление
|
||||
### С реквизитами
|
||||
|
||||
```json
|
||||
{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] }
|
||||
{
|
||||
"type": "Catalog", "name": "Организации",
|
||||
"descriptionLength": 100,
|
||||
"attributes": ["ИНН: String(12)", "КПП: String(9)", "Директор: CatalogRef.ФизическиеЛица"]
|
||||
}
|
||||
```
|
||||
|
||||
### Константа
|
||||
### С табличной частью
|
||||
|
||||
```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
|
||||
{
|
||||
@@ -117,70 +107,19 @@ Constant (Константа), DefinedType (ОпределяемыйТип), Com
|
||||
}
|
||||
```
|
||||
|
||||
### План обмена
|
||||
### Batch — несколько объектов в одном файле
|
||||
|
||||
```json
|
||||
{ "type": "ExchangePlan", "name": "ОбменССайтом", "attributes": ["АдресСервера: String(200)"] }
|
||||
```
|
||||
|
||||
### Журнал документов
|
||||
|
||||
```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)"] }
|
||||
[
|
||||
{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] },
|
||||
{ "type": "Catalog", "name": "Валюты" },
|
||||
{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" }
|
||||
]
|
||||
```
|
||||
|
||||
## Что генерируется
|
||||
|
||||
- `{OutputDir}/{TypePlural}/{Name}.xml` — метаданные объекта
|
||||
- `{OutputDir}/{TypePlural}/{Name}/Ext/ObjectModule.bsl` — модуль объекта (Catalog, Document, Report, DataProcessor, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task)
|
||||
- `{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)
|
||||
- `{TypePlural}/{Name}.xml` — метаданные объекта
|
||||
- `{TypePlural}/{Name}/Ext/*.bsl` — модули (ObjectModule, RecordSetModule, Module — зависит от типа)
|
||||
- `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
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -21,6 +21,28 @@ if (-not (Test-Path $JsonPath)) {
|
||||
$json = Get-Content -Raw -Encoding UTF8 $JsonPath
|
||||
$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"
|
||||
if (-not $def.type -and $def.objectType) {
|
||||
$def | Add-Member -NotePropertyName "type" -NotePropertyValue $def.objectType
|
||||
@@ -163,6 +185,10 @@ $script:typeSynonyms["бизнеспроцессссылка"] = "Bus
|
||||
$script:typeSynonyms["задачассылка"] = "TaskRef"
|
||||
$script:typeSynonyms["определяемыйтип"] = "DefinedType"
|
||||
$script:typeSynonyms["definedtype"] = "DefinedType"
|
||||
# English lowercase ref synonyms
|
||||
$script:typeSynonyms["catalogref"] = "CatalogRef"
|
||||
$script:typeSynonyms["documentref"] = "DocumentRef"
|
||||
$script:typeSynonyms["enumref"] = "EnumRef"
|
||||
|
||||
function Resolve-TypeStr {
|
||||
param([string]$typeStr)
|
||||
@@ -217,7 +243,7 @@ function Emit-TypeContent {
|
||||
|
||||
# String or String(N)
|
||||
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:StringQualifiers>"
|
||||
X "$indent`t<v8:Length>$len</v8:Length>"
|
||||
@@ -226,6 +252,17 @@ function Emit-TypeContent {
|
||||
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)
|
||||
if ($typeStr -match '^Number\((\d+),(\d+)(,nonneg)?\)$') {
|
||||
$digits = $Matches[1]
|
||||
@@ -263,6 +300,12 @@ function Emit-TypeContent {
|
||||
return
|
||||
}
|
||||
|
||||
# ValueStorage
|
||||
if ($typeStr -eq "ValueStorage") {
|
||||
X "$indent<v8:Type>xs:base64Binary</v8:Type>"
|
||||
return
|
||||
}
|
||||
|
||||
# Reference types — use local xmlns declaration for 1C compatibility
|
||||
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>"
|
||||
@@ -311,6 +354,21 @@ function Emit-FillValue {
|
||||
|
||||
# --- 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 {
|
||||
param($val)
|
||||
|
||||
@@ -347,7 +405,7 @@ function Parse-AttributeShorthand {
|
||||
$name = "$($val.name)"
|
||||
return @{
|
||||
name = $name
|
||||
type = if ($val.type) { "$($val.type)" } else { "" }
|
||||
type = Build-TypeStr $val
|
||||
synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name }
|
||||
comment = if ($val.comment) { "$($val.comment)" } else { "" }
|
||||
flags = @(if ($val.flags) { $val.flags } else { @() })
|
||||
@@ -436,13 +494,16 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "CalculationRegisterList"; category = "List" }
|
||||
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
|
||||
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
|
||||
@{ prefix = "RecalculationsManager"; category = "Recalcs" }
|
||||
)
|
||||
"ChartOfAccounts" = @(
|
||||
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
|
||||
@{ prefix = "ChartOfAccountsRef"; category = "Ref" }
|
||||
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
|
||||
@{ prefix = "ChartOfAccountsList"; category = "List" }
|
||||
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
|
||||
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
|
||||
@{ prefix = "ChartOfAccountsRef"; category = "Ref" }
|
||||
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
|
||||
@{ prefix = "ChartOfAccountsList"; category = "List" }
|
||||
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
|
||||
@{ prefix = "ChartOfAccountsExtDimensionTypes"; category = "ExtDimensionTypes" }
|
||||
@{ prefix = "ChartOfAccountsExtDimensionTypesRow"; category = "ExtDimensionTypesRow" }
|
||||
)
|
||||
"ChartOfCharacteristicTypes" = @(
|
||||
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
|
||||
@@ -487,6 +548,9 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "ExchangePlanList"; category = "List" }
|
||||
@{ prefix = "ExchangePlanManager"; category = "Manager" }
|
||||
)
|
||||
"DefinedType" = @(
|
||||
@{ prefix = "DefinedType"; category = "DefinedType" }
|
||||
)
|
||||
"DocumentJournal" = @(
|
||||
@{ prefix = "DocumentJournalSelection"; category = "Selection" }
|
||||
@{ prefix = "DocumentJournalList"; category = "List" }
|
||||
@@ -592,9 +656,26 @@ function Emit-TabularStandardAttributes {
|
||||
|
||||
# --- 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 {
|
||||
param([string]$indent, $parsed, [string]$context)
|
||||
# $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register"
|
||||
$attrName = $parsed.name
|
||||
if ($script:reservedAttrNames.ContainsKey($attrName) -or $script:reservedAttrNames.ContainsValue($attrName)) {
|
||||
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C."
|
||||
}
|
||||
$uuid = New-Guid-String
|
||||
X "$indent<Attribute uuid=`"$uuid`">"
|
||||
X "$indent`t<Properties>"
|
||||
@@ -1055,7 +1136,7 @@ function Emit-DocumentProperties {
|
||||
if ($regRecords.Count -gt 0) {
|
||||
X "$i<RegisterRecords>"
|
||||
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>"
|
||||
} else {
|
||||
@@ -1117,7 +1198,8 @@ function Emit-ConstantProperties {
|
||||
X "$i<Comment/>"
|
||||
|
||||
# Type
|
||||
$valueType = if ($def.valueType) { "$($def.valueType)" } else { "String" }
|
||||
$valueType = Build-TypeStr $def
|
||||
if (-not $valueType) { $valueType = "String" }
|
||||
Emit-ValueType $i $valueType
|
||||
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
@@ -1242,10 +1324,12 @@ function Emit-DefinedTypeProperties {
|
||||
Emit-MLText $i "Synonym" $synonym
|
||||
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 = @()
|
||||
if ($def.valueTypes) {
|
||||
$valueTypes = @($def.valueTypes)
|
||||
} elseif ($def.valueType) {
|
||||
$valueTypes = @($def.valueType)
|
||||
}
|
||||
if ($valueTypes.Count -gt 0) {
|
||||
X "$i<Type>"
|
||||
@@ -1322,6 +1406,10 @@ function Emit-ScheduledJobProperties {
|
||||
X "$i<Comment/>"
|
||||
|
||||
$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>"
|
||||
|
||||
$description = if ($def.description) { "$($def.description)" } else { $synonym }
|
||||
@@ -1368,6 +1456,10 @@ function Emit-EventSubscriptionProperties {
|
||||
X "$i<Event>$event</Event>"
|
||||
|
||||
$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>"
|
||||
}
|
||||
|
||||
@@ -1440,19 +1532,13 @@ function Emit-ExchangePlanProperties {
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$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" }
|
||||
$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<CodeType>$codeType</CodeType>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
X "$i<CheckUnique>$checkUnique</CheckUnique>"
|
||||
X "$i<Autonumbering>$autonumbering</Autonumbering>"
|
||||
|
||||
Emit-StandardAttributes $i "ExchangePlan"
|
||||
|
||||
@@ -1509,13 +1595,11 @@ function Emit-ChartOfCharacteristicTypesProperties {
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
|
||||
$codeType = if ($def.codeType) { "$($def.codeType)" } else { "String" }
|
||||
$codeAllowedLength = if ($def.codeAllowedLength) { "$($def.codeAllowedLength)" } else { "Variable" }
|
||||
$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<CodeType>$codeType</CodeType>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
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:string</v8:Type>"
|
||||
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</v8:StringQualifiers>"
|
||||
X "$i`t<v8:Type>xs:decimal</v8:Type>"
|
||||
@@ -1685,14 +1769,10 @@ function Emit-ChartOfAccountsProperties {
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
X "$i<CodeSeries>$codeSeries</CodeSeries>"
|
||||
X "$i<CheckUnique>false</CheckUnique>"
|
||||
X "$i<Autonumbering>true</Autonumbering>"
|
||||
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
|
||||
X "$i<AutoOrderByCode>$autoOrder</AutoOrderByCode>"
|
||||
X "$i<OrderLength>$orderLength</OrderLength>"
|
||||
|
||||
$hierarchical = if ($def.hierarchical -eq $true) { "true" } else { "false" }
|
||||
X "$i<Hierarchical>$hierarchical</Hierarchical>"
|
||||
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
|
||||
Emit-StandardAttributes $i "ChartOfAccounts"
|
||||
@@ -1796,18 +1876,14 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
$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" }
|
||||
$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<CodeType>$codeType</CodeType>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
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>"
|
||||
|
||||
# BaseCalculationTypes
|
||||
@@ -1941,6 +2017,13 @@ function Emit-BusinessProcessProperties {
|
||||
Emit-StandardAttributes $i "BusinessProcess"
|
||||
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<InputByString>"
|
||||
X "$i`t<xr:Field>BusinessProcess.$objName.StandardAttribute.Number</xr:Field>"
|
||||
@@ -2330,13 +2413,11 @@ function Emit-AddressingAttribute {
|
||||
$addressingDimension = ""
|
||||
$indexing = "Index"
|
||||
|
||||
if ($addrDef -is [string]) {
|
||||
$name = "$addrDef"
|
||||
$attrSynonym = Split-CamelCase $name
|
||||
} else {
|
||||
$name = "$($addrDef.name)"
|
||||
$attrSynonym = if ($addrDef.synonym) { "$($addrDef.synonym)" } else { Split-CamelCase $name }
|
||||
if ($addrDef.type) { $typeStr = "$($addrDef.type)" }
|
||||
$parsed = Parse-AttributeShorthand $addrDef
|
||||
$name = $parsed.name
|
||||
$attrSynonym = $parsed.synonym
|
||||
$typeStr = $parsed.type
|
||||
if ($addrDef -isnot [string]) {
|
||||
if ($addrDef.addressingDimension) { $addressingDimension = "$($addrDef.addressingDimension)" }
|
||||
if ($addrDef.indexing) { $indexing = "$($addrDef.indexing)" }
|
||||
}
|
||||
@@ -2708,7 +2789,7 @@ if ($objType -in $typesWithModule) {
|
||||
if ($objType -eq "ExchangePlan") {
|
||||
$contentPath = Join-Path $extDir "Content.xml"
|
||||
if (-not (Test-Path $contentPath)) {
|
||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/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)
|
||||
$modulesCreated += $contentPath
|
||||
}
|
||||
@@ -2716,7 +2797,7 @@ if ($objType -eq "ExchangePlan") {
|
||||
if ($objType -eq "BusinessProcess") {
|
||||
$flowchartPath = Join-Path $extDir "Flowchart.xml"
|
||||
if (-not (Test-Path $flowchartPath)) {
|
||||
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`"/>`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)
|
||||
$modulesCreated += $flowchartPath
|
||||
}
|
||||
@@ -2842,3 +2923,24 @@ switch ($regResult) {
|
||||
"no-childobj" { Write-Warning "Configuration.xml found but <ChildObjects> not found" }
|
||||
"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
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
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)
|
||||
|
||||
# --- 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"
|
||||
if not defn.get('type') and defn.get('objectType'):
|
||||
defn['type'] = defn['objectType']
|
||||
@@ -175,6 +198,10 @@ type_synonyms = {
|
||||
'задачассылка': 'TaskRef',
|
||||
'определяемыйтип': 'DefinedType',
|
||||
'definedtype': 'DefinedType',
|
||||
# English lowercase ref synonyms
|
||||
'catalogref': 'CatalogRef',
|
||||
'documentref': 'DocumentRef',
|
||||
'enumref': 'EnumRef',
|
||||
}
|
||||
|
||||
def resolve_type_str(type_str):
|
||||
@@ -221,13 +248,23 @@ def emit_type_content(indent, type_str):
|
||||
# String or String(N)
|
||||
m = re.match(r'^String(\((\d+)\))?$', type_str)
|
||||
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:StringQualifiers>')
|
||||
X(f'{indent}\t<v8:Length>{length}</v8:Length>')
|
||||
X(f'{indent}\t<v8:AllowedLength>Variable</v8:AllowedLength>')
|
||||
X(f'{indent}</v8:StringQualifiers>')
|
||||
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)
|
||||
m = re.match(r'^Number\((\d+),(\d+)(,nonneg)?\)$', type_str)
|
||||
if m:
|
||||
@@ -260,6 +297,11 @@ def emit_type_content(indent, type_str):
|
||||
dt_name = m.group(1)
|
||||
X(f'{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>')
|
||||
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
|
||||
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$', type_str)
|
||||
if m:
|
||||
@@ -296,6 +338,17 @@ def emit_fill_value(indent, type_str):
|
||||
# 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):
|
||||
if isinstance(val, str):
|
||||
parsed = {
|
||||
@@ -322,7 +375,7 @@ def parse_attribute_shorthand(val):
|
||||
name = str(val.get('name', ''))
|
||||
return {
|
||||
'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),
|
||||
'comment': str(val['comment']) if val.get('comment') else '',
|
||||
'flags': list(val.get('flags', [])),
|
||||
@@ -406,6 +459,7 @@ generated_types = {
|
||||
{'prefix': 'CalculationRegisterList', 'category': 'List'},
|
||||
{'prefix': 'CalculationRegisterRecordSet', 'category': 'RecordSet'},
|
||||
{'prefix': 'CalculationRegisterRecordKey', 'category': 'RecordKey'},
|
||||
{'prefix': 'RecalculationsManager', 'category': 'Recalcs'},
|
||||
],
|
||||
'ChartOfAccounts': [
|
||||
{'prefix': 'ChartOfAccountsObject', 'category': 'Object'},
|
||||
@@ -413,6 +467,8 @@ generated_types = {
|
||||
{'prefix': 'ChartOfAccountsSelection', 'category': 'Selection'},
|
||||
{'prefix': 'ChartOfAccountsList', 'category': 'List'},
|
||||
{'prefix': 'ChartOfAccountsManager', 'category': 'Manager'},
|
||||
{'prefix': 'ChartOfAccountsExtDimensionTypes', 'category': 'ExtDimensionTypes'},
|
||||
{'prefix': 'ChartOfAccountsExtDimensionTypesRow', 'category': 'ExtDimensionTypesRow'},
|
||||
],
|
||||
'ChartOfCharacteristicTypes': [
|
||||
{'prefix': 'ChartOfCharacteristicTypesObject', 'category': 'Object'},
|
||||
@@ -457,6 +513,9 @@ generated_types = {
|
||||
{'prefix': 'ExchangePlanList', 'category': 'List'},
|
||||
{'prefix': 'ExchangePlanManager', 'category': 'Manager'},
|
||||
],
|
||||
'DefinedType': [
|
||||
{'prefix': 'DefinedType', 'category': 'DefinedType'},
|
||||
],
|
||||
'DocumentJournal': [
|
||||
{'prefix': 'DocumentJournalSelection', 'category': 'Selection'},
|
||||
{'prefix': 'DocumentJournalList', 'category': 'List'},
|
||||
@@ -554,7 +613,40 @@ def emit_tabular_standard_attributes(indent):
|
||||
# 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):
|
||||
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()
|
||||
X(f'{indent}<Attribute uuid="{uid}">')
|
||||
X(f'{indent}\t<Properties>')
|
||||
@@ -929,7 +1021,7 @@ def emit_document_properties(indent):
|
||||
if reg_records:
|
||||
X(f'{i}<RegisterRecords>')
|
||||
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>')
|
||||
else:
|
||||
X(f'{i}<RegisterRecords/>')
|
||||
@@ -975,7 +1067,8 @@ def emit_constant_properties(indent):
|
||||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
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)
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
X(f'{i}<DefaultForm/>')
|
||||
@@ -1072,7 +1165,11 @@ def emit_defined_type_properties(indent):
|
||||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
X(f'{i}<Comment/>')
|
||||
# Accept both valueType and 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:
|
||||
X(f'{i}<Type>')
|
||||
for vt in value_types:
|
||||
@@ -1143,6 +1240,9 @@ def emit_scheduled_job_properties(indent):
|
||||
emit_mltext(i, 'Synonym', synonym)
|
||||
X(f'{i}<Comment/>')
|
||||
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>')
|
||||
description = str(defn['description']) if defn.get('description') else synonym
|
||||
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'
|
||||
X(f'{i}<Event>{event}</Event>')
|
||||
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>')
|
||||
|
||||
# --- 13b. Report, DataProcessor ---
|
||||
@@ -1250,18 +1353,12 @@ def emit_exchange_plan_properties(indent):
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
|
||||
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||
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}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
|
||||
X(f'{i}<EditType>InDialog</EditType>')
|
||||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||||
emit_standard_attributes(i, 'ExchangePlan')
|
||||
distributed = 'true' if defn.get('distributedInfoBase') 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>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||||
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||
code_allowed_length = str(defn['codeAllowedLength']) if defn.get('codeAllowedLength') else 'Variable'
|
||||
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}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
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:string</v8:Type>')
|
||||
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</v8:StringQualifiers>')
|
||||
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}<CodeSeries>{code_series}</CodeSeries>')
|
||||
X(f'{i}<CheckUnique>false</CheckUnique>')
|
||||
X(f'{i}<Autonumbering>true</Autonumbering>')
|
||||
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
|
||||
X(f'{i}<AutoOrderByCode>{auto_order}</AutoOrderByCode>')
|
||||
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>')
|
||||
emit_standard_attributes(i, 'ChartOfAccounts')
|
||||
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'
|
||||
code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||
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}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
|
||||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||||
dependence = str(defn['dependenceOnCalculationTypes']) if defn.get('dependenceOnCalculationTypes') else 'NotUsed'
|
||||
dependence = str(defn['dependenceOnCalculationTypes']) if defn.get('dependenceOnCalculationTypes') else 'DontUse'
|
||||
X(f'{i}<DependenceOnCalculationTypes>{dependence}</DependenceOnCalculationTypes>')
|
||||
base_types = list(defn.get('baseCalculationTypes', []))
|
||||
if base_types:
|
||||
@@ -1664,6 +1752,11 @@ def emit_business_process_properties(indent):
|
||||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||||
emit_standard_attributes(i, 'BusinessProcess')
|
||||
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}<InputByString>')
|
||||
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 = ''
|
||||
addressing_dimension = ''
|
||||
indexing = 'Index'
|
||||
if isinstance(addr_def, str):
|
||||
name = addr_def
|
||||
attr_synonym = split_camel_case(name)
|
||||
else:
|
||||
name = str(addr_def.get('name', ''))
|
||||
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'])
|
||||
parsed = parse_attribute_shorthand(addr_def)
|
||||
name = parsed['name']
|
||||
attr_synonym = parsed['synonym']
|
||||
type_str = parsed['type']
|
||||
if not isinstance(addr_def, str):
|
||||
if addr_def.get('addressingDimension'):
|
||||
addressing_dimension = str(addr_def['addressingDimension'])
|
||||
if addr_def.get('indexing'):
|
||||
@@ -2335,14 +2425,14 @@ if obj_type in types_with_module:
|
||||
if obj_type == 'ExchangePlan':
|
||||
content_path = os.path.join(ext_dir, 'Content.xml')
|
||||
if not os.path.isfile(content_path):
|
||||
content_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/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)
|
||||
modules_created.append(content_path)
|
||||
|
||||
if obj_type == 'BusinessProcess':
|
||||
flowchart_path = os.path.join(ext_dir, 'Flowchart.xml')
|
||||
if not os.path.isfile(flowchart_path):
|
||||
flowchart_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses"/>\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)
|
||||
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)
|
||||
elif reg_result == 'no-config':
|
||||
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"')
|
||||
|
||||
@@ -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
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -297,7 +297,7 @@ function Build-TypeContentXml {
|
||||
|
||||
# String or String(N)
|
||||
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:StringQualifiers>") | 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 {
|
||||
param($parsed, [string]$context, [string]$indent)
|
||||
|
||||
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
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
import argparse
|
||||
@@ -214,7 +214,7 @@ def build_type_content_xml(indent, type_str):
|
||||
# String or String(N)
|
||||
m = re.match(r"^String(\((\d+)\))?$", type_str)
|
||||
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:StringQualifiers>")
|
||||
lines.append(f"{indent}\t<v8:Length>{length}</v8:Length>")
|
||||
@@ -600,10 +600,38 @@ def get_attribute_context():
|
||||
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):
|
||||
"""Build XML fragment string for an Attribute element."""
|
||||
if not 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()
|
||||
lines = []
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: meta-validate
|
||||
description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности
|
||||
argument-hint: <ObjectPath> [-MaxErrors 30]
|
||||
argument-hint: <ObjectPath> [-MaxErrors 30] — pipe-separated paths for batch
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -16,18 +16,21 @@ allowed-tools:
|
||||
|
||||
```
|
||||
/meta-validate <ObjectPath>
|
||||
/meta-validate path1.xml|path2.xml|path3.xml — batch mode
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|------------|:------------:|--------------|-------------------------------------------------|
|
||||
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта. Несколько путей через `\|` для batch |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок (per object) |
|
||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
|
||||
|
||||
**Batch mode**: при нескольких путях через `|` каждый объект валидируется отдельно, в конце выводится сводка `=== Batch: N objects, X passed, Y failed ===`.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
@@ -94,6 +97,9 @@ Errors: 1, Warnings: 1
|
||||
|
||||
# С записью в файл
|
||||
... -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
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,31 @@ param(
|
||||
$ErrorActionPreference = "Stop"
|
||||
[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 ---
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
|
||||
@@ -54,6 +79,19 @@ if (-not (Test-Path $ObjectPath)) {
|
||||
|
||||
$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 ---
|
||||
|
||||
$script:errors = 0
|
||||
@@ -216,6 +254,15 @@ $validPropertyValues = @{
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"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 ---
|
||||
@@ -649,6 +696,41 @@ if ($childObjNode) {
|
||||
|
||||
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 ---
|
||||
|
||||
function Check-Uniqueness {
|
||||
@@ -887,6 +969,97 @@ if ($propsNode) {
|
||||
$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) {
|
||||
@@ -991,6 +1164,129 @@ if ($mdType -eq "HTTPService" -and $childObjNode) {
|
||||
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 ---
|
||||
|
||||
& $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
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
@@ -18,10 +19,32 @@ parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
parser.add_argument("-OutFile", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
object_path = args.ObjectPath
|
||||
max_errors = args.MaxErrors
|
||||
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 ─────────────────────────────────────────────
|
||||
|
||||
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)
|
||||
|
||||
# ── 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 ────────────────────────────────────
|
||||
|
||||
errors = 0
|
||||
@@ -216,6 +251,15 @@ valid_property_values = {
|
||||
"FillChecking": ["DontCheck", "ShowError", "ShowWarning"],
|
||||
"Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"],
|
||||
"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 ───────────────────────────────────────────────
|
||||
@@ -626,6 +670,37 @@ if stopped:
|
||||
finalize()
|
||||
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 ─────────────────────────────────
|
||||
|
||||
|
||||
@@ -841,6 +916,88 @@ if props_node is not None:
|
||||
check10_ok = False
|
||||
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:
|
||||
report_ok("10. Cross-property consistency")
|
||||
|
||||
@@ -929,6 +1086,120 @@ elif md_type == "WebService" and child_obj_node is not None:
|
||||
else:
|
||||
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 ──────────────────────────────────────────────
|
||||
|
||||
finalize()
|
||||
|
||||
+31
-11
@@ -1,6 +1,6 @@
|
||||
# Meta DSL — спецификация JSON-формата для объектов метаданных 1С
|
||||
|
||||
Версия: 2.0
|
||||
Версия: 2.1
|
||||
|
||||
## Обзор
|
||||
|
||||
@@ -112,7 +112,7 @@ JSON DSL для описания объектов метаданных конф
|
||||
### 4.1 Строковая форма
|
||||
|
||||
```
|
||||
"ИмяРеквизита" → String (без квалификаторов)
|
||||
"ИмяРеквизита" → String(10) по умолчанию
|
||||
"ИмяРеквизита: Тип" → с типом
|
||||
"ИмяРеквизита: Тип | 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 Флаги
|
||||
|
||||
| Флаг | Действие | Применимость |
|
||||
@@ -243,8 +254,13 @@ JSON DSL для описания объектов метаданных конф
|
||||
| Поле JSON | Умолчание | XML элемент |
|
||||
|-----------|----------|-------------|
|
||||
| `valueType` | `String` | Type |
|
||||
| `length` | — | Длина строки (если valueType=String) |
|
||||
| `precision` | — | Точность числа (если valueType=Number) |
|
||||
| `dataLockControlMode` | `Automatic` | DataLockControlMode |
|
||||
|
||||
`valueType` + `length`/`precision` работают аналогично раздельной форме типа (§4.2):
|
||||
`"valueType": "String", "length": 100` → `String(100)`.
|
||||
|
||||
### 7.5 InformationRegister
|
||||
|
||||
| Поле JSON | Умолчание | XML элемент |
|
||||
@@ -277,11 +293,13 @@ JSON DSL для описания объектов метаданных конф
|
||||
| Поле JSON | Умолчание | XML элемент |
|
||||
|-----------|----------|-------------|
|
||||
| `valueTypes` | `[]` | Type (составной — массив `v8:Type`) |
|
||||
| `valueType` | — | Алиас для `valueTypes` (принимает строку или массив) |
|
||||
|
||||
Без ChildObjects и модулей.
|
||||
Без ChildObjects и модулей. Принимается как `valueTypes` (мн.ч.), так и `valueType` (ед.ч.).
|
||||
|
||||
```json
|
||||
{ "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] }
|
||||
{ "type": "DefinedType", "name": "ФлагАктивности", "valueType": "Boolean" }
|
||||
```
|
||||
|
||||
### 7.8 CommonModule
|
||||
@@ -313,7 +331,7 @@ JSON DSL для описания объектов метаданных конф
|
||||
|
||||
| Поле JSON | Умолчание | XML элемент |
|
||||
|-----------|----------|-------------|
|
||||
| `methodName` | `""` | MethodName |
|
||||
| `methodName` | `""` | MethodName (авто-префикс `CommonModule.`) |
|
||||
| `description` | = synonym | Description |
|
||||
| `key` | `""` | Key |
|
||||
| `use` | `false` | Use |
|
||||
@@ -323,6 +341,8 @@ JSON DSL для описания объектов метаданных конф
|
||||
|
||||
Без ChildObjects и модулей.
|
||||
|
||||
Формат `methodName`: `"МодульСервер.Процедура"` — при компиляции авто-дополняется до `CommonModule.МодульСервер.Процедура`. Если уже содержит `CommonModule.` — оставляется как есть.
|
||||
|
||||
```json
|
||||
{ "type": "ScheduledJob", "name": "ОбменДанными", "methodName": "ОбменДаннымиСервер.Выполнить", "use": true }
|
||||
```
|
||||
@@ -333,12 +353,14 @@ JSON DSL для описания объектов метаданных конф
|
||||
|-----------|----------|-------------|
|
||||
| `source` | `[]` | Source (массив `v8:Type`, формат `cfg:XxxObject.Name`) |
|
||||
| `event` | `BeforeWrite` | Event |
|
||||
| `handler` | `""` | Handler |
|
||||
| `handler` | `""` | Handler (авто-префикс `CommonModule.`) |
|
||||
|
||||
Без ChildObjects и модулей.
|
||||
|
||||
Значения `event`: `BeforeWrite`, `OnWrite`, `BeforeDelete`, `OnReadAtServer`, `FillCheckProcessing` и др.
|
||||
|
||||
Формат `handler`: `"МодульСервер.Процедура"` — при компиляции авто-дополняется до `CommonModule.МодульСервер.Процедура`. Если уже содержит `CommonModule.` — оставляется как есть.
|
||||
|
||||
```json
|
||||
{ "type": "EventSubscription", "name": "ПередЗаписьюКонтрагента", "source": ["CatalogObject.Контрагенты"], "event": "BeforeWrite", "handler": "ОбщегоНазначенияСервер.ПередЗаписьюКонтрагента" }
|
||||
```
|
||||
@@ -382,11 +404,8 @@ JSON DSL для описания объектов метаданных конф
|
||||
| Поле JSON | Умолчание | XML элемент |
|
||||
|-----------|----------|-------------|
|
||||
| `codeLength` | `9` | CodeLength |
|
||||
| `codeType` | `String` | CodeType |
|
||||
| `codeAllowedLength` | `Variable` | CodeAllowedLength |
|
||||
| `descriptionLength` | `100` | DescriptionLength |
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `checkUnique` | `false` | CheckUnique |
|
||||
| `distributedInfoBase` | `false` | DistributedInfoBase |
|
||||
| `includeConfigurationExtensions` | `false` | IncludeConfigurationExtensions |
|
||||
| `dataLockControlMode` | `Automatic` | DataLockControlMode |
|
||||
@@ -419,7 +438,7 @@ JSON DSL для описания объектов метаданных конф
|
||||
| `attributes` | `[]` | → Attribute в ChildObjects |
|
||||
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
|
||||
|
||||
\* Если `valueTypes` не указан, по умолчанию: Boolean, String, Number(15,2), DateTime.
|
||||
\* Если `valueTypes` не указан, по умолчанию: Boolean, String(100), Number(15,2), DateTime.
|
||||
|
||||
Модули: `Ext/ObjectModule.bsl` (пустой).
|
||||
|
||||
@@ -580,6 +599,7 @@ DSL для `columns` (§12).
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `dataLockControlMode` | `Automatic` | DataLockControlMode |
|
||||
| `fullTextSearch` | `Use` | FullTextSearch |
|
||||
| `task` | `""` | Task (ссылка на Task.XXX) |
|
||||
| `attributes` | `[]` | → Attribute в ChildObjects |
|
||||
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
|
||||
|
||||
@@ -587,7 +607,7 @@ DSL для `columns` (§12).
|
||||
Дополнительно: `Ext/Flowchart.xml` (заглушка карты маршрута).
|
||||
|
||||
```json
|
||||
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] }
|
||||
{ "type": "BusinessProcess", "name": "Задание", "task": "Task.ЗадачаИсполнителя", "attributes": ["Описание: String(200)"] }
|
||||
```
|
||||
|
||||
### 7.21 Task
|
||||
@@ -904,7 +924,7 @@ DSL для `columns` (§12).
|
||||
### Бизнес-процесс
|
||||
|
||||
```json
|
||||
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] }
|
||||
{ "type": "BusinessProcess", "name": "Задание", "task": "Task.ЗадачаИсполнителя", "attributes": ["Описание: String(200)"] }
|
||||
```
|
||||
|
||||
### Задача
|
||||
|
||||
Reference in New Issue
Block a user