feat(meta-edit): add-predefined — предопределённые элементы (Фаза 2.3, v1.19)

Первая операция meta-edit над отдельным файлом Ext/Predefined.xml. add-predefined
добавляет предопределённые (Catalog, ChartOfCharacteristicTypes): строка
"(Код) Имя [Наименование]" или объект {name,code,description,isFolder,childItems}
(дерево групп). Тип кода (String/Number) — из <CodeType> объекта; xsiType по типу;
version из корня. Inline + JSON. Порт эмиттера Resolve-PredefItem/Build-PredefItemXml
из meta-compile. ps1+py.

ИНВАРИАНТ: существующие <Item id=GUID> сохраняются побайтово (текстовый append),
новые получают свежий id — id существующих сущностей не меняются. check-uuid-invariant.mjs
расширен проверкой сохранения id предопределённых при add.

Cert: структура БАЙТ-В-БАЙТ = meta-compile (ps1 и py, id нормализован); GUID-сохранение
при добавлении к существующим; load+UpdateDBCfg в 1С успешны. meta-edit 17/17 ps1+py.
Отложено: remove/set-predefined (вложенное удаление), ПланСчетов/ПланРасчёта (свои грамматики).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-12 12:08:52 +03:00
parent 870203622a
commit c178e04f92
11 changed files with 687 additions and 7 deletions
@@ -84,6 +84,27 @@ Batch через `;;` — можно указать разные ТЧ: `"Тов
Формат аналогичен `modify-attribute`: `ИмяТЧ: ключ=значение, ключ=значение`.
## add-predefined
Добавить предопределённые элементы (Catalog, ChartOfCharacteristicTypes). Существующие элементы и их
идентификаторы сохраняются, новые получают свежий id.
Inline — строка `(Код) Имя [Наименование]` (batch через `;;`; `[Наименование]` необязательно — иначе авто из имени):
```powershell
-Operation add-predefined -Value "(001) Основной ;; (002) Резервный [Резервный склад]"
```
JSON — строки и/или объекты (для групп с вложенными):
```json
{ "add": { "predefined": [
"(001) Основной",
{ "name": "Группа", "isFolder": true, "childItems": ["(002) Вложенный"] }
] } }
```
Ключи объекта: `name`, `code`, `description`, `isFolder`, `childItems` (дерево). Тип кода (строковый/числовой)
берётся из объекта автоматически.
## add-enumValue / add-form / add-template / add-command
Просто имена (batch через `;;`):
+105 -2
View File
@@ -1,4 +1,4 @@
# meta-edit v1.18 — Edit existing 1C metadata object XML (+свойства-списки DataLockFields/RegisteredDocuments)
# meta-edit v1.19 — Edit existing 1C metadata object XML (+add-predefined предопределённые Ext/Predefined.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -12,7 +12,7 @@ param(
"add-attribute", "add-ts", "add-dimension", "add-resource",
"add-enumValue", "add-column", "add-form", "add-template", "add-command",
"add-owner", "add-registerRecord", "add-basedOn", "add-inputByString",
"add-dataLockField", "add-registeredDocument",
"add-dataLockField", "add-registeredDocument", "add-predefined",
"remove-attribute", "remove-ts", "remove-dimension", "remove-resource",
"remove-enumValue", "remove-column", "remove-form", "remove-template", "remove-command",
"remove-owner", "remove-registerRecord", "remove-basedOn", "remove-inputByString",
@@ -1519,6 +1519,16 @@ function Convert-InlineToDefinition([string]$operation, [string]$value) {
return $def
}
# Предопределённые (Ext/Predefined.xml) — отдельный файл; строим { <op>: { predefined: [...] } }.
if ($target -eq 'predefined') {
$items = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$inner = New-Object PSCustomObject
$inner | Add-Member -NotePropertyName 'predefined' -NotePropertyValue $items
$def = New-Object PSCustomObject
$def | Add-Member -NotePropertyName $op -NotePropertyValue $inner
return $def
}
# TS attribute operations: dot notation "TSName.AttrDef"
if ($target -eq "ts-attribute") {
$items = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
@@ -1761,6 +1771,10 @@ function Process-Add($addDef) {
$addDef.PSObject.Properties | ForEach-Object {
$rawKey = $_.Name
$items = $_.Value
if ($rawKey -in @('predefined','предопределенные','предопределённые')) {
Add-PredefinedItems $items
return
}
$childType = Resolve-ChildTypeKey $rawKey
if (-not $childType) {
@@ -2956,6 +2970,95 @@ function Set-ComplexProperty([string]$propertyName, [string[]]$values) {
# Section 13: Main processing
# ============================================================
# ============================================================
# Predefined data (Ext/Predefined.xml) — add предопределённых (Catalog/ChartOfCharacteristicTypes).
# Существующие <Item id=GUID> сохраняются побайтово (текстовый append), новые получают свежий GUID —
# инвариант «не менять id существующей сущности».
# ============================================================
$script:predefXsiTypeByObj = @{
'Catalog' = 'CatalogPredefinedItems'
'ChartOfCharacteristicTypes' = 'PlanOfCharacteristicKindPredefinedItems'
}
function Get-PredefinedPath {
$objDir = Join-Path (Split-Path $resolvedPath) $script:objName
return (Join-Path (Join-Path $objDir "Ext") "Predefined.xml")
}
function Get-ObjectCodeType {
foreach ($ch in $script:propertiesEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'CodeType') { return $ch.InnerText.Trim() }
}
return 'String'
}
# Элемент DSL: строка "(Код) Имя [Наименование]" ЛИБО объект {name,code,description,isFolder,childItems}.
function Resolve-PredefItem($val) {
if ($val -is [string]) {
$s = "$val"; $descRaw = $null; $hasDesc = $false
if ($s -match '\[(.*)\]') { $descRaw = $Matches[1]; $hasDesc = $true; $s = $s -replace '\s*\[.*\]', '' }
$m = [regex]::Match($s.Trim(), '^\s*(?:\(([^)]*)\)\s*)?(\S+)\s*$')
$name = $m.Groups[2].Value
$code = if ($m.Groups[1].Success) { $m.Groups[1].Value } else { '' }
$desc = if ($hasDesc) { $descRaw } else { Split-CamelCase $name }
return @{ name = $name; code = $code; desc = $desc; isFolder = $false; children = @() }
}
$gv = { param($o, [string[]]$keys) foreach ($k in $keys) { if ($o.PSObject.Properties[$k]) { return $o.$k } } return $null }
$name = "$(& $gv $val @('name','имя'))"
$codeV = & $gv $val @('code','код'); $code = if ($null -ne $codeV) { "$codeV" } else { '' }
$hasDesc = $val.PSObject.Properties['description'] -or $val.PSObject.Properties['наименование']
$descV = & $gv $val @('description','наименование')
$desc = if ($hasDesc) { "$descV" } else { Split-CamelCase $name }
$isFolder = ((& $gv $val @('isFolder','группа')) -eq $true)
$subs = & $gv $val @('childItems','подчиненные')
return @{ name = $name; code = $code; desc = $desc; isFolder = $isFolder; children = @(if ($subs) { @($subs) } else { @() }) }
}
function Build-PredefItemXml([string]$indent, $val, [string]$codeType) {
$r = Resolve-PredefItem $val
$sb = New-Object System.Text.StringBuilder
[void]$sb.Append("$indent<Item id=`"$(New-Guid-String)`">`r`n")
[void]$sb.Append("$indent`t<Name>$(Esc-XmlText $r.name)</Name>`r`n")
if (-not $r.code) { [void]$sb.Append("$indent`t<Code/>`r`n") }
elseif ($codeType -eq 'Number') { [void]$sb.Append("$indent`t<Code xsi:type=`"xs:decimal`">$(Esc-XmlText $r.code)</Code>`r`n") }
else { [void]$sb.Append("$indent`t<Code>$(Esc-XmlText $r.code)</Code>`r`n") }
if ($r.desc -eq '') { [void]$sb.Append("$indent`t<Description/>`r`n") }
else { [void]$sb.Append("$indent`t<Description>$(Esc-XmlText $r.desc)</Description>`r`n") }
[void]$sb.Append("$indent`t<IsFolder>$(if ($r.isFolder) { 'true' } else { 'false' })</IsFolder>`r`n")
if ($r.children.Count -gt 0) {
[void]$sb.Append("$indent`t<ChildItems>`r`n")
foreach ($c in $r.children) { [void]$sb.Append((Build-PredefItemXml "$indent`t`t" $c $codeType)) }
[void]$sb.Append("$indent`t</ChildItems>`r`n")
}
[void]$sb.Append("$indent</Item>`r`n")
return $sb.ToString()
}
function Add-PredefinedItems($items) {
$xsiType = $script:predefXsiTypeByObj[$script:objType]
if (-not $xsiType) { Write-Error "add-predefined: тип объекта '$($script:objType)' не поддержан (только Catalog, ChartOfCharacteristicTypes)"; exit 1 }
$codeType = Get-ObjectCodeType
$version = $script:xmlDoc.DocumentElement.GetAttribute("version")
$path = Get-PredefinedPath
$itemsXml = ""
foreach ($it in @($items)) { $itemsXml += (Build-PredefItemXml "`t" $it $codeType) }
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
if (Test-Path $path) {
$text = [System.IO.File]::ReadAllText($path, $utf8Bom)
$text = $text.Replace("</PredefinedData>", "$itemsXml</PredefinedData>")
} else {
$extDir = Split-Path $path
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
$hdr = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" 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`" xsi:type=`"$xsiType`" version=`"$version`">`r`n"
$text = "$hdr$itemsXml</PredefinedData>`r`n"
}
[System.IO.File]::WriteAllText($path, $text, $utf8Bom)
$n = @($items).Count
Info "Added $n predefined item(s) → $path"
$script:addCount += $n
}
# --- Inline mode conversion ---
if ($Operation) {
$def = Convert-InlineToDefinition $Operation $Value
+120 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-edit v1.18 — Edit existing 1C metadata object XML (+свойства-списки DataLockFields/RegisteredDocuments)
# meta-edit v1.19 — Edit existing 1C metadata object XML (+add-predefined предопределённые Ext/Predefined.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -203,6 +203,7 @@ md_ns = ""
properties_el = None
child_objects_el = None
obj_name = ""
resolved_path = ""
add_count = 0
remove_count = 0
@@ -1458,6 +1459,11 @@ def convert_inline_to_definition(operation, value):
complex_action = "set" if op == "set" else op
return {"_complex": [{"action": complex_action, "property": prop_name, "values": values}]}
# Предопределённые (Ext/Predefined.xml) — отдельный файл; строим { <op>: { predefined: [...] } }.
if target == "predefined":
items = [v.strip() for v in value.split(";;") if v.strip()]
return {op: {"predefined": items}}
# TS attribute operations: dot notation "TSName.AttrDef"
if target == "ts-attribute":
items = [v.strip() for v in value.split(";;") if v.strip()]
@@ -1667,6 +1673,9 @@ def process_add(add_def):
global add_count
for raw_key, items in add_def.items():
if raw_key in ('predefined', 'предопределенные', 'предопределённые'):
add_predefined_items(items)
continue
child_type = resolve_child_type_key(raw_key)
if not child_type:
@@ -2872,18 +2881,126 @@ def save_xml(tree, path):
# Main
# ============================================================
# ============================================================
# Predefined data (Ext/Predefined.xml) — add предопределённых (Catalog/ChartOfCharacteristicTypes).
# Существующие <Item id=GUID> сохраняются побайтово (текстовый append), новые получают свежий GUID.
# ============================================================
predef_xsi_type_by_obj = {
'Catalog': 'CatalogPredefinedItems',
'ChartOfCharacteristicTypes': 'PlanOfCharacteristicKindPredefinedItems',
}
def get_predefined_path():
return os.path.join(os.path.dirname(resolved_path), obj_name, "Ext", "Predefined.xml")
def get_object_code_type():
for ch in properties_el:
if localname(ch) == 'CodeType':
return (ch.text or '').strip()
return 'String'
def resolve_predef_item(val):
if isinstance(val, str):
s = val
desc_raw = None
has_desc = False
m = re.search(r'\[(.*)\]', s)
if m:
desc_raw = m.group(1)
has_desc = True
s = re.sub(r'\s*\[.*\]', '', s)
mm = re.match(r'^\s*(?:\(([^)]*)\)\s*)?(\S+)\s*$', s.strip())
name = mm.group(2) if mm else s.strip()
code = mm.group(1) if (mm and mm.group(1) is not None) else ''
desc = desc_raw if has_desc else split_camel_case(name)
return {'name': name, 'code': code, 'desc': desc, 'isFolder': False, 'children': []}
def gv(o, keys):
for k in keys:
if isinstance(o, dict) and k in o:
return o[k]
return None
name = str(gv(val, ['name', 'имя']) or '')
code_v = gv(val, ['code', 'код'])
code = str(code_v) if code_v is not None else ''
has_desc = isinstance(val, dict) and ('description' in val or 'наименование' in val)
desc_v = gv(val, ['description', 'наименование'])
desc = str(desc_v) if has_desc else split_camel_case(name)
is_folder = gv(val, ['isFolder', 'группа']) is True
subs = gv(val, ['childItems', 'подчиненные'])
return {'name': name, 'code': code, 'desc': desc if desc is not None else '',
'isFolder': is_folder, 'children': list(subs) if subs else []}
def build_predef_item_xml(indent, val, code_type):
r = resolve_predef_item(val)
parts = [f'{indent}<Item id="{new_uuid()}">']
parts.append(f'{indent}\t<Name>{esc_xml_text(r["name"])}</Name>')
if not r['code']:
parts.append(f'{indent}\t<Code/>')
elif code_type == 'Number':
parts.append(f'{indent}\t<Code xsi:type="xs:decimal">{esc_xml_text(r["code"])}</Code>')
else:
parts.append(f'{indent}\t<Code>{esc_xml_text(r["code"])}</Code>')
if r['desc'] == '':
parts.append(f'{indent}\t<Description/>')
else:
parts.append(f'{indent}\t<Description>{esc_xml_text(r["desc"])}</Description>')
parts.append(f'{indent}\t<IsFolder>{"true" if r["isFolder"] else "false"}</IsFolder>')
if r['children']:
parts.append(f'{indent}\t<ChildItems>')
for c in r['children']:
parts.append(build_predef_item_xml(indent + '\t\t', c, code_type).rstrip('\r\n'))
parts.append(f'{indent}\t</ChildItems>')
parts.append(f'{indent}</Item>')
return '\r\n'.join(parts) + '\r\n'
def add_predefined_items(items):
global add_count
xsi_type = predef_xsi_type_by_obj.get(obj_type)
if not xsi_type:
print(f"add-predefined: тип объекта '{obj_type}' не поддержан (только Catalog, ChartOfCharacteristicTypes)", file=sys.stderr)
sys.exit(1)
code_type = get_object_code_type()
version = xml_root.get("version")
path = get_predefined_path()
item_list = items if isinstance(items, list) else [items]
items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list)
if os.path.exists(path):
with open(path, 'r', encoding='utf-8-sig') as f:
text = f.read()
text = text.replace('</PredefinedData>', items_xml + '</PredefinedData>')
else:
os.makedirs(os.path.dirname(path), exist_ok=True)
hdr = ('<?xml version="1.0" encoding="UTF-8"?>\r\n<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" '
'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" '
f'xsi:type="{xsi_type}" version="{version}">\r\n')
text = hdr + items_xml + '</PredefinedData>\r\n'
with open(path, 'wb') as f:
f.write(b'\xef\xbb\xbf')
f.write(text.encode('utf-8'))
info(f"Added {len(item_list)} predefined item(s) -> {path}")
add_count += len(item_list)
def main():
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
global xml_tree, xml_root, obj_element, obj_type, md_ns
global properties_el, child_objects_el, obj_name
global properties_el, child_objects_el, obj_name, resolved_path
global add_count, remove_count, modify_count, warn_count
valid_operations = [
"add-attribute", "add-ts", "add-dimension", "add-resource",
"add-enumValue", "add-column", "add-form", "add-template", "add-command",
"add-owner", "add-registerRecord", "add-basedOn", "add-inputByString",
"add-dataLockField", "add-registeredDocument",
"add-dataLockField", "add-registeredDocument", "add-predefined",
"remove-attribute", "remove-ts", "remove-dimension", "remove-resource",
"remove-enumValue", "remove-column", "remove-form", "remove-template", "remove-command",
"remove-owner", "remove-registerRecord", "remove-basedOn", "remove-inputByString",
@@ -0,0 +1,21 @@
{
"name": "add-predefined: предопределённые элементы + группа с вложенными (Ext/Predefined.xml)",
"setup": "empty-config",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "Catalog", "name": "Спр" },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "Catalogs/Спр" },
"input": {
"add": {
"predefined": [
"(01) Первый",
"(02) Второй [Второй элемент]",
{ "name": "Группа", "isFolder": true, "childItems": ["(03) Вложенный"] }
]
}
}
}
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Спр" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Спр" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Спр" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Спр" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Спр" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Спр</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Спр</v8:content>
</v8:item>
</Synonym>
<Comment />
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners />
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics />
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Спр.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Спр.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm />
<DefaultFolderForm />
<DefaultListForm />
<DefaultChoiceForm />
<DefaultFolderChoiceForm />
<AuxiliaryObjectForm />
<AuxiliaryFolderForm />
<AuxiliaryListForm />
<AuxiliaryChoiceForm />
<AuxiliaryFolderChoiceForm />
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn />
<DataLockFields />
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation />
<ExtendedObjectPresentation />
<ListPresentation />
<ExtendedListPresentation />
<Explanation />
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects />
</Catalog>
</MetaDataObject>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" 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" xsi:type="CatalogPredefinedItems" version="2.17">
<Item id="UUID-001">
<Name>Первый</Name>
<Code>01</Code>
<Description>Первый</Description>
<IsFolder>false</IsFolder>
</Item>
<Item id="UUID-002">
<Name>Второй</Name>
<Code>02</Code>
<Description>Второй элемент</Description>
<IsFolder>false</IsFolder>
</Item>
<Item id="UUID-003">
<Name>Группа</Name>
<Code/>
<Description>Группа</Description>
<IsFolder>true</IsFolder>
<ChildItems>
<Item id="UUID-004">
<Name>Вложенный</Name>
<Code>03</Code>
<Description>Вложенный</Description>
<IsFolder>false</IsFolder>
</Item>
</ChildItems>
</Item>
</PredefinedData>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Catalog>Спр</Catalog>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
+14 -2
View File
@@ -76,13 +76,22 @@ for (const runtime of runtimes) {
},
tabularSections: { 'Товары': { modify: { 'Цена': { name: 'ЦенаНовая' } } } },
},
add: { attributes: ['НовыйРекв: Boolean'] },
add: { attributes: ['НовыйРекв: Boolean'], predefined: ['(1) ПервыйПредоп', '(2) ВторойПредоп'] },
remove: { attributes: ['Удаляемый'] },
}), 'utf8');
skill(runtime, 'meta-edit', ['-ObjectPath', objXml, '-DefinitionFile', edit, '-NoValidate'], work);
const after = collectUuids(objXml);
// 3b. Инвариант для предопределённых: добавление ещё элементов не меняет id существующих <Item>.
const predefXml = join(work, 'Catalogs', 'Спр', 'Ext', 'Predefined.xml');
const predefIds = (p) => new Set([...readFileSync(p, 'utf8').matchAll(/<Item id="([0-9a-f-]{36})"/g)].map(m => m[1]));
const predefBefore = predefIds(predefXml);
const edit2 = join(work, 'e2.json');
writeFileSync(edit2, JSON.stringify({ add: { predefined: ['(3) ТретийПредоп'] } }), 'utf8');
skill(runtime, 'meta-edit', ['-ObjectPath', objXml, '-DefinitionFile', edit2, '-NoValidate'], work);
const predefAfter = predefIds(predefXml);
// 3. Инвариант: каждый uuid, существовавший ДО правки (кроме намеренно удалённого),
// должен присутствовать ПОСЛЕ (переименование/смена типа/структурные свойства НЕ меняют id).
const fail = (msg) => { console.log(`[${runtime}] НАРУШЕНИЕ: ${msg}`); failures++; };
@@ -93,7 +102,10 @@ for (const runtime of runtimes) {
if (!after.has(uuid)) fail(`uuid ${uuid} пропал после правки (перегенерирован?)`);
}
if (removedUuid && after.has(removedUuid)) fail(`uuid удалённого реквизита ${removedUuid} остался (не удалён?)`);
console.log(`[${runtime}] проверено ${checked} uuid (объект + GeneratedType + реквизиты/ТЧ), удалённый: ${removedUuid ? 'ок' : 'НЕ НАЙДЕН'}`);
for (const id of predefBefore) {
if (!predefAfter.has(id)) fail(`id предопределённого элемента ${id} пропал после добавления новых (перегенерирован?)`);
}
console.log(`[${runtime}] проверено ${checked} uuid объекта + ${predefBefore.size} id предопределённых (сохранены при add)`);
} catch (e) {
console.log(`[${runtime}] ОШИБКА прогона: ${(e.stderr || e.message || '').toString().slice(0, 300)}`);
failures++;