mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
Add script-based /role-compile and /role-validate skills
Convert /role-compile from instruction-based to PowerShell script with JSON DSL: - Presets (@view, @edit, @use) for common right sets - String shorthand and object form with RLS support - Russian synonym translation for object types and rights - Auto UUID generation, UTF-8 BOM output Add /role-validate for structural validation of Rights.xml: - XML well-formedness, namespace, global flags - Right name validation per object type with typo suggestions - RLS condition and template checks - Optional metadata validation (UUID, Name, Synonym) Update README and role-guide with new skills documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: role-compile
|
||||
description: Создание роли 1С — метаданные и Rights.xml из описания прав
|
||||
argument-hint: <RoleName> <RolesDir>
|
||||
argument-hint: <JsonPath> <RolesDir>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -9,199 +9,331 @@ allowed-tools:
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /role-compile — создание роли 1С
|
||||
# /role-compile — генерация роли 1С из JSON DSL
|
||||
|
||||
Создаёт файлы роли (метаданные + Rights.xml) по описанию прав. Скрипта нет — агент генерирует XML по шаблонам ниже.
|
||||
Принимает компактное JSON-определение роли и генерирует два файла: метаданные (`Roles/Имя.xml`) и права (`Roles/Имя/Ext/Rights.xml`). UUID генерируется автоматически.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/role-compile <RoleName> <RolesDir>
|
||||
/role-compile <JsonPath> <RolesDir>
|
||||
```
|
||||
|
||||
- **RoleName** — программное имя роли
|
||||
- **RolesDir** — каталог `Roles/` в исходниках конфигурации
|
||||
## Параметры
|
||||
|
||||
## Файловая структура и регистрация
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|:------------:|----------|
|
||||
| JsonPath | да | Путь к JSON-определению роли |
|
||||
| RolesDir | да | Каталог `Roles/` в исходниках конфигурации |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\role-compile\scripts\role-compile.ps1 -JsonPath "<json>" -OutputDir "<RolesDir>"
|
||||
```
|
||||
|
||||
## Выходные файлы
|
||||
|
||||
```
|
||||
Roles/
|
||||
ИмяРоли.xml ← метаданные (uuid, имя, синоним)
|
||||
RolesDir/
|
||||
ИмяРоли.xml ← метаданные (uuid, имя, синоним)
|
||||
ИмяРоли/
|
||||
Ext/
|
||||
Rights.xml ← определение прав
|
||||
Rights.xml ← определение прав
|
||||
```
|
||||
|
||||
В `Configuration.xml` добавить `<Role>ИмяРоли</Role>` в секцию `<ChildObjects>`.
|
||||
После генерации: добавить `<Role>ИмяРоли</Role>` в `<ChildObjects>` файла `Configuration.xml`.
|
||||
|
||||
## Шаблон метаданных: Roles/ИмяРоли.xml
|
||||
## JSON DSL — справка
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"
|
||||
xmlns:v8="http://v8.1c.ru/8.1/data/core"
|
||||
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
version="2.17">
|
||||
<Role uuid="GENERATE-UUID-HERE">
|
||||
<Properties>
|
||||
<Name>ИмяРоли</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Отображаемое имя роли</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
</Properties>
|
||||
</Role>
|
||||
</MetaDataObject>
|
||||
### Структура верхнего уровня
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ИмяРоли",
|
||||
"synonym": "Отображаемое имя роли",
|
||||
"comment": "",
|
||||
"setForNewObjects": false,
|
||||
"setForAttributesByDefault": true,
|
||||
"independentRightsOfChildObjects": false,
|
||||
"objects": [ ... ],
|
||||
"templates": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
**UUID:** `powershell.exe -Command "[guid]::NewGuid().ToString()"`
|
||||
- `name` — программное имя роли (обязательно)
|
||||
- `synonym` — отображаемое имя (по умолчанию = name)
|
||||
- `comment` — комментарий (по умолчанию пусто)
|
||||
- Глобальные флаги — по умолчанию `false`, `true`, `false`
|
||||
|
||||
## Шаблон прав: Roles/ИмяРоли/Ext/Rights.xml
|
||||
### Объекты: два формата
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Rights xmlns="http://v8.1c.ru/8.2/roles"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="Rights" version="2.17">
|
||||
<setForNewObjects>false</setForNewObjects>
|
||||
<setForAttributesByDefault>true</setForAttributesByDefault>
|
||||
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
|
||||
<!-- блоки <object> -->
|
||||
</Rights>
|
||||
```
|
||||
Массив `objects` принимает строки (shorthand) и объекты (полная форма).
|
||||
|
||||
NB: namespace `http://v8.1c.ru/8.2/roles` (исторически 8.2, не 8.3).
|
||||
|
||||
## Формат блока прав
|
||||
|
||||
```xml
|
||||
<object>
|
||||
<name>Catalog.Номенклатура</name>
|
||||
<right><name>Read</name><value>true</value></right>
|
||||
<right><name>View</name><value>true</value></right>
|
||||
</object>
|
||||
```
|
||||
|
||||
Имя объекта — dot-нотация: `ТипОбъекта.Имя[.ТипВложенного.ИмяВложенного]`.
|
||||
|
||||
## Практические наборы прав
|
||||
|
||||
### Catalog / ExchangePlan
|
||||
|
||||
| Набор | Права |
|
||||
|-------|-------|
|
||||
| Чтение | Read, View, InputByString |
|
||||
| Полные | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark |
|
||||
|
||||
### Document
|
||||
|
||||
| Набор | Права |
|
||||
|-------|-------|
|
||||
| Чтение | Read, View, InputByString |
|
||||
| Полные | Read, Insert, Update, Delete, View, Edit, InputByString, Posting, UndoPosting, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted |
|
||||
|
||||
### InformationRegister / AccumulationRegister / AccountingRegister
|
||||
|
||||
| Набор | Права |
|
||||
|-------|-------|
|
||||
| Чтение | Read, View |
|
||||
| Полные | Read, Update, View, Edit |
|
||||
|
||||
TotalsControl — только для управления итогами, обычно не нужно.
|
||||
|
||||
### Простые типы
|
||||
|
||||
| Тип | Права |
|
||||
|-----|-------|
|
||||
| `DataProcessor` / `Report` | Use, View |
|
||||
| `Constant` | Read, Update, View, Edit (чтение: Read, View) |
|
||||
| `CommonForm` / `CommonCommand` / `Subsystem` / `FilterCriterion` | View |
|
||||
| `DocumentJournal` | Read, View |
|
||||
| `Sequence` | Read, Update |
|
||||
| `SessionParameter` | Get (+ Set если пишет) |
|
||||
| `CommonAttribute` | View (+ Edit если редактирует) |
|
||||
| `WebService` / `HTTPService` / `IntegrationService` | Use |
|
||||
| `CalculationRegister` | Read, View |
|
||||
|
||||
### Редкие ссылочные типы
|
||||
|
||||
| Тип | Особенности (относительно Catalog) |
|
||||
|-----|-------|
|
||||
| `ChartOfAccounts`, `ChartOfCharacteristicTypes`, `ChartOfCalculationTypes` | + Predefined-права (InteractiveDeletePredefinedData и др.) |
|
||||
| `BusinessProcess` | + Start, InteractiveStart, InteractiveActivate |
|
||||
| `Task` | + Execute, InteractiveExecute, InteractiveActivate |
|
||||
|
||||
### Типы БЕЗ прав в ролях
|
||||
|
||||
Enum, FunctionalOption, DefinedType, CommonModule, CommonPicture, CommonTemplate — не фигурируют в Rights.xml.
|
||||
|
||||
### Вложенные объекты (права: View, Edit)
|
||||
#### Строковый shorthand
|
||||
|
||||
```
|
||||
Catalog.Контрагенты.Attribute.ИНН
|
||||
Document.Реализация.StandardAttribute.Posted
|
||||
Document.Реализация.TabularSection.Товары
|
||||
InformationRegister.Цены.Dimension.Номенклатура
|
||||
InformationRegister.Цены.Resource.Цена
|
||||
Catalog.Контрагенты.Command.ОткрытьКарточку ← только View
|
||||
Task.Задача.AddressingAttribute.Исполнитель
|
||||
"ОбъектМетаданных: @пресет"
|
||||
"ОбъектМетаданных: Право1, Право2"
|
||||
```
|
||||
|
||||
Используются для точечного запрета: `<value>false</value>` на конкретный реквизит.
|
||||
|
||||
### Configuration
|
||||
|
||||
Объект: `Configuration.ИмяКонфигурации`. Ключевые права: Administration, DataAdministration, ThinClient, WebClient, ThickClient, MobileClient, ExternalConnection, Output, SaveUserData, InteractiveOpenExtDataProcessors, InteractiveOpenExtReports, MainWindowModeNormal, MainWindowModeWorkplace, MainWindowModeEmbeddedWorkplace, MainWindowModeFullscreenWorkplace, MainWindowModeKiosk, AnalyticsSystemClient.
|
||||
|
||||
> DataHistory-права (ReadDataHistory, UpdateDataHistory и др.) существуют у Catalog, Document, Register, Constant — но используются крайне редко, в типовых ролях практически не встречаются.
|
||||
|
||||
## RLS (ограничения на уровне записей)
|
||||
|
||||
Внутрь `<right>`, после `<value>`. Применяется к Read, Update, Insert, Delete.
|
||||
|
||||
```xml
|
||||
<right>
|
||||
<name>Read</name>
|
||||
<value>true</value>
|
||||
<restrictionByCondition>
|
||||
<condition>#ИмяШаблона("Параметр1", "Параметр2")</condition>
|
||||
</restrictionByCondition>
|
||||
</right>
|
||||
Примеры:
|
||||
```json
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
"Document.Реализация: @edit",
|
||||
"InformationRegister.Цены: Read, Update",
|
||||
"DataProcessor.Загрузка: @use"
|
||||
]
|
||||
```
|
||||
|
||||
Шаблоны — в конце Rights.xml, после всех `<object>`:
|
||||
#### Объектная форма (для RLS и переопределений)
|
||||
|
||||
```xml
|
||||
<restrictionTemplate>
|
||||
<name>ИмяШаблона(Параметр1, Параметр2)</name>
|
||||
<condition>Текст шаблона</condition>
|
||||
</restrictionTemplate>
|
||||
```json
|
||||
{
|
||||
"name": "Document.Реализация",
|
||||
"preset": "view",
|
||||
"rights": { "Delete": false },
|
||||
"rls": { "Read": "#ДляОбъекта(\"\")" }
|
||||
}
|
||||
```
|
||||
|
||||
`&` в условии → `&`. Типичные шаблоны: ДляОбъекта, ПоЗначениям, ДляРегистра.
|
||||
- `preset` — базовый набор прав (`"view"`, `"edit"`, `"use"`)
|
||||
- `rights` — переопределения: dict `{"Right": true/false}` или массив `["Right1", "Right2"]`
|
||||
- `rls` — RLS-ограничения: `{"ИмяПрава": "текст условия"}`
|
||||
|
||||
## Пример: роль для регламентного задания
|
||||
### Пресеты (`@view`, `@edit`, `@use`)
|
||||
|
||||
```xml
|
||||
<object>
|
||||
<name>Catalog.Валюты</name>
|
||||
<right><name>Read</name><value>true</value></right>
|
||||
</object>
|
||||
<object>
|
||||
<name>InformationRegister.КурсыВалют</name>
|
||||
<right><name>Read</name><value>true</value></right>
|
||||
<right><name>Update</name><value>true</value></right>
|
||||
</object>
|
||||
<object>
|
||||
<name>Constant.ОсновнаяВалюта</name>
|
||||
<right><name>Read</name><value>true</value></right>
|
||||
</object>
|
||||
Пресеты обозначаются `@` в строковом формате. В объектной форме ключ `preset` без `@`.
|
||||
|
||||
#### `@view` — просмотр
|
||||
|
||||
| Тип объекта | Права |
|
||||
|-------------|-------|
|
||||
| Catalog, ExchangePlan, Document, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task | Read, View, InputByString |
|
||||
| InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, Constant, DocumentJournal | Read, View |
|
||||
| Sequence | Read |
|
||||
| CommonForm, CommonCommand, Subsystem, FilterCriterion, CommonAttribute | View |
|
||||
| SessionParameter | Get |
|
||||
| Configuration | ThinClient, WebClient, Output, SaveUserData, MainWindowModeNormal |
|
||||
|
||||
#### `@edit` — полное редактирование
|
||||
|
||||
| Тип объекта | Права |
|
||||
|-------------|-------|
|
||||
| Catalog, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark |
|
||||
| Document | Read, Insert, Update, Delete, View, Edit, InputByString, Posting, UndoPosting, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted |
|
||||
| BusinessProcess | Read, Insert, Update, Delete, View, Edit, InputByString, Start, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveActivate, InteractiveStart |
|
||||
| Task | Read, Insert, Update, Delete, View, Edit, InputByString, Execute, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveActivate, InteractiveExecute |
|
||||
| InformationRegister, AccumulationRegister, AccountingRegister, Constant | Read, Update, View, Edit |
|
||||
| DocumentJournal | Read, View |
|
||||
| Sequence | Read, Update |
|
||||
| SessionParameter | Get, Set |
|
||||
| CommonAttribute | View, Edit |
|
||||
|
||||
#### `@use` — использование
|
||||
|
||||
| Тип объекта | Права |
|
||||
|-------------|-------|
|
||||
| DataProcessor, Report | Use, View |
|
||||
| CommonForm, CommonCommand, Subsystem | View |
|
||||
| WebService, HTTPService, IntegrationService | Use |
|
||||
|
||||
Если пресет не определён для типа объекта — предупреждение с подсказкой доступных.
|
||||
|
||||
### Русские синонимы
|
||||
|
||||
Скрипт автоматически транслирует русские имена в английские. Можно смешивать: `"Справочник.Контрагенты: Чтение, View"` — работает.
|
||||
|
||||
**Типы объектов:**
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Справочник` | Catalog |
|
||||
| `Документ` | Document |
|
||||
| `РегистрСведений` | InformationRegister |
|
||||
| `РегистрНакопления` | AccumulationRegister |
|
||||
| `РегистрБухгалтерии` | AccountingRegister |
|
||||
| `РегистрРасчета` | CalculationRegister |
|
||||
| `Константа` | Constant |
|
||||
| `ПланСчетов` | ChartOfAccounts |
|
||||
| `ПланВидовХарактеристик` | ChartOfCharacteristicTypes |
|
||||
| `ПланВидовРасчета` | ChartOfCalculationTypes |
|
||||
| `ПланОбмена` | ExchangePlan |
|
||||
| `БизнесПроцесс` | BusinessProcess |
|
||||
| `Задача` | Task |
|
||||
| `Обработка` | DataProcessor |
|
||||
| `Отчет` | Report |
|
||||
| `ОбщаяФорма` | CommonForm |
|
||||
| `ОбщаяКоманда` | CommonCommand |
|
||||
| `Подсистема` | Subsystem |
|
||||
| `КритерийОтбора` | FilterCriterion |
|
||||
| `ЖурналДокументов` | DocumentJournal |
|
||||
| `Последовательность` | Sequence |
|
||||
| `ВебСервис` | WebService |
|
||||
| `HTTPСервис` | HTTPService |
|
||||
| `СервисИнтеграции` | IntegrationService |
|
||||
| `ПараметрСеанса` | SessionParameter |
|
||||
| `ОбщийРеквизит` | CommonAttribute |
|
||||
| `Конфигурация` | Configuration |
|
||||
| `Перечисление` | Enum |
|
||||
|
||||
Вложенные типы: `Реквизит` → Attribute, `СтандартныйРеквизит` → StandardAttribute, `ТабличнаяЧасть` → TabularSection, `Измерение` → Dimension, `Ресурс` → Resource, `Команда` → Command, `РеквизитАдресации` → AddressingAttribute.
|
||||
|
||||
**Права (основные):**
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Чтение` | Read |
|
||||
| `Добавление` | Insert |
|
||||
| `Изменение` | Update |
|
||||
| `Удаление` | Delete |
|
||||
| `Просмотр` | View |
|
||||
| `Редактирование` | Edit |
|
||||
| `ВводПоСтроке` | InputByString |
|
||||
| `Проведение` | Posting |
|
||||
| `ОтменаПроведения` | UndoPosting |
|
||||
| `Использование` | Use |
|
||||
| `Получение` | Get |
|
||||
| `Установка` | Set |
|
||||
| `Старт` | Start |
|
||||
| `Выполнение` | Execute |
|
||||
| `УправлениеИтогами` | TotalsControl |
|
||||
|
||||
**Права (интерактивные):**
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `ИнтерактивноеДобавление` | InteractiveInsert |
|
||||
| `ИнтерактивнаяПометкаУдаления` | InteractiveSetDeletionMark |
|
||||
| `ИнтерактивноеСнятиеПометкиУдаления` | InteractiveClearDeletionMark |
|
||||
| `ИнтерактивноеУдаление` | InteractiveDelete |
|
||||
| `ИнтерактивноеУдалениеПомеченных` | InteractiveDeleteMarked |
|
||||
| `ИнтерактивноеПроведение` | InteractivePosting |
|
||||
| `ИнтерактивноеПроведениеНеоперативное` | InteractivePostingRegular |
|
||||
| `ИнтерактивнаяОтменаПроведения` | InteractiveUndoPosting |
|
||||
| `ИнтерактивноеИзменениеПроведенных` | InteractiveChangeOfPosted |
|
||||
| `ИнтерактивныйСтарт` | InteractiveStart |
|
||||
| `ИнтерактивнаяАктивация` | InteractiveActivate |
|
||||
| `ИнтерактивноеВыполнение` | InteractiveExecute |
|
||||
|
||||
**Права (конфигурация):**
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Администрирование` | Administration |
|
||||
| `АдминистрированиеДанных` | DataAdministration |
|
||||
| `ТонкийКлиент` | ThinClient |
|
||||
| `ТолстыйКлиент` | ThickClient |
|
||||
| `ВебКлиент` | WebClient |
|
||||
| `МобильныйКлиент` | MobileClient |
|
||||
| `ВнешнееСоединение` | ExternalConnection |
|
||||
| `Вывод` | Output |
|
||||
| `СохранениеДанныхПользователя` | SaveUserData |
|
||||
|
||||
### Шаблоны ограничений (RLS templates)
|
||||
|
||||
```json
|
||||
"templates": [
|
||||
{
|
||||
"name": "ДляОбъекта(Модификатор)",
|
||||
"condition": "// текст шаблона\nГДЕ 1=1\n&Модификатор"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Фоновые задания не требуют Interactive/View/Edit-прав и прав конфигурации (ThinClient, WebClient и др.) — только программные (Read, Insert, Update, Delete, Posting).
|
||||
`&` в условии автоматически экранируется в `&` в XML.
|
||||
|
||||
## Примеры
|
||||
|
||||
### Простая роль (только пресеты)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ЧтениеНоменклатуры",
|
||||
"synonym": "Чтение номенклатуры",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
"Catalog.Контрагенты: @view",
|
||||
"DataProcessor.Загрузка: @use"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Роль для регламентного задания
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ОбновлениеЦен",
|
||||
"synonym": "Обновление цен номенклатуры",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: Read",
|
||||
"Catalog.Валюты: Read",
|
||||
"InformationRegister.ЦеныНоменклатуры: Read, Update",
|
||||
"Constant.ОсновнаяВалюта: Read"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Роль с RLS
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ЧтениеДокументовПоОрганизации",
|
||||
"synonym": "Чтение документов (ограничение по организации)",
|
||||
"objects": [
|
||||
"Catalog.Организации: @view",
|
||||
{
|
||||
"name": "Document.РеализацияТоваровУслуг",
|
||||
"preset": "view",
|
||||
"rls": {
|
||||
"Read": "#ДляОбъекта(\"\")"
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"name": "ДляОбъекта(Модификатор)",
|
||||
"condition": "ГДЕ Организация = &ТекущаяОрганизация"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Роль с русскими синонимами
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ПросмотрДанных",
|
||||
"synonym": "Просмотр данных",
|
||||
"objects": [
|
||||
"Справочник.Контрагенты: @view",
|
||||
"Документ.Реализация: Чтение, Просмотр",
|
||||
"РегистрСведений.Цены: @edit",
|
||||
"Обработка.ЗагрузкаДанных: @use"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Роль с переопределением прав из пресета
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ОграниченноеРедактирование",
|
||||
"synonym": "Редактирование без удаления",
|
||||
"objects": [
|
||||
{
|
||||
"name": "Catalog.Контрагенты",
|
||||
"preset": "edit",
|
||||
"rights": { "Delete": false }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/role-validate <RightsPath> [MetadataPath] — проверка корректности XML, прав, RLS
|
||||
/role-info <RightsPath> — визуальная сводка структуры
|
||||
```
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$JsonPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$OutputDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- 1. Load and validate JSON ---
|
||||
|
||||
if (-not (Test-Path $JsonPath)) {
|
||||
Write-Error "File not found: $JsonPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$json = Get-Content -Raw -Encoding UTF8 $JsonPath
|
||||
$def = $json | ConvertFrom-Json
|
||||
|
||||
if (-not $def.name) {
|
||||
Write-Error "JSON must have 'name' field (role programmatic name)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$roleName = "$($def.name)"
|
||||
$synonym = if ($def.synonym) { "$($def.synonym)" } else { $roleName }
|
||||
$comment = if ($def.comment) { "$($def.comment)" } else { "" }
|
||||
|
||||
# --- 2. XML helpers ---
|
||||
|
||||
$script:xmlBuf = $null
|
||||
|
||||
function X {
|
||||
param([string]$text)
|
||||
$script:xmlBuf.AppendLine($text) | Out-Null
|
||||
}
|
||||
|
||||
function Esc-Xml {
|
||||
param([string]$s)
|
||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||
}
|
||||
|
||||
# --- 3. Russian synonyms → canonical English names ---
|
||||
|
||||
$script:typeAliases = @{
|
||||
"Справочник" = "Catalog"
|
||||
"Документ" = "Document"
|
||||
"РегистрСведений" = "InformationRegister"
|
||||
"РегистрНакопления" = "AccumulationRegister"
|
||||
"РегистрБухгалтерии" = "AccountingRegister"
|
||||
"РегистрРасчета" = "CalculationRegister"
|
||||
"Константа" = "Constant"
|
||||
"ПланСчетов" = "ChartOfAccounts"
|
||||
"ПланВидовХарактеристик" = "ChartOfCharacteristicTypes"
|
||||
"ПланВидовРасчета" = "ChartOfCalculationTypes"
|
||||
"ПланОбмена" = "ExchangePlan"
|
||||
"БизнесПроцесс" = "BusinessProcess"
|
||||
"Задача" = "Task"
|
||||
"Обработка" = "DataProcessor"
|
||||
"Отчет" = "Report"
|
||||
"ОбщаяФорма" = "CommonForm"
|
||||
"ОбщаяКоманда" = "CommonCommand"
|
||||
"Подсистема" = "Subsystem"
|
||||
"КритерийОтбора" = "FilterCriterion"
|
||||
"ЖурналДокументов" = "DocumentJournal"
|
||||
"Последовательность" = "Sequence"
|
||||
"ВебСервис" = "WebService"
|
||||
"HTTPСервис" = "HTTPService"
|
||||
"СервисИнтеграции" = "IntegrationService"
|
||||
"ПараметрСеанса" = "SessionParameter"
|
||||
"ОбщийРеквизит" = "CommonAttribute"
|
||||
"Конфигурация" = "Configuration"
|
||||
"Перечисление" = "Enum"
|
||||
# Nested
|
||||
"Реквизит" = "Attribute"
|
||||
"СтандартныйРеквизит" = "StandardAttribute"
|
||||
"ТабличнаяЧасть" = "TabularSection"
|
||||
"Измерение" = "Dimension"
|
||||
"Ресурс" = "Resource"
|
||||
"Команда" = "Command"
|
||||
"РеквизитАдресации" = "AddressingAttribute"
|
||||
}
|
||||
|
||||
$script:rightAliases = @{
|
||||
"Чтение" = "Read"
|
||||
"Добавление" = "Insert"
|
||||
"Изменение" = "Update"
|
||||
"Удаление" = "Delete"
|
||||
"Просмотр" = "View"
|
||||
"Редактирование" = "Edit"
|
||||
"ВводПоСтроке" = "InputByString"
|
||||
"Проведение" = "Posting"
|
||||
"ОтменаПроведения" = "UndoPosting"
|
||||
"ИнтерактивноеДобавление" = "InteractiveInsert"
|
||||
"ИнтерактивнаяПометкаУдаления" = "InteractiveSetDeletionMark"
|
||||
"ИнтерактивноеСнятиеПометкиУдаления" = "InteractiveClearDeletionMark"
|
||||
"ИнтерактивноеУдаление" = "InteractiveDelete"
|
||||
"ИнтерактивноеУдалениеПомеченных" = "InteractiveDeleteMarked"
|
||||
"ИнтерактивноеПроведение" = "InteractivePosting"
|
||||
"ИнтерактивноеПроведениеНеоперативное" = "InteractivePostingRegular"
|
||||
"ИнтерактивнаяОтменаПроведения" = "InteractiveUndoPosting"
|
||||
"ИнтерактивноеИзменениеПроведенных" = "InteractiveChangeOfPosted"
|
||||
"Использование" = "Use"
|
||||
"Получение" = "Get"
|
||||
"Установка" = "Set"
|
||||
"Старт" = "Start"
|
||||
"ИнтерактивныйСтарт" = "InteractiveStart"
|
||||
"ИнтерактивнаяАктивация" = "InteractiveActivate"
|
||||
"Выполнение" = "Execute"
|
||||
"ИнтерактивноеВыполнение" = "InteractiveExecute"
|
||||
"УправлениеИтогами" = "TotalsControl"
|
||||
"Администрирование" = "Administration"
|
||||
"АдминистрированиеДанных" = "DataAdministration"
|
||||
"ТонкийКлиент" = "ThinClient"
|
||||
"ВебКлиент" = "WebClient"
|
||||
"ТолстыйКлиент" = "ThickClient"
|
||||
"ВнешнееСоединение" = "ExternalConnection"
|
||||
"Вывод" = "Output"
|
||||
"СохранениеДанныхПользователя" = "SaveUserData"
|
||||
"МобильныйКлиент" = "MobileClient"
|
||||
}
|
||||
|
||||
# Translate Russian object name to English (e.g. "Справочник.Контрагенты" → "Catalog.Контрагенты")
|
||||
function Translate-ObjectName {
|
||||
param([string]$name)
|
||||
$parts = $name.Split(".")
|
||||
$result = @()
|
||||
foreach ($p in $parts) {
|
||||
if ($script:typeAliases.ContainsKey($p)) {
|
||||
$result += $script:typeAliases[$p]
|
||||
} else {
|
||||
$result += $p
|
||||
}
|
||||
}
|
||||
return $result -join "."
|
||||
}
|
||||
|
||||
# Translate Russian right name to English (e.g. "Чтение" → "Read")
|
||||
function Translate-RightName {
|
||||
param([string]$name)
|
||||
if ($script:rightAliases.ContainsKey($name)) {
|
||||
return $script:rightAliases[$name]
|
||||
}
|
||||
return $name
|
||||
}
|
||||
|
||||
# --- 4. Known rights per object type (source: docs/1c-role-spec.md) ---
|
||||
|
||||
$script:knownRights = @{
|
||||
"Configuration" = @(
|
||||
"Administration","DataAdministration","UpdateDataBaseConfiguration",
|
||||
"ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode",
|
||||
"ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection",
|
||||
"Automation","Output","SaveUserData","TechnicalSpecialistMode",
|
||||
"InteractiveOpenExtDataProcessors","InteractiveOpenExtReports",
|
||||
"AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration",
|
||||
"MainWindowModeNormal","MainWindowModeWorkplace",
|
||||
"MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk"
|
||||
)
|
||||
"Catalog" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"Document" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Posting","UndoPosting",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting",
|
||||
"InteractiveChangeOfPosted",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"InformationRegister" = @(
|
||||
"Read","Update","View","Edit","TotalsControl",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"AccountingRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"CalculationRegister" = @("Read","View")
|
||||
"Constant" = @(
|
||||
"Read","Update","View","Edit",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfAccounts" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment"
|
||||
)
|
||||
"ChartOfCharacteristicTypes" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfCalculationTypes" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData"
|
||||
)
|
||||
"ExchangePlan" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"BusinessProcess" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveActivate","InteractiveStart"
|
||||
)
|
||||
"Task" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveActivate","InteractiveExecute"
|
||||
)
|
||||
"DataProcessor" = @("Use","View")
|
||||
"Report" = @("Use","View")
|
||||
"CommonForm" = @("View")
|
||||
"CommonCommand" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"FilterCriterion" = @("View")
|
||||
"DocumentJournal" = @("Read","View")
|
||||
"Sequence" = @("Read","Update")
|
||||
"WebService" = @("Use")
|
||||
"HTTPService" = @("Use")
|
||||
"IntegrationService" = @("Use")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
}
|
||||
|
||||
# Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute
|
||||
$script:nestedRights = @("View","Edit")
|
||||
$script:commandRights = @("View")
|
||||
|
||||
# --- 4. Presets (@view, @edit, @use) ---
|
||||
|
||||
$script:presets = @{
|
||||
"view" = @{
|
||||
"Catalog" = @("Read","View","InputByString")
|
||||
"ExchangePlan" = @("Read","View","InputByString")
|
||||
"Document" = @("Read","View","InputByString")
|
||||
"ChartOfAccounts" = @("Read","View","InputByString")
|
||||
"ChartOfCharacteristicTypes" = @("Read","View","InputByString")
|
||||
"ChartOfCalculationTypes" = @("Read","View","InputByString")
|
||||
"BusinessProcess" = @("Read","View","InputByString")
|
||||
"Task" = @("Read","View","InputByString")
|
||||
"InformationRegister" = @("Read","View")
|
||||
"AccumulationRegister" = @("Read","View")
|
||||
"AccountingRegister" = @("Read","View")
|
||||
"CalculationRegister" = @("Read","View")
|
||||
"Constant" = @("Read","View")
|
||||
"DocumentJournal" = @("Read","View")
|
||||
"Sequence" = @("Read")
|
||||
"CommonForm" = @("View")
|
||||
"CommonCommand" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"FilterCriterion" = @("View")
|
||||
"SessionParameter" = @("Get")
|
||||
"CommonAttribute" = @("View")
|
||||
"Configuration" = @("ThinClient","WebClient","Output","SaveUserData","MainWindowModeNormal")
|
||||
}
|
||||
"edit" = @{
|
||||
"Catalog" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
||||
"ExchangePlan" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
||||
"Document" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Posting","UndoPosting","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting","InteractiveChangeOfPosted")
|
||||
"ChartOfAccounts" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
||||
"ChartOfCharacteristicTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
||||
"ChartOfCalculationTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
||||
"BusinessProcess" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveStart")
|
||||
"Task" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveExecute")
|
||||
"InformationRegister" = @("Read","Update","View","Edit")
|
||||
"AccumulationRegister" = @("Read","Update","View","Edit")
|
||||
"AccountingRegister" = @("Read","Update","View","Edit")
|
||||
"Constant" = @("Read","Update","View","Edit")
|
||||
"DocumentJournal" = @("Read","View")
|
||||
"Sequence" = @("Read","Update")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
}
|
||||
"use" = @{
|
||||
"DataProcessor" = @("Use","View")
|
||||
"Report" = @("Use","View")
|
||||
"CommonForm" = @("View")
|
||||
"CommonCommand" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"WebService" = @("Use")
|
||||
"HTTPService" = @("Use")
|
||||
"IntegrationService" = @("Use")
|
||||
}
|
||||
}
|
||||
|
||||
# --- 5. Helpers ---
|
||||
|
||||
function Get-ObjectType {
|
||||
param([string]$objectName)
|
||||
$dotIdx = $objectName.IndexOf(".")
|
||||
if ($dotIdx -lt 0) { return $objectName }
|
||||
return $objectName.Substring(0, $dotIdx)
|
||||
}
|
||||
|
||||
function Is-NestedObject {
|
||||
param([string]$objectName)
|
||||
return ($objectName.Split(".").Count -ge 3)
|
||||
}
|
||||
|
||||
function Resolve-Preset {
|
||||
param([string]$objectType, [string]$presetName)
|
||||
|
||||
$preset = $presetName.TrimStart('@')
|
||||
|
||||
if (-not $script:presets.ContainsKey($preset)) {
|
||||
Write-Warning "Unknown preset '@$preset'. Known: @view, @edit, @use"
|
||||
return @()
|
||||
}
|
||||
|
||||
$typeMap = $script:presets[$preset]
|
||||
if (-not $typeMap.ContainsKey($objectType)) {
|
||||
$available = @()
|
||||
foreach ($k in $script:presets.Keys) {
|
||||
if ($script:presets[$k].ContainsKey($objectType)) {
|
||||
$available += "@$k"
|
||||
}
|
||||
}
|
||||
$availStr = if ($available.Count -gt 0) { $available -join ", " } else { "none" }
|
||||
Write-Warning "Preset '@$preset' not defined for type '$objectType'. Available: $availStr"
|
||||
return @()
|
||||
}
|
||||
|
||||
return @($typeMap[$objectType])
|
||||
}
|
||||
|
||||
function Validate-RightName {
|
||||
param([string]$objectName, [string]$rightName)
|
||||
|
||||
$objectType = Get-ObjectType $objectName
|
||||
|
||||
if (Is-NestedObject $objectName) {
|
||||
if ($objectName -match '\.Command\.') {
|
||||
if ($rightName -notin $script:commandRights) {
|
||||
Write-Warning "${objectName}: '$rightName' not valid for commands (only: View)"
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
if ($rightName -notin $script:nestedRights) {
|
||||
Write-Warning "${objectName}: '$rightName' not valid for nested objects (only: View, Edit)"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
if (-not $script:knownRights.ContainsKey($objectType)) {
|
||||
Write-Warning "${objectName}: unknown object type '$objectType'"
|
||||
return $true
|
||||
}
|
||||
|
||||
$validRights = $script:knownRights[$objectType]
|
||||
if ($rightName -notin $validRights) {
|
||||
$suggestions = @($validRights | Where-Object {
|
||||
$_ -like "*$rightName*" -or $rightName -like "*$_*"
|
||||
})
|
||||
$sugStr = if ($suggestions.Count -gt 0) { " Did you mean: $($suggestions -join ', ')?" } else { "" }
|
||||
Write-Warning "${objectName}: unknown right '$rightName'.$sugStr"
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# --- 6. Parse object entries ---
|
||||
|
||||
function Parse-ObjectEntry {
|
||||
param($entry)
|
||||
|
||||
# --- String shorthand ---
|
||||
if ($entry -is [string]) {
|
||||
$colonIdx = $entry.IndexOf(':')
|
||||
if ($colonIdx -lt 0) {
|
||||
Write-Warning "Invalid string '$entry' -- expected 'Object.Name: @preset' or 'Object.Name: Right1, Right2'"
|
||||
return $null
|
||||
}
|
||||
$objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim())
|
||||
$rightsStr = $entry.Substring($colonIdx + 1).Trim()
|
||||
$objectType = Get-ObjectType $objName
|
||||
|
||||
if ($rightsStr.StartsWith('@')) {
|
||||
$rightNames = @(Resolve-Preset -objectType $objectType -presetName $rightsStr)
|
||||
} else {
|
||||
$rightNames = @($rightsStr -split ',\s*' | ForEach-Object { Translate-RightName $_.Trim() } | Where-Object { $_ })
|
||||
foreach ($r in $rightNames) {
|
||||
Validate-RightName -objectName $objName -rightName $r | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$rights = @()
|
||||
foreach ($r in $rightNames) {
|
||||
$rights += ,@{Name=$r; Value="true"; Condition=$null}
|
||||
}
|
||||
return @{ Name = $objName; Rights = $rights }
|
||||
}
|
||||
|
||||
# --- Object form ---
|
||||
$objName = Translate-ObjectName "$($entry.name)"
|
||||
if (-not $objName) {
|
||||
Write-Warning "Object entry missing 'name' field"
|
||||
return $null
|
||||
}
|
||||
|
||||
$objectType = Get-ObjectType $objName
|
||||
$rightsMap = [ordered]@{}
|
||||
|
||||
# 1) Start with preset
|
||||
if ($entry.preset) {
|
||||
$presetRights = @(Resolve-Preset -objectType $objectType -presetName "$($entry.preset)")
|
||||
foreach ($r in $presetRights) {
|
||||
$rightsMap[$r] = @{Value="true"; Condition=$null}
|
||||
}
|
||||
}
|
||||
|
||||
# 2) Apply explicit rights
|
||||
if ($entry.rights) {
|
||||
if ($entry.rights -is [array]) {
|
||||
foreach ($r in $entry.rights) {
|
||||
$rName = Translate-RightName "$r"
|
||||
Validate-RightName -objectName $objName -rightName $rName | Out-Null
|
||||
$rightsMap[$rName] = @{Value="true"; Condition=$null}
|
||||
}
|
||||
} else {
|
||||
foreach ($p in $entry.rights.PSObject.Properties) {
|
||||
$rName = Translate-RightName $p.Name
|
||||
Validate-RightName -objectName $objName -rightName $rName | Out-Null
|
||||
$boolVal = $p.Value
|
||||
if ($boolVal -eq $true -or "$boolVal" -eq "True") {
|
||||
$rightsMap[$rName] = @{Value="true"; Condition=$null}
|
||||
} else {
|
||||
$rightsMap[$rName] = @{Value="false"; Condition=$null}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 3) Apply RLS conditions
|
||||
if ($entry.rls) {
|
||||
foreach ($p in $entry.rls.PSObject.Properties) {
|
||||
$rlsRight = Translate-RightName $p.Name
|
||||
if ($rightsMap.Contains($rlsRight)) {
|
||||
$rightsMap[$rlsRight].Condition = "$($p.Value)"
|
||||
} else {
|
||||
Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Convert to array
|
||||
$rights = @()
|
||||
foreach ($k in $rightsMap.Keys) {
|
||||
$rights += ,@{
|
||||
Name = $k
|
||||
Value = $rightsMap[$k].Value
|
||||
Condition = $rightsMap[$k].Condition
|
||||
}
|
||||
}
|
||||
|
||||
return @{ Name = $objName; Rights = $rights }
|
||||
}
|
||||
|
||||
# --- 7. Parse all object entries ---
|
||||
|
||||
$parsedObjects = @()
|
||||
if ($def.objects) {
|
||||
foreach ($entry in $def.objects) {
|
||||
$parsed = Parse-ObjectEntry -entry $entry
|
||||
if ($parsed) {
|
||||
$parsedObjects += ,$parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 8. Generate UUID ---
|
||||
|
||||
$uuid = [guid]::NewGuid().ToString()
|
||||
|
||||
# --- 9. Emit metadata XML (Roles/Name.xml) ---
|
||||
|
||||
$script:xmlBuf = New-Object System.Text.StringBuilder 4096
|
||||
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||
X ' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||
X ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||
X ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||
X ' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||
X ' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||
X ' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||
X ' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||
X ' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||
X ' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||
X ' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||
X ' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||
X ' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||
X ' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||
X ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
X ' version="2.17">'
|
||||
X " <Role uuid=`"$uuid`">"
|
||||
X ' <Properties>'
|
||||
X " <Name>$roleName</Name>"
|
||||
X ' <Synonym>'
|
||||
X ' <v8:item>'
|
||||
X ' <v8:lang>ru</v8:lang>'
|
||||
X " <v8:content>$(Esc-Xml $synonym)</v8:content>"
|
||||
X ' </v8:item>'
|
||||
X ' </Synonym>'
|
||||
if ($comment) {
|
||||
X " <Comment>$(Esc-Xml $comment)</Comment>"
|
||||
} else {
|
||||
X ' <Comment/>'
|
||||
}
|
||||
X ' </Properties>'
|
||||
X ' </Role>'
|
||||
X '</MetaDataObject>'
|
||||
|
||||
$metadataXml = $script:xmlBuf.ToString()
|
||||
|
||||
# --- 10. Emit Rights XML (Roles/Name/Ext/Rights.xml) ---
|
||||
|
||||
$script:xmlBuf = New-Object System.Text.StringBuilder 8192
|
||||
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X '<Rights xmlns="http://v8.1c.ru/8.2/roles"'
|
||||
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
X ' xsi:type="Rights" version="2.17">'
|
||||
|
||||
# Global flags (defaults match typical 1C roles)
|
||||
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
|
||||
$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" }
|
||||
$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" }
|
||||
|
||||
X " <setForNewObjects>$sfno</setForNewObjects>"
|
||||
X " <setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
||||
X " <independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
||||
|
||||
# Object blocks
|
||||
$totalRights = 0
|
||||
foreach ($obj in $parsedObjects) {
|
||||
X ' <object>'
|
||||
X " <name>$($obj.Name)</name>"
|
||||
foreach ($right in $obj.Rights) {
|
||||
X ' <right>'
|
||||
X " <name>$($right.Name)</name>"
|
||||
X " <value>$($right.Value)</value>"
|
||||
if ($right.Condition) {
|
||||
X ' <restrictionByCondition>'
|
||||
X " <condition>$(Esc-Xml $right.Condition)</condition>"
|
||||
X ' </restrictionByCondition>'
|
||||
}
|
||||
X ' </right>'
|
||||
$totalRights++
|
||||
}
|
||||
X ' </object>'
|
||||
}
|
||||
|
||||
# RLS restriction templates
|
||||
$templateCount = 0
|
||||
if ($def.templates) {
|
||||
foreach ($tpl in $def.templates) {
|
||||
X ' <restrictionTemplate>'
|
||||
X " <name>$(Esc-Xml "$($tpl.name)")</name>"
|
||||
X " <condition>$(Esc-Xml "$($tpl.condition)")</condition>"
|
||||
X ' </restrictionTemplate>'
|
||||
$templateCount++
|
||||
}
|
||||
}
|
||||
|
||||
X '</Rights>'
|
||||
|
||||
$rightsXml = $script:xmlBuf.ToString()
|
||||
|
||||
# --- 11. Write output files ---
|
||||
|
||||
$outDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) {
|
||||
$OutputDir
|
||||
} else {
|
||||
Join-Path (Get-Location) $OutputDir
|
||||
}
|
||||
|
||||
# Metadata: OutputDir/RoleName.xml
|
||||
$metadataPath = Join-Path $outDir "$roleName.xml"
|
||||
if (-not (Test-Path $outDir)) {
|
||||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Rights: OutputDir/RoleName/Ext/Rights.xml
|
||||
$roleSubDir = Join-Path $outDir $roleName
|
||||
$extDir = Join-Path $roleSubDir "Ext"
|
||||
$rightsPath = Join-Path $extDir "Rights.xml"
|
||||
if (-not (Test-Path $extDir)) {
|
||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc)
|
||||
[System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc)
|
||||
|
||||
# --- 12. Summary ---
|
||||
|
||||
Write-Host "[OK] Role '$roleName' compiled"
|
||||
Write-Host " UUID: $uuid"
|
||||
Write-Host " Metadata: $metadataPath"
|
||||
Write-Host " Rights: $rightsPath"
|
||||
Write-Host " Objects: $($parsedObjects.Count), Rights: $totalRights, Templates: $templateCount"
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: role-validate
|
||||
description: Валидация структурной корректности роли 1С (Rights.xml) — формат, права, RLS, шаблоны
|
||||
argument-hint: <RightsPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
---
|
||||
|
||||
# /role-validate — валидация роли 1С
|
||||
|
||||
Проверяет корректность `Rights.xml` роли: формат XML, namespace, глобальные флаги, типы объектов, имена прав, RLS-ограничения, шаблоны. Опционально проверяет метаданные роли (UUID, имя, синоним).
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/role-validate <RightsPath> [MetadataPath]
|
||||
```
|
||||
|
||||
## Запуск скрипта
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude\skills\role-validate\scripts\role-validate.ps1 -RightsPath <path> [-MetadataPath <path>] [-OutFile <output.txt>]
|
||||
```
|
||||
|
||||
### Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|:------------:|----------|
|
||||
| `-RightsPath` | да | Путь к `Rights.xml` роли |
|
||||
| `-MetadataPath` | нет | Путь к метаданным роли (`Roles/ИмяРоли.xml`) |
|
||||
| `-OutFile` | нет | Записать результат в файл (UTF-8 BOM). Без этого — вывод в консоль |
|
||||
|
||||
**Важно:** Для кириллических путей используй `-OutFile` и читай результат через Read tool.
|
||||
|
||||
## Проверки
|
||||
|
||||
### Rights.xml
|
||||
1. XML well-formed — парсинг без ошибок
|
||||
2. Корневой элемент `<Rights>` с namespace `http://v8.1c.ru/8.2/roles`
|
||||
3. Три глобальных флага: `setForNewObjects`, `setForAttributesByDefault`, `independentRightsOfChildObjects`
|
||||
4. Для каждого `<object>`:
|
||||
- `<name>` не пуст
|
||||
- Тип объекта распознан (Catalog, Document, InformationRegister и т.д.)
|
||||
- Каждое `<right>` имеет `<name>` и `<value>` (`true`/`false`)
|
||||
- Имя права валидно для данного типа объекта (с подсказкой при опечатке)
|
||||
5. Вложенные объекты (3+ сегмента через `.`): допустимы только View, Edit (или Use для IntegrationServiceChannel)
|
||||
6. RLS `<restrictionByCondition>`: `<condition>` не пуст
|
||||
7. Шаблоны `<restrictionTemplate>`: `<name>` и `<condition>` не пусты
|
||||
|
||||
### Метаданные (опционально)
|
||||
- Элемент `<Role>` найден
|
||||
- UUID в корректном формате
|
||||
- `<Name>` не пуст
|
||||
- `<Synonym>` присутствует
|
||||
|
||||
## Формат вывода
|
||||
|
||||
```
|
||||
Validating: Roles/МояРоль/Ext/Rights.xml
|
||||
OK XML well-formed
|
||||
OK Root element: <Rights> with correct namespace
|
||||
OK 3 global flags present
|
||||
WARN Document.Реализация: unknown right 'Rea'. Did you mean: Read?
|
||||
OK 12 objects, 45 rights
|
||||
OK 2 RLS restrictions
|
||||
OK 1 templates: ДляОбъекта
|
||||
|
||||
OK Metadata: UUID valid (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
||||
OK Metadata: Name = МояРоль
|
||||
OK Metadata: Synonym present
|
||||
---
|
||||
Result: 0 error(s), 1 warning(s)
|
||||
```
|
||||
|
||||
### Уровни сообщений
|
||||
|
||||
| Маркер | Значение |
|
||||
|--------|----------|
|
||||
| `OK` | Проверка пройдена |
|
||||
| `WARN` | Предупреждение (неизвестный тип объекта, подозрительное имя права) |
|
||||
| `ERR` | Ошибка (невалидный XML, отсутствие обязательных элементов) |
|
||||
|
||||
Код возврата: `0` — без ошибок, `1` — есть ошибки.
|
||||
|
||||
## Примеры
|
||||
|
||||
### Только Rights.xml
|
||||
|
||||
```
|
||||
/role-validate upload/acc_8.3.20/Roles/БазовыеПраваБП/Ext/Rights.xml
|
||||
```
|
||||
|
||||
### С проверкой метаданных
|
||||
|
||||
```
|
||||
/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml
|
||||
```
|
||||
|
||||
### Верификация после /role-compile
|
||||
|
||||
```
|
||||
/role-compile role.json Roles/
|
||||
/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml
|
||||
```
|
||||
@@ -0,0 +1,424 @@
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RightsPath,
|
||||
|
||||
[string]$MetadataPath,
|
||||
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- 1. Known rights per object type ---
|
||||
|
||||
$script:knownRights = @{
|
||||
"Configuration" = @(
|
||||
"Administration","DataAdministration","UpdateDataBaseConfiguration",
|
||||
"ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode",
|
||||
"ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection",
|
||||
"Automation","Output","SaveUserData","TechnicalSpecialistMode",
|
||||
"InteractiveOpenExtDataProcessors","InteractiveOpenExtReports",
|
||||
"AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration",
|
||||
"MainWindowModeNormal","MainWindowModeWorkplace",
|
||||
"MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk"
|
||||
)
|
||||
"Catalog" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"Document" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Posting","UndoPosting",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting",
|
||||
"InteractiveChangeOfPosted",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"InformationRegister" = @(
|
||||
"Read","Update","View","Edit","TotalsControl",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"AccountingRegister" = @("Read","Update","View","Edit","TotalsControl")
|
||||
"CalculationRegister" = @("Read","View")
|
||||
"Constant" = @(
|
||||
"Read","Update","View","Edit",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfAccounts" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ReadDataHistoryOfMissingData",
|
||||
"UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment"
|
||||
)
|
||||
"ChartOfCharacteristicTypes" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"ChartOfCalculationTypes" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete",
|
||||
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
||||
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData"
|
||||
)
|
||||
"ExchangePlan" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveDeleteMarked",
|
||||
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
||||
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
||||
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
||||
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
||||
)
|
||||
"BusinessProcess" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveActivate","InteractiveStart"
|
||||
)
|
||||
"Task" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
||||
"InteractiveDelete","InteractiveActivate","InteractiveExecute"
|
||||
)
|
||||
"DataProcessor" = @("Use","View")
|
||||
"Report" = @("Use","View")
|
||||
"CommonForm" = @("View")
|
||||
"CommonCommand" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"FilterCriterion" = @("View")
|
||||
"DocumentJournal" = @("Read","View")
|
||||
"Sequence" = @("Read","Update")
|
||||
"WebService" = @("Use")
|
||||
"HTTPService" = @("Use")
|
||||
"IntegrationService" = @("Use")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
}
|
||||
|
||||
$script:nestedRights = @("View","Edit")
|
||||
$script:channelRights = @("Use")
|
||||
$script:commandRights = @("View")
|
||||
|
||||
# --- 2. Output helpers ---
|
||||
|
||||
$script:lines = @()
|
||||
$script:errors = 0
|
||||
$script:warnings = 0
|
||||
|
||||
function Out-OK {
|
||||
param([string]$msg)
|
||||
$script:lines += " OK $msg"
|
||||
}
|
||||
|
||||
function Out-WARN {
|
||||
param([string]$msg)
|
||||
$script:warnings++
|
||||
$script:lines += " WARN $msg"
|
||||
}
|
||||
|
||||
function Out-ERR {
|
||||
param([string]$msg)
|
||||
$script:errors++
|
||||
$script:lines += " ERR $msg"
|
||||
}
|
||||
|
||||
function Get-ObjectType {
|
||||
param([string]$name)
|
||||
$dotIdx = $name.IndexOf(".")
|
||||
if ($dotIdx -lt 0) { return $name }
|
||||
return $name.Substring(0, $dotIdx)
|
||||
}
|
||||
|
||||
function Is-NestedObject {
|
||||
param([string]$name)
|
||||
return ($name.Split(".").Count -ge 3)
|
||||
}
|
||||
|
||||
function Find-Similar {
|
||||
param([string]$needle, [string[]]$haystack)
|
||||
$result = @($haystack | Where-Object {
|
||||
$_ -like "*$needle*" -or $needle -like "*$_*"
|
||||
})
|
||||
if ($result.Count -gt 3) { $result = $result[0..2] }
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- 3. Validate Rights.xml ---
|
||||
|
||||
$script:lines += "Validating: $RightsPath"
|
||||
|
||||
if (-not (Test-Path $RightsPath)) {
|
||||
Out-ERR "File not found: $RightsPath"
|
||||
$script:lines += "---"
|
||||
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)"
|
||||
$output = $script:lines -join "`n"
|
||||
if ($OutFile) {
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($OutFile, $output, $enc)
|
||||
} else {
|
||||
Write-Host $output
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 3a. Parse XML
|
||||
try {
|
||||
[xml]$xml = Get-Content -Path $RightsPath -Encoding UTF8
|
||||
Out-OK "XML well-formed"
|
||||
} catch {
|
||||
Out-ERR "XML parse error: $($_.Exception.Message)"
|
||||
$script:lines += "---"
|
||||
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)"
|
||||
$output = $script:lines -join "`n"
|
||||
if ($OutFile) {
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($OutFile, $output, $enc)
|
||||
} else {
|
||||
Write-Host $output
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
$root = $xml.DocumentElement
|
||||
$rightsNs = "http://v8.1c.ru/8.2/roles"
|
||||
|
||||
# 3b. Check root element
|
||||
if ($root.LocalName -ne "Rights") {
|
||||
Out-ERR "Root element is '$($root.LocalName)', expected 'Rights'"
|
||||
} elseif ($root.NamespaceURI -ne $rightsNs) {
|
||||
Out-WARN "Namespace is '$($root.NamespaceURI)', expected '$rightsNs'"
|
||||
} else {
|
||||
Out-OK "Root element: <Rights> with correct namespace"
|
||||
}
|
||||
|
||||
# 3c. Global flags
|
||||
$flagNames = @("setForNewObjects","setForAttributesByDefault","independentRightsOfChildObjects")
|
||||
$flagsFound = 0
|
||||
foreach ($fn in $flagNames) {
|
||||
$node = $root.GetElementsByTagName($fn, $rightsNs)
|
||||
if ($node.Count -gt 0) {
|
||||
$val = $node[0].InnerText
|
||||
if ($val -ne "true" -and $val -ne "false") {
|
||||
Out-WARN "$fn = '$val' (expected 'true' or 'false')"
|
||||
}
|
||||
$flagsFound++
|
||||
} else {
|
||||
Out-WARN "Missing global flag: $fn"
|
||||
}
|
||||
}
|
||||
if ($flagsFound -eq 3) {
|
||||
Out-OK "3 global flags present"
|
||||
}
|
||||
|
||||
# 3d. Objects
|
||||
$objects = $root.GetElementsByTagName("object", $rightsNs)
|
||||
$objCount = $objects.Count
|
||||
$rightCount = 0
|
||||
$rlsCount = 0
|
||||
|
||||
foreach ($obj in $objects) {
|
||||
$objName = ""
|
||||
foreach ($child in $obj.ChildNodes) {
|
||||
if ($child.LocalName -eq "name") {
|
||||
$objName = $child.InnerText
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $objName) {
|
||||
Out-ERR "Object without <name>"
|
||||
continue
|
||||
}
|
||||
|
||||
$objectType = Get-ObjectType $objName
|
||||
$isNested = Is-NestedObject $objName
|
||||
|
||||
# Check object type is known
|
||||
if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) {
|
||||
Out-WARN "${objName}: unknown object type '$objectType'"
|
||||
}
|
||||
|
||||
# Check rights
|
||||
foreach ($child in $obj.ChildNodes) {
|
||||
if ($child.LocalName -ne "right") { continue }
|
||||
|
||||
$rName = ""
|
||||
$rValue = ""
|
||||
$hasRLS = $false
|
||||
|
||||
foreach ($rc in $child.ChildNodes) {
|
||||
if ($rc.LocalName -eq "name") { $rName = $rc.InnerText }
|
||||
if ($rc.LocalName -eq "value") { $rValue = $rc.InnerText }
|
||||
if ($rc.LocalName -eq "restrictionByCondition") {
|
||||
$hasRLS = $true
|
||||
$rlsCount++
|
||||
# Check condition not empty
|
||||
$condNode = $null
|
||||
foreach ($rcc in $rc.ChildNodes) {
|
||||
if ($rcc.LocalName -eq "condition") { $condNode = $rcc }
|
||||
}
|
||||
if (-not $condNode -or -not $condNode.InnerText) {
|
||||
Out-WARN "${objName}: RLS condition for '$rName' is empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $rName) {
|
||||
Out-ERR "${objName}: <right> without <name>"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($rValue -ne "true" -and $rValue -ne "false") {
|
||||
Out-ERR "${objName}: right '$rName' has invalid value '$rValue'"
|
||||
continue
|
||||
}
|
||||
|
||||
$rightCount++
|
||||
|
||||
# Validate right name
|
||||
if ($isNested) {
|
||||
if ($objName -match '\.Command\.') {
|
||||
if ($rName -notin $script:commandRights) {
|
||||
Out-WARN "${objName}: '$rName' not valid for commands (only: View)"
|
||||
}
|
||||
} elseif ($objName -match '\.IntegrationServiceChannel\.') {
|
||||
if ($rName -notin $script:channelRights) {
|
||||
Out-WARN "${objName}: '$rName' not valid for channels (only: Use)"
|
||||
}
|
||||
} else {
|
||||
if ($rName -notin $script:nestedRights) {
|
||||
Out-WARN "${objName}: '$rName' not valid for nested objects (only: View, Edit)"
|
||||
}
|
||||
}
|
||||
} elseif ($script:knownRights.ContainsKey($objectType)) {
|
||||
$validRights = $script:knownRights[$objectType]
|
||||
if ($rName -notin $validRights) {
|
||||
$similar = Find-Similar -needle $rName -haystack $validRights
|
||||
$sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" }
|
||||
Out-WARN "${objName}: unknown right '$rName'.$sugStr"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Out-OK "$objCount objects, $rightCount rights"
|
||||
if ($rlsCount -gt 0) {
|
||||
Out-OK "$rlsCount RLS restrictions"
|
||||
}
|
||||
|
||||
# 3e. Templates
|
||||
$templates = $root.GetElementsByTagName("restrictionTemplate", $rightsNs)
|
||||
if ($templates.Count -gt 0) {
|
||||
$tplNames = @()
|
||||
foreach ($tpl in $templates) {
|
||||
$tName = ""
|
||||
$tCond = ""
|
||||
foreach ($child in $tpl.ChildNodes) {
|
||||
if ($child.LocalName -eq "name") { $tName = $child.InnerText }
|
||||
if ($child.LocalName -eq "condition") { $tCond = $child.InnerText }
|
||||
}
|
||||
if (-not $tName) {
|
||||
Out-WARN "Restriction template without <name>"
|
||||
} else {
|
||||
$parenIdx = $tName.IndexOf("(")
|
||||
$shortName = if ($parenIdx -gt 0) { $tName.Substring(0, $parenIdx) } else { $tName }
|
||||
$tplNames += $shortName
|
||||
}
|
||||
if (-not $tCond) {
|
||||
Out-WARN "Template '$tName': empty <condition>"
|
||||
}
|
||||
}
|
||||
Out-OK "$($templates.Count) templates: $($tplNames -join ', ')"
|
||||
}
|
||||
|
||||
# --- 4. Validate metadata (optional) ---
|
||||
|
||||
if ($MetadataPath) {
|
||||
$script:lines += ""
|
||||
|
||||
if (-not (Test-Path $MetadataPath)) {
|
||||
Out-ERR "Metadata file not found: $MetadataPath"
|
||||
} else {
|
||||
try {
|
||||
[xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8
|
||||
$roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']")
|
||||
if (-not $roleNode) {
|
||||
Out-ERR "Metadata: <Role> element not found"
|
||||
} else {
|
||||
$uuid = $roleNode.GetAttribute("uuid")
|
||||
if ($uuid -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
|
||||
Out-OK "Metadata: UUID valid ($uuid)"
|
||||
} else {
|
||||
Out-ERR "Metadata: invalid UUID format '$uuid'"
|
||||
}
|
||||
|
||||
$nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']")
|
||||
if ($nameNode -and $nameNode.InnerText) {
|
||||
Out-OK "Metadata: Name = $($nameNode.InnerText)"
|
||||
} else {
|
||||
Out-ERR "Metadata: <Name> is empty or missing"
|
||||
}
|
||||
|
||||
$synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']")
|
||||
if ($synNode -and $synNode.InnerXml) {
|
||||
Out-OK "Metadata: Synonym present"
|
||||
} else {
|
||||
Out-WARN "Metadata: <Synonym> is empty"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Out-ERR "Metadata XML parse error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 5. Summary ---
|
||||
|
||||
$script:lines += "---"
|
||||
$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)"
|
||||
|
||||
$output = $script:lines -join "`n"
|
||||
|
||||
if ($OutFile) {
|
||||
$outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile }
|
||||
$outDir = [System.IO.Path]::GetDirectoryName($outPath)
|
||||
if (-not (Test-Path $outDir)) {
|
||||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||
}
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($outPath, $output, $enc)
|
||||
Write-Host "[OK] Validation result written to: $outPath"
|
||||
} else {
|
||||
Write-Host $output
|
||||
}
|
||||
|
||||
if ($script:errors -gt 0) { exit 1 } else { exit 0 }
|
||||
@@ -22,7 +22,7 @@
|
||||
| Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
||||
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
|
||||
| Управляемые формы (Form) | 6 навыков `/form-*` | Создание, анализ, генерация, модификация, валидация управляемых форм | [Подробнее](docs/form-guide.md) |
|
||||
| Роли (Role) | 2 навыка `/role-*` | Анализ прав роли, создание роли из описания | [Подробнее](docs/role-guide.md) |
|
||||
| Роли (Role) | 3 навыка `/role-*` | Анализ прав роли, создание из JSON DSL, валидация | [Подробнее](docs/role-guide.md) |
|
||||
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
|
||||
|
||||
## Требования
|
||||
@@ -66,7 +66,8 @@
|
||||
├── form-edit/ # Добавление элементов в форму
|
||||
├── form-patterns/ # Справочник паттернов компоновки форм
|
||||
├── role-info/ # Анализ прав роли
|
||||
├── role-compile/ # Создание роли из описания
|
||||
├── role-compile/ # Создание роли из JSON DSL
|
||||
├── role-validate/ # Валидация роли
|
||||
└── img-grid/ # Сетка для анализа изображений
|
||||
docs/
|
||||
├── epf-guide.md # Гайд: внешние обработки
|
||||
|
||||
+102
-16
@@ -1,13 +1,73 @@
|
||||
# Роли (Role)
|
||||
|
||||
Навыки группы `/role-*` позволяют анализировать и создавать роли 1С — XML-файлы прав доступа (Rights.xml) и метаданных.
|
||||
Навыки группы `/role-*` позволяют анализировать, создавать и проверять роли 1С — XML-файлы прав доступа (Rights.xml) и метаданных.
|
||||
|
||||
## Навыки
|
||||
|
||||
| Навык | Параметры | Описание |
|
||||
|-------|-----------|----------|
|
||||
| `/role-info` | `<RightsPath>` | Компактная сводка прав: объекты по типам, только разрешённые, RLS, шаблоны |
|
||||
| `/role-compile` | `<RoleName> <RolesDir>` | Создание роли: метаданные + Rights.xml по описанию прав |
|
||||
| `/role-compile` | `<JsonPath> <RolesDir>` | Генерация роли из JSON DSL: метаданные + Rights.xml, UUID автоматически |
|
||||
| `/role-validate` | `<RightsPath> [MetadataPath]` | Валидация структурной корректности: XML, namespace, права, RLS, шаблоны |
|
||||
|
||||
## Рабочий цикл
|
||||
|
||||
```
|
||||
Описание прав (текст) → JSON DSL → /role-compile → XML-исходники → /role-validate
|
||||
→ /role-info
|
||||
```
|
||||
|
||||
1. Claude формирует JSON-определение роли (с пресетами или явными правами)
|
||||
2. `/role-compile` генерирует `Roles/ИмяРоли.xml` + `Roles/ИмяРоли/Ext/Rights.xml`
|
||||
3. `/role-validate` проверяет корректность сгенерированного XML
|
||||
4. `/role-info` выводит компактную сводку для визуальной проверки
|
||||
|
||||
## JSON DSL — компактный формат
|
||||
|
||||
Роли описываются в JSON с двумя уровнями детализации:
|
||||
|
||||
### Строковый shorthand (простые роли)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ЧтениеНоменклатуры",
|
||||
"synonym": "Чтение номенклатуры",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
"Catalog.Контрагенты: @view",
|
||||
"DataProcessor.Загрузка: @use"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Формат строки: `Тип.Имя: @пресет` или `Тип.Имя: Право1, Право2`.
|
||||
|
||||
### Объектная форма (RLS, переопределения)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Document.Реализация",
|
||||
"preset": "view",
|
||||
"rights": { "Delete": false },
|
||||
"rls": { "Read": "#ДляОбъекта(\"\")" }
|
||||
}
|
||||
```
|
||||
|
||||
Форматы можно смешивать в одном массиве `objects`.
|
||||
|
||||
### Пресеты
|
||||
|
||||
| Пресет | Действие |
|
||||
|--------|----------|
|
||||
| `@view` | Просмотр: Read, View, InputByString (для справочников/документов); Read, View (для регистров) |
|
||||
| `@edit` | Полное редактирование: CRUD + Interactive* + Posting (для документов) |
|
||||
| `@use` | Использование: Use, View (для обработок/отчётов/сервисов) |
|
||||
|
||||
`@` обязателен в строковом shorthand. В объектной форме — ключ `preset` без `@`.
|
||||
|
||||
### Русские синонимы
|
||||
|
||||
Скрипт принимает русские имена типов и прав: `Справочник` → Catalog, `Чтение` → Read, `Проведение` → Posting и т.д. Полный список — в [SKILL.md навыка](../.claude/skills/role-compile/SKILL.md).
|
||||
|
||||
## Сценарии использования
|
||||
|
||||
@@ -22,15 +82,6 @@ Claude вызовет `/role-info`, получит компактную свод
|
||||
- где есть ограничения RLS
|
||||
- какие шаблоны ограничений используются
|
||||
|
||||
### Создание роли для регламентного задания
|
||||
|
||||
```
|
||||
> Проанализируй модуль регламентного задания ОбновлениеКурсовВалют
|
||||
> и создай роль с минимальными правами для его выполнения
|
||||
```
|
||||
|
||||
Claude проанализирует код, определит используемые объекты метаданных, и вызовет `/role-compile` для создания роли с нужными правами (Read, Update, Posting и т.д.).
|
||||
|
||||
### Создание роли по описанию
|
||||
|
||||
```
|
||||
@@ -41,11 +92,46 @@ Claude проанализирует код, определит использу
|
||||
> - Регистр ЦеныНоменклатуры: чтение
|
||||
```
|
||||
|
||||
Рабочий цикл:
|
||||
1. Claude генерирует `Roles/МенеджерПродаж.xml` (метаданные с UUID)
|
||||
2. Claude генерирует `Roles/МенеджерПродаж/Ext/Rights.xml` (права)
|
||||
3. Регистрирует роль в `Configuration.xml` (`<ChildObjects>`)
|
||||
4. Проверяет результат через `/role-info`
|
||||
Claude сформирует JSON с пресетами:
|
||||
```json
|
||||
{
|
||||
"name": "МенеджерПродаж",
|
||||
"synonym": "Менеджер продаж",
|
||||
"objects": [
|
||||
"Document.РеализацияТоваровУслуг: @edit",
|
||||
"Catalog.Контрагенты: @view",
|
||||
"Catalog.Номенклатура: @view",
|
||||
"InformationRegister.ЦеныНоменклатуры: @view"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
И вызовет `/role-compile` → `/role-validate` → `/role-info`.
|
||||
|
||||
### Создание роли для регламентного задания
|
||||
|
||||
```
|
||||
> Проанализируй модуль регламентного задания ОбновлениеКурсовВалют
|
||||
> и создай роль с минимальными правами для его выполнения
|
||||
```
|
||||
|
||||
Claude проанализирует код, определит используемые объекты, создаст JSON с точечными правами (без пресетов — только нужные права), и скомпилирует роль.
|
||||
|
||||
### Создание роли с RLS
|
||||
|
||||
```
|
||||
> Создай роль для чтения документов с ограничением по организации
|
||||
```
|
||||
|
||||
Claude использует объектную форму JSON с шаблонами ограничений.
|
||||
|
||||
### Проверка существующей роли
|
||||
|
||||
```
|
||||
> Проверь корректность роли Roles/МояРоль/Ext/Rights.xml
|
||||
```
|
||||
|
||||
Claude вызовет `/role-validate` и покажет результат: ошибки (невалидный XML, отсутствующие элементы) и предупреждения (неизвестные типы объектов, подозрительные имена прав с подсказками).
|
||||
|
||||
## Структура файлов роли
|
||||
|
||||
|
||||
Reference in New Issue
Block a user