mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-08 03:00:52 +03:00
feat(form-add): формы таблиц внешних источников данных
form-add отказывал видом объекта 'Table', хотя платформа такие формы делает, а в DSL таблицы есть слоты defaultObjectForm/defaultRecordForm/defaultListForm/defaultChoiceForm. Инструкция при этом обещала обратное — строка исправлена. Таблица — единственный вид, чьи ссылки трёхчастные: слот ExternalDataSource.<И>.Table.<Т>.Form.<Ф>, главный реквизит ExternalDataSourceTableObject.<И>.<Т>, MainTable динамического списка ExternalDataSource.<И>.Table.<Т>. Имя источника в файле таблицы не хранится — берётся из пути ExternalDataSources/<И>/Tables/<Т>.xml, единственное место, где навык смотрит на путь. Набор назначений зависит от вида данных таблицы — замерено загрузками на 8.3.24.1691: ObjectData даёт Object/List/Choice, NonobjectData — Record/List/Choice. Неверная пара схемой формы не отвергается: она валит загрузку ВСЕЙ конфигурации «Исключением XDTO при чтении файла» без указания причины, поэтому form-add проверяет её сам и объясняет, а без -Purpose у таблицы с составным ключом основной становится форма записи. form-validate: типы ExternalDataSourceTable* добавлены в список известных cfg-префиксов — иначе корректная форма получала предупреждение «unrecognized cfg prefix». Проверено: form-add и form-validate зелёные в обоих рантаймах, гарды прошли (в том числе check-form-purposes: спека и оба порта сходятся), снэпшот eds-table-forms принят платформой, формы всех четырёх назначений загружены в базу и выгружены обратно со ссылками без потерь. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
co-authored by
Claude Opus 5
parent
a38f30c9dd
commit
fb211a61ac
@@ -62,10 +62,15 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-add.ps1" -Obje
|
||||
| DocumentJournal, FilterCriterion | List, Custom |
|
||||
| Enum | List, Choice, Custom |
|
||||
| SettingsStorage | Save, Load, Custom |
|
||||
| Table (таблица внешнего источника) | `tableDataType=ObjectData` — Object, List, Choice, Custom; `NonobjectData` — Record, List, Choice, Custom |
|
||||
|
||||
Недопустимое сочетание отклоняется со списком доступных для этого типа. У константы собственных
|
||||
форм нет — для неё используется общая форма (`CommonForm`).
|
||||
|
||||
Таблица внешнего источника адресуется файлом таблицы:
|
||||
`ExternalDataSources/<Источник>/Tables/<Таблица>.xml`. Ссылки в такой форме трёхчастные;
|
||||
без `-Purpose` берётся форма объекта, а у таблицы с составным ключом — форма записи.
|
||||
|
||||
## Примеры
|
||||
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-add v1.28 — Add managed form to 1C config object
|
||||
# form-add v1.29 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -370,6 +370,15 @@ $formKinds = @{
|
||||
"Load" = @{ MainAttr = $null; AttrName = $null; Slot = "DefaultLoadForm" }
|
||||
"Custom" = @{ MainAttr = $null; AttrName = $null; Slot = $null }
|
||||
}
|
||||
# Таблица внешнего источника — единственный вид, чьё имя в ссылках трёхчастное
|
||||
# (Источник.Таблица): подставляется {2}, а не {1}.
|
||||
"Table" = @{
|
||||
"Object" = @{ MainAttr = "ExternalDataSourceTableObject.{2}"; AttrName = "Объект"; Slot = "DefaultObjectForm"; SavedData = $true; Primary = $true }
|
||||
"Record" = @{ MainAttr = "ExternalDataSourceTableRecordManager.{2}"; AttrName = "Запись"; Slot = "DefaultRecordForm"; SavedData = $true }
|
||||
"List" = @{ MainAttr = "DynamicList"; AttrName = "Список"; Slot = "DefaultListForm" }
|
||||
"Choice" = @{ MainAttr = "DynamicList"; AttrName = "Список"; Slot = "DefaultChoiceForm" }
|
||||
"Custom" = @{ MainAttr = $null; AttrName = $null; Slot = $null }
|
||||
}
|
||||
}
|
||||
|
||||
# Виды, у которых свойство DefaultForm есть, но собственных форм не бывает — отказ с причиной,
|
||||
@@ -422,6 +431,25 @@ if (-not $objectName) {
|
||||
Write-Host ""
|
||||
Write-Host "=== form-add ==="
|
||||
Write-Host ""
|
||||
# Ссылка на объект и имя для типов формы. У всех видов это "Вид.Имя", и только
|
||||
# у таблицы внешнего источника — "ExternalDataSource.<Источник>.Table.<Таблица>", а в именах
|
||||
# типов — "<Источник>.<Таблица>". Имя источника в самом файле таблицы не хранится —
|
||||
# единственное место, где навык смотрит на путь: ExternalDataSources/<Источник>/Tables/<Таблица>.xml
|
||||
$objectQualifiedName = $objectName
|
||||
$objectRef = "$objectType.$objectName"
|
||||
if ($objectType -eq "Table") {
|
||||
$tablesDir = Split-Path -Parent $objectXmlFull.Path
|
||||
$edsSource = Split-Path -Leaf (Split-Path -Parent $tablesDir)
|
||||
if (-not $edsSource -or (Split-Path -Leaf $tablesDir) -ne "Tables") {
|
||||
Write-Error "Таблица внешнего источника ожидается по пути ExternalDataSources/<Источник>/Tables/<Таблица>.xml, а не '$($objectXmlFull.Path)'"
|
||||
exit 1
|
||||
}
|
||||
$objectQualifiedName = "$edsSource.$objectName"
|
||||
$objectRef = "ExternalDataSource.$edsSource.Table.$objectName"
|
||||
$tdtNode = $xmlDoc.SelectSingleNode("//md:Table/md:Properties/md:TableDataType", $nsMgr)
|
||||
$tableDataType = if ($tdtNode) { $tdtNode.InnerText.Trim() } else { "ObjectData" }
|
||||
}
|
||||
|
||||
Write-Host "Object: $objectType.$objectName"
|
||||
|
||||
# --- Фаза 2: Валидация Purpose ---
|
||||
@@ -457,8 +485,14 @@ if ($Purpose) {
|
||||
}
|
||||
}
|
||||
if (-not $Purpose) {
|
||||
foreach ($p in $kindPurposes.Keys) {
|
||||
if ($kindPurposes[$p].Primary) { $Purpose = $p; break }
|
||||
if ($objectType -eq "Table" -and $tableDataType -eq "NonobjectData") {
|
||||
# Пометка Primary в таблице видов одна на вид, а у таблицы с составным ключом
|
||||
# формы объекта не бывает — основной становится форма записи.
|
||||
$Purpose = "Record"
|
||||
} else {
|
||||
foreach ($p in $kindPurposes.Keys) {
|
||||
if ($kindPurposes[$p].Primary) { $Purpose = $p; break }
|
||||
}
|
||||
}
|
||||
}
|
||||
$purposeKey = $null
|
||||
@@ -472,6 +506,20 @@ if (-not $purposeKey) {
|
||||
$Purpose = $purposeKey
|
||||
$purposeRule = $kindPurposes[$Purpose]
|
||||
|
||||
# У таблицы внешнего источника набор назначений зависит от вида данных (замерено на
|
||||
# 8.3.24.1691): ObjectData — Object/List/Choice, NonobjectData — Record/List/Choice. Неверная пара
|
||||
# не отвергается схемой формы, а валит загрузку всей конфигурации «Исключением XDTO» без причины.
|
||||
if ($objectType -eq "Table") {
|
||||
if ($tableDataType -eq "NonobjectData" -and $Purpose -eq "Object") {
|
||||
Write-Error "Таблица '$objectName' с составным ключом (TableDataType=NonobjectData): формы объекта у неё нет — используйте -Purpose Record."
|
||||
exit 1
|
||||
}
|
||||
if ($tableDataType -ne "NonobjectData" -and $Purpose -eq "Record") {
|
||||
Write-Error "Таблица '$objectName' с ключом из одного поля (TableDataType=ObjectData): формы записи у неё нет — используйте -Purpose Object."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Гард от повторения дефекта: запись таблицы обязана быть заполненной. Пустой MainAttr — это
|
||||
# произвольная форма (законное состояние), а вот наполовину заполненная запись означала бы, что
|
||||
# таблицу правили невнимательно, и в XML уйдёт мусор вроде `cfg:.Журнал`.
|
||||
@@ -561,13 +609,13 @@ $formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||
# реквизита брался из отдельной карты, и отсутствие вида в ней давало `cfg:.Имя` — молча.
|
||||
$attributesBlock = ""
|
||||
if ($purposeRule.MainAttr) {
|
||||
$mainAttrType = $purposeRule.MainAttr -f $objectType, $objectName
|
||||
$mainAttrType = $purposeRule.MainAttr -f $objectType, $objectName, $objectQualifiedName
|
||||
$mainAttrName = $purposeRule.AttrName
|
||||
|
||||
# Динамический список несёт MainTable, остальные типы — SavedData по записи таблицы.
|
||||
$tailLines = ""
|
||||
if ($mainAttrType -eq "DynamicList") {
|
||||
$mainTable = "$objectType.$objectName"
|
||||
$mainTable = $objectRef
|
||||
$tailLines = "`n`t`t`t<Settings xsi:type=""DynamicList"">`n`t`t`t`t<MainTable>$mainTable</MainTable>`n`t`t`t</Settings>"
|
||||
} elseif ($purposeRule.SavedData) {
|
||||
$tailLines = "`n`t`t`t<SavedData>true</SavedData>"
|
||||
@@ -713,7 +761,7 @@ if ($insertBefore) {
|
||||
$existingForms = $childObjects.SelectNodes("md:Form", $nsMgr)
|
||||
$isFirstFormForPurpose = $false
|
||||
$defaultPropName = $null
|
||||
$defaultValue = "$objectType.$objectName.Form.$FormName"
|
||||
$defaultValue = "$objectRef.Form.$FormName"
|
||||
|
||||
# Свойство «основная форма» — из записи таблицы. Раньше выбиралось по одному Purpose без учёта
|
||||
# вида, и для журнала писалось DefaultListForm, которого у журнала нет: слот не находился, навык
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-add v1.28 — Add managed form to 1C config object (Python port)
|
||||
# form-add v1.29 — Add managed form to 1C config object (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -559,6 +559,17 @@ def main():
|
||||
"Load": {"main_attr": None, "attr_name": None, "slot": "DefaultLoadForm"},
|
||||
"Custom": {"main_attr": None, "attr_name": None, "slot": None},
|
||||
},
|
||||
# Таблица внешнего источника — единственный вид, чьё имя в ссылках трёхчастное
|
||||
# (Источник.Таблица): подставляется {2}, а не {1}.
|
||||
"Table": {
|
||||
"Object": {"main_attr": "ExternalDataSourceTableObject.{2}", "attr_name": "Объект",
|
||||
"slot": "DefaultObjectForm", "saved_data": True, "primary": True},
|
||||
"Record": {"main_attr": "ExternalDataSourceTableRecordManager.{2}", "attr_name": "Запись",
|
||||
"slot": "DefaultRecordForm", "saved_data": True},
|
||||
"List": {"main_attr": "DynamicList", "attr_name": "Список", "slot": "DefaultListForm"},
|
||||
"Choice": {"main_attr": "DynamicList", "attr_name": "Список", "slot": "DefaultChoiceForm"},
|
||||
"Custom": {"main_attr": None, "attr_name": None, "slot": None},
|
||||
},
|
||||
}
|
||||
|
||||
# Виды, у которых свойство DefaultForm есть, но собственных форм не бывает.
|
||||
@@ -604,6 +615,26 @@ def main():
|
||||
sys.exit(1)
|
||||
object_name = name_node.text
|
||||
|
||||
# Ссылка на объект и имя для типов формы. У всех видов это "Вид.Имя", и только
|
||||
# у таблицы внешнего источника — "ExternalDataSource.<Источник>.Table.<Таблица>", а в именах
|
||||
# типов — "<Источник>.<Таблица>". Имя источника в самом файле таблицы не хранится —
|
||||
# единственное место, где навык смотрит на путь: ExternalDataSources/<Источник>/Tables/<Таблица>.xml
|
||||
object_qualified_name = object_name
|
||||
object_ref = f"{object_type}.{object_name}"
|
||||
table_data_type = "ObjectData"
|
||||
if object_type == "Table":
|
||||
tables_dir = os.path.dirname(object_xml_full)
|
||||
eds_source = os.path.basename(os.path.dirname(tables_dir))
|
||||
if not eds_source or os.path.basename(tables_dir) != "Tables":
|
||||
print("Таблица внешнего источника ожидается по пути "
|
||||
f"ExternalDataSources/<Источник>/Tables/<Таблица>.xml, а не '{object_xml_full}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
object_qualified_name = f"{eds_source}.{object_name}"
|
||||
object_ref = f"ExternalDataSource.{eds_source}.Table.{object_name}"
|
||||
tdt_node = root.find(".//md:Table/md:Properties/md:TableDataType", NSMAP)
|
||||
if tdt_node is not None and tdt_node.text:
|
||||
table_data_type = tdt_node.text.strip()
|
||||
|
||||
print()
|
||||
print("=== form-add ===")
|
||||
print()
|
||||
@@ -641,10 +672,15 @@ def main():
|
||||
purpose = purpose_synonyms[purpose_probe]
|
||||
|
||||
if not purpose:
|
||||
for k, rule in kind_purposes.items():
|
||||
if rule.get("primary"):
|
||||
purpose = k
|
||||
break
|
||||
if object_type == "Table" and table_data_type == "NonobjectData":
|
||||
# Пометка primary в таблице видов одна на вид, а у таблицы с составным ключом
|
||||
# формы объекта не бывает — основной становится форма записи.
|
||||
purpose = "Record"
|
||||
else:
|
||||
for k, rule in kind_purposes.items():
|
||||
if rule.get("primary"):
|
||||
purpose = k
|
||||
break
|
||||
purpose_key = None
|
||||
for k in kind_purposes:
|
||||
if k.lower() == purpose.lower():
|
||||
@@ -657,6 +693,19 @@ def main():
|
||||
purpose = purpose_key
|
||||
purpose_rule = kind_purposes[purpose]
|
||||
|
||||
# У таблицы внешнего источника набор назначений зависит от вида данных (замерено на
|
||||
# 8.3.24.1691): ObjectData — Object/List/Choice, NonobjectData — Record/List/Choice. Неверная пара
|
||||
# не отвергается схемой формы, а валит загрузку всей конфигурации «Исключением XDTO» без причины.
|
||||
if object_type == "Table":
|
||||
if table_data_type == "NonobjectData" and purpose == "Object":
|
||||
print(f"Таблица '{object_name}' с составным ключом (TableDataType=NonobjectData): "
|
||||
"формы объекта у неё нет — используйте -Purpose Record.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if table_data_type != "NonobjectData" and purpose == "Record":
|
||||
print(f"Таблица '{object_name}' с ключом из одного поля (TableDataType=ObjectData): "
|
||||
"формы записи у неё нет — используйте -Purpose Object.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Гард от повторения дефекта: запись таблицы обязана быть заполненной. Пустой main_attr —
|
||||
# это произвольная форма (законное состояние), а наполовину заполненная запись означала бы,
|
||||
# что таблицу правили невнимательно, и в XML уйдёт мусор вроде `cfg:.Журнал`.
|
||||
@@ -724,13 +773,13 @@ def main():
|
||||
# реквизита брался из отдельной карты, и отсутствие вида в ней давало `cfg:.Имя` — молча.
|
||||
attributes_block = ''
|
||||
if purpose_rule.get("main_attr"):
|
||||
main_attr_type = purpose_rule["main_attr"].format(object_type, object_name)
|
||||
main_attr_type = purpose_rule["main_attr"].format(object_type, object_name, object_qualified_name)
|
||||
main_attr_name = purpose_rule["attr_name"]
|
||||
|
||||
# Динамический список несёт MainTable, остальные типы — SavedData по записи таблицы.
|
||||
tail_lines = ''
|
||||
if main_attr_type == "DynamicList":
|
||||
main_table = f"{object_type}.{object_name}"
|
||||
main_table = object_ref
|
||||
tail_lines = ('\t\t\t<Settings xsi:type="DynamicList">\n'
|
||||
f'\t\t\t\t<MainTable>{main_table}</MainTable>\n'
|
||||
'\t\t\t</Settings>\n')
|
||||
@@ -857,7 +906,7 @@ def main():
|
||||
# --- SetDefault ---
|
||||
|
||||
is_first_form_for_purpose = False
|
||||
default_value = f"{object_type}.{object_name}.Form.{form_name}"
|
||||
default_value = f"{object_ref}.Form.{form_name}"
|
||||
|
||||
# Свойство «основная форма» — из записи таблицы. Раньше выбиралось по одному purpose без
|
||||
# учёта вида, и для журнала писалось DefaultListForm, которого у журнала нет: слот не
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.18 — Validate 1C managed form
|
||||
# form-validate v1.19 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -928,6 +928,8 @@ $validCfgPrefixes = @(
|
||||
"ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef"
|
||||
"DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef"
|
||||
"ExternalDataProcessorObject","ExternalReportObject"
|
||||
"ExternalDataSourceTableObject","ExternalDataSourceTableRecordManager"
|
||||
"ExternalDataSourceTableRef"
|
||||
"InformationRegisterRecordManager","InformationRegisterRecordSet"
|
||||
"ReportObject","TaskObject","TaskRef"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.18 — Validate 1C managed form
|
||||
# form-validate v1.19 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -66,6 +66,8 @@ VALID_CFG_PREFIXES = {
|
||||
'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef',
|
||||
'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef',
|
||||
'ExternalDataProcessorObject', 'ExternalReportObject',
|
||||
'ExternalDataSourceTableObject', 'ExternalDataSourceTableRecordManager',
|
||||
'ExternalDataSourceTableRef',
|
||||
'InformationRegisterRecordManager', 'InformationRegisterRecordSet',
|
||||
'ReportObject', 'TaskObject', 'TaskRef',
|
||||
}
|
||||
|
||||
@@ -143,4 +143,5 @@ uuid, а таблицы, которых нет в описании, остану
|
||||
## Не поддерживается
|
||||
|
||||
- **Кубы OLAP** (`Cube`, `DimensionTable`, `Dimension`, `Resource`).
|
||||
- **Формы и модули** таблиц — формы добавляют `form-add` и `form-compile`.
|
||||
- **Формы и модули** таблиц — форму добавляет навык `form-add`, содержимое собирает `form-compile`.
|
||||
Набор назначений зависит от `tableDataType`: `ObjectData` — Object/List/Choice, `NonobjectData` — Record/List/Choice.
|
||||
|
||||
Reference in New Issue
Block a user