diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 index 88a1cf6b..92af562b 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 @@ -1,4 +1,4 @@ -# cfe-borrow v1.7 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.8 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$ExtensionPath, @@ -444,6 +444,14 @@ function Read-SourceObject { $srcProps[$propName] = $propNode.InnerText.Trim() } } + # DefinedType: carry the definition. A type alias is meaningless as a bare shell — + # the platform needs its underlying type (e.g. to know a column is a summable Number for totals). + if ($typeName -eq "DefinedType") { + $typeNode = $propsNode.SelectSingleNode("md:Type", $srcNs) + if ($typeNode) { + $srcProps["__TypeXml"] = [regex]::Replace($typeNode.OuterXml, '\s+xmlns(?::\w+)?="[^"]*"', '') + } + } } return @{ @@ -1057,7 +1065,8 @@ function Collect-FormDataPaths { if ($segments.Count -ge 2) { $seg1 = $segments[1] if ($script:standardFields -contains $seg1) { continue } - $deepPaths += @{ ObjectAttr = $seg0; SubAttr = $seg1 } + $seg2 = if ($segments.Count -ge 3) { $segments[2] } else { $null } + $deepPaths += @{ ObjectAttr = $seg0; SubAttr = $seg1; SubSubAttr = $seg2 } } } } @@ -1082,7 +1091,7 @@ function Collect-FormDataPaths { $seen = @{} $uniqueDeep = @() foreach ($dp in $deepPaths) { - $key = "$($dp.ObjectAttr).$($dp.SubAttr)" + $key = "$($dp.ObjectAttr).$($dp.SubAttr).$($dp.SubSubAttr)" if (-not $seen.ContainsKey($key)) { $seen[$key] = $true $uniqueDeep += $dp @@ -1516,79 +1525,46 @@ function Borrow-MainAttribute { # Step 5: Handle deep paths (Form mode only) if ($mode -eq "Form" -and $deepPaths.Count -gt 0) { - # Filter out deep paths where ObjectAttr is a TabularSection (those are TS column refs, not deep attribute refs) - $realDeep = @() + # Top-level ref deep paths: Объект.. — borrow the ref attribute's catalog with the sub-attribute + $deepByAttr = @{} foreach ($dp in $deepPaths) { - if (-not $tsNames.ContainsKey($dp.ObjectAttr)) { $realDeep += $dp } + if ($tsNames.ContainsKey($dp.ObjectAttr)) { continue } + if (-not $deepByAttr.ContainsKey($dp.ObjectAttr)) { $deepByAttr[$dp.ObjectAttr] = @() } + if ($deepByAttr[$dp.ObjectAttr] -notcontains $dp.SubAttr) { $deepByAttr[$dp.ObjectAttr] += $dp.SubAttr } } - - if ($realDeep.Count -gt 0) { - Info " Processing $($realDeep.Count) deep path(s)..." - - # Group by ObjectAttr → target catalog - $deepByAttr = @{} - foreach ($dp in $realDeep) { - if (-not $deepByAttr.ContainsKey($dp.ObjectAttr)) { $deepByAttr[$dp.ObjectAttr] = @() } - $deepByAttr[$dp.ObjectAttr] += $dp.SubAttr - } - + if ($deepByAttr.Count -gt 0) { + Info " Processing $($deepByAttr.Count) deep path attribute(s)..." foreach ($attrName in $deepByAttr.Keys) { - # Find the attribute's type to determine target catalog $attrInfo = $srcAttrs | Where-Object { $_.Name -eq $attrName } | Select-Object -First 1 if (-not $attrInfo) { continue } - - # Extract catalog name from type: cfg:CatalogRef.XXX $catMatch = [regex]::Match($attrInfo.TypeXml, 'cfg:(\w+)Ref\.(\w+)') if (-not $catMatch.Success) { continue } + Borrow-DeepTargetAttrs $catMatch.Groups[1].Value $catMatch.Groups[2].Value $deepByAttr[$attrName] + } + } - $targetTypeName = $catMatch.Groups[1].Value - $targetObjName = $catMatch.Groups[2].Value - - # Ensure target is borrowed - if (-not (Test-ObjectBorrowed $targetTypeName $targetObjName)) { - $tSrc = Read-SourceObject $targetTypeName $targetObjName - $tBorrowedXml = Build-BorrowedObjectXml $targetTypeName $targetObjName $tSrc.Uuid $tSrc.Properties - $tTargetDir = Join-Path $extDir $childTypeDirMap[$targetTypeName] - if (-not (Test-Path $tTargetDir)) { - New-Item -ItemType Directory -Path $tTargetDir -Force | Out-Null - } - $tTargetFile = Join-Path $tTargetDir "${targetObjName}.xml" - [System.IO.File]::WriteAllText($tTargetFile, $tBorrowedXml, $encBom) - Add-ToChildObjects $targetTypeName $targetObjName - $script:borrowedFiles += $tTargetFile - Info " Auto-borrowed for deep path: ${targetTypeName}.${targetObjName}" - } - - # Resolve sub-attributes in target catalog - $subNames = @{} - foreach ($sn in $deepByAttr[$attrName]) { $subNames[$sn] = $true } - $subResolved = Resolve-SourceAttributes $targetTypeName $targetObjName $subNames - - if ($subResolved.Attributes.Count -gt 0) { - Merge-AttributesIntoObject $targetTypeName $targetObjName $subResolved.Attributes - - # Collect and borrow ref types from deep attributes - $subTypeXmls = @() - foreach ($sa in $subResolved.Attributes) { $subTypeXmls += $sa.TypeXml } - $subRefTypes = Collect-ReferenceTypes $subTypeXmls - foreach ($srt in $subRefTypes) { - if (-not $childTypeDirMap.ContainsKey($srt.TypeName)) { continue } - if (Test-ObjectBorrowed $srt.TypeName $srt.ObjName) { continue } - $sSrcFile = Join-Path (Join-Path $cfgDir $childTypeDirMap[$srt.TypeName]) "$($srt.ObjName).xml" - if (-not (Test-Path $sSrcFile)) { continue } - $sSrc = Read-SourceObject $srt.TypeName $srt.ObjName - $sBorrowedXml = Build-BorrowedObjectXml $srt.TypeName $srt.ObjName $sSrc.Uuid $sSrc.Properties - $sTargetDir = Join-Path $extDir $childTypeDirMap[$srt.TypeName] - if (-not (Test-Path $sTargetDir)) { - New-Item -ItemType Directory -Path $sTargetDir -Force | Out-Null - } - $sTargetFile = Join-Path $sTargetDir "$($srt.ObjName).xml" - [System.IO.File]::WriteAllText($sTargetFile, $sBorrowedXml, $encBom) - Add-ToChildObjects $srt.TypeName $srt.ObjName - $script:borrowedFiles += $sTargetFile - Info " Auto-borrowed (deep): $($srt.TypeName).$($srt.ObjName)" - } - } + # Tabular-section deep paths: Объект.<ТЧ>.<Колонка>. — borrow the column's catalog with the sub-attribute + $tsDeepByCol = @{} + foreach ($dp in $deepPaths) { + if (-not $tsNames.ContainsKey($dp.ObjectAttr)) { continue } + if (-not $dp.SubSubAttr) { continue } + if ($script:standardFields -contains $dp.SubSubAttr) { continue } + $k = "$($dp.ObjectAttr)|$($dp.SubAttr)" + if (-not $tsDeepByCol.ContainsKey($k)) { $tsDeepByCol[$k] = @() } + if ($tsDeepByCol[$k] -notcontains $dp.SubSubAttr) { $tsDeepByCol[$k] += $dp.SubSubAttr } + } + if ($tsDeepByCol.Count -gt 0) { + Info " Processing $($tsDeepByCol.Count) tabular-section deep path(s)..." + foreach ($k in $tsDeepByCol.Keys) { + $parts = $k.Split("|") + $tsName = $parts[0]; $colName = $parts[1] + $tsInfo = $srcTS | Where-Object { $_.Name -eq $tsName } | Select-Object -First 1 + if (-not $tsInfo) { continue } + $colInfo = $tsInfo.Attributes | Where-Object { $_.Name -eq $colName } | Select-Object -First 1 + if (-not $colInfo) { continue } + $catMatch = [regex]::Match($colInfo.TypeXml, 'cfg:(\w+)Ref\.(\w+)') + if (-not $catMatch.Success) { continue } + Borrow-DeepTargetAttrs $catMatch.Groups[1].Value $catMatch.Groups[2].Value $tsDeepByCol[$k] } } } @@ -1596,6 +1572,57 @@ function Borrow-MainAttribute { Info " Main attribute borrowing complete" } +# --- 11i. Helper: borrow a deep-path target catalog together with the referenced sub-attributes --- +# Used for both Объект.. (top-level ref attr) and Объект.<ТЧ>.<Колонка>. (tabular-section +# column ref). Mirrors Designer: the referenced catalog is adopted WITH the sub-attributes the form shows, +# otherwise the platform rejects the deep DataPath ("Неверный путь к данным"). +function Borrow-DeepTargetAttrs { + param([string]$targetTypeName, [string]$targetObjName, $subAttrNames) + + $encBomLocal = New-Object System.Text.UTF8Encoding($true) + + # Ensure target is borrowed (shell) + if (-not (Test-ObjectBorrowed $targetTypeName $targetObjName)) { + $tSrc = Read-SourceObject $targetTypeName $targetObjName + $tBorrowedXml = Build-BorrowedObjectXml $targetTypeName $targetObjName $tSrc.Uuid $tSrc.Properties + $tTargetDir = Join-Path $extDir $childTypeDirMap[$targetTypeName] + if (-not (Test-Path $tTargetDir)) { New-Item -ItemType Directory -Path $tTargetDir -Force | Out-Null } + $tTargetFile = Join-Path $tTargetDir "${targetObjName}.xml" + [System.IO.File]::WriteAllText($tTargetFile, $tBorrowedXml, $encBomLocal) + Add-ToChildObjects $targetTypeName $targetObjName + $script:borrowedFiles += $tTargetFile + Info " Auto-borrowed for deep path: ${targetTypeName}.${targetObjName}" + } + + # Resolve sub-attributes in target catalog and merge them in + $subNames = @{} + foreach ($sn in $subAttrNames) { $subNames[$sn] = $true } + $subResolved = Resolve-SourceAttributes $targetTypeName $targetObjName $subNames + if ($subResolved.Attributes.Count -gt 0) { + Merge-AttributesIntoObject $targetTypeName $targetObjName $subResolved.Attributes + + # Borrow ref types referenced by the sub-attributes + $subTypeXmls = @() + foreach ($sa in $subResolved.Attributes) { $subTypeXmls += $sa.TypeXml } + $subRefTypes = Collect-ReferenceTypes $subTypeXmls + foreach ($srt in $subRefTypes) { + if (-not $childTypeDirMap.ContainsKey($srt.TypeName)) { continue } + if (Test-ObjectBorrowed $srt.TypeName $srt.ObjName) { continue } + $sSrcFile = Join-Path (Join-Path $cfgDir $childTypeDirMap[$srt.TypeName]) "$($srt.ObjName).xml" + if (-not (Test-Path $sSrcFile)) { continue } + $sSrc = Read-SourceObject $srt.TypeName $srt.ObjName + $sBorrowedXml = Build-BorrowedObjectXml $srt.TypeName $srt.ObjName $sSrc.Uuid $sSrc.Properties + $sTargetDir = Join-Path $extDir $childTypeDirMap[$srt.TypeName] + if (-not (Test-Path $sTargetDir)) { New-Item -ItemType Directory -Path $sTargetDir -Force | Out-Null } + $sTargetFile = Join-Path $sTargetDir "$($srt.ObjName).xml" + [System.IO.File]::WriteAllText($sTargetFile, $sBorrowedXml, $encBomLocal) + Add-ToChildObjects $srt.TypeName $srt.ObjName + $script:borrowedFiles += $sTargetFile + Info " Auto-borrowed (deep): $($srt.TypeName).$($srt.ObjName)" + } + } +} + # --- 12. Helper: build borrowed object XML --- function Build-BorrowedObjectXml { param( @@ -1634,6 +1661,11 @@ function Build-BorrowedObjectXml { } } + # DefinedType: emit the carried definition (needed for the alias to resolve, e.g. totals) + if ($typeName -eq "DefinedType" -and $sourceProps.ContainsKey("__TypeXml")) { + $sb.AppendLine("`t`t`t$($sourceProps['__TypeXml'])") | Out-Null + } + $sb.AppendLine("`t`t") | Out-Null # ChildObjects (for types that need it) diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py index 5daf9713..0d3f4620 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cfe-borrow v1.7 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.8 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -492,6 +492,13 @@ def main(): prop_node = props_node.find(f"{{{MD_NS}}}{prop_name}") if prop_node is not None: src_props[prop_name] = (prop_node.text or "").strip() + # DefinedType: carry the definition. A type alias is meaningless as a bare shell — + # the platform needs its underlying type (e.g. to know a column is a summable Number for totals). + if type_name == "DefinedType": + type_node = props_node.find(f"{{{MD_NS}}}Type") + if type_node is not None: + type_xml = etree.tostring(type_node, encoding="unicode") + src_props["__TypeXml"] = re.sub(r'\s+xmlns(?::\w+)?="[^"]*"', '', type_xml) return {"Uuid": src_uuid, "Properties": src_props, "Element": src_el} @@ -563,6 +570,10 @@ def main(): prop_val = source_props.get(prop_name, "false") lines.append(f"\t\t\t<{prop_name}>{prop_val}") + # DefinedType: emit the carried definition (needed for the alias to resolve, e.g. totals) + if type_name == "DefinedType" and "__TypeXml" in source_props: + lines.append(f"\t\t\t{source_props['__TypeXml']}") + lines.append("\t\t") if type_name in TYPES_WITH_CHILD_OBJECTS: @@ -688,7 +699,8 @@ def main(): seg1 = segments[1] if seg1 in STANDARD_FIELDS: continue - deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1}) + seg2 = segments[2] if len(segments) >= 3 else None + deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2}) # Also scan Объект.X — object attributes referenced by filter/conditional-appearance # fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too. @@ -703,13 +715,14 @@ def main(): seg1 = segments[1] if seg1 in STANDARD_FIELDS: continue - deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1}) + seg2 = segments[2] if len(segments) >= 3 else None + deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2}) # Deduplicate deep paths seen = set() unique_deep = [] for dp in deep_paths: - key = f"{dp['ObjectAttr']}.{dp['SubAttr']}" + key = f"{dp['ObjectAttr']}.{dp['SubAttr']}.{dp.get('SubSubAttr')}" if key not in seen: seen.add(key) unique_deep.append(dp) @@ -1065,79 +1078,93 @@ def main(): # Step 5: Handle deep paths (Form mode only) if mode == "Form" and deep_paths: - # Filter out deep paths where ObjectAttr is a TabularSection - real_deep = [dp for dp in deep_paths if dp["ObjectAttr"] not in ts_names] - - if real_deep: - info(f" Processing {len(real_deep)} deep path(s)...") - - # Group by ObjectAttr -> target catalog - deep_by_attr = {} - for dp in real_deep: - if dp["ObjectAttr"] not in deep_by_attr: - deep_by_attr[dp["ObjectAttr"]] = [] + # Top-level ref deep paths: Объект.. — borrow the ref attribute's catalog with the sub-attribute + deep_by_attr = {} + for dp in deep_paths: + if dp["ObjectAttr"] in ts_names: + continue + deep_by_attr.setdefault(dp["ObjectAttr"], []) + if dp["SubAttr"] not in deep_by_attr[dp["ObjectAttr"]]: deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"]) - + if deep_by_attr: + info(f" Processing {len(deep_by_attr)} deep path attribute(s)...") for attr_name, sub_attr_names in deep_by_attr.items(): - # Find the attribute's type to determine target catalog - attr_info = None - for a in src_attrs: - if a["Name"] == attr_name: - attr_info = a - break + attr_info = next((a for a in src_attrs if a["Name"] == attr_name), None) if not attr_info: continue - - # Extract catalog name from type: cfg:CatalogRef.XXX cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"]) if not cat_match: continue + borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names) - target_type_name = cat_match.group(1) - target_obj_name = cat_match.group(2) - - # Ensure target is borrowed - if not test_object_borrowed(target_type_name, target_obj_name): - t_src = read_source_object(target_type_name, target_obj_name) - t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"]) - t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name]) - os.makedirs(t_target_dir, exist_ok=True) - t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml") - save_text_bom(t_target_file, t_borrowed_xml) - add_to_child_objects(target_type_name, target_obj_name) - borrowed_files.append(t_target_file) - info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}") - - # Resolve sub-attributes in target catalog - sub_names = {sn: True for sn in sub_attr_names} - sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names) - - if sub_resolved["Attributes"]: - merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"]) - - # Collect and borrow ref types from deep attributes - sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]] - sub_ref_types = collect_reference_types(sub_type_xmls) - for srt in sub_ref_types: - if srt["TypeName"] not in CHILD_TYPE_DIR_MAP: - continue - if test_object_borrowed(srt["TypeName"], srt["ObjName"]): - continue - s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml") - if not os.path.isfile(s_src_file): - continue - s_src = read_source_object(srt["TypeName"], srt["ObjName"]) - s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"]) - s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]]) - os.makedirs(s_target_dir, exist_ok=True) - s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml") - save_text_bom(s_target_file, s_borrowed_xml) - add_to_child_objects(srt["TypeName"], srt["ObjName"]) - borrowed_files.append(s_target_file) - info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}") + # Tabular-section deep paths: Объект.<ТЧ>.<Колонка>. — borrow the column's catalog with the sub-attribute + ts_deep_by_col = {} + for dp in deep_paths: + if dp["ObjectAttr"] not in ts_names: + continue + if not dp.get("SubSubAttr"): + continue + if dp["SubSubAttr"] in STANDARD_FIELDS: + continue + k = (dp["ObjectAttr"], dp["SubAttr"]) + ts_deep_by_col.setdefault(k, []) + if dp["SubSubAttr"] not in ts_deep_by_col[k]: + ts_deep_by_col[k].append(dp["SubSubAttr"]) + if ts_deep_by_col: + info(f" Processing {len(ts_deep_by_col)} tabular-section deep path(s)...") + for (ts_name, col_name), sub_attr_names in ts_deep_by_col.items(): + ts_info = next((t for t in src_ts if t["Name"] == ts_name), None) + if not ts_info: + continue + col_info = next((c for c in ts_info["Attributes"] if c["Name"] == col_name), None) + if not col_info: + continue + cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', col_info["TypeXml"]) + if not cat_match: + continue + borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names) info(" Main attribute borrowing complete") + def borrow_deep_target_attrs(target_type_name, target_obj_name, sub_attr_names): + # Borrow a deep-path target catalog together with the referenced sub-attributes, for both + # Объект.. and Объект.<ТЧ>.<Колонка>.. Mirrors Designer: the referenced catalog + # is adopted WITH the sub-attributes the form shows, else the platform rejects the deep DataPath. + if not test_object_borrowed(target_type_name, target_obj_name): + t_src = read_source_object(target_type_name, target_obj_name) + t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"]) + t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name]) + os.makedirs(t_target_dir, exist_ok=True) + t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml") + save_text_bom(t_target_file, t_borrowed_xml) + add_to_child_objects(target_type_name, target_obj_name) + borrowed_files.append(t_target_file) + info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}") + + sub_names = {sn: True for sn in sub_attr_names} + sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names) + if sub_resolved["Attributes"]: + merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"]) + sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]] + sub_ref_types = collect_reference_types(sub_type_xmls) + for srt in sub_ref_types: + if srt["TypeName"] not in CHILD_TYPE_DIR_MAP: + continue + if test_object_borrowed(srt["TypeName"], srt["ObjName"]): + continue + s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml") + if not os.path.isfile(s_src_file): + continue + s_src = read_source_object(srt["TypeName"], srt["ObjName"]) + s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"]) + s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]]) + os.makedirs(s_target_dir, exist_ok=True) + s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml") + save_text_bom(s_target_file, s_borrowed_xml) + add_to_child_objects(srt["TypeName"], srt["ObjName"]) + borrowed_files.append(s_target_file) + info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}") + def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False): dir_name = CHILD_TYPE_DIR_MAP[type_name]