From 42b96bbd2161aedca16ba1ca6dee2d38e3744beb Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 1 May 2026 19:13:18 +0300 Subject: [PATCH] =?UTF-8?q?feat(cf-*):=20set-home-page=20+=20drill-down=20?= =?UTF-8?q?-Section=20home-page=20+=20form-ref=20=D0=B2=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cf-edit: новая операция set-home-page перезаписывает Ext/HomePageWorkArea.xml. DSL принимает template (OneColumn/TwoColumnsEqualWidth/TwoColumnsVariableWidth), left/right с записями форм (строка или объект form/height/visibility/roles). Тихая нормализация ссылок: русские типы, 3-сегмент → авто-Form, файловые пути - cf-info: краткая HP-сводка (template + счётчики) в overview/full, детальный вид через -Section home-page (alias -Name) с раскладкой и переопределениями ролей - cf-validate: Check 9 — валидация ссылок на формы из HomePageWorkArea и Default*Form свойств; битая ссылка → error - reference.md: убран реализационный шум (canonical sort, авто-нормализация форм, panelDef detail, секция авто-валидации); путь src/ в примерах вместо репо-специфичных 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/cf-edit/SKILL.md | 13 +- .claude/skills/cf-edit/reference.md | 69 +++-- .claude/skills/cf-edit/scripts/cf-edit.ps1 | 193 +++++++++++++- .claude/skills/cf-edit/scripts/cf-edit.py | 170 +++++++++++- .claude/skills/cf-info/SKILL.md | 14 +- .claude/skills/cf-info/scripts/cf-info.ps1 | 113 +++++++- .claude/skills/cf-info/scripts/cf-info.py | 101 ++++++- .../cf-validate/scripts/cf-validate.ps1 | 68 ++++- .../skills/cf-validate/scripts/cf-validate.py | 56 +++- tests/skills/cases/cf-edit/set-home-page.json | 24 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../snapshots/set-home-page/Configuration.xml | 251 ++++++++++++++++++ .../Ext/ClientApplicationInterface.xml | 18 ++ .../set-home-page/Ext/HomePageWorkArea.xml | 45 ++++ .../set-home-page/Languages/Русский.xml | 16 ++ .../Ext/ClientApplicationInterface.xml | 18 ++ 20 files changed, 1218 insertions(+), 41 deletions(-) create mode 100644 tests/skills/cases/cf-edit/set-home-page.json create mode 100644 tests/skills/cases/cf-edit/snapshots/add-default-role/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/add-objects/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/modify-multiple-props/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/remove-object/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-default-roles/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-home-page/Configuration.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/HomePageWorkArea.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-home-page/Languages/Русский.xml create mode 100644 tests/skills/cases/cf-edit/snapshots/set-version/Ext/ClientApplicationInterface.xml diff --git a/.claude/skills/cf-edit/SKILL.md b/.claude/skills/cf-edit/SKILL.md index fd4f1141..8c4901cb 100644 --- a/.claude/skills/cf-edit/SKILL.md +++ b/.claude/skills/cf-edit/SKILL.md @@ -1,6 +1,6 @@ --- name: cf-edit -description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей +description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу argument-hint: -ConfigPath -Operation -Value allowed-tools: - Bash @@ -38,6 +38,7 @@ powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -Conf | `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию | | `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию | | `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) | +| `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) | Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md) @@ -45,15 +46,15 @@ powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -Conf ```powershell # Изменить версию и поставщика -... -ConfigPath test-tmp/cf -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С" +... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С" # Добавить объекты -... -ConfigPath test-tmp/cf -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ" +... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ" # Удалить объект -... -ConfigPath test-tmp/cf -Operation remove-childObject -Value "Catalog.Устаревший" +... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший" # Роли по умолчанию -... -ConfigPath test-tmp/cf -Operation add-defaultRole -Value "ПолныеПрава" -... -ConfigPath test-tmp/cf -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор" +... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава" +... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор" ``` diff --git a/.claude/skills/cf-edit/reference.md b/.claude/skills/cf-edit/reference.md index f394237f..efaa5231 100644 --- a/.claude/skills/cf-edit/reference.md +++ b/.claude/skills/cf-edit/reference.md @@ -35,14 +35,7 @@ Формат: `Type.Name` — XML-тип и имя объекта через точку. -**Важно про `add-childObject`**: операция регистрирует в `` Configuration.xml только объект, **файл которого уже существует на диске** (например `Catalogs/Товары.xml`). Если файла нет — скрипт падает с exit 1 и подсказкой. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его в Configuration.xml за один вызов. - -Когда `add-childObject` всё-таки нужен: откатили Configuration.xml (или перезаписали из выгрузки БД), а файлы объектов остались — нужно восстановить ссылки в ``. - -При добавлении объект вставляется в каноническую позицию: -1. Находит последний элемент того же типа → вставляет после -2. Если тип отсутствует → находит последний элемент предшествующего типа → вставляет после -3. Внутри одного типа — алфавитный порядок +**Важно про `add-childObject`**: регистрирует в `` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов. Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"` @@ -91,12 +84,59 @@ Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат" ] ``` -**Через `-Value`** (CLI): передавай тот же объект как JSON-строку: -```powershell -... -Operation set-panels -Value '{"top":["open"],"left":["sections"]}' -``` +Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`. -`` для всех 5 панелей пишется автоматически — они всегда доступны пользователю через «Вид → Настройка панелей», даже если не размещены по умолчанию. +## set-home-page + +Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует. + +`value` — объект: + +| Ключ | Канонич. (XML) | Описание | +|------|----------------|----------| +| `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` | +| `left` | `LeftColumn` | массив записей форм | +| `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) | + +Принимаются и короткие и канонич. ключи (XML-имена) — оба работают. + +**Запись формы** — одна из: +- Строка `"
"` — только имя формы, дефолты `height=10`, `visibility=true` +- Объект `{form, height?, visibility?, roles?}` + +| Поле | Канонич. | Дефолт | Описание | +|------|----------|--------|----------| +| `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) | +| `height` | `Height` | `10` | Высота | +| `visibility` | `Visibility` | `true` | Общая видимость (``) | +| `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям | + +**Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`. + +**Пример:** +```json +[ + { + "operation": "set-home-page", + "value": { + "template": "TwoColumnsVariableWidth", + "left": [ + "CommonForm.НачалоРаботы", + { "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false }, + { "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 }, + { + "form": "CommonForm.РабочийСтолОператора", + "visibility": false, + "roles": { "Role.Оператор": true, "Role.ПолныеПрава": false } + } + ], + "right": [ + { "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 } + ] + } + } +] +``` ## DefinitionFile (JSON) @@ -108,6 +148,3 @@ Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат" ] ``` -## Авто-валидация - -После сохранения автоматически запускается `cf-validate` (если не указан `-NoValidate`). diff --git a/.claude/skills/cf-edit/scripts/cf-edit.ps1 b/.claude/skills/cf-edit/scripts/cf-edit.ps1 index 45faa035..5da5fc4f 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.ps1 +++ b/.claude/skills/cf-edit/scripts/cf-edit.ps1 @@ -1,9 +1,9 @@ -# cf-edit v1.3 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.4 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, [string]$DefinitionFile, - [ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles","set-panels")] + [ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles","set-panels","set-home-page")] [string]$Operation, [string]$Value, [switch]$NoValidate @@ -558,6 +558,194 @@ $bodyBlock$declarations Info "Wrote panel layout: $caiPath" } +# --- Operation: set-home-page --- +# Russian → English type aliases for form-ref normalization +$script:ruTypeMap = @{ + "справочник" = "Catalog" + "документ" = "Document" + "перечисление" = "Enum" + "отчёт" = "Report" + "отчет" = "Report" + "обработка" = "DataProcessor" + "общаяформа" = "CommonForm" + "журналдокументов" = "DocumentJournal" + "планвидовхарактеристик" = "ChartOfCharacteristicTypes" + "плансчетов" = "ChartOfAccounts" + "планвидоврасчета" = "ChartOfCalculationTypes" + "планвидоврасчёта" = "ChartOfCalculationTypes" + "регистрсведений" = "InformationRegister" + "регистрнакопления" = "AccumulationRegister" + "регистрбухгалтерии" = "AccountingRegister" + "регистррасчета" = "CalculationRegister" + "регистррасчёта" = "CalculationRegister" + "бизнеспроцесс" = "BusinessProcess" + "задача" = "Task" + "планобмена" = "ExchangePlan" + "хранилищенастроек" = "SettingsStorage" +} +# plural folder → singular type +$script:dirToType = @{} +foreach ($k in $script:typeToDir.Keys) { $script:dirToType[$script:typeToDir[$k].ToLowerInvariant()] = $k } + +function Normalize-FormRef([string]$s) { + $s = $s.Trim() + if (-not $s) { return $s } + # UUID — leave as-is + if ($s -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { return $s } + # Path form? + if ($s.Contains("/") -or $s.Contains("\")) { + $parts = $s.Replace("\","/").Split("/") | Where-Object { $_ -ne "" -and $_.ToLowerInvariant() -ne "ext" } + # Strip trailing Form.xml + if ($parts.Count -gt 0 -and $parts[-1].ToLowerInvariant() -eq "form.xml") { + $parts = @($parts[0..($parts.Count - 2)]) + } + if ($parts.Count -ge 2) { + $typeDir = $parts[0] + $typeSingular = $script:dirToType[$typeDir.ToLowerInvariant()] + if ($typeSingular) { + if ($typeSingular -eq "CommonForm" -and $parts.Count -ge 2) { + return "CommonForm.$($parts[1])" + } + if ($parts.Count -ge 4 -and $parts[2].ToLowerInvariant() -eq "forms") { + return "$typeSingular.$($parts[1]).Form.$($parts[3])" + } + } + } + return $s + } + # Dot form — translate Russian head and 'Форма' segment, auto-insert 'Form' + $segs = $s.Split(".") + if ($segs.Count -ge 1) { + $head = $segs[0].ToLowerInvariant() + if ($script:ruTypeMap.ContainsKey($head)) { $segs[0] = $script:ruTypeMap[$head] } + for ($i = 1; $i -lt $segs.Count; $i++) { + if ($segs[$i] -eq "Форма") { $segs[$i] = "Form" } + } + # Auto-insert Form: for object types with 3 segments (Type.Object.FormName) + if ($segs.Count -eq 3 -and $script:typeOrder -contains $segs[0] -and $segs[0] -ne "CommonForm") { + $segs = @($segs[0], $segs[1], "Form", $segs[2]) + } + } + return ($segs -join ".") +} + +# Accept short DSL or canonical XML keys (silently) +function Get-FieldValue($obj, [string[]]$keys) { + foreach ($k in $keys) { + if ($obj.PSObject.Properties[$k]) { return $obj.PSObject.Properties[$k].Value } + } + return $null +} + +function Build-HomePageItemXml($entry, [string]$indent) { + # Resolve fields + if ($entry -is [string]) { + $formRef = Normalize-FormRef $entry + $height = 10 + $common = $true + $roles = $null + } else { + $formRaw = Get-FieldValue $entry @("form","Form") + if (-not $formRaw) { Write-Error "Home page item: 'form' is required, got: $($entry | ConvertTo-Json -Compress)"; exit 1 } + $formRef = Normalize-FormRef ([string]$formRaw) + $h = Get-FieldValue $entry @("height","Height") + $height = if ($null -ne $h) { [int]$h } else { 10 } + $vis = Get-FieldValue $entry @("visibility","Visibility") + $common = if ($null -ne $vis) { [bool]$vis } else { $true } + $roles = Get-FieldValue $entry @("roles") + } + + $visParts = @() + $visParts += "$indent`t`t$($common.ToString().ToLower())" + if ($roles) { + # roles is PSCustomObject {Role.X: bool, ...} + foreach ($prop in $roles.PSObject.Properties) { + $rname = $prop.Name + if (-not $rname.StartsWith("Role.") -and -not ($rname -match '^[0-9a-fA-F]{8}-')) { $rname = "Role.$rname" } + $rval = ([bool]$prop.Value).ToString().ToLower() + $escName = [System.Security.SecurityElement]::Escape($rname) + $visParts += "$indent`t`t$rval" + } + } + $visBlock = $visParts -join "`r`n" + $escForm = [System.Security.SecurityElement]::Escape($formRef) + return @" +$indent +$indent`t$escForm +$indent`t$height +$indent`t +$visBlock +$indent`t +$indent +"@ +} + +function Do-SetHomePage($valArg) { + $layout = $valArg + if ($layout -is [string]) { + try { $layout = $layout | ConvertFrom-Json } catch { + Write-Error "set-home-page value must be valid JSON object"; exit 1 + } + } + if (-not $layout) { Write-Error "set-home-page value is empty"; exit 1 } + + $allowedTemplates = @("OneColumn","TwoColumnsEqualWidth","TwoColumnsVariableWidth") + $tmpl = Get-FieldValue $layout @("template","WorkingAreaTemplate") + if (-not $tmpl) { $tmpl = "TwoColumnsEqualWidth" } + if ($allowedTemplates -notcontains $tmpl) { + Write-Error "Unknown template '$tmpl'. Allowed: $($allowedTemplates -join ', ')"; exit 1 + } + + $leftItems = Get-FieldValue $layout @("left","LeftColumn") + $rightItems = Get-FieldValue $layout @("right","RightColumn") + + # Reject unknown keys + $known = @("template","WorkingAreaTemplate","left","LeftColumn","right","RightColumn") + foreach ($prop in $layout.PSObject.Properties) { + if ($known -notcontains $prop.Name) { + Write-Error "Unknown key '$($prop.Name)'. Allowed: template, left, right"; exit 1 + } + } + + if ($tmpl -eq "OneColumn" -and $rightItems) { + Write-Error "Template 'OneColumn' cannot have items in 'right' column"; exit 1 + } + + function Build-Column([string]$tag, $items) { + if (-not $items) { return "`t<$tag/>" } + if ($items -isnot [System.Array] -and $items -isnot [System.Collections.IList]) { + $items = @($items) + } + if ($items.Count -eq 0) { return "`t<$tag/>" } + $itemBlocks = @() + foreach ($it in $items) { + $itemBlocks += Build-HomePageItemXml $it "`t`t" + } + $body = $itemBlocks -join "`r`n" + return "`t<$tag>`r`n$body`r`n`t" + } + + $leftXml = Build-Column "LeftColumn" $leftItems + $rightXml = Build-Column "RightColumn" $rightItems + + $hpXml = @" + + + $tmpl +$leftXml +$rightXml + +"@ + + $extDir = Join-Path $script:configDir "Ext" + if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null } + $hpPath = Join-Path $extDir "HomePageWorkArea.xml" + $utf8Bom = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($hpPath, $hpXml, $utf8Bom) + $script:modifyCount++ + Info "Wrote home page layout: $hpPath" +} + # --- Operation: set-defaultRoles --- function Do-SetDefaultRoles([string]$batchVal) { $items = Parse-BatchValue $batchVal @@ -634,6 +822,7 @@ foreach ($op in $operations) { "remove-defaultRole" { Do-RemoveDefaultRole $opValueStr } "set-defaultRoles" { Do-SetDefaultRoles $opValueStr } "set-panels" { Do-SetPanels $opValue } + "set-home-page" { Do-SetHomePage $opValue } default { Write-Error "Unknown operation: $opName"; exit 1 } } } diff --git a/.claude/skills/cf-edit/scripts/cf-edit.py b/.claude/skills/cf-edit/scripts/cf-edit.py index ca679bb8..ccd98c92 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.py +++ b/.claude/skills/cf-edit/scripts/cf-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-edit v1.3 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.4 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -162,7 +162,7 @@ def main(): parser = argparse.ArgumentParser(description="Edit 1C configuration root (Configuration.xml)", allow_abbrev=False) parser.add_argument("-ConfigPath", "-Path", required=True) parser.add_argument("-DefinitionFile", default=None) - parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels"]) + parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels", "set-home-page"]) parser.add_argument("-Value", default=None) parser.add_argument("-NoValidate", action="store_true") args = parser.parse_args() @@ -593,6 +593,170 @@ def main(): modify_count += 1 info(f"Wrote panel layout: {cai_path}") + # --- set-home-page (writes Ext/HomePageWorkArea.xml from scratch) --- + RU_TYPE_MAP = { + "справочник": "Catalog", "документ": "Document", "перечисление": "Enum", + "отчёт": "Report", "отчет": "Report", "обработка": "DataProcessor", + "общаяформа": "CommonForm", "журналдокументов": "DocumentJournal", + "планвидовхарактеристик": "ChartOfCharacteristicTypes", + "плансчетов": "ChartOfAccounts", + "планвидоврасчета": "ChartOfCalculationTypes", + "планвидоврасчёта": "ChartOfCalculationTypes", + "регистрсведений": "InformationRegister", + "регистрнакопления": "AccumulationRegister", + "регистрбухгалтерии": "AccountingRegister", + "регистррасчета": "CalculationRegister", + "регистррасчёта": "CalculationRegister", + "бизнеспроцесс": "BusinessProcess", + "задача": "Task", "планобмена": "ExchangePlan", + "хранилищенастроек": "SettingsStorage", + } + DIR_TO_TYPE = {v.lower(): k for k, v in TYPE_TO_DIR.items()} + UUID_RE = __import__("re").compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") + + def normalize_form_ref(s): + s = (s or "").strip() + if not s: + return s + if UUID_RE.match(s): + return s + if "/" in s or "\\" in s: + parts = [p for p in s.replace("\\", "/").split("/") if p and p.lower() != "ext"] + if parts and parts[-1].lower() == "form.xml": + parts = parts[:-1] + if len(parts) >= 2: + type_dir = parts[0] + type_singular = DIR_TO_TYPE.get(type_dir.lower()) + if type_singular: + if type_singular == "CommonForm" and len(parts) >= 2: + return f"CommonForm.{parts[1]}" + if len(parts) >= 4 and parts[2].lower() == "forms": + return f"{type_singular}.{parts[1]}.Form.{parts[3]}" + return s + segs = s.split(".") + if segs: + head = segs[0].lower() + if head in RU_TYPE_MAP: + segs[0] = RU_TYPE_MAP[head] + for i in range(1, len(segs)): + if segs[i] == "Форма": + segs[i] = "Form" + if len(segs) == 3 and segs[0] in TYPE_ORDER and segs[0] != "CommonForm": + segs = [segs[0], segs[1], "Form", segs[2]] + return ".".join(segs) + + def get_field(obj, keys): + for k in keys: + if isinstance(obj, dict) and k in obj: + return obj[k] + return None + + def build_home_page_item_xml(entry, indent): + if isinstance(entry, str): + form_ref = normalize_form_ref(entry) + height = 10 + common = True + roles = None + elif isinstance(entry, dict): + form_raw = get_field(entry, ["form", "Form"]) + if not form_raw: + print(f"Home page item: 'form' is required, got: {entry!r}", file=sys.stderr) + sys.exit(1) + form_ref = normalize_form_ref(str(form_raw)) + h = get_field(entry, ["height", "Height"]) + height = int(h) if h is not None else 10 + vis = get_field(entry, ["visibility", "Visibility"]) + common = bool(vis) if vis is not None else True + roles = get_field(entry, ["roles"]) + else: + print(f"Home page item must be string or object, got: {entry!r}", file=sys.stderr) + sys.exit(1) + + vis_parts = [f"{indent}\t\t{str(common).lower()}"] + if roles and isinstance(roles, dict): + for rname, rval in roles.items(): + if not rname.startswith("Role.") and not UUID_RE.match(rname): + rname = f"Role.{rname}" + rval_s = str(bool(rval)).lower() + vis_parts.append(f'{indent}\t\t{rval_s}') + vis_block = "\r\n".join(vis_parts) + esc_form = html_escape(form_ref, quote=True) + return ( + f"{indent}\r\n" + f"{indent}\t
{esc_form}
\r\n" + f"{indent}\t{height}\r\n" + f"{indent}\t\r\n" + f"{vis_block}\r\n" + f"{indent}\t\r\n" + f"{indent}
" + ) + + def do_set_home_page(value): + nonlocal modify_count + layout = value + if isinstance(layout, str): + try: + layout = json.loads(layout) + except json.JSONDecodeError: + print("set-home-page value must be valid JSON object", file=sys.stderr) + sys.exit(1) + if not isinstance(layout, dict) or not layout: + print("set-home-page value must be non-empty object", file=sys.stderr) + sys.exit(1) + + allowed_templates = ("OneColumn", "TwoColumnsEqualWidth", "TwoColumnsVariableWidth") + tmpl = get_field(layout, ["template", "WorkingAreaTemplate"]) or "TwoColumnsEqualWidth" + if tmpl not in allowed_templates: + print(f"Unknown template '{tmpl}'. Allowed: {', '.join(allowed_templates)}", file=sys.stderr) + sys.exit(1) + + left_items = get_field(layout, ["left", "LeftColumn"]) + right_items = get_field(layout, ["right", "RightColumn"]) + + known = {"template", "WorkingAreaTemplate", "left", "LeftColumn", "right", "RightColumn"} + for k in layout.keys(): + if k not in known: + print(f"Unknown key '{k}'. Allowed: template, left, right", file=sys.stderr) + sys.exit(1) + + if tmpl == "OneColumn" and right_items: + print("Template 'OneColumn' cannot have items in 'right' column", file=sys.stderr) + sys.exit(1) + + def build_column(tag, items): + if not items: + return f"\t<{tag}/>" + if not isinstance(items, list): + items = [items] + if not items: + return f"\t<{tag}/>" + blocks = [build_home_page_item_xml(it, "\t\t") for it in items] + body = "\r\n".join(blocks) + return f"\t<{tag}>\r\n{body}\r\n\t" + + left_xml = build_column("LeftColumn", left_items) + right_xml = build_column("RightColumn", right_items) + + hp_xml = ( + '\r\n' + '\r\n' + f'\t{tmpl}\r\n' + f'{left_xml}\r\n' + f'{right_xml}\r\n' + '' + ) + + ext_dir = os.path.join(config_dir, "Ext") + os.makedirs(ext_dir, exist_ok=True) + hp_path = os.path.join(ext_dir, "HomePageWorkArea.xml") + with open(hp_path, "w", encoding="utf-8-sig", newline="") as fh: + fh.write(hp_xml) + modify_count += 1 + info(f"Wrote home page layout: {hp_path}") + # --- Execute operations --- operations = [] if args.DefinitionFile: @@ -626,6 +790,8 @@ def main(): do_set_default_roles(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "set-panels": do_set_panels(op_value) + elif op_name == "set-home-page": + do_set_home_page(op_value) else: print(f"Unknown operation: {op_name}", file=sys.stderr) sys.exit(1) diff --git a/.claude/skills/cf-info/SKILL.md b/.claude/skills/cf-info/SKILL.md index eccccecb..1963142d 100644 --- a/.claude/skills/cf-info/SKILL.md +++ b/.claude/skills/cf-info/SKILL.md @@ -1,7 +1,7 @@ --- name: cf-info description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки -argument-hint: [-Mode overview|brief|full] +argument-hint: [-Mode overview|brief|full] [-Section home-page] allowed-tools: - Bash - Read @@ -18,6 +18,7 @@ allowed-tools: |----------|----------| | `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки | | `Mode` | Режим: `overview` (default), `brief`, `full` | +| `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` | | `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) | | `OutFile` | Записать результат в файл (UTF-8 BOM) | @@ -37,14 +38,17 @@ powershell.exe -NoProfile -File .claude/skills/cf-info/scripts/cf-info.ps1 -Conf ```powershell # Обзор пустой конфигурации -... -ConfigPath upload/cfempty +... -ConfigPath src # Краткая сводка реальной конфигурации -... -ConfigPath upload/acc_8.3.24 -Mode brief +... -ConfigPath src -Mode brief # Полная информация -... -ConfigPath upload/acc_8.3.24 -Mode full +... -ConfigPath src -Mode full # С пагинацией -... -ConfigPath upload/acc_8.3.24 -Mode full -Limit 50 -Offset 100 +... -ConfigPath src -Mode full -Limit 50 -Offset 100 + +# Drill-down: только начальная страница (раскладка форм с ролями) +... -ConfigPath src -Section home-page ``` diff --git a/.claude/skills/cf-info/scripts/cf-info.ps1 b/.claude/skills/cf-info/scripts/cf-info.ps1 index bd9479c1..d85df9aa 100644 --- a/.claude/skills/cf-info/scripts/cf-info.ps1 +++ b/.claude/skills/cf-info/scripts/cf-info.ps1 @@ -1,9 +1,12 @@ -# cf-info v1.1 — Compact summary of 1C configuration root +# cf-info v1.2 — Compact summary of 1C configuration root # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath, [ValidateSet("overview","brief","full")] [string]$Mode = "overview", + [Alias('Name')] + [ValidateSet("home-page")] + [string]$Section, [int]$Limit = 150, [int]$Offset = 0, [string]$OutFile @@ -171,6 +174,62 @@ function Format-LayoutSlots($slots) { $script:panelLayout = Get-PanelsLayout +# --- Read home page layout (Ext/HomePageWorkArea.xml) --- +function Get-HomePageLayout { + $configDir = [System.IO.Path]::GetDirectoryName($ConfigPath) + $hpPath = Join-Path (Join-Path $configDir "Ext") "HomePageWorkArea.xml" + if (-not (Test-Path $hpPath)) { return $null } + try { [xml]$hpDoc = Get-Content -Path $hpPath -Encoding UTF8 } catch { return $null } + if (-not $hpDoc.DocumentElement) { return $null } + $hpNs = New-Object System.Xml.XmlNamespaceManager($hpDoc.NameTable) + $hpNs.AddNamespace("hp", "http://v8.1c.ru/8.3/xcf/extrnprops") + $hpNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable") + $result = [ordered]@{ template = ""; left = @(); right = @() } + $tmplNode = $hpDoc.DocumentElement.SelectSingleNode("hp:WorkingAreaTemplate", $hpNs) + if ($tmplNode) { $result.template = $tmplNode.InnerText.Trim() } + foreach ($colName in @("LeftColumn","RightColumn")) { + $colNode = $hpDoc.DocumentElement.SelectSingleNode("hp:$colName", $hpNs) + if (-not $colNode) { continue } + $items = @() + foreach ($item in $colNode.SelectNodes("hp:Item", $hpNs)) { + $f = $item.SelectSingleNode("hp:Form", $hpNs) + $h = $item.SelectSingleNode("hp:Height", $hpNs) + $visNode = $item.SelectSingleNode("hp:Visibility", $hpNs) + $common = $true + $roles = @() + if ($visNode) { + $cn = $visNode.SelectSingleNode("xr:Common", $hpNs) + if ($cn) { $common = ($cn.InnerText.Trim() -eq "true") } + foreach ($v in $visNode.SelectNodes("xr:Value", $hpNs)) { + $roles += @{ name = $v.GetAttribute("name"); value = ($v.InnerText.Trim() -eq "true") } + } + } + $items += [ordered]@{ + form = if ($f) { $f.InnerText.Trim() } else { "" } + height = if ($h) { [int]$h.InnerText.Trim() } else { 10 } + common = $common + roles = $roles + } + } + if ($colName -eq "LeftColumn") { $result.left = $items } else { $result.right = $items } + } + return $result +} + +$script:homePage = Get-HomePageLayout + +function Format-HomePageItem($it, [bool]$detailed) { + $badges = @() + $badges += "h=$($it.height)" + if (-not $it.common) { $badges += "скрыта" } + if ($it.roles.Count -gt 0) { + if ($detailed) { $badges += "роли: $($it.roles.Count)" } + else { $badges += "+$($it.roles.Count) ролей" } + } + $tail = if ($badges.Count -gt 0) { " (" + ($badges -join ", ") + ")" } else { "" } + return " $($it.form)$tail" +} + # --- Count objects in ChildObjects --- $objectCounts = [ordered]@{} $totalObjects = 0 @@ -207,7 +266,7 @@ $cfgDbSpaces = Get-PropText "DatabaseTablespacesUseMode" $cfgWindowMode = Get-PropText "MainClientApplicationWindowMode" # --- BRIEF mode --- -if ($Mode -eq "brief") { +if ($Mode -eq "brief" -and -not $Section) { $synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" } $verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" } $compatPart = if ($cfgCompat) { " | $cfgCompat" } else { "" } @@ -215,7 +274,7 @@ if ($Mode -eq "brief") { } # --- OVERVIEW mode --- -if ($Mode -eq "overview") { +if ($Mode -eq "overview" -and -not $Section) { $synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" } $verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" } Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ===" @@ -251,6 +310,16 @@ if ($Mode -eq "overview") { } } + # Home page layout (brief summary) + if ($script:homePage) { + $ln = $script:homePage.left.Count + $rn = $script:homePage.right.Count + Out "--- Начальная страница ---" + Out " Шаблон: $($script:homePage.template)" + Out " LeftColumn: $ln, RightColumn: $rn (детали: -Section home-page)" + Out "" + } + # Object counts table Out "--- Состав ($totalObjects объектов) ---" Out "" @@ -273,8 +342,34 @@ if ($Mode -eq "overview") { } } +# --- Drill-down: -Section home-page --- +if ($Section -eq "home-page") { + if (-not $script:homePage) { + Out "Файл Ext/HomePageWorkArea.xml не найден" + } else { + Out "=== Начальная страница: $cfgName ===" + Out "" + Out "Шаблон: $($script:homePage.template)" + Out "" + foreach ($side in @(@("LeftColumn","left"), @("RightColumn","right"))) { + $items = $script:homePage[$side[1]] + $lbl = $side[0] + if ($items.Count -eq 0) { Out "${lbl}: —"; Out ""; continue } + Out "${lbl} ($($items.Count)):" + foreach ($it in $items) { + Out (Format-HomePageItem $it $true) + foreach ($r in $it.roles) { + $rval = if ($r.value) { "true" } else { "false" } + Out " $($r.name): $rval" + } + } + Out "" + } + } +} + # --- FULL mode --- -if ($Mode -eq "full") { +if ($Mode -eq "full" -and -not $Section) { $synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" } $verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" } Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ===" @@ -362,6 +457,16 @@ if ($Mode -eq "full") { Out "" } + # --- Section: Home page (brief summary) --- + if ($script:homePage) { + $ln = $script:homePage.left.Count + $rn = $script:homePage.right.Count + Out "--- Начальная страница ---" + Out " Шаблон: $($script:homePage.template)" + Out " LeftColumn: $ln, RightColumn: $rn (детали: -Section home-page)" + Out "" + } + # --- Section: Storages & default forms --- Out "--- Хранилища и формы по умолчанию ---" $storageProps = @("CommonSettingsStorage","ReportsUserSettingsStorage","ReportsVariantsStorage","FormDataSettingsStorage","DynamicListsUserSettingsStorage","URLExternalDataStorage") diff --git a/.claude/skills/cf-info/scripts/cf-info.py b/.claude/skills/cf-info/scripts/cf-info.py index 64e87381..bd781e4d 100644 --- a/.claude/skills/cf-info/scripts/cf-info.py +++ b/.claude/skills/cf-info/scripts/cf-info.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-info v1.1 — Compact summary of 1C configuration root +# cf-info v1.2 — Compact summary of 1C configuration root # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -15,6 +15,7 @@ sys.stderr.reconfigure(encoding="utf-8") parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False) parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory") parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview", help="Output mode") +parser.add_argument("-Section", "-Name", choices=["home-page"], default=None, help="Drill-down section (alias: -Name)") parser.add_argument("-Limit", type=int, default=150, help="Max lines to show") parser.add_argument("-Offset", type=int, default=0, help="Lines to skip") parser.add_argument("-OutFile", default="", help="Write output to file") @@ -172,6 +173,61 @@ def format_layout_slots(slots): panel_layout = get_panels_layout() +# --- Read home page layout (Ext/HomePageWorkArea.xml) --- +HP_NS = "http://v8.1c.ru/8.3/xcf/extrnprops" +XR_NS_HP = "http://v8.1c.ru/8.3/xcf/readable" + +def get_home_page_layout(): + cfg_dir = os.path.dirname(config_path) + hp_path = os.path.join(cfg_dir, "Ext", "HomePageWorkArea.xml") + if not os.path.isfile(hp_path): + return None + try: + hp_tree = etree.parse(hp_path) + except Exception: + return None + hp_root = hp_tree.getroot() + result = {"template": "", "left": [], "right": []} + tn = hp_root.find(f"{{{HP_NS}}}WorkingAreaTemplate") + if tn is not None and tn.text: + result["template"] = tn.text.strip() + for col_name, key in (("LeftColumn", "left"), ("RightColumn", "right")): + col = hp_root.find(f"{{{HP_NS}}}{col_name}") + if col is None: + continue + items = [] + for it in col.findall(f"{{{HP_NS}}}Item"): + f = it.find(f"{{{HP_NS}}}Form") + h = it.find(f"{{{HP_NS}}}Height") + vis = it.find(f"{{{HP_NS}}}Visibility") + common = True + roles = [] + if vis is not None: + cn = vis.find(f"{{{XR_NS_HP}}}Common") + if cn is not None and cn.text: + common = cn.text.strip() == "true" + for v in vis.findall(f"{{{XR_NS_HP}}}Value"): + roles.append({"name": v.get("name", ""), "value": (v.text or "").strip() == "true"}) + items.append({ + "form": (f.text or "").strip() if f is not None else "", + "height": int((h.text or "10").strip()) if h is not None else 10, + "common": common, + "roles": roles, + }) + result[key] = items + return result + +home_page = get_home_page_layout() + +def format_home_page_item(it, detailed): + badges = [f"h={it['height']}"] + if not it["common"]: + badges.append("скрыта") + if it["roles"]: + badges.append(f"роли: {len(it['roles'])}" if detailed else f"+{len(it['roles'])} ролей") + tail = f" ({', '.join(badges)})" if badges else "" + return f" {it['form']}{tail}" + # --- Count objects in ChildObjects --- object_counts = OrderedDict() total_objects = 0 @@ -206,14 +262,14 @@ cfg_db_spaces = get_prop_text("DatabaseTablespacesUseMode") cfg_window_mode = get_prop_text("MainClientApplicationWindowMode") # --- BRIEF mode --- -if args.Mode == "brief": +if args.Mode == "brief" and not args.Section: syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else "" ver_part = f" v{cfg_version}" if cfg_version else "" compat_part = f" | {cfg_compat}" if cfg_compat else "" out(f"Конфигурация: {cfg_name}{syn_part}{ver_part} | {total_objects} объектов{compat_part}") # --- OVERVIEW mode --- -if args.Mode == "overview": +if args.Mode == "overview" and not args.Section: syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else "" ver_part = f" v{cfg_version}" if cfg_version else "" out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===") @@ -241,6 +297,13 @@ if args.Mode == "overview": out(f" {s.ljust(7)} {format_layout_slots(panel_layout[s])}") out() + # Home page (brief summary) + if home_page: + out("--- Начальная страница ---") + out(f" Шаблон: {home_page['template']}") + out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)") + out() + # Object counts table out(f"--- Состав ({total_objects} объектов) ---") out() @@ -261,7 +324,30 @@ if args.Mode == "overview": out(f" {padded} {count}") # --- FULL mode --- -if args.Mode == "full": +# --- Drill-down: -Section home-page --- +if args.Section == "home-page": + if not home_page: + out("Файл Ext/HomePageWorkArea.xml не найден") + else: + out(f"=== Начальная страница: {cfg_name} ===") + out() + out(f"Шаблон: {home_page['template']}") + out() + for col_lbl, col_key in (("LeftColumn", "left"), ("RightColumn", "right")): + items = home_page[col_key] + if not items: + out(f"{col_lbl}: —") + out() + continue + out(f"{col_lbl} ({len(items)}):") + for it in items: + out(format_home_page_item(it, True)) + for r in it["roles"]: + rval = "true" if r["value"] else "false" + out(f" {r['name']}: {rval}") + out() + +if args.Mode == "full" and not args.Section: syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else "" ver_part = f" v{cfg_version}" if cfg_version else "" out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===") @@ -350,6 +436,13 @@ if args.Mode == "full": out(f" объявлено: {', '.join(panel_layout['declared'])}") out() + # --- Section: Home page (brief summary) --- + if home_page: + out("--- Начальная страница ---") + out(f" Шаблон: {home_page['template']}") + out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)") + out() + # --- Section: Storages & default forms --- out("--- Хранилища и формы по умолчанию ---") storage_props = [ diff --git a/.claude/skills/cf-validate/scripts/cf-validate.ps1 b/.claude/skills/cf-validate/scripts/cf-validate.ps1 index 06d2bcb0..f7c071a4 100644 --- a/.claude/skills/cf-validate/scripts/cf-validate.ps1 +++ b/.claude/skills/cf-validate/scripts/cf-validate.ps1 @@ -1,4 +1,4 @@ -# cf-validate v1.2 — Validate 1C configuration root structure +# cf-validate v1.3 — Validate 1C configuration root structure # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -536,6 +536,72 @@ if ($childObjNode) { } } +# --- Check 9: Form references (HomePageWorkArea + Properties) --- +function Test-FormRef([string]$ref) { + if (-not $ref) { return $true } + # UUID — cannot verify without scanning all forms; skip + if ($ref -match $guidPattern) { return $true } + $parts = $ref.Split(".") + if ($parts.Count -eq 2 -and $parts[0] -eq "CommonForm") { + $p = Join-Path (Join-Path (Join-Path $configDir "CommonForms") $parts[1]) "Form.xml" + $pExt = Join-Path (Join-Path (Join-Path (Join-Path $configDir "CommonForms") $parts[1]) "Ext") "Form.xml" + return (Test-Path $p) -or (Test-Path $pExt) + } + if ($parts.Count -eq 4 -and $parts[2] -eq "Form" -and $childTypeDirMap.ContainsKey($parts[0])) { + $dir = $childTypeDirMap[$parts[0]] + $p = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $configDir $dir) $parts[1]) "Forms") $parts[3]) "Form.xml" + $pExt = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $configDir $dir) $parts[1]) "Forms") $parts[3]) "Ext") "Form.xml" + return (Test-Path $p) -or (Test-Path $pExt) + } + return $false +} + +$formRefsChecked = 0 +$formRefErrors = @() + +# HomePageWorkArea +$hpPath = Join-Path (Join-Path $configDir "Ext") "HomePageWorkArea.xml" +if (Test-Path $hpPath) { + try { + [xml]$hpDoc = Get-Content -Path $hpPath -Encoding UTF8 + $hpNs = New-Object System.Xml.XmlNamespaceManager($hpDoc.NameTable) + $hpNs.AddNamespace("hp", "http://v8.1c.ru/8.3/xcf/extrnprops") + foreach ($f in $hpDoc.DocumentElement.SelectNodes("//hp:Item/hp:Form", $hpNs)) { + $ref = $f.InnerText.Trim() + if (-not $ref) { continue } + $formRefsChecked++ + if (-not (Test-FormRef $ref)) { + $formRefErrors += "HomePageWorkArea.Form '$ref' — file not found" + } + } + } catch { + $formRefErrors += "HomePageWorkArea.xml: parse error — $($_.Exception.Message)" + } +} + +# Properties: DefaultXxxForm refs +if ($propsNode) { + $formProps = @("DefaultReportForm","DefaultReportVariantForm","DefaultReportSettingsForm","DefaultDynamicListSettingsForm","DefaultSearchForm","DefaultDataHistoryChangeHistoryForm","DefaultDataHistoryVersionDataForm","DefaultDataHistoryVersionDifferencesForm","DefaultCollaborationSystemUsersChoiceForm","DefaultConstantsForm") + foreach ($pn in $formProps) { + $node = $propsNode.SelectSingleNode("md:$pn", $ns) + if ($node -and $node.InnerText.Trim()) { + $ref = $node.InnerText.Trim() + $formRefsChecked++ + if (-not (Test-FormRef $ref)) { + $formRefErrors += "Properties.$pn '$ref' — form not found" + } + } + } +} + +if ($formRefsChecked -eq 0) { + Report-OK "9. Form references: none to check" +} elseif ($formRefErrors.Count -eq 0) { + Report-OK "9. Form references: $formRefsChecked verified" +} else { + foreach ($err in $formRefErrors) { Report-Error "9. $err" } +} + # --- Final output --- & $finalize diff --git a/.claude/skills/cf-validate/scripts/cf-validate.py b/.claude/skills/cf-validate/scripts/cf-validate.py index 30a901cd..df1426d9 100644 --- a/.claude/skills/cf-validate/scripts/cf-validate.py +++ b/.claude/skills/cf-validate/scripts/cf-validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-validate v1.2 — Validate 1C configuration XML structure +# cf-validate v1.3 — Validate 1C configuration XML structure # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills """Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages.""" import sys, os, argparse, re @@ -537,6 +537,60 @@ def main(): else: pass # no ChildObjects + # --- Check 9: Form references (HomePageWorkArea + Properties) --- + def test_form_ref(ref): + if not ref: + return True + if GUID_PATTERN.match(ref): + return True + parts = ref.split('.') + if len(parts) == 2 and parts[0] == 'CommonForm': + p = os.path.join(config_dir, 'CommonForms', parts[1], 'Form.xml') + p_ext = os.path.join(config_dir, 'CommonForms', parts[1], 'Ext', 'Form.xml') + return os.path.isfile(p) or os.path.isfile(p_ext) + if len(parts) == 4 and parts[2] == 'Form' and parts[0] in CHILD_TYPE_DIR_MAP: + d = CHILD_TYPE_DIR_MAP[parts[0]] + p = os.path.join(config_dir, d, parts[1], 'Forms', parts[3], 'Form.xml') + p_ext = os.path.join(config_dir, d, parts[1], 'Forms', parts[3], 'Ext', 'Form.xml') + return os.path.isfile(p) or os.path.isfile(p_ext) + return False + + form_refs_checked = 0 + form_ref_errors = [] + + hp_path = os.path.join(config_dir, 'Ext', 'HomePageWorkArea.xml') + if os.path.isfile(hp_path): + try: + hp_tree = etree.parse(hp_path) + HP_NS = 'http://v8.1c.ru/8.3/xcf/extrnprops' + for f in hp_tree.getroot().iter(f'{{{HP_NS}}}Form'): + ref = (f.text or '').strip() + if not ref: + continue + form_refs_checked += 1 + if not test_form_ref(ref): + form_ref_errors.append(f"HomePageWorkArea.Form '{ref}' — file not found") + except Exception as e: + form_ref_errors.append(f'HomePageWorkArea.xml: parse error — {e}') + + if props_node is not None: + form_props = ['DefaultReportForm','DefaultReportVariantForm','DefaultReportSettingsForm','DefaultDynamicListSettingsForm','DefaultSearchForm','DefaultDataHistoryChangeHistoryForm','DefaultDataHistoryVersionDataForm','DefaultDataHistoryVersionDifferencesForm','DefaultCollaborationSystemUsersChoiceForm','DefaultConstantsForm'] + for pn in form_props: + node = props_node.find(f'md:{pn}', NS) + if node is not None and node.text and node.text.strip(): + ref = node.text.strip() + form_refs_checked += 1 + if not test_form_ref(ref): + form_ref_errors.append(f"Properties.{pn} '{ref}' — form not found") + + if form_refs_checked == 0: + r.ok('9. Form references: none to check') + elif not form_ref_errors: + r.ok(f'9. Form references: {form_refs_checked} verified') + else: + for err in form_ref_errors: + r.error(f'9. {err}') + # --- Final output --- r.finalize(out_file) sys.exit(1 if r.errors > 0 else 0) diff --git a/tests/skills/cases/cf-edit/set-home-page.json b/tests/skills/cases/cf-edit/set-home-page.json new file mode 100644 index 00000000..9fd96fd5 --- /dev/null +++ b/tests/skills/cases/cf-edit/set-home-page.json @@ -0,0 +1,24 @@ +{ + "name": "Установить начальную страницу (template + left/right + visibility/roles)", + "input": [ + { + "operation": "set-home-page", + "value": { + "template": "TwoColumnsVariableWidth", + "left": [ + "CommonForm.НачалоРаботы", + { "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false }, + { "form": "Справочник.Контрагенты.ФормаСписка", "height": 50 }, + { + "form": "CommonForm.РабочийСтолОператора", + "visibility": false, + "roles": { "Role.Оператор": true, "ПолныеПрава": false } + } + ], + "right": [ + { "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 } + ] + } + } + ] +} diff --git a/tests/skills/cases/cf-edit/snapshots/add-default-role/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/add-default-role/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/add-default-role/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/add-objects/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/add-objects/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/add-objects/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/modify-multiple-props/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/modify-multiple-props/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/modify-multiple-props/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/remove-object/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/remove-object/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/remove-object/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-default-roles/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/set-default-roles/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-default-roles/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-home-page/Configuration.xml b/tests/skills/cases/cf-edit/snapshots/set-home-page/Configuration.xml new file mode 100644 index 00000000..ae8620a1 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-home-page/Configuration.xml @@ -0,0 +1,251 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + + TestConfig + + + ru + TestConfig + + + + + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + + + Biometrics + true + + + Location + false + + + BackgroundLocation + false + + + BluetoothPrinters + false + + + WiFiPrinters + false + + + Contacts + false + + + Calendars + false + + + PushNotifications + false + + + LocalNotifications + false + + + InAppPurchases + false + + + PersonalComputerFileExchange + false + + + Ads + false + + + NumberDialing + false + + + CallProcessing + false + + + CallLog + false + + + AutoSendSMS + false + + + ReceiveSMS + false + + + SMSLog + false + + + Camera + false + + + Microphone + false + + + MusicLibrary + false + + + PictureAndVideoLibraries + false + + + AudioPlaybackAndVibration + false + + + BackgroundAudioPlaybackAndVibration + false + + + InstallPackages + false + + + OSBackup + true + + + ApplicationUsageStatistics + false + + + BarcodeScanning + false + + + BackgroundAudioRecording + false + + + AllFilesAccess + false + + + Videoconferences + false + + + NFC + false + + + DocumentScanning + false + + + SpeechToText + false + + + Geofences + false + + + IncomingShareRequests + false + + + AllIncomingShareRequestsTypesProcessing + false + + + + + + Normal + + + Language.Русский + + + + + + Managed + NotAutoFree + DontUse + DontUse + TaxiEnableVersion8_2 + DontUse + Version8_3_24 + + + + Русский + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/HomePageWorkArea.xml b/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/HomePageWorkArea.xml new file mode 100644 index 00000000..a5ade617 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-home-page/Ext/HomePageWorkArea.xml @@ -0,0 +1,45 @@ + + + TwoColumnsVariableWidth + + +
CommonForm.НачалоРаботы
+ 10 + + true + +
+ +
CommonForm.СписокЗадач
+ 100 + + false + +
+ +
Catalog.Контрагенты.Form.ФормаСписка
+ 50 + + true + +
+ +
CommonForm.РабочийСтолОператора
+ 10 + + false + true + false + +
+
+ + +
DataProcessor.Поиск.Form.ФормаПоиска
+ 30 + + true + +
+
+
\ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-home-page/Languages/Русский.xml b/tests/skills/cases/cf-edit/snapshots/set-home-page/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-home-page/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-version/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/set-version/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-version/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file