mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-15 07:15:17 +03:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fa279c354 | |||
| 28a2a34c84 | |||
| f0f1e88aaa | |||
| e7cbf306a0 | |||
| 610720334b | |||
| 5090deb5bc | |||
| 8b0bcf0194 | |||
| 529a5cacae | |||
| 8b0f55f1cc | |||
| 54cbc69a59 | |||
| ac3047cf55 | |||
| 5da154adea | |||
| f4748d76af | |||
| b992cd11c5 | |||
| fc76407877 | |||
| a55195ab66 | |||
| 1eff62de42 | |||
| eb87be5c04 | |||
| 43ed9ba142 | |||
| 588382cec1 | |||
| e0197683e1 | |||
| 96dad75b2f | |||
| 5c734202b6 | |||
| a92bce05fb | |||
| b8ebbf6a6f | |||
| 43ba6ce16c | |||
| 51e37f9874 | |||
| 62e864e474 | |||
| ddebd7b6df | |||
| 3d16e35e80 | |||
| 56822c4533 | |||
| 32bf9c1a3f | |||
| c94f86a9cd | |||
| 8b5fed98e0 | |||
| 9e677cfc61 | |||
| 211a4726d6 | |||
| 91b39b758b | |||
| 4af69f1600 | |||
| 8c7c442705 | |||
| c541d51f33 | |||
| a650325baf | |||
| 6c19846051 | |||
| eef4f4bcea | |||
| 2c553fee98 | |||
| 95e4674825 | |||
| 9751840cc8 | |||
| f257bb428c | |||
| 71e3691cf1 | |||
| 1af318325d | |||
| 986480748e | |||
| 71309d2bc2 | |||
| e1f81a0cde | |||
| e8cb5440d8 | |||
| c496047c6c | |||
| df9541470c | |||
| 7561faf736 | |||
| 2849087fd9 | |||
| 105171cdc2 | |||
| c9cd0d62ab | |||
| c1a0a54971 | |||
| 927c0827f3 | |||
| 56cd18a6b4 | |||
| 3ac1d425cd | |||
| 3c596f4550 | |||
| 36d29a51a9 | |||
| 11e961c816 | |||
| 05ca810461 | |||
| a0407b74dc | |||
| 3aad254399 | |||
| 07753921be | |||
| ba0c71fa45 | |||
| 33c9fdade0 | |||
| 1c1fe7b2d9 | |||
| 0bd2587e74 | |||
| 6f17b1c2f6 | |||
| 36ad686316 | |||
| 66e37fb8cc | |||
| 99c77e1dde | |||
| 8d6612027f | |||
| c3b67a18cb | |||
| 4d1b66638c | |||
| 363a9f34f2 | |||
| 4f8ce7b747 | |||
| fc48d68ed1 | |||
| 3e34ec0bdd | |||
| fff2e83960 | |||
| 1ff209849f | |||
| 1a8415283e | |||
| db1e78a534 | |||
| a828f1847f | |||
| 3e8159b591 | |||
| 57bb964c1e | |||
| 41c4b6b1f7 | |||
| ffb0ee740d | |||
| f5e487096f | |||
| 6d5c1a0b19 | |||
| b322c02fdb | |||
| 61ef7ac891 | |||
| ba19b4111d | |||
| ded11437c5 | |||
| 5eda7f8eb3 | |||
| f39a0d9c5e | |||
| 2347859bdd |
@@ -14,10 +14,7 @@
|
||||
"policy": {
|
||||
"installation": "AVAILABLE"
|
||||
},
|
||||
"category": "Development",
|
||||
"interface": {
|
||||
"shortDescription": "PowerShell runtime (Windows-first)"
|
||||
}
|
||||
"category": "Development"
|
||||
},
|
||||
{
|
||||
"name": "1c-skills-py",
|
||||
@@ -29,10 +26,7 @@
|
||||
"policy": {
|
||||
"installation": "AVAILABLE"
|
||||
},
|
||||
"category": "Development",
|
||||
"interface": {
|
||||
"shortDescription": "Python runtime (Linux/Mac/Windows)"
|
||||
}
|
||||
"category": "Development"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "1c-skills",
|
||||
"source": "./",
|
||||
"description": "[PowerShell] Полный цикл разработки на платформе 1С:Предприятие 8.3 — от создания конфигураций, расширений, внешних обработок и отчётов до загрузки изменений в информационную базу, обновления, запуска, публикации на веб-сервере, тестирования через веб-клиент и записи видеоинструкций."
|
||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент."
|
||||
},
|
||||
{
|
||||
"name": "1c-skills-py",
|
||||
@@ -18,7 +18,7 @@
|
||||
"repo": "Nikolay-Shirokov/cc-1c-skills",
|
||||
"ref": "port-claude-code-py"
|
||||
},
|
||||
"description": "[Python] Полный цикл разработки на платформе 1С:Предприятие 8.3 — Linux/Mac или когда PowerShell недоступен."
|
||||
"description": "[Python] То же — для Linux/Mac или когда PowerShell недоступен."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
||||
"name": "1c-skills",
|
||||
"description": "[PowerShell] Полный цикл разработки на платформе 1С:Предприятие 8.3 — от создания конфигураций, расширений, внешних обработок и отчётов до загрузки изменений в информационную базу, обновления, запуска, публикации на веб-сервере, тестирования через веб-клиент и записи видеоинструкций.",
|
||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
||||
"author": {
|
||||
"name": "Nikolay Shirokov"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# epf-init v1.0 — Init 1C external data processor scaffold
|
||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -10,6 +10,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-init v1.0 — Init 1C external data processor scaffold
|
||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external data processor."""
|
||||
import sys, os, argparse, uuid
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# erf-init v1.0 — Init 1C external report scaffold
|
||||
# erf-init v1.1 — Init 1C external report scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# erf-init v1.0 — Init 1C external report scaffold
|
||||
# erf-init v1.1 — Init 1C external report scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external report."""
|
||||
import sys, os, argparse, uuid
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-add v1.4 — Add managed form to 1C config object
|
||||
# form-add v1.5 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -15,6 +15,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Detect XML format version ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-add v1.4 — Add managed form to 1C config object
|
||||
# form-add v1.5 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.20 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.21 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -1912,6 +1912,7 @@ function Emit-Element {
|
||||
# input-specific
|
||||
"multiLine"=1;"passwordMode"=1;"choiceButton"=1;"clearButton"=1
|
||||
"spinButton"=1;"dropListButton"=1;"markIncomplete"=1;"skipOnInput"=1;"inputHint"=1
|
||||
"textEdit"=1
|
||||
# label/hyperlink
|
||||
"hyperlink"=1
|
||||
# group-specific
|
||||
@@ -2137,6 +2138,7 @@ function Emit-Input {
|
||||
if ($el.spinButton -eq $true) { X "$inner<SpinButton>true</SpinButton>" }
|
||||
if ($el.dropListButton -eq $true) { X "$inner<DropListButton>true</DropListButton>" }
|
||||
if ($el.markIncomplete -eq $true) { X "$inner<AutoMarkIncomplete>true</AutoMarkIncomplete>" }
|
||||
if ($el.textEdit -eq $false) { X "$inner<TextEdit>false</TextEdit>" }
|
||||
if ($el.skipOnInput -eq $true) { X "$inner<SkipOnInput>true</SkipOnInput>" }
|
||||
$hasAmw = $el.PSObject.Properties.Name -contains 'autoMaxWidth'
|
||||
if ($hasAmw) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.20 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.21 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -1350,6 +1350,7 @@ KNOWN_KEYS = {
|
||||
"maxWidth", "maxHeight",
|
||||
"multiLine", "passwordMode", "choiceButton", "clearButton",
|
||||
"spinButton", "dropListButton", "markIncomplete", "skipOnInput", "inputHint",
|
||||
"textEdit",
|
||||
"hyperlink",
|
||||
"showTitle", "united", "collapsed",
|
||||
"children", "columns",
|
||||
@@ -1940,6 +1941,8 @@ def emit_input(lines, el, name, eid, indent):
|
||||
lines.append(f'{inner}<DropListButton>true</DropListButton>')
|
||||
if el.get('markIncomplete') is True:
|
||||
lines.append(f'{inner}<AutoMarkIncomplete>true</AutoMarkIncomplete>')
|
||||
if el.get('textEdit') is False:
|
||||
lines.append(f'{inner}<TextEdit>false</TextEdit>')
|
||||
if el.get('skipOnInput') is True:
|
||||
lines.append(f'{inner}<SkipOnInput>true</SkipOnInput>')
|
||||
if 'autoMaxWidth' in el:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-remove v1.1 — Remove form from 1C object
|
||||
# form-remove v1.2 — Remove form from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# remove-form v1.0 — Remove form from 1C object
|
||||
# remove-form v1.1 — Remove form from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# form-validate v1.6 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -366,13 +366,51 @@ if (-not $stopped) {
|
||||
$dataPath = $dpNode.InnerText.Trim()
|
||||
if (-not $dataPath) { continue }
|
||||
|
||||
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if ($dataPath -match '^\d+$' -or $dataPath -match '^\d+/\d+:[0-9a-fA-F-]+$') {
|
||||
continue
|
||||
}
|
||||
|
||||
$pathChecked++
|
||||
|
||||
# Extract root segment of path, strip array indices like [0]
|
||||
$cleanPath = $dataPath -replace '\[\d+\]', ''
|
||||
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
|
||||
if ($cleanPath.StartsWith('~')) { $cleanPath = $cleanPath.Substring(1) }
|
||||
$segments = $cleanPath -split '\.'
|
||||
$rootAttr = $segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if ($rootAttr -eq 'Items') {
|
||||
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
|
||||
Report-Warn "[$tag] '$elName': DataPath='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
|
||||
continue
|
||||
}
|
||||
$tableName = $segments[1]
|
||||
$tableEl = $null
|
||||
foreach ($candidate in $allElements) {
|
||||
if ($candidate.Tag -eq 'Table' -and $candidate.Name -eq $tableName) {
|
||||
$tableEl = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $tableEl) {
|
||||
Report-Error "[$tag] '$elName': DataPath='$dataPath' — table element '$tableName' not found"
|
||||
$pathErrors++
|
||||
continue
|
||||
}
|
||||
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
|
||||
# Table without DataPath — can't resolve further, accept silently
|
||||
continue
|
||||
}
|
||||
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
|
||||
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
|
||||
$rootAttr = ($tableDp -split '\.')[0]
|
||||
}
|
||||
|
||||
if (-not $attrMap.ContainsKey($rootAttr)) {
|
||||
Report-Error "[$tag] '$elName': DataPath='$dataPath' — attribute '$rootAttr' not found"
|
||||
$pathErrors++
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# form-validate v1.6 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -376,12 +376,44 @@ def main():
|
||||
if not data_path:
|
||||
continue
|
||||
|
||||
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if re.match(r'^\d+$', data_path) or re.match(r'^\d+/\d+:[0-9a-fA-F-]+$', data_path):
|
||||
continue
|
||||
|
||||
path_checked += 1
|
||||
|
||||
clean_path = re.sub(r'\[\d+\]', '', data_path)
|
||||
# Strip leading '~' (current row of DynamicList: ~\u0421\u043f\u0438\u0441\u043e\u043a.\u041f\u043e\u043b\u0435)
|
||||
if clean_path.startswith('~'):
|
||||
clean_path = clean_path[1:]
|
||||
segments = clean_path.split(".")
|
||||
root_attr = segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... \u2014 table element, not attribute
|
||||
if root_attr == 'Items':
|
||||
if len(segments) < 3 or segments[2] != 'CurrentData':
|
||||
report_warn(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 unknown Items.* shape, expected Items.<Table>.CurrentData.*")
|
||||
continue
|
||||
table_name = segments[1]
|
||||
table_el = None
|
||||
for candidate in all_elements:
|
||||
if candidate["Tag"] == 'Table' and candidate["Name"] == table_name:
|
||||
table_el = candidate
|
||||
break
|
||||
if table_el is None:
|
||||
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 table element '{table_name}' not found")
|
||||
path_errors += 1
|
||||
continue
|
||||
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
|
||||
if table_dp_node is None or not (table_dp_node.text or "").strip():
|
||||
continue
|
||||
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
|
||||
if table_dp.startswith('~'):
|
||||
table_dp = table_dp[1:]
|
||||
root_attr = table_dp.split(".")[0]
|
||||
|
||||
if root_attr not in attr_map:
|
||||
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 attribute '{root_attr}' not found")
|
||||
path_errors += 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# help-add v1.3 — Add built-in help to 1C object
|
||||
# help-add v1.4 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -10,6 +10,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-help v1.3 — Add built-in help to 1C object
|
||||
# add-help v1.4 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.11 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.12 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -502,6 +502,7 @@ function Parse-AttributeShorthand {
|
||||
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
|
||||
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
|
||||
multiLine = if ($val.multiLine -eq $true) { $true } else { $false }
|
||||
choiceHistoryOnInput = if ($val.choiceHistoryOnInput) { "$($val.choiceHistoryOnInput)" } else { "" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,7 +823,8 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<CreateOnInput>Auto</CreateOnInput>"
|
||||
X "$indent`t`t<ChoiceForm/>"
|
||||
X "$indent`t`t<LinkByType/>"
|
||||
X "$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>"
|
||||
$chi = if ($parsed.choiceHistoryOnInput) { $parsed.choiceHistoryOnInput } else { "Auto" }
|
||||
X "$indent`t`t<ChoiceHistoryOnInput>$chi</ChoiceHistoryOnInput>"
|
||||
|
||||
# Use — only for catalog top-level attributes
|
||||
if ($context -eq "catalog") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.11 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.12 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -465,6 +465,7 @@ def parse_attribute_shorthand(val):
|
||||
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
|
||||
'indexing': str(val['indexing']) if val.get('indexing') else '',
|
||||
'multiLine': True if val.get('multiLine') is True else False,
|
||||
'choiceHistoryOnInput': str(val['choiceHistoryOnInput']) if val.get('choiceHistoryOnInput') else '',
|
||||
}
|
||||
|
||||
def parse_enum_value_shorthand(val):
|
||||
@@ -774,7 +775,8 @@ def emit_attribute(indent, parsed, context):
|
||||
X(f'{indent}\t\t<CreateOnInput>Auto</CreateOnInput>')
|
||||
X(f'{indent}\t\t<ChoiceForm/>')
|
||||
X(f'{indent}\t\t<LinkByType/>')
|
||||
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
|
||||
chi = parsed.get('choiceHistoryOnInput') or 'Auto'
|
||||
X(f'{indent}\t\t<ChoiceHistoryOnInput>{chi}</ChoiceHistoryOnInput>')
|
||||
if context == 'catalog':
|
||||
X(f'{indent}\t\t<Use>ForItem</Use>')
|
||||
if context not in ('processor', 'processor-tabular'):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-compile v1.21 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.22 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -1130,6 +1130,8 @@ function Emit-ParamValue {
|
||||
X "$indent<value xsi:type=`"xs:decimal`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($type -match '^string') {
|
||||
X "$indent<value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($type -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.') {
|
||||
X "$indent<value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml $valStr)</value>"
|
||||
} else {
|
||||
# Guess from value
|
||||
if ($valStr -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-compile v1.21 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.22 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -811,6 +811,8 @@ def emit_param_value(lines, type_str, val, indent):
|
||||
lines.append(f'{indent}<value xsi:type="xs:decimal">{esc_xml(val_str)}</value>')
|
||||
elif type_str and re.match(r'^string', type_str):
|
||||
lines.append(f'{indent}<value xsi:type="xs:string">{esc_xml(val_str)}</value>')
|
||||
elif type_str and re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.', type_str):
|
||||
lines.append(f'{indent}<value xsi:type="dcscor:DesignTimeValue">{esc_xml(val_str)}</value>')
|
||||
else:
|
||||
# Guess from value
|
||||
if re.match(r'^\d{4}-\d{2}-\d{2}T', val_str):
|
||||
|
||||
@@ -54,9 +54,12 @@ Shorthand: `"Имя [Заголовок]: тип @роль #ограничени
|
||||
|
||||
### add-total — добавить итог
|
||||
|
||||
Shorthand: `"<dataPath>: <выражение>"`. Если выражение — известная аггрегатная функция без скобок (`Сумма`, `Количество`, `Минимум`, `Максимум`, `Среднее`), оно автоматически оборачивается в `Func(dataPath)`. Если функция со скобками или произвольное выражение — используется как есть.
|
||||
|
||||
```
|
||||
"Цена: Среднее"
|
||||
"Стоимость: Сумма(Кол * Цена)"
|
||||
"Цена: Среднее" # → Среднее(Цена)
|
||||
"Стоимость: Сумма(Кол * Цена)" # → как есть
|
||||
"Проверка: Проверка" # identity: выражение = Проверка
|
||||
```
|
||||
|
||||
### add-calculated-field — добавить вычисляемое поле
|
||||
@@ -80,24 +83,46 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noFilter
|
||||
"Организация: CatalogRef.Организации"
|
||||
```
|
||||
|
||||
Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет `<title>`.
|
||||
Shorthand: `"Имя [Заголовок]: тип = значение [availableValue=список] [@флаги]"`. `[Заголовок]` опциональный — добавляет `<title>`.
|
||||
|
||||
`@autoDates` генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра — для БСП-отчётов, чтобы получить пару полей «Начало/Конец» в панели быстрых настроек.
|
||||
Флаги:
|
||||
- `@autoDates` — генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра.
|
||||
- `@hidden` — скрывает параметр от пользовательских настроек (для параметров-констант, используемых в запросе).
|
||||
- `@always` — параметр всегда подставляется в запрос. Часто вместе с `@hidden`, но используется и отдельно (для видимых обязательных параметров типа отчётного периода).
|
||||
|
||||
```
|
||||
"ПС: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка @hidden"
|
||||
"Период: StandardPeriod = LastMonth @always"
|
||||
"ПСчет: ChartOfAccountsRef.Хозрасчетный = ПланСчетов.Хозрасчетный.X @hidden @always"
|
||||
"Округление: EnumRef.Округления = Окр1 availableValue=Перечисление.Округления.Окр1: руб., Перечисление.Округления.Окр1000: тыс."
|
||||
```
|
||||
|
||||
`availableValue=` задаёт начальный список допустимых значений. Формат списка: `v1[: p1], v2[: p2], ...` — элементы через `,`, представление после `:`. Если в значении или представлении встречается `,` или `:` — оборачивай в одинарные кавычки `'...'`:
|
||||
|
||||
```
|
||||
"Округление: ... = Окр1 availableValue=Окр1_00: 'руб., коп.', Окр1: руб."
|
||||
```
|
||||
|
||||
### modify-parameter — изменить существующий параметр
|
||||
|
||||
Находит параметр по имени, добавляет/обновляет свойства.
|
||||
Shorthand: `"ИмяПараметра [Заголовок] [ключ=значение]... [@флаги]"`. Находит параметр по имени, обновляет указанные свойства.
|
||||
|
||||
```
|
||||
"ПорядокОкругления use=Always"
|
||||
"ПорядокОкругления [Округление сумм] denyIncompleteValues=true"
|
||||
"ПериодОтчета [Отчетный период]" # только title
|
||||
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1 presentation=руб."
|
||||
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1: руб., Перечисление.Округления.Окр1000: тыс."
|
||||
"СчетПС value=ПланСчетов.Хозрасчетный.КассаПредприятия"
|
||||
"Контрагент @hidden @always"
|
||||
```
|
||||
|
||||
`[Заголовок]` опциональный — устанавливает или заменяет `<title>`. Можно вызывать без других kv-пар, чтобы только обновить title.
|
||||
|
||||
`availableValue=` добавляет один элемент списка допустимых значений (можно несколько через `;;`). Тип значения определяется автоматически (DesignTimeValue для ссылок).
|
||||
`availableValue=` **заменяет весь список** допустимых значений (старые удаляются). Формат и кавычки — те же, что в `add-parameter`.
|
||||
|
||||
`value=` заменяет значение параметра (тип значения подбирается автоматически по объявленному типу параметра).
|
||||
|
||||
Флаги `@hidden` / `@always` — те же, что и в `add-parameter`. Идемпотентны.
|
||||
|
||||
### rename-parameter — переименовать параметр
|
||||
|
||||
@@ -231,13 +256,17 @@ Value — имена ресурсов (как в полях/вычисляемы
|
||||
|
||||
### patch-query — точечная замена в тексте запроса
|
||||
|
||||
Shorthand: `"старое => новое"`. Заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`.
|
||||
Shorthand: `"старое => новое [@once]"`. По умолчанию заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`.
|
||||
|
||||
```
|
||||
"СубконтоДт1) В => СубконтоКт1) В"
|
||||
"ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ"
|
||||
"КАК ВТ_СтароеИмя => КАК ВТ_НовоеИмя @once"
|
||||
```
|
||||
|
||||
`@once` — упасть с ошибкой, если в запросе не **ровно одно** вхождение. Защищает от случайных замен в комментариях и однотипных идентификаторах.
|
||||
|
||||
Многострочные подстроки поддерживаются — переводы строк в `старое`/`новое` сравниваются буквально (включая отступы).
|
||||
|
||||
### set-outputParameter — установить параметр вывода
|
||||
|
||||
```
|
||||
@@ -249,16 +278,27 @@ Shorthand: `"старое => новое"`. Заменяет все вхожде
|
||||
|
||||
### set-structure — установить структуру варианта
|
||||
|
||||
Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — детальные записи. Заменяет всю структуру. Не поддерживает пакетный режим.
|
||||
Shorthand: `"Поле1 > Поле2 > details"`. `>` — вложенный уровень группировки, `,` — несколько полей в одном уровне, `details` — детальные записи. **Заменяет всю структуру полностью** (включая Selection/order/filter/conditionalAppearance каждой группы). Для точечной модификации полей группировки с сохранением настроек — используй `modify-structure`. Не поддерживает пакетный режим.
|
||||
|
||||
```
|
||||
"Организация > Номенклатура > details"
|
||||
"Валюта, НаименованиеБанка, ИНН"
|
||||
"details"
|
||||
"СчетМеждународногоУчета @name=ДанныеОтчета"
|
||||
```
|
||||
|
||||
`@name=Имя` — присваивает имя группировке (`<dcsset:name>`). Используется для привязки шаблонов через `groupName`.
|
||||
|
||||
### modify-structure — изменить поля группировки существующей группы
|
||||
|
||||
Тот же shorthand что и `set-structure`. Находит группу по `@name=`, заменяет только `<groupItems>` (поля группировки). Selection/order/filter/conditionalAppearance/outputParameters группы сохраняются. Без `@name=` — ошибка.
|
||||
|
||||
```
|
||||
"Валюта @name=ДанныеОтчета"
|
||||
"Валюта, НаименованиеБанка @name=ДанныеОтчета"
|
||||
"details @name=ДанныеОтчета"
|
||||
```
|
||||
|
||||
### modify-field — изменить существующее поле
|
||||
|
||||
Тот же shorthand что и `add-field`. Находит по dataPath, объединяет свойства (непустые переопределяют), сохраняет позицию.
|
||||
@@ -267,6 +307,23 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
|
||||
"Цена [Цена USD]: decimal(10,4) @dimension"
|
||||
```
|
||||
|
||||
### set-field-role — установить роль поля
|
||||
|
||||
Shorthand: `"<dataPath> [@флаги] [kv=значение]"`. **Полностью заменяет** роль поля. Если в значении только dataPath без флагов/kv — удаляет роль.
|
||||
|
||||
```
|
||||
"Сумма" # снять роль полностью
|
||||
"СуммаОстаток @balance" # простая балансовая роль
|
||||
"СуммаНач @balance balanceGroupName=Сумма balanceType=OpeningBalance" # с уточнением
|
||||
"Контрагент @dimension parentDimension=Группа"
|
||||
"Период @period" # роль периода
|
||||
```
|
||||
|
||||
Флаги: `@balance`, `@dimension`, `@account`, `@period`, `@required`, `@autoOrder`, `@ignoreNullValues`.
|
||||
KV: `balanceGroupName`, `balanceType` (OpeningBalance/ClosingBalance), `parentDimension`, `accountTypeExpression`, `orderType` (Asc/Desc), `expression`, `periodNumber`, `periodType`.
|
||||
|
||||
Поддерживает пакетный режим (`;;`).
|
||||
|
||||
### modify-filter — изменить существующий фильтр
|
||||
|
||||
Тот же shorthand что и `add-filter`. Находит по полю, обновляет оператор/значение/флаги. См. правило для `<use>` ниже.
|
||||
@@ -294,6 +351,7 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
|
||||
| `clear-selection` | `*` | Очищает все элементы selection |
|
||||
| `clear-order` | `*` | Очищает все элементы order |
|
||||
| `clear-filter` | `*` | Очищает все элементы filter |
|
||||
| `clear-conditionalAppearance` | `*` | Очищает все правила условного оформления |
|
||||
|
||||
## Верификация
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.11 — Atomic 1C DCS editor
|
||||
# skd-edit v1.18 — Atomic 1C DCS editor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -11,9 +11,9 @@ param(
|
||||
"add-dataParameter","add-order","add-selection","add-dataSetLink",
|
||||
"add-dataSet","add-variant","add-conditionalAppearance","add-drilldown",
|
||||
"set-query","patch-query","set-outputParameter","set-structure",
|
||||
"modify-field","modify-filter","modify-dataParameter","modify-parameter",
|
||||
"modify-field","modify-filter","modify-dataParameter","modify-parameter","modify-structure","set-field-role",
|
||||
"rename-parameter","reorder-parameters",
|
||||
"clear-selection","clear-order","clear-filter",
|
||||
"clear-selection","clear-order","clear-filter","clear-conditionalAppearance",
|
||||
"remove-field","remove-total","remove-calculated-field","remove-parameter","remove-filter")]
|
||||
[string]$Operation,
|
||||
|
||||
@@ -238,14 +238,25 @@ function Read-FieldProperties($fieldEl) {
|
||||
function Parse-TotalShorthand {
|
||||
param([string]$s)
|
||||
|
||||
# "DataPath: Func" or "DataPath: Func(expr)" or "DataPath: ИмяРесурса" (identity)
|
||||
$parts = $s -split ':', 2
|
||||
$dataPath = $parts[0].Trim()
|
||||
$funcPart = $parts[1].Trim()
|
||||
|
||||
# Known DCS aggregate functions (ru + en)
|
||||
$aggFuncs = @('Сумма','Количество','Минимум','Максимум','Среднее',
|
||||
'Sum','Count','Min','Max','Avg',
|
||||
'Minimum','Maximum','Average')
|
||||
|
||||
if ($funcPart -match '^\w+\(') {
|
||||
# Already has expression form: Func(expr)
|
||||
return @{ dataPath = $dataPath; expression = $funcPart }
|
||||
} else {
|
||||
} elseif ($funcPart -in $aggFuncs) {
|
||||
# Short: Func → Func(DataPath)
|
||||
return @{ dataPath = $dataPath; expression = "$funcPart($dataPath)" }
|
||||
} else {
|
||||
# Identity or custom expression — use as-is
|
||||
return @{ dataPath = $dataPath; expression = $funcPart }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,13 +308,29 @@ function Parse-CalcShorthand {
|
||||
function Parse-ParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null }
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null; hidden = $false; always = $false; availableValues = @() }
|
||||
|
||||
# Extract availableValue=... (must be before main parse — captures to end of string)
|
||||
if ($s -match '\s*availableValue=(.+)$') {
|
||||
$result.availableValues = Parse-AvailableValueList $Matches[1].Trim()
|
||||
$s = ($s -replace '\s*availableValue=.+$', '').Trim()
|
||||
}
|
||||
|
||||
if ($s -match '@autoDates') {
|
||||
$result.autoDates = $true
|
||||
$s = $s -replace '\s*@autoDates', ''
|
||||
}
|
||||
|
||||
if ($s -match '@hidden\b') {
|
||||
$result.hidden = $true
|
||||
$s = $s -replace '\s*@hidden\b', ''
|
||||
}
|
||||
|
||||
if ($s -match '@always\b') {
|
||||
$result.always = $true
|
||||
$s = $s -replace '\s*@always\b', ''
|
||||
}
|
||||
|
||||
# Extract optional [Title] (mirrors Parse-FieldShorthand)
|
||||
if ($s -match '\[([^\]]*)\]') {
|
||||
$result.title = $Matches[1].Trim()
|
||||
@@ -581,15 +608,17 @@ function Parse-StructureShorthand {
|
||||
$seg = $segments[$i].Trim()
|
||||
$group = @{ type = "group" }
|
||||
|
||||
if ($seg -match '@name=(.+)') {
|
||||
$group["name"] = $Matches[1].Trim()
|
||||
$seg = ($seg -replace '\s*@name=.+', '').Trim()
|
||||
if ($seg -match '@name=(?:"([^"]+)"|''([^'']+)''|(\S+))') {
|
||||
$rawName = if ($Matches[1]) { $Matches[1] } elseif ($Matches[2]) { $Matches[2] } else { $Matches[3] }
|
||||
$group["name"] = $rawName.Trim()
|
||||
$seg = ($seg -replace '\s*@name=(?:"[^"]+"|''[^'']+''|\S+)', '').Trim()
|
||||
}
|
||||
|
||||
if ($seg -match '^(?i)(details|детали)$') {
|
||||
$group["groupBy"] = @()
|
||||
} else {
|
||||
$group["groupBy"] = @($seg)
|
||||
$fields = @($seg -split '\s*,\s*' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
$group["groupBy"] = $fields
|
||||
}
|
||||
|
||||
if ($null -ne $innermost) {
|
||||
@@ -614,6 +643,75 @@ function Parse-OutputParamShorthand {
|
||||
return @{ key = $s.Trim(); value = "" }
|
||||
}
|
||||
|
||||
function Parse-AvailableValueList {
|
||||
# Returns array of @{ value=...; presentation=... } from comma-separated list.
|
||||
# Items can use 'single' or "double" quotes (stripped). Quoted spans preserve commas/colons.
|
||||
param([string]$s)
|
||||
|
||||
$result = @()
|
||||
if (-not $s) { return ,$result }
|
||||
|
||||
# Tokenize by ',' respecting quoted spans
|
||||
$items = @()
|
||||
$buf = New-Object System.Text.StringBuilder
|
||||
$inQuote = $null
|
||||
for ($i = 0; $i -lt $s.Length; $i++) {
|
||||
$ch = $s[$i]
|
||||
if ($inQuote) {
|
||||
[void]$buf.Append($ch)
|
||||
if ($ch -eq $inQuote) { $inQuote = $null }
|
||||
} elseif ($ch -eq "'" -or $ch -eq '"') {
|
||||
$inQuote = $ch
|
||||
[void]$buf.Append($ch)
|
||||
} elseif ($ch -eq ',') {
|
||||
$items += $buf.ToString()
|
||||
[void]$buf.Clear()
|
||||
} else {
|
||||
[void]$buf.Append($ch)
|
||||
}
|
||||
}
|
||||
if ($buf.Length -gt 0) { $items += $buf.ToString() }
|
||||
|
||||
# For each item: split into value[:presentation], strip quotes
|
||||
$stripQuotes = {
|
||||
param($t)
|
||||
$t = $t.Trim()
|
||||
if ($t.Length -ge 2 -and (($t[0] -eq "'" -and $t[-1] -eq "'") -or ($t[0] -eq '"' -and $t[-1] -eq '"'))) {
|
||||
return $t.Substring(1, $t.Length - 2)
|
||||
}
|
||||
return $t
|
||||
}
|
||||
|
||||
foreach ($raw in $items) {
|
||||
$item = $raw.Trim()
|
||||
if (-not $item) { continue }
|
||||
|
||||
# Find first ':' outside quotes
|
||||
$colonIdx = -1
|
||||
$q = $null
|
||||
for ($j = 0; $j -lt $item.Length; $j++) {
|
||||
$c = $item[$j]
|
||||
if ($q) {
|
||||
if ($c -eq $q) { $q = $null }
|
||||
} elseif ($c -eq "'" -or $c -eq '"') {
|
||||
$q = $c
|
||||
} elseif ($c -eq ':') {
|
||||
$colonIdx = $j; break
|
||||
}
|
||||
}
|
||||
|
||||
if ($colonIdx -ge 0) {
|
||||
$valPart = $item.Substring(0, $colonIdx)
|
||||
$presPart = $item.Substring($colonIdx + 1)
|
||||
$result += @{ value = (& $stripQuotes $valPart); presentation = (& $stripQuotes $presPart) }
|
||||
} else {
|
||||
$result += @{ value = (& $stripQuotes $item); presentation = "" }
|
||||
}
|
||||
}
|
||||
|
||||
return ,$result
|
||||
}
|
||||
|
||||
# --- 4. Build-* functions (XML fragment generators) ---
|
||||
|
||||
function Build-ValueTypeXml {
|
||||
@@ -804,6 +902,68 @@ function Build-CalcFieldFragment {
|
||||
return $lines -join "`r`n"
|
||||
}
|
||||
|
||||
function Build-ParamValueXml {
|
||||
# Returns array of XML lines for a <value xsi:type=...>...</value> element (or StandardPeriod block).
|
||||
# Selects xsi:type by declared type, then falls back to value pattern.
|
||||
param([string]$type, [string]$value, [string]$indent, [string]$tagName = "value", [string]$tagNs = "")
|
||||
|
||||
$i = $indent
|
||||
$valStr = "$value"
|
||||
$open = if ($tagNs) { "$tagNs`:$tagName" } else { $tagName }
|
||||
$lines = @()
|
||||
|
||||
if ($type -eq "StandardPeriod") {
|
||||
$lines += "$i<$open xsi:type=`"v8:StandardPeriod`">"
|
||||
$lines += "$i`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $valStr)</v8:variant>"
|
||||
$lines += "$i`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
$lines += "$i`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
$lines += "$i</$open>"
|
||||
return $lines
|
||||
}
|
||||
|
||||
$xsi = $null
|
||||
if ($type -match '^date') { $xsi = "xs:dateTime" }
|
||||
elseif ($type -eq "boolean") { $xsi = "xs:boolean" }
|
||||
elseif ($type -match '^decimal') { $xsi = "xs:decimal" }
|
||||
elseif ($type -match '^string') { $xsi = "xs:string" }
|
||||
elseif ($type -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.') {
|
||||
$xsi = "dcscor:DesignTimeValue"
|
||||
}
|
||||
else {
|
||||
# Type unknown or empty — guess from value
|
||||
if ($valStr -match '^\d{4}-\d{2}-\d{2}T') { $xsi = "xs:dateTime" }
|
||||
elseif ($valStr -eq "true" -or $valStr -eq "false") { $xsi = "xs:boolean" }
|
||||
elseif ($valStr -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or
|
||||
$valStr -match '^(Catalog|Document|Enum|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') {
|
||||
$xsi = "dcscor:DesignTimeValue"
|
||||
}
|
||||
else { $xsi = "xs:string" }
|
||||
}
|
||||
|
||||
$lines += "$i<$open xsi:type=`"$xsi`">$(Esc-Xml $valStr)</$open>"
|
||||
return $lines
|
||||
}
|
||||
|
||||
function Build-AvailableValueFragment {
|
||||
# Returns XML lines (array) for a single <availableValue> block.
|
||||
param($item, [string]$declaredType, [string]$indent)
|
||||
|
||||
$lines = @()
|
||||
$lines += "$indent<availableValue>"
|
||||
$valueLines = Build-ParamValueXml -type $declaredType -value $item.value -indent "$indent`t"
|
||||
foreach ($vl in $valueLines) { $lines += $vl }
|
||||
if ($item.presentation) {
|
||||
$lines += "$indent`t<presentation xsi:type=`"v8:LocalStringType`">"
|
||||
$lines += "$indent`t`t<v8:item>"
|
||||
$lines += "$indent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
$lines += "$indent`t`t`t<v8:content>$(Esc-Xml $item.presentation)</v8:content>"
|
||||
$lines += "$indent`t`t</v8:item>"
|
||||
$lines += "$indent`t</presentation>"
|
||||
}
|
||||
$lines += "$indent</availableValue>"
|
||||
return $lines
|
||||
}
|
||||
|
||||
function Build-ParamFragment {
|
||||
param($parsed, [string]$indent)
|
||||
|
||||
@@ -825,24 +985,26 @@ function Build-ParamFragment {
|
||||
}
|
||||
|
||||
if ($null -ne $parsed.value) {
|
||||
$valStr = "$($parsed.value)"
|
||||
if ($parsed.type -eq "StandardPeriod") {
|
||||
$lines += "$i`t<value xsi:type=`"v8:StandardPeriod`">"
|
||||
$lines += "$i`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $valStr)</v8:variant>"
|
||||
$lines += "$i`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
$lines += "$i`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
$lines += "$i`t</value>"
|
||||
} elseif ($parsed.type -match '^date') {
|
||||
$lines += "$i`t<value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($parsed.type -eq "boolean") {
|
||||
$lines += "$i`t<value xsi:type=`"xs:boolean`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($parsed.type -match '^decimal') {
|
||||
$lines += "$i`t<value xsi:type=`"xs:decimal`">$(Esc-Xml $valStr)</value>"
|
||||
} else {
|
||||
$lines += "$i`t<value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</value>"
|
||||
$valueLines = Build-ParamValueXml -type $parsed.type -value $parsed.value -indent "$i`t"
|
||||
foreach ($vl in $valueLines) { $lines += $vl }
|
||||
}
|
||||
|
||||
if ($parsed.hidden) {
|
||||
$lines += "$i`t<useRestriction>true</useRestriction>"
|
||||
$lines += "$i`t<availableAsField>false</availableAsField>"
|
||||
}
|
||||
|
||||
if ($parsed.availableValues -and $parsed.availableValues.Count -gt 0) {
|
||||
foreach ($av in $parsed.availableValues) {
|
||||
$avLines = Build-AvailableValueFragment -item $av -declaredType $parsed.type -indent "$i`t"
|
||||
foreach ($l in $avLines) { $lines += $l }
|
||||
}
|
||||
}
|
||||
|
||||
if ($parsed.always) {
|
||||
$lines += "$i`t<use>Always</use>"
|
||||
}
|
||||
|
||||
$lines += "$i</parameter>"
|
||||
$fragments += ($lines -join "`r`n")
|
||||
|
||||
@@ -1565,10 +1727,10 @@ $corNs = "http://v8.1c.ru/8.1/data-composition-system/core"
|
||||
|
||||
# --- 7. Batch value splitting ---
|
||||
|
||||
if ($Operation -eq "set-query" -or $Operation -eq "set-structure" -or $Operation -eq "add-dataSet") {
|
||||
if ($Operation -eq "set-query" -or $Operation -eq "set-structure" -or $Operation -eq "modify-structure" -or $Operation -eq "add-dataSet") {
|
||||
$values = @($Value)
|
||||
} elseif ($Operation -eq "patch-query") {
|
||||
$values = @($Value -split ';;' | Where-Object { $_.Trim() })
|
||||
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
} elseif ($Operation -eq "add-drilldown") {
|
||||
if ($Value.Contains(';;')) {
|
||||
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
@@ -1766,6 +1928,12 @@ switch ($Operation) {
|
||||
$paramName = $parts[0].Trim()
|
||||
$rest = if ($parts.Count -gt 1) { $parts[1].Trim() } else { "" }
|
||||
|
||||
# Extract @hidden / @always flags
|
||||
$flagHidden = $false
|
||||
$flagAlways = $false
|
||||
if ($rest -match '@hidden\b') { $flagHidden = $true; $rest = ($rest -replace '\s*@hidden\b', '').Trim() }
|
||||
if ($rest -match '@always\b') { $flagAlways = $true; $rest = ($rest -replace '\s*@always\b', '').Trim() }
|
||||
|
||||
# Find parameter element
|
||||
$paramEl = Find-ElementByChildValue $xmlDoc.DocumentElement "parameter" "name" $paramName $schNs
|
||||
if (-not $paramEl) {
|
||||
@@ -1810,15 +1978,63 @@ switch ($Operation) {
|
||||
$avPart = $rest.Substring($avIdx)
|
||||
}
|
||||
|
||||
# Process simple key=value pairs (use, denyIncompleteValues, etc.)
|
||||
# Process simple key=value pairs (use, denyIncompleteValues, value, etc.)
|
||||
if ($simpleRest) {
|
||||
$kvPairs = [regex]::Matches($simpleRest, '(\w+)=(\S+)')
|
||||
foreach ($kv in $kvPairs) {
|
||||
$key = $kv.Groups[1].Value
|
||||
$value = $kv.Groups[2].Value
|
||||
|
||||
$existing = $paramEl.SelectSingleNode($key)
|
||||
if ($existing) {
|
||||
# Namespace-aware lookup (children live in $schNs)
|
||||
$existing = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq $key -and $ch.NamespaceURI -eq $schNs) {
|
||||
$existing = $ch; break
|
||||
}
|
||||
}
|
||||
|
||||
if ($key -eq "value") {
|
||||
# Special-case: rebuild <value> with correct xsi:type from <valueType>
|
||||
$declaredType = ""
|
||||
$vtEl = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'valueType' -and $ch.NamespaceURI -eq $schNs) { $vtEl = $ch; break }
|
||||
}
|
||||
if ($vtEl) {
|
||||
foreach ($tnode in $vtEl.ChildNodes) {
|
||||
if ($tnode.NodeType -eq 'Element' -and $tnode.LocalName -eq 'Type') {
|
||||
$declaredType = $tnode.InnerText.Trim() -replace '^d\d+p\d+:', ''
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
$valueLines = Build-ParamValueXml -type $declaredType -value $value -indent $childIndent
|
||||
$fragXml = $valueLines -join "`r`n"
|
||||
|
||||
$wasExisting = ($null -ne $existing)
|
||||
if ($existing) {
|
||||
# Capture position by next-element sibling, then remove existing
|
||||
$refNode = $existing.NextSibling
|
||||
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
|
||||
$refNode = $refNode.NextSibling
|
||||
}
|
||||
Remove-NodeWithWhitespace $existing
|
||||
} else {
|
||||
# Insert before useRestriction/availableValue/denyIncompleteValues/use
|
||||
$refNode = $null
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('useRestriction','availableValue','denyIncompleteValues','use')) {
|
||||
$refNode = $child; break
|
||||
}
|
||||
}
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $paramEl $node $refNode $childIndent
|
||||
}
|
||||
$verb = if ($wasExisting) { "updated" } else { "added" }
|
||||
Write-Host "[OK] Parameter `"$paramName`": value $verb to $value"
|
||||
} elseif ($existing) {
|
||||
$existing.InnerText = $value
|
||||
Write-Host "[OK] Parameter `"$paramName`": $key updated to $value"
|
||||
} else {
|
||||
@@ -1841,46 +2057,102 @@ switch ($Operation) {
|
||||
}
|
||||
}
|
||||
|
||||
# Process availableValue
|
||||
# Process availableValue — replace whole list with new items
|
||||
if ($avPart) {
|
||||
$avRest = $avPart -replace '^availableValue=', ''
|
||||
# Parse: "Перечисление...X presentation=текст с пробелами"
|
||||
$avParts = $avRest -split '\s+presentation=', 2
|
||||
$avValue = $avParts[0].Trim()
|
||||
$avPresentation = if ($avParts.Count -gt 1) { $avParts[1].Trim() } else { "" }
|
||||
$avRest = ($avPart -replace '^availableValue=', '').Trim()
|
||||
$avItems = Parse-AvailableValueList $avRest
|
||||
|
||||
# Detect value type
|
||||
$avType = "xs:string"
|
||||
if ($avValue -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$avType = "dcscor:DesignTimeValue"
|
||||
# Detect value type: prefer declared <valueType> of the parameter, else guess from value
|
||||
$declaredType = ""
|
||||
$vtEl = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'valueType' -and $ch.NamespaceURI -eq $schNs) { $vtEl = $ch; break }
|
||||
}
|
||||
if ($vtEl) {
|
||||
foreach ($tnode in $vtEl.ChildNodes) {
|
||||
if ($tnode.NodeType -eq 'Element' -and $tnode.LocalName -eq 'Type') {
|
||||
$declaredType = $tnode.InnerText.Trim() -replace '^d\d+p\d+:', ''
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$avLines = @()
|
||||
$avLines += "$childIndent<availableValue>"
|
||||
$avLines += "$childIndent`t<value xsi:type=`"$avType`">$(Esc-Xml $avValue)</value>"
|
||||
if ($avPresentation) {
|
||||
$avLines += "$childIndent`t<presentation xsi:type=`"v8:LocalStringType`">"
|
||||
$avLines += "$childIndent`t`t<v8:item>"
|
||||
$avLines += "$childIndent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
$avLines += "$childIndent`t`t`t<v8:content>$(Esc-Xml $avPresentation)</v8:content>"
|
||||
$avLines += "$childIndent`t`t</v8:item>"
|
||||
$avLines += "$childIndent`t</presentation>"
|
||||
# Remove all existing <availableValue> elements
|
||||
$toRemove = @()
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'availableValue' -and $ch.NamespaceURI -eq $schNs) {
|
||||
$toRemove += $ch
|
||||
}
|
||||
}
|
||||
$avLines += "$childIndent</availableValue>"
|
||||
$fragXml = $avLines -join "`r`n"
|
||||
foreach ($el in $toRemove) { Remove-NodeWithWhitespace $el }
|
||||
|
||||
# Insert before first of (denyIncompleteValues, use) in document order
|
||||
# Insert each new <availableValue> before (denyIncompleteValues, use)
|
||||
$refNode = $null
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and ($child.LocalName -eq 'denyIncompleteValues' -or $child.LocalName -eq 'use')) {
|
||||
$refNode = $child; break
|
||||
}
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $paramEl $node $refNode $childIndent
|
||||
foreach ($av in $avItems) {
|
||||
$avLines = Build-AvailableValueFragment -item $av -declaredType $declaredType -indent $childIndent
|
||||
$fragXml = $avLines -join "`r`n"
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $paramEl $node $refNode $childIndent
|
||||
}
|
||||
}
|
||||
Write-Host "[OK] Parameter `"$paramName`": availableValue added"
|
||||
Write-Host "[OK] Parameter `"$paramName`": availableValue set to $($avItems.Count) item(s)"
|
||||
}
|
||||
|
||||
# Process @hidden / @always flags (idempotent)
|
||||
if ($flagHidden) {
|
||||
# useRestriction → true (insert after <value>, before <expression>/<availableAsField>/...)
|
||||
$urEl = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'useRestriction' -and $ch.NamespaceURI -eq $schNs) { $urEl = $ch; break }
|
||||
}
|
||||
if ($urEl) {
|
||||
if ($urEl.InnerText.Trim() -ne 'true') { $urEl.InnerText = 'true' }
|
||||
} else {
|
||||
$refNode = $null
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('expression','availableAsField','availableValue','denyIncompleteValues','use')) { $refNode = $child; break }
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc "$childIndent<useRestriction>true</useRestriction>"
|
||||
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refNode $childIndent }
|
||||
}
|
||||
|
||||
# availableAsField → false (insert after <expression>, before <availableValue>/<denyIncompleteValues>/<use>)
|
||||
$afEl = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'availableAsField' -and $ch.NamespaceURI -eq $schNs) { $afEl = $ch; break }
|
||||
}
|
||||
if ($afEl) {
|
||||
if ($afEl.InnerText.Trim() -ne 'false') { $afEl.InnerText = 'false' }
|
||||
} else {
|
||||
$refNode = $null
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('availableValue','denyIncompleteValues','use')) { $refNode = $child; break }
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc "$childIndent<availableAsField>false</availableAsField>"
|
||||
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refNode $childIndent }
|
||||
}
|
||||
|
||||
Write-Host "[OK] Parameter `"$paramName`": @hidden applied"
|
||||
}
|
||||
|
||||
if ($flagAlways) {
|
||||
$useEl = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $schNs) { $useEl = $ch; break }
|
||||
}
|
||||
if ($useEl) {
|
||||
if ($useEl.InnerText.Trim() -ne 'Always') { $useEl.InnerText = 'Always' }
|
||||
} else {
|
||||
$nodes = Import-Fragment $xmlDoc "$childIndent<use>Always</use>"
|
||||
foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $null $childIndent }
|
||||
}
|
||||
Write-Host "[OK] Parameter `"$paramName`": @always applied"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2211,6 +2483,12 @@ switch ($Operation) {
|
||||
}
|
||||
|
||||
foreach ($val in $values) {
|
||||
$once = $false
|
||||
if ($val -match '@once\b') {
|
||||
$once = $true
|
||||
$val = ($val -replace '\s*@once\b', '').Trim()
|
||||
}
|
||||
|
||||
$sepIdx = $val.IndexOf(" => ")
|
||||
if ($sepIdx -lt 0) {
|
||||
Write-Error "patch-query value must contain ' => ' separator: old => new"
|
||||
@@ -2219,12 +2497,20 @@ switch ($Operation) {
|
||||
$oldStr = $val.Substring(0, $sepIdx)
|
||||
$newStr = $val.Substring($sepIdx + 4)
|
||||
$queryText = $queryEl.InnerText
|
||||
if (-not $queryText.Contains($oldStr)) {
|
||||
|
||||
$count = ([regex]::Matches($queryText, [regex]::Escape($oldStr))).Count
|
||||
if ($count -eq 0) {
|
||||
Write-Error "Substring not found in query of dataset '$dsName': $oldStr"
|
||||
exit 1
|
||||
}
|
||||
if ($once -and $count -ne 1) {
|
||||
Write-Error "@once: expected 1 occurrence of '$oldStr' in dataset '$dsName', found $count"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$queryEl.InnerText = $queryText.Replace($oldStr, $newStr)
|
||||
Write-Host "[OK] Query patched in dataset `"$dsName`": replaced '$oldStr'"
|
||||
$suffix = if ($once) { " (1 occurrence)" } else { " ($count occurrence(s))" }
|
||||
Write-Host "[OK] Query patched in dataset `"$dsName`": replaced '$oldStr'$suffix"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2289,6 +2575,102 @@ switch ($Operation) {
|
||||
Write-Host "[OK] Structure set in variant `"$varName`": $Value"
|
||||
}
|
||||
|
||||
"modify-structure" {
|
||||
$settings = Resolve-VariantSettings
|
||||
$varName = Get-VariantName
|
||||
|
||||
$structItems = Parse-StructureShorthand $Value
|
||||
|
||||
# Flatten parsed tree into (name, groupBy) targets
|
||||
$targets = @()
|
||||
$stack = New-Object System.Collections.Stack
|
||||
foreach ($it in $structItems) { $stack.Push($it) }
|
||||
while ($stack.Count -gt 0) {
|
||||
$it = $stack.Pop()
|
||||
if ($it["name"]) {
|
||||
$targets += @{ name = $it["name"]; groupBy = $it["groupBy"] }
|
||||
}
|
||||
if ($it["children"]) {
|
||||
foreach ($ch in $it["children"]) { $stack.Push($ch) }
|
||||
}
|
||||
}
|
||||
|
||||
if ($targets.Count -eq 0) {
|
||||
Write-Error "modify-structure requires @name= for at least one group: $Value"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("dcsset", $setNs)
|
||||
$nsMgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
|
||||
foreach ($t in $targets) {
|
||||
$groupEl = $settings.SelectSingleNode(".//dcsset:item[@xsi:type='dcsset:StructureItemGroup'][dcsset:name='$($t.name)']", $nsMgr)
|
||||
if (-not $groupEl) {
|
||||
Write-Host "[WARN] Group with @name=`"$($t.name)`" not found — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
$giEl = $null
|
||||
foreach ($ch in $groupEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'groupItems' -and $ch.NamespaceURI -eq $setNs) {
|
||||
$giEl = $ch; break
|
||||
}
|
||||
}
|
||||
$groupIndent = Get-ChildIndent $groupEl
|
||||
if (-not $giEl) {
|
||||
# Create <groupItems> after <name>, before <order>/<selection>/...
|
||||
$nameEl = $null
|
||||
$refAfterName = $null
|
||||
foreach ($ch in $groupEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'name' -and $ch.NamespaceURI -eq $setNs) {
|
||||
$nameEl = $ch
|
||||
} elseif ($ch.NodeType -eq 'Element' -and $nameEl -and -not $refAfterName) {
|
||||
$refAfterName = $ch; break
|
||||
}
|
||||
}
|
||||
$giFrag = "$groupIndent<dcsset:groupItems></dcsset:groupItems>"
|
||||
$nodes = Import-Fragment $xmlDoc $giFrag
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $groupEl $node $refAfterName $groupIndent
|
||||
}
|
||||
# Re-find
|
||||
foreach ($ch in $groupEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'groupItems' -and $ch.NamespaceURI -eq $setNs) {
|
||||
$giEl = $ch; break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$toRemove = @()
|
||||
foreach ($ch in $giEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element') { $toRemove += $ch }
|
||||
}
|
||||
foreach ($el in $toRemove) { Remove-NodeWithWhitespace $el }
|
||||
|
||||
$itemIndent = "$groupIndent`t"
|
||||
|
||||
foreach ($field in $t.groupBy) {
|
||||
$lines = @()
|
||||
$lines += "$itemIndent<dcsset:item xsi:type=`"dcsset:GroupItemField`">"
|
||||
$lines += "$itemIndent`t<dcsset:field>$(Esc-Xml $field)</dcsset:field>"
|
||||
$lines += "$itemIndent`t<dcsset:groupType>Items</dcsset:groupType>"
|
||||
$lines += "$itemIndent`t<dcsset:periodAdditionType>None</dcsset:periodAdditionType>"
|
||||
$lines += "$itemIndent`t<dcsset:periodAdditionBegin xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</dcsset:periodAdditionBegin>"
|
||||
$lines += "$itemIndent`t<dcsset:periodAdditionEnd xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</dcsset:periodAdditionEnd>"
|
||||
$lines += "$itemIndent</dcsset:item>"
|
||||
$fragXml = $lines -join "`r`n"
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $giEl $node $null $itemIndent
|
||||
}
|
||||
}
|
||||
|
||||
$desc = if ($t.groupBy.Count -eq 0) { "details" } else { $t.groupBy -join ', ' }
|
||||
Write-Host "[OK] Group `"$($t.name)`" groupItems updated: $desc"
|
||||
}
|
||||
}
|
||||
|
||||
"add-dataSetLink" {
|
||||
foreach ($val in $values) {
|
||||
$parsed = Parse-DataSetLinkShorthand $val
|
||||
@@ -2476,6 +2858,18 @@ switch ($Operation) {
|
||||
}
|
||||
}
|
||||
|
||||
"clear-conditionalAppearance" {
|
||||
$settings = Resolve-VariantSettings
|
||||
$varName = Get-VariantName
|
||||
$caEl = Find-FirstElement $settings @("conditionalAppearance") $setNs
|
||||
if ($caEl) {
|
||||
Clear-ContainerChildren $caEl
|
||||
Write-Host "[OK] ConditionalAppearance cleared in variant `"$varName`""
|
||||
} else {
|
||||
Write-Host "[INFO] No conditionalAppearance section in variant `"$varName`""
|
||||
}
|
||||
}
|
||||
|
||||
"modify-filter" {
|
||||
$settings = Resolve-VariantSettings
|
||||
$varName = Get-VariantName
|
||||
@@ -2675,6 +3069,87 @@ switch ($Operation) {
|
||||
}
|
||||
}
|
||||
|
||||
"set-field-role" {
|
||||
$dsNode = Resolve-DataSet
|
||||
$dsName = Get-DataSetName $dsNode
|
||||
|
||||
foreach ($val in $values) {
|
||||
# Parse shorthand: "dataPath [@flag ...] [kv=value ...]"
|
||||
$s = $val.Trim()
|
||||
|
||||
# Extract @flags
|
||||
$flags = @()
|
||||
$flagMatches = [regex]::Matches($s, '@(\w+)')
|
||||
foreach ($m in $flagMatches) { $flags += $m.Groups[1].Value }
|
||||
$s = [regex]::Replace($s, '\s*@\w+', '').Trim()
|
||||
|
||||
# Extract kv=value (value is non-whitespace)
|
||||
$kv = [ordered]@{}
|
||||
$kvMatches = [regex]::Matches($s, '(\w+)=(\S+)')
|
||||
foreach ($m in $kvMatches) { $kv[$m.Groups[1].Value] = $m.Groups[2].Value }
|
||||
$s = [regex]::Replace($s, '\s*\w+=\S+', '').Trim()
|
||||
|
||||
$dataPath = $s
|
||||
if (-not $dataPath) {
|
||||
Write-Host "[WARN] set-field-role: empty dataPath in `"$val`""
|
||||
continue
|
||||
}
|
||||
|
||||
$fieldEl = Find-ElementByChildValue $dsNode "field" "dataPath" $dataPath $schNs
|
||||
if (-not $fieldEl) {
|
||||
Write-Host "[WARN] Field `"$dataPath`" not found in dataset `"$dsName`""
|
||||
continue
|
||||
}
|
||||
|
||||
$fieldIndent = Get-ChildIndent $fieldEl
|
||||
|
||||
# Remove existing <role>
|
||||
$oldRole = $null
|
||||
foreach ($ch in $fieldEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'role' -and $ch.NamespaceURI -eq $schNs) { $oldRole = $ch; break }
|
||||
}
|
||||
if ($oldRole) { Remove-NodeWithWhitespace $oldRole }
|
||||
|
||||
# Empty spec — remove only
|
||||
if ($flags.Count -eq 0 -and $kv.Count -eq 0) {
|
||||
Write-Host "[OK] Field `"$dataPath`" role cleared"
|
||||
continue
|
||||
}
|
||||
|
||||
# Build new <role>
|
||||
$lines = @()
|
||||
$lines += "$fieldIndent<role>"
|
||||
foreach ($flag in $flags) {
|
||||
if ($flag -eq 'period') {
|
||||
$lines += "$fieldIndent`t<dcscom:periodNumber>1</dcscom:periodNumber>"
|
||||
$lines += "$fieldIndent`t<dcscom:periodType>Main</dcscom:periodType>"
|
||||
} else {
|
||||
$lines += "$fieldIndent`t<dcscom:$flag>true</dcscom:$flag>"
|
||||
}
|
||||
}
|
||||
foreach ($k in $kv.Keys) {
|
||||
$lines += "$fieldIndent`t<dcscom:$k>$(Esc-Xml $kv[$k])</dcscom:$k>"
|
||||
}
|
||||
$lines += "$fieldIndent</role>"
|
||||
$fragXml = $lines -join "`r`n"
|
||||
|
||||
# Insert before <valueType>, else before <inputParameters>, else at end
|
||||
$refNode = $null
|
||||
foreach ($ch in $fieldEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -in @('valueType','inputParameters') -and $ch.NamespaceURI -eq $schNs) { $refNode = $ch; break }
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $fieldEl $node $refNode $fieldIndent
|
||||
}
|
||||
|
||||
$desc = @()
|
||||
if ($flags.Count -gt 0) { $desc += ($flags | ForEach-Object { "@$_" }) -join ' ' }
|
||||
if ($kv.Count -gt 0) { $desc += ($kv.Keys | ForEach-Object { "$_=$($kv[$_])" }) -join ' ' }
|
||||
Write-Host "[OK] Field `"$dataPath`" role set: $($desc -join ' ')"
|
||||
}
|
||||
}
|
||||
|
||||
"remove-field" {
|
||||
$dsNode = Resolve-DataSet
|
||||
$dsName = Get-DataSetName $dsNode
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.11 — Atomic 1C DCS editor (Python port)
|
||||
# skd-edit v1.18 — Atomic 1C DCS editor (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -18,9 +18,9 @@ VALID_OPS = [
|
||||
"add-dataParameter", "add-order", "add-selection", "add-dataSetLink",
|
||||
"add-dataSet", "add-variant", "add-conditionalAppearance", "add-drilldown",
|
||||
"set-query", "patch-query", "set-outputParameter", "set-structure",
|
||||
"modify-field", "modify-filter", "modify-dataParameter", "modify-parameter",
|
||||
"modify-field", "modify-filter", "modify-dataParameter", "modify-parameter", "modify-structure", "set-field-role",
|
||||
"rename-parameter", "reorder-parameters",
|
||||
"clear-selection", "clear-order", "clear-filter",
|
||||
"clear-selection", "clear-order", "clear-filter", "clear-conditionalAppearance",
|
||||
"remove-field", "remove-total", "remove-calculated-field", "remove-parameter", "remove-filter",
|
||||
]
|
||||
|
||||
@@ -250,13 +250,21 @@ def read_field_properties(field_el):
|
||||
|
||||
|
||||
def parse_total_shorthand(s):
|
||||
# "DataPath: Func" or "DataPath: Func(expr)" or "DataPath: ИмяРесурса" (identity)
|
||||
parts = s.split(":", 1)
|
||||
data_path = parts[0].strip()
|
||||
func_part = parts[1].strip()
|
||||
|
||||
agg_funcs = {'Сумма', 'Количество', 'Минимум', 'Максимум', 'Среднее',
|
||||
'Sum', 'Count', 'Min', 'Max', 'Avg',
|
||||
'Minimum', 'Maximum', 'Average'}
|
||||
|
||||
if re.match(r'^\w+\(', func_part):
|
||||
return {"dataPath": data_path, "expression": func_part}
|
||||
else:
|
||||
elif func_part in agg_funcs:
|
||||
return {"dataPath": data_path, "expression": f"{func_part}({data_path})"}
|
||||
else:
|
||||
return {"dataPath": data_path, "expression": func_part}
|
||||
|
||||
|
||||
def parse_calc_shorthand(s):
|
||||
@@ -299,12 +307,26 @@ def parse_calc_shorthand(s):
|
||||
|
||||
|
||||
def parse_param_shorthand(s):
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None}
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None, "hidden": False, "always": False, "availableValues": []}
|
||||
|
||||
# Extract availableValue=... (must be before main parse — captures to end of string)
|
||||
m_av = re.search(r'\s*availableValue=(.+)$', s)
|
||||
if m_av:
|
||||
result["availableValues"] = parse_available_value_list(m_av.group(1).strip())
|
||||
s = re.sub(r'\s*availableValue=.+$', '', s).strip()
|
||||
|
||||
if re.search(r'@autoDates', s):
|
||||
result["autoDates"] = True
|
||||
s = re.sub(r'\s*@autoDates', '', s)
|
||||
|
||||
if re.search(r'@hidden\b', s):
|
||||
result["hidden"] = True
|
||||
s = re.sub(r'\s*@hidden\b', '', s)
|
||||
|
||||
if re.search(r'@always\b', s):
|
||||
result["always"] = True
|
||||
s = re.sub(r'\s*@always\b', '', s)
|
||||
|
||||
# Extract optional [Title] (mirrors parse_field_shorthand)
|
||||
m = re.search(r'\[([^\]]*)\]', s)
|
||||
if m:
|
||||
@@ -545,15 +567,17 @@ def parse_structure_shorthand(s):
|
||||
seg = segments[i].strip()
|
||||
group = {"type": "group"}
|
||||
|
||||
name_m = re.search(r'\s*@name=(.+)', seg)
|
||||
name_m = re.search(r'@name=(?:"([^"]+)"|\'([^\']+)\'|(\S+))', seg)
|
||||
if name_m:
|
||||
group["name"] = name_m.group(1).strip()
|
||||
seg = re.sub(r'\s*@name=.+', '', seg).strip()
|
||||
raw_name = name_m.group(1) or name_m.group(2) or name_m.group(3)
|
||||
group["name"] = raw_name.strip()
|
||||
seg = re.sub(r'\s*@name=(?:"[^"]+"|\'[^\']+\'|\S+)', '', seg).strip()
|
||||
|
||||
if re.match(r'^(details|\u0434\u0435\u0442\u0430\u043b\u0438)$', seg, re.IGNORECASE):
|
||||
group["groupBy"] = []
|
||||
else:
|
||||
group["groupBy"] = [seg]
|
||||
fields = [f.strip() for f in re.split(r'\s*,\s*', seg) if f.strip()]
|
||||
group["groupBy"] = fields
|
||||
|
||||
if innermost is not None:
|
||||
group["children"] = [innermost]
|
||||
@@ -571,6 +595,80 @@ def parse_output_param_shorthand(s):
|
||||
return {"key": s.strip(), "value": ""}
|
||||
|
||||
|
||||
def parse_available_value_list(s):
|
||||
"""Returns list of {value, presentation} from comma-separated list.
|
||||
Items can use single/double quotes (stripped). Quoted spans preserve commas/colons."""
|
||||
if not s:
|
||||
return []
|
||||
|
||||
# Tokenize by ',' respecting quoted spans
|
||||
items = []
|
||||
buf = []
|
||||
in_quote = None
|
||||
for ch in s:
|
||||
if in_quote:
|
||||
buf.append(ch)
|
||||
if ch == in_quote:
|
||||
in_quote = None
|
||||
elif ch in ("'", '"'):
|
||||
in_quote = ch
|
||||
buf.append(ch)
|
||||
elif ch == ',':
|
||||
items.append("".join(buf))
|
||||
buf = []
|
||||
else:
|
||||
buf.append(ch)
|
||||
if buf:
|
||||
items.append("".join(buf))
|
||||
|
||||
def strip_quotes(t):
|
||||
t = t.strip()
|
||||
if len(t) >= 2 and ((t[0] == "'" and t[-1] == "'") or (t[0] == '"' and t[-1] == '"')):
|
||||
return t[1:-1]
|
||||
return t
|
||||
|
||||
result = []
|
||||
for raw in items:
|
||||
item = raw.strip()
|
||||
if not item:
|
||||
continue
|
||||
# Find first ':' outside quotes
|
||||
colon_idx = -1
|
||||
q = None
|
||||
for j, c in enumerate(item):
|
||||
if q:
|
||||
if c == q:
|
||||
q = None
|
||||
elif c in ("'", '"'):
|
||||
q = c
|
||||
elif c == ':':
|
||||
colon_idx = j
|
||||
break
|
||||
if colon_idx >= 0:
|
||||
val_part = item[:colon_idx]
|
||||
pres_part = item[colon_idx + 1:]
|
||||
result.append({"value": strip_quotes(val_part), "presentation": strip_quotes(pres_part)})
|
||||
else:
|
||||
result.append({"value": strip_quotes(item), "presentation": ""})
|
||||
return result
|
||||
|
||||
|
||||
def build_available_value_fragment(item, declared_type, indent):
|
||||
"""Return XML lines for a single <availableValue> block."""
|
||||
lines = [f"{indent}<availableValue>"]
|
||||
for vl in build_param_value_xml(declared_type, item["value"], f"{indent}\t"):
|
||||
lines.append(vl)
|
||||
if item.get("presentation"):
|
||||
lines.append(f'{indent}\t<presentation xsi:type="v8:LocalStringType">')
|
||||
lines.append(f"{indent}\t\t<v8:item>")
|
||||
lines.append(f"{indent}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{indent}\t\t\t<v8:content>{esc_xml(item['presentation'])}</v8:content>")
|
||||
lines.append(f"{indent}\t\t</v8:item>")
|
||||
lines.append(f"{indent}\t</presentation>")
|
||||
lines.append(f"{indent}</availableValue>")
|
||||
return lines
|
||||
|
||||
|
||||
# ── 4. Build-* functions (XML fragment generators) ──────────
|
||||
|
||||
def build_value_type_xml(type_str, indent):
|
||||
@@ -724,6 +822,47 @@ def build_calc_field_fragment(parsed, indent):
|
||||
return "\r\n".join(lines)
|
||||
|
||||
|
||||
def build_param_value_xml(type_str, value, indent, tag_name="value", tag_ns=""):
|
||||
"""Return list of XML lines for <value xsi:type=...>...</value>."""
|
||||
val_str = "" if value is None else str(value)
|
||||
open_tag = f"{tag_ns}:{tag_name}" if tag_ns else tag_name
|
||||
lines = []
|
||||
|
||||
if type_str == "StandardPeriod":
|
||||
lines.append(f'{indent}<{open_tag} xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{indent}\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(val_str)}</v8:variant>')
|
||||
lines.append(f"{indent}\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>")
|
||||
lines.append(f"{indent}\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>")
|
||||
lines.append(f"{indent}</{open_tag}>")
|
||||
return lines
|
||||
|
||||
t = type_str or ""
|
||||
xsi = None
|
||||
if t.startswith("date"):
|
||||
xsi = "xs:dateTime"
|
||||
elif t == "boolean":
|
||||
xsi = "xs:boolean"
|
||||
elif t.startswith("decimal"):
|
||||
xsi = "xs:decimal"
|
||||
elif t.startswith("string"):
|
||||
xsi = "xs:string"
|
||||
elif re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.', t):
|
||||
xsi = "dcscor:DesignTimeValue"
|
||||
else:
|
||||
if re.match(r'^\d{4}-\d{2}-\d{2}T', val_str):
|
||||
xsi = "xs:dateTime"
|
||||
elif val_str in ("true", "false"):
|
||||
xsi = "xs:boolean"
|
||||
elif re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.', val_str) or \
|
||||
re.match(r'^(Catalog|Document|Enum|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.', val_str):
|
||||
xsi = "dcscor:DesignTimeValue"
|
||||
else:
|
||||
xsi = "xs:string"
|
||||
|
||||
lines.append(f'{indent}<{open_tag} xsi:type="{xsi}">{esc_xml(val_str)}</{open_tag}>')
|
||||
return lines
|
||||
|
||||
|
||||
def build_param_fragment(parsed, indent):
|
||||
i = indent
|
||||
fragments = []
|
||||
@@ -739,21 +878,19 @@ def build_param_fragment(parsed, indent):
|
||||
lines.append(f"{i}\t</valueType>")
|
||||
|
||||
if parsed["value"] is not None:
|
||||
val_str = str(parsed["value"])
|
||||
if parsed.get("type") == "StandardPeriod":
|
||||
lines.append(f'{i}\t<value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{i}\t\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(val_str)}</v8:variant>')
|
||||
lines.append(f"{i}\t\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>")
|
||||
lines.append(f"{i}\t\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>")
|
||||
lines.append(f"{i}\t</value>")
|
||||
elif parsed.get("type", "").startswith("date"):
|
||||
lines.append(f'{i}\t<value xsi:type="xs:dateTime">{esc_xml(val_str)}</value>')
|
||||
elif parsed.get("type") == "boolean":
|
||||
lines.append(f'{i}\t<value xsi:type="xs:boolean">{esc_xml(val_str)}</value>')
|
||||
elif parsed.get("type", "").startswith("decimal"):
|
||||
lines.append(f'{i}\t<value xsi:type="xs:decimal">{esc_xml(val_str)}</value>')
|
||||
else:
|
||||
lines.append(f'{i}\t<value xsi:type="xs:string">{esc_xml(val_str)}</value>')
|
||||
for vl in build_param_value_xml(parsed.get("type", ""), parsed["value"], f"{i}\t"):
|
||||
lines.append(vl)
|
||||
|
||||
if parsed.get("hidden"):
|
||||
lines.append(f"{i}\t<useRestriction>true</useRestriction>")
|
||||
lines.append(f"{i}\t<availableAsField>false</availableAsField>")
|
||||
|
||||
for av in parsed.get("availableValues", []) or []:
|
||||
for l in build_available_value_fragment(av, parsed.get("type", ""), f"{i}\t"):
|
||||
lines.append(l)
|
||||
|
||||
if parsed.get("always"):
|
||||
lines.append(f"{i}\t<use>Always</use>")
|
||||
|
||||
lines.append(f"{i}</parameter>")
|
||||
fragments.append("\r\n".join(lines))
|
||||
@@ -1361,10 +1498,10 @@ xml_doc = tree.getroot()
|
||||
|
||||
# ── 7. Batch value splitting ────────────────────────────────
|
||||
|
||||
if operation in ("set-query", "set-structure", "add-dataSet"):
|
||||
if operation in ("set-query", "set-structure", "modify-structure", "add-dataSet"):
|
||||
values = [value_arg]
|
||||
elif operation == "patch-query":
|
||||
values = [v for v in value_arg.split(";;") if v.strip()]
|
||||
values = [v.strip() for v in value_arg.split(";;") if v.strip()]
|
||||
elif operation == "add-drilldown":
|
||||
if ";;" in value_arg:
|
||||
values = [v.strip() for v in value_arg.split(";;") if v.strip()]
|
||||
@@ -1537,6 +1674,15 @@ elif operation == "modify-parameter":
|
||||
param_name = parts[0].strip()
|
||||
rest = parts[1].strip() if len(parts) > 1 else ""
|
||||
|
||||
flag_hidden = False
|
||||
flag_always = False
|
||||
if re.search(r'@hidden\b', rest):
|
||||
flag_hidden = True
|
||||
rest = re.sub(r'\s*@hidden\b', '', rest).strip()
|
||||
if re.search(r'@always\b', rest):
|
||||
flag_always = True
|
||||
rest = re.sub(r'\s*@always\b', '', rest).strip()
|
||||
|
||||
param_el = find_element_by_child_value(xml_doc, "parameter", "name", param_name, SCH_NS)
|
||||
if param_el is None:
|
||||
print(f'[WARN] Parameter "{param_name}" not found -- skipped')
|
||||
@@ -1568,8 +1714,33 @@ elif operation == "modify-parameter":
|
||||
if simple_rest:
|
||||
for m in re.finditer(r'(\w+)=(\S+)', simple_rest):
|
||||
key, value = m.group(1), m.group(2)
|
||||
existing = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == key), None)
|
||||
if existing is not None:
|
||||
existing = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == key and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
|
||||
if key == "value":
|
||||
# Rebuild <value> with correct xsi:type from <valueType>
|
||||
declared_type = ""
|
||||
vt_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "valueType" and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
if vt_el is not None:
|
||||
for tnode in vt_el:
|
||||
if isinstance(tnode.tag, str) and local_name(tnode) == "Type":
|
||||
declared_type = re.sub(r'^d\d+p\d+:', '', (tnode.text or "").strip())
|
||||
break
|
||||
value_lines = build_param_value_xml(declared_type, value, child_indent)
|
||||
frag_xml = "\r\n".join(value_lines)
|
||||
was_existing = existing is not None
|
||||
if existing is not None:
|
||||
# Find next-element sibling as ref before removing
|
||||
idx = list(param_el).index(existing)
|
||||
ref_node = param_el[idx + 1] if idx + 1 < len(param_el) else None
|
||||
remove_node_with_whitespace(existing)
|
||||
else:
|
||||
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("useRestriction", "availableValue", "denyIncompleteValues", "use")), None)
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
verb = "updated" if was_existing else "added"
|
||||
print(f'[OK] Parameter "{param_name}": value {verb} to {value}')
|
||||
elif existing is not None:
|
||||
existing.text = value
|
||||
print(f'[OK] Parameter "{param_name}": {key} updated to {value}')
|
||||
else:
|
||||
@@ -1585,38 +1756,71 @@ elif operation == "modify-parameter":
|
||||
|
||||
# Process availableValue
|
||||
if av_part:
|
||||
av_rest = av_part[len("availableValue="):]
|
||||
# Parse: "Перечисление...X presentation=текст с пробелами"
|
||||
av_parts = re.split(r'\s+presentation=', av_rest, 1)
|
||||
av_value = av_parts[0].strip()
|
||||
av_presentation = av_parts[1].strip() if len(av_parts) > 1 else ""
|
||||
av_rest = av_part[len("availableValue="):].strip()
|
||||
av_items = parse_available_value_list(av_rest)
|
||||
|
||||
av_type = "xs:string"
|
||||
if re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', av_value):
|
||||
av_type = "dcscor:DesignTimeValue"
|
||||
# Prefer declared <valueType> of the parameter; fall back to value pattern
|
||||
declared_type = ""
|
||||
vt_el = None
|
||||
for ch in param_el:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "valueType" and etree.QName(ch.tag).namespace == SCH_NS:
|
||||
vt_el = ch
|
||||
break
|
||||
if vt_el is not None:
|
||||
for tnode in vt_el:
|
||||
if isinstance(tnode.tag, str) and local_name(tnode) == "Type":
|
||||
declared_type = re.sub(r'^d\d+p\d+:', '', (tnode.text or "").strip())
|
||||
break
|
||||
|
||||
av_lines = [f"{child_indent}<availableValue>"]
|
||||
av_lines.append(f'{child_indent}\t<value xsi:type="{av_type}">{esc_xml(av_value)}</value>')
|
||||
if av_presentation:
|
||||
av_lines.append(f'{child_indent}\t<presentation xsi:type="v8:LocalStringType">')
|
||||
av_lines.append(f"{child_indent}\t\t<v8:item>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:content>{esc_xml(av_presentation)}</v8:content>")
|
||||
av_lines.append(f"{child_indent}\t\t</v8:item>")
|
||||
av_lines.append(f"{child_indent}\t</presentation>")
|
||||
av_lines.append(f"{child_indent}</availableValue>")
|
||||
frag_xml = "\r\n".join(av_lines)
|
||||
# Remove all existing <availableValue>
|
||||
to_remove = [ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "availableValue" and etree.QName(ch.tag).namespace == SCH_NS]
|
||||
for el in to_remove:
|
||||
remove_node_with_whitespace(el)
|
||||
|
||||
# Insert before first of (denyIncompleteValues, use) in document order
|
||||
# Insert each new <availableValue> before (denyIncompleteValues, use)
|
||||
ref_node = None
|
||||
for child in param_el:
|
||||
if isinstance(child.tag, str) and local_name(child) in ("denyIncompleteValues", "use"):
|
||||
ref_node = child
|
||||
break
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": availableValue added')
|
||||
for av in av_items:
|
||||
av_lines = build_available_value_fragment(av, declared_type, child_indent)
|
||||
frag_xml = "\r\n".join(av_lines)
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": availableValue set to {len(av_items)} item(s)')
|
||||
|
||||
if flag_hidden:
|
||||
ur_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "useRestriction" and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
if ur_el is not None:
|
||||
if (ur_el.text or "").strip() != "true":
|
||||
ur_el.text = "true"
|
||||
else:
|
||||
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("expression", "availableAsField", "availableValue", "denyIncompleteValues", "use")), None)
|
||||
for node in import_fragment(xml_doc, f"{child_indent}<useRestriction>true</useRestriction>"):
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
|
||||
af_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "availableAsField" and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
if af_el is not None:
|
||||
if (af_el.text or "").strip() != "false":
|
||||
af_el.text = "false"
|
||||
else:
|
||||
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("availableValue", "denyIncompleteValues", "use")), None)
|
||||
for node in import_fragment(xml_doc, f"{child_indent}<availableAsField>false</availableAsField>"):
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
|
||||
print(f'[OK] Parameter "{param_name}": @hidden applied')
|
||||
|
||||
if flag_always:
|
||||
use_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "use" and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
if use_el is not None:
|
||||
if (use_el.text or "").strip() != "Always":
|
||||
use_el.text = "Always"
|
||||
else:
|
||||
for node in import_fragment(xml_doc, f"{child_indent}<use>Always</use>"):
|
||||
insert_before_element(param_el, node, None, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": @always applied')
|
||||
|
||||
elif operation == "rename-parameter":
|
||||
root = xml_doc
|
||||
@@ -1868,6 +2072,11 @@ elif operation == "patch-query":
|
||||
print(f"No <query> element found in dataset '{ds_name}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for val in values:
|
||||
once = False
|
||||
if re.search(r'@once\b', val):
|
||||
once = True
|
||||
val = re.sub(r'\s*@once\b', '', val).strip()
|
||||
|
||||
sep_idx = val.find(" => ")
|
||||
if sep_idx < 0:
|
||||
print("patch-query value must contain ' => ' separator: old => new", file=sys.stderr)
|
||||
@@ -1875,11 +2084,18 @@ elif operation == "patch-query":
|
||||
old_str = val[:sep_idx]
|
||||
new_str = val[sep_idx + 4:]
|
||||
query_text = query_el.text or ""
|
||||
if old_str not in query_text:
|
||||
|
||||
count = query_text.count(old_str)
|
||||
if count == 0:
|
||||
print(f"Substring not found in query of dataset '{ds_name}': {old_str}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if once and count != 1:
|
||||
print(f"@once: expected 1 occurrence of '{old_str}' in dataset '{ds_name}', found {count}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
query_el.text = query_text.replace(old_str, new_str)
|
||||
print(f'[OK] Query patched in dataset "{ds_name}": replaced \'{old_str}\'')
|
||||
suffix = " (1 occurrence)" if once else f" ({count} occurrence(s))"
|
||||
print(f'[OK] Query patched in dataset "{ds_name}": replaced \'{old_str}\'{suffix}')
|
||||
|
||||
elif operation == "set-outputParameter":
|
||||
settings = resolve_variant_settings()
|
||||
@@ -1922,6 +2138,85 @@ elif operation == "set-structure":
|
||||
|
||||
print(f'[OK] Structure set in variant "{var_name}": {value_arg}')
|
||||
|
||||
elif operation == "modify-structure":
|
||||
settings = resolve_variant_settings()
|
||||
var_name = get_variant_name()
|
||||
|
||||
struct_items = parse_structure_shorthand(value_arg)
|
||||
|
||||
# Flatten parsed tree into (name, groupBy) targets
|
||||
targets = []
|
||||
stack = list(struct_items)
|
||||
while stack:
|
||||
it = stack.pop()
|
||||
if it.get("name"):
|
||||
targets.append({"name": it["name"], "groupBy": it.get("groupBy", [])})
|
||||
for ch in it.get("children", []) or []:
|
||||
stack.append(ch)
|
||||
|
||||
if not targets:
|
||||
print(f"modify-structure requires @name= for at least one group: {value_arg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
ns = {"dcsset": SET_NS, "xsi": XSI_NS}
|
||||
|
||||
for t in targets:
|
||||
xpath = f".//dcsset:item[@xsi:type='dcsset:StructureItemGroup'][dcsset:name='{t['name']}']"
|
||||
group_el = settings.find(xpath, ns)
|
||||
if group_el is None:
|
||||
print(f'[WARN] Group with @name="{t["name"]}" not found — skipped')
|
||||
continue
|
||||
|
||||
gi_el = None
|
||||
for ch in group_el:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "groupItems" and etree.QName(ch.tag).namespace == SET_NS:
|
||||
gi_el = ch
|
||||
break
|
||||
|
||||
group_indent = get_child_indent(group_el)
|
||||
if gi_el is None:
|
||||
# Insert <groupItems> after <name>, before first non-name sibling
|
||||
ref_after_name = None
|
||||
saw_name = False
|
||||
for ch in group_el:
|
||||
if not isinstance(ch.tag, str):
|
||||
continue
|
||||
if local_name(ch) == "name" and etree.QName(ch.tag).namespace == SET_NS:
|
||||
saw_name = True
|
||||
elif saw_name:
|
||||
ref_after_name = ch
|
||||
break
|
||||
gi_frag = f"{group_indent}<dcsset:groupItems></dcsset:groupItems>"
|
||||
for node in import_fragment(xml_doc, gi_frag):
|
||||
insert_before_element(group_el, node, ref_after_name, group_indent)
|
||||
for ch in group_el:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "groupItems" and etree.QName(ch.tag).namespace == SET_NS:
|
||||
gi_el = ch
|
||||
break
|
||||
|
||||
to_remove = [ch for ch in gi_el if isinstance(ch.tag, str)]
|
||||
for el in to_remove:
|
||||
remove_node_with_whitespace(el)
|
||||
|
||||
item_indent = group_indent + "\t"
|
||||
|
||||
for field in t["groupBy"]:
|
||||
lines = [
|
||||
f'{item_indent}<dcsset:item xsi:type="dcsset:GroupItemField">',
|
||||
f'{item_indent}\t<dcsset:field>{esc_xml(field)}</dcsset:field>',
|
||||
f'{item_indent}\t<dcsset:groupType>Items</dcsset:groupType>',
|
||||
f'{item_indent}\t<dcsset:periodAdditionType>None</dcsset:periodAdditionType>',
|
||||
f'{item_indent}\t<dcsset:periodAdditionBegin xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionBegin>',
|
||||
f'{item_indent}\t<dcsset:periodAdditionEnd xsi:type="xs:dateTime">0001-01-01T00:00:00</dcsset:periodAdditionEnd>',
|
||||
f'{item_indent}</dcsset:item>',
|
||||
]
|
||||
frag_xml = "\r\n".join(lines)
|
||||
for node in import_fragment(xml_doc, frag_xml):
|
||||
insert_before_element(gi_el, node, None, item_indent)
|
||||
|
||||
desc = "details" if not t["groupBy"] else ", ".join(t["groupBy"])
|
||||
print(f'[OK] Group "{t["name"]}" groupItems updated: {desc}')
|
||||
|
||||
elif operation == "add-dataSetLink":
|
||||
for val in values:
|
||||
parsed = parse_data_set_link_shorthand(val)
|
||||
@@ -2085,6 +2380,16 @@ elif operation == "clear-filter":
|
||||
else:
|
||||
print(f'[INFO] No filter section in variant "{var_name}"')
|
||||
|
||||
elif operation == "clear-conditionalAppearance":
|
||||
settings = resolve_variant_settings()
|
||||
var_name = get_variant_name()
|
||||
ca_el = find_first_element(settings, ["conditionalAppearance"], SET_NS)
|
||||
if ca_el is not None:
|
||||
clear_container_children(ca_el)
|
||||
print(f'[OK] ConditionalAppearance cleared in variant "{var_name}"')
|
||||
else:
|
||||
print(f'[INFO] No conditionalAppearance section in variant "{var_name}"')
|
||||
|
||||
elif operation == "modify-filter":
|
||||
settings = resolve_variant_settings()
|
||||
var_name = get_variant_name()
|
||||
@@ -2237,6 +2542,69 @@ elif operation == "modify-field":
|
||||
|
||||
print(f'[OK] Field "{field_name}" modified in dataset "{ds_name}"')
|
||||
|
||||
elif operation == "set-field-role":
|
||||
ds_node = resolve_data_set()
|
||||
ds_name = get_data_set_name(ds_node)
|
||||
|
||||
for val in values:
|
||||
s = val.strip()
|
||||
|
||||
flags = []
|
||||
for m in re.finditer(r'@(\w+)', s):
|
||||
flags.append(m.group(1))
|
||||
s = re.sub(r'\s*@\w+', '', s).strip()
|
||||
|
||||
kv = []
|
||||
for m in re.finditer(r'(\w+)=(\S+)', s):
|
||||
kv.append((m.group(1), m.group(2)))
|
||||
s = re.sub(r'\s*\w+=\S+', '', s).strip()
|
||||
|
||||
data_path = s
|
||||
if not data_path:
|
||||
print(f'[WARN] set-field-role: empty dataPath in "{val}"')
|
||||
continue
|
||||
|
||||
field_el = find_element_by_child_value(ds_node, "field", "dataPath", data_path, SCH_NS)
|
||||
if field_el is None:
|
||||
print(f'[WARN] Field "{data_path}" not found in dataset "{ds_name}"')
|
||||
continue
|
||||
|
||||
field_indent = get_child_indent(field_el)
|
||||
|
||||
# Remove existing <role>
|
||||
old_role = next((ch for ch in field_el if isinstance(ch.tag, str) and local_name(ch) == "role" and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
if old_role is not None:
|
||||
remove_node_with_whitespace(old_role)
|
||||
|
||||
# Empty spec — remove only
|
||||
if not flags and not kv:
|
||||
print(f'[OK] Field "{data_path}" role cleared')
|
||||
continue
|
||||
|
||||
# Build new <role>
|
||||
lines = [f"{field_indent}<role>"]
|
||||
for flag in flags:
|
||||
if flag == "period":
|
||||
lines.append(f"{field_indent}\t<dcscom:periodNumber>1</dcscom:periodNumber>")
|
||||
lines.append(f"{field_indent}\t<dcscom:periodType>Main</dcscom:periodType>")
|
||||
else:
|
||||
lines.append(f"{field_indent}\t<dcscom:{flag}>true</dcscom:{flag}>")
|
||||
for k, v in kv:
|
||||
lines.append(f"{field_indent}\t<dcscom:{k}>{esc_xml(v)}</dcscom:{k}>")
|
||||
lines.append(f"{field_indent}</role>")
|
||||
frag_xml = "\r\n".join(lines)
|
||||
|
||||
ref_node = next((ch for ch in field_el if isinstance(ch.tag, str) and local_name(ch) in ("valueType", "inputParameters") and etree.QName(ch.tag).namespace == SCH_NS), None)
|
||||
for node in import_fragment(xml_doc, frag_xml):
|
||||
insert_before_element(field_el, node, ref_node, field_indent)
|
||||
|
||||
parts = []
|
||||
if flags:
|
||||
parts.append(" ".join(f"@{f}" for f in flags))
|
||||
if kv:
|
||||
parts.append(" ".join(f"{k}={v}" for k, v in kv))
|
||||
print(f'[OK] Field "{data_path}" role set: {" ".join(parts)}')
|
||||
|
||||
elif operation == "remove-field":
|
||||
ds_node = resolve_data_set()
|
||||
ds_name = get_data_set_name(ds_node)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-info v1.3 — Analyze 1C DCS structure
|
||||
# skd-info v1.4 — Analyze 1C DCS structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
@@ -824,8 +824,14 @@ function Show-Fields {
|
||||
$roleParts = @()
|
||||
if ($role) {
|
||||
foreach ($child in $role.ChildNodes) {
|
||||
if ($child.NodeType -eq "Element" -and $child.InnerText -eq "true") {
|
||||
if ($child.NodeType -ne "Element") { continue }
|
||||
$txt = $child.InnerText.Trim()
|
||||
if ($txt -eq "true") {
|
||||
$roleParts += $child.LocalName
|
||||
} elseif ($txt -eq "false") {
|
||||
# skip default-false flags
|
||||
} else {
|
||||
$roleParts += "$($child.LocalName)=$txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-info v1.3 — Analyze 1C DCS structure
|
||||
# skd-info v1.4 — Analyze 1C DCS structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -777,8 +777,15 @@ def main():
|
||||
role_parts = []
|
||||
if role is not None:
|
||||
for child in role:
|
||||
if isinstance(child.tag, str) and (child.text or "").strip() == "true":
|
||||
if not isinstance(child.tag, str):
|
||||
continue
|
||||
txt = (child.text or "").strip()
|
||||
if txt == "true":
|
||||
role_parts.append(localname(child))
|
||||
elif txt == "false":
|
||||
pass
|
||||
else:
|
||||
role_parts.append(f"{localname(child)}={txt}")
|
||||
info["role"] = ", ".join(role_parts)
|
||||
|
||||
# UseRestriction
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# template-add v1.4 — Add template to 1C object
|
||||
# template-add v1.5 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -20,6 +20,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Маппинг типов ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-template v1.4 — Add template to 1C object
|
||||
# add-template v1.5 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# template-remove v1.1 — Remove template from 1C object
|
||||
# template-remove v1.2 — Remove template from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -12,6 +12,8 @@ param(
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# remove-template v1.0 — Remove template from 1C object
|
||||
# remove-template v1.1 — Remove template from 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
|
||||
@@ -529,3 +529,7 @@ On error (auto-screenshot taken):
|
||||
- **Cyrillic in bash** — use `cat <<'SCRIPT' | node $RUN exec -` to avoid escaping issues
|
||||
- **Non-breaking spaces** — 1C uses `\u00a0` instead of regular spaces. All matching is normalized internally
|
||||
- **Section panel display** — `navigateSection()` works with any panel position (side, top) but requires "Picture and text" or "Text" display mode. Icon-only mode is not supported — API cannot read section names from icons alone
|
||||
|
||||
## Regression suites
|
||||
|
||||
When the user asks to cover a 1C solution with automated regression — multi-file test suites with assertions, hooks, tags, retries, Allure/JUnit reports, multi-user process tests — switch to the `test` mode. See [regress.md](regress.md) for authoring discipline, recon flow (metadata + live walkthrough via `exec`), per-application folder layout, ready-to-paste templates, and failure triage. Default to ad-hoc `run`/`exec` for single-script automation — `test` is the specialised mode for project-wide coverage.
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
# Regression suite authoring
|
||||
|
||||
Use this when the user asks to cover a 1C solution with automated regression tests, build out a test suite, or run an existing suite and analyse failures. For ad-hoc single-script automation, stay with the `run`/`exec` modes from SKILL.md instead.
|
||||
|
||||
The runner is the same `run.mjs`. The mode is `test`:
|
||||
|
||||
```bash
|
||||
node $RUN test [url] <dir|file> [flags]
|
||||
```
|
||||
|
||||
Tests live next to the project they cover (not inside the skill). Convention: `tests/` at the project root, with `_hooks.mjs` and `webtest.config.mjs` at the suite root. Tests are ES modules with `*.test.mjs` suffix.
|
||||
|
||||
## When to choose `test` over `exec`
|
||||
|
||||
| Goal | Mode |
|
||||
|------|------|
|
||||
| Explore a form, prototype a single step, debug one selector | `exec` (interactive session) |
|
||||
| **Walk through a scenario live before committing it as a test** | `exec` first, then `test` |
|
||||
| Reproduce a bug as a failing test before fixing it | `test` |
|
||||
| Cover a feature so future changes are checked automatically | `test` |
|
||||
| Run the project's regression on a new build | `test` |
|
||||
| Generate a screencast walkthrough | `exec` with `startRecording` |
|
||||
|
||||
Don't write a `.test.mjs` for a one-shot user request. Don't drive a regression suite through chained `exec` calls.
|
||||
|
||||
## Before writing tests — recon
|
||||
|
||||
Two layers, in order. Don't skip either.
|
||||
|
||||
### 1. Static recon — metadata
|
||||
|
||||
Never invent identifiers. For every metadata object the user mentions (or that you decide to cover), run the matching info skill first:
|
||||
|
||||
| Object type | Skill |
|
||||
|-------------|-------|
|
||||
| Catalog/document/register attributes, tabular sections | `/meta-info` |
|
||||
| Form layout — fields, buttons, tabs, tables | `/form-info` |
|
||||
| DCS report — fields, parameters, filters | `/skd-info` |
|
||||
| Spreadsheet template areas/parameters | `/mxl-info` |
|
||||
| Role rights / restrictions | `/role-info` |
|
||||
| Subsystem composition / command interface | `/subsystem-info` |
|
||||
|
||||
This gives the real Russian field labels, command names, column headers, table-section names. Without it, fuzzy matching will silently land on the wrong element, or fail with no useful diagnostic.
|
||||
|
||||
If the user names objects you cannot find: stop and ask. Do not guess.
|
||||
|
||||
### 2. Live recon — interactive walkthrough
|
||||
|
||||
For any non-trivial scenario, walk the path live in `exec` mode before writing it down. Metadata tells you what exists; the live walkthrough tells you what actually happens — which button posts the document, which dialog 1C raises, how the form looks after `clickElement('Создать')`, what fields are required, where `wait()` is genuinely needed.
|
||||
|
||||
```bash
|
||||
# Start a session (background).
|
||||
node $RUN start http://localhost:9191/myapp/ru_RU
|
||||
|
||||
# Step the scenario interactively. After each step, inspect.
|
||||
cat <<'EOF' | node $RUN exec -
|
||||
await navigateSection('Склад');
|
||||
const cmds = await getCommands();
|
||||
console.log(cmds);
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | node $RUN exec -
|
||||
await openCommand('Приходная накладная');
|
||||
await clickElement('Создать');
|
||||
const s = await getFormState();
|
||||
console.log(JSON.stringify(s.fields.map(f => ({ name: f.name, label: f.label, required: f.required })), null, 2));
|
||||
console.log('buttons:', s.buttons.map(b => b.name));
|
||||
console.log('tables:', s.tables.map(t => ({ name: t.name, label: t.label, columns: t.columns })));
|
||||
EOF
|
||||
|
||||
# Try the actions you plan to encode. If a step fails, fix and re-try
|
||||
# before transcribing it.
|
||||
cat <<'EOF' | node $RUN exec -
|
||||
await fillFields({ 'Контрагент': 'ООО Север' });
|
||||
await fillTableRow({ 'Номенклатура': 'Товар 01', 'Количество': '5' },
|
||||
{ table: 'Товары', add: true });
|
||||
await clickElement('Провести и закрыть');
|
||||
console.log(JSON.stringify(await getFormState()));
|
||||
EOF
|
||||
|
||||
# When done, stop the session (or leave it for the next test you write).
|
||||
node $RUN stop
|
||||
```
|
||||
|
||||
What to record from the walkthrough into the test:
|
||||
- Exact button names (`'Провести и закрыть'`, not `'Сохранить'`).
|
||||
- Field labels as 1C renders them (with possible non-breaking spaces — `fillFields` normalises, but be exact).
|
||||
- Table section names from `getFormState().tables[].name`/`label` for multi-grid forms.
|
||||
- Required `wait()` durations — only where a real async event happens (report generation, server-side calculation). Default actions await internally.
|
||||
- The shape of `getFormState()` after each action — gives you the right `assert.equal(...)` paths.
|
||||
|
||||
After this, transcribe the working sequence into `*.test.mjs`, wrap each chunk in `step('...', async () => { ... })`, add assertions for the invariants you saw. Run the file once with `node $RUN test path/to/file.test.mjs` to confirm.
|
||||
|
||||
When live recon is overkill: trivial reads (`navigateSection` + `readTable` + assert non-empty), or scenarios you've already proven once in this session. When it's essential: anything with confirmation dialogs, posting/cancellation flows, reports with custom filters, multi-grid forms, or user-customised forms you've never seen.
|
||||
|
||||
## Suite layout
|
||||
|
||||
**Each application gets its own subfolder under `tests/`.** A single repo may host several independent suites side by side — they must not share `_hooks.mjs` or `webtest.config.mjs`, because each suite restores a different DB, publishes to a different URL, and ships its own test data.
|
||||
|
||||
```
|
||||
tests/
|
||||
web-test/ # engine self-tests (reserved if our repo layout)
|
||||
<app-name>/ # application regression — one per solution
|
||||
_hooks.mjs
|
||||
webtest.config.mjs
|
||||
01-login/
|
||||
02-counterparties/
|
||||
...
|
||||
<another-app>/ # second solution, fully isolated
|
||||
_hooks.mjs
|
||||
...
|
||||
```
|
||||
|
||||
`<app-name>` is the project/extension slug (`acc-payroll`, `erp-customisation`, etc.). Pick something stable and pass it on the CLI:
|
||||
|
||||
```bash
|
||||
node $RUN test tests/<app-name>/
|
||||
```
|
||||
|
||||
Inside the application subfolder, organize by **feature**, not by metadata kind. Numeric prefixes on both folder and file enforce run order (discovery is alphabetic by full path).
|
||||
|
||||
```
|
||||
tests/<app-name>/
|
||||
_hooks.mjs # stand prep + cross-cutting hooks (optional)
|
||||
webtest.config.mjs # url, contexts, defaults (optional)
|
||||
01-login/
|
||||
01-open-base.test.mjs
|
||||
02-section-navigation.test.mjs
|
||||
02-counterparties/
|
||||
01-create.test.mjs
|
||||
02-edit-phone.test.mjs
|
||||
03-goods-receipt/
|
||||
01-fill.test.mjs
|
||||
02-post.test.mjs
|
||||
03-unpost.test.mjs
|
||||
04-balance-report/
|
||||
01-generate.test.mjs
|
||||
02-warehouse-filter.test.mjs
|
||||
05-approval-process/
|
||||
01-end-to-end.test.mjs # multi-user
|
||||
```
|
||||
|
||||
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported. Only the application-root copies are loaded.
|
||||
|
||||
## Test file anatomy
|
||||
|
||||
```js
|
||||
export const name = 'Создание контрагента'; // required
|
||||
export const tags = ['catalog', 'create']; // optional, used for filtering + Allure
|
||||
export const timeout = 60000; // optional, default 30000
|
||||
// export const skip = 'pending fix #123'; // optional: true | string
|
||||
// export const only = true; // debug-only — never commit
|
||||
// export const context = 'manager'; // optional, single non-default context
|
||||
// export const contexts = ['clerk', 'manager']; // optional, multi-user test
|
||||
// export const severity = 'critical'; // optional, overrides config severity
|
||||
|
||||
export async function setup(ctx) {
|
||||
// per-test prep — runs before default. Skip if not needed.
|
||||
}
|
||||
|
||||
export async function teardown(ctx) {
|
||||
// per-test cleanup — runs after default, always (even on failure).
|
||||
}
|
||||
|
||||
export default async function(ctx) {
|
||||
const { navigateSection, openCommand, clickElement, fillFields,
|
||||
readTable, closeForm, getFormState,
|
||||
assert, step, log } = ctx;
|
||||
|
||||
await step('Открыть список контрагентов', async () => {
|
||||
await navigateSection('Продажи');
|
||||
await openCommand('Контрагенты');
|
||||
});
|
||||
|
||||
await step('Создать нового контрагента', async () => {
|
||||
await clickElement('Создать');
|
||||
await fillFields({ 'Наименование': 'Тест ' + Date.now() });
|
||||
await clickElement('Записать и закрыть');
|
||||
});
|
||||
|
||||
await step('Убедиться, что элемент появился в списке', async () => {
|
||||
const t = await readTable();
|
||||
assert.tableHasRow(t, r => r['Наименование']?.startsWith('Тест '));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The runner injects every `browser.mjs` export into `ctx` plus `assert`, `step`, `log`, `testInfo`, `testResult` (afterEach only). For multi-context tests, each context name is its own scoped namespace (`ctx.clerk.clickElement(...)` etc.) — `step`/`assert` stay top-level.
|
||||
|
||||
**Step names — in Russian, descriptive.** Step labels surface in the console output, in JSON/JUnit, and as Allure step nodes. Russian-speaking QA reads them. Use a full action phrase (`'Создать нового контрагента'`, `'Проверить наличие документа в списке'`), not a tag (`'create'`, `'verify'`) and not a transliteration. Same applies to `export const name` and `displayName` in `webtest.config.mjs`.
|
||||
|
||||
## webtest.config.mjs
|
||||
|
||||
```js
|
||||
export default {
|
||||
// Single-context: just url.
|
||||
url: 'http://localhost:9191/myapp/ru_RU',
|
||||
|
||||
// OR multi-context: named contexts. Each test picks via `context`/`contexts` exports.
|
||||
// contexts: {
|
||||
// clerk: { url: 'http://localhost:9191/myapp-clerk/ru_RU', displayName: 'Кладовщик' },
|
||||
// manager: { url: 'http://localhost:9191/myapp-manager/ru_RU', displayName: 'Менеджер' },
|
||||
// },
|
||||
// defaultContext: 'clerk',
|
||||
|
||||
timeout: 30000,
|
||||
retries: 0,
|
||||
screenshot: 'on-failure',
|
||||
record: false,
|
||||
|
||||
// Severity → tags mapping for Allure. Each tag at most one bucket.
|
||||
severity: {
|
||||
critical: ['smoke', 'crud'],
|
||||
minor: ['recording'],
|
||||
},
|
||||
defaultSeverity: 'normal',
|
||||
};
|
||||
```
|
||||
|
||||
CLI flags override config. Recommend latin context IDs + Russian `displayName` for video badges.
|
||||
|
||||
## _hooks.mjs
|
||||
|
||||
Two layers. Infra hooks run without a browser; testlevel hooks receive `ctx`.
|
||||
|
||||
```js
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
// Infra — runs once around the whole suite.
|
||||
export async function prepare({ hookArgs, log, config }) {
|
||||
// Restore DB, publish to Apache, build EPF, etc.
|
||||
// hookArgs = everything after `--` on the CLI. Parse yourself.
|
||||
if (hookArgs.includes('--rebuild-stand')) { /* full rebuild */ }
|
||||
// Use idempotent hash-locks to skip work on warm starts.
|
||||
}
|
||||
|
||||
export async function cleanup({ log, config }) {
|
||||
// Tear down or leave the stand running. Choose per project.
|
||||
}
|
||||
|
||||
// Testlevel — runs with browser ctx.
|
||||
export async function beforeAll(ctx) { /* once after first context opens */ }
|
||||
export async function afterAll(ctx) { /* once before final teardown */ }
|
||||
export async function beforeEach(ctx) { /* ctx.testInfo is set */ }
|
||||
export async function afterEach(ctx) { /* ctx.testResult is set */ }
|
||||
|
||||
// Per-context — runs whenever a context is created/closed.
|
||||
export async function afterOpenContext(ctx, name, spec) { /* spec = config.contexts[name] */ }
|
||||
export async function beforeCloseContext(ctx, name, spec) { }
|
||||
```
|
||||
|
||||
Built-in state reset (`dismissPendingErrors` + close all forms) runs after `afterEach` automatically. Don't reimplement it.
|
||||
|
||||
**Where to put data setup:**
|
||||
- DB restore, publication, EPF build → `prepare()`. Make it idempotent (hash-locks on inputs — config sources, EPF spec, DB dump) so warm starts skip everything but a liveness probe.
|
||||
- Test-specific seed data (the document this test will edit, the counterparty it expects) → per-test `setup`.
|
||||
- Shared session-wide warmup → `beforeAll`.
|
||||
|
||||
## Ready-to-paste patterns
|
||||
|
||||
### Catalog full cycle
|
||||
|
||||
```js
|
||||
await step('Создать контрагента', async () => {
|
||||
await navigateSection('Продажи');
|
||||
await openCommand('Контрагенты');
|
||||
await clickElement('Создать');
|
||||
await fillFields({ 'Наименование': 'ТД Тест', 'ИНН': '7707083893' });
|
||||
await clickElement('Записать и закрыть');
|
||||
});
|
||||
await step('Проверить наличие в списке', async () => {
|
||||
const t = await readTable({ maxRows: 50 });
|
||||
assert.tableHasRow(t, { 'Наименование': 'ТД Тест' });
|
||||
});
|
||||
await step('Удалить контрагента и подтвердить удаление', async () => {
|
||||
await clickElement('ТД Тест');
|
||||
const page = await getPage();
|
||||
await page.keyboard.press('Delete');
|
||||
await clickElement('Да');
|
||||
});
|
||||
```
|
||||
|
||||
### Document create + post
|
||||
|
||||
```js
|
||||
const marker = 'Тест-' + Date.now();
|
||||
await openCommand('Приходная накладная');
|
||||
await clickElement('Создать');
|
||||
await fillFields({ 'Контрагент': 'ООО Север', 'Комментарий': marker });
|
||||
await fillTableRow(
|
||||
{ 'Номенклатура': 'Товар 01', 'Количество': '5', 'Цена': '100' },
|
||||
{ table: 'Товары', add: true }
|
||||
);
|
||||
await clickElement('Провести и закрыть');
|
||||
// Verify: re-open list, filter or scan, assert by `marker`.
|
||||
```
|
||||
|
||||
Use a unique marker (`Date.now()` or random suffix) so re-runs don't collide. Identify your own row by it, not by position or natural keys that may already exist in the DB.
|
||||
|
||||
### DCS report
|
||||
|
||||
```js
|
||||
await openCommand('Остатки товаров');
|
||||
// Reset user settings — 1C persists them between sessions.
|
||||
await clickElement('Ещё');
|
||||
await clickElement('Установить стандартные настройки');
|
||||
|
||||
await selectValue('Номенклатура', 'Товар 02'); // auto-enables the filter checkbox
|
||||
await clickElement('Сформировать');
|
||||
await wait(3);
|
||||
const r = await readSpreadsheet();
|
||||
assert.deepEqual(r.headers, ['Номенклатура', 'Количество', 'Сумма']);
|
||||
assert.ok(r.data.length >= 1);
|
||||
assert.ok(r.totals?.['Сумма']);
|
||||
```
|
||||
|
||||
### Multi-user process
|
||||
|
||||
```js
|
||||
export const contexts = ['clerk', 'manager'];
|
||||
|
||||
export default async function({ clerk, manager, step, assert }) {
|
||||
await step('Кладовщик создаёт накладную', async () => {
|
||||
await clerk.navigateSection('Склад');
|
||||
await clerk.openCommand('Приходные накладные');
|
||||
await clerk.clickElement('Создать');
|
||||
await clerk.fillFields({ 'Контрагент': 'ООО Север' });
|
||||
await clerk.clickElement('Записать');
|
||||
});
|
||||
await step('Менеджер утверждает накладную', async () => {
|
||||
await manager.navigateSection('Согласование');
|
||||
await manager.openCommand('На утверждении');
|
||||
await manager.clickElement('ООО Север', { dblclick: true });
|
||||
await manager.clickElement('Утвердить');
|
||||
});
|
||||
await step('Кладовщик видит новый статус', async () => {
|
||||
const s = await clerk.getFormState();
|
||||
assert.equal(s.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
|
||||
});
|
||||
await step('Освободить сессию кладовщика', async () => {
|
||||
await manager.closeContext('clerk'); // free a 1C license for the next test
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
License caveat: stock 1C allows ~2 web sessions concurrently. Close contexts you no longer need before the next multi-user test starts.
|
||||
|
||||
### Failing-test repro
|
||||
|
||||
```js
|
||||
export const name = 'Bug #123: накладная без контрагента не должна проводиться';
|
||||
export const tags = ['bug', 'validation'];
|
||||
|
||||
export default async function({ openCommand, clickElement, getFormState, assert, step }) {
|
||||
await openCommand('Приходные накладные');
|
||||
await clickElement('Создать');
|
||||
await clickElement('Провести');
|
||||
const s = await getFormState();
|
||||
assert.ok(s.errorModal || s.fields.find(f => f.name === 'Контрагент')?.required,
|
||||
'Должна быть ошибка валидации или поле помечено обязательным');
|
||||
}
|
||||
```
|
||||
|
||||
Write it red first, hand it to the user, fix the underlying issue, re-run green.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
node $RUN test tests/<app-name>/ # full app suite
|
||||
node $RUN test tests/<app-name>/03-goods-receipt/ # one feature folder
|
||||
node $RUN test tests/<app-name>/02-counterparties/01-create.test.mjs # one file
|
||||
node $RUN test tests/<app-name>/ --tags=smoke # by tag (intersection)
|
||||
node $RUN test tests/<app-name>/ --grep='накладн' # by name regex
|
||||
node $RUN test tests/<app-name>/ --bail --retry=1 # stop on first fail, allow 1 retry
|
||||
node $RUN test tests/<app-name>/ --report=allure-results --format=allure --report-dir=allure-results
|
||||
node $RUN test tests/<app-name>/ -- --rebuild-stand # everything after `--` goes to hooks
|
||||
```
|
||||
|
||||
Default report is JSON when `--report=…` is given. Allure needs `--format=allure` + a directory. JUnit similarly with `--format=junit`.
|
||||
|
||||
### Allure static config — `_allure/` directory
|
||||
|
||||
The runner copies `<testDir>/_allure/` into the report directory before generating Allure output. Standard Allure convention applies — three files are typically used:
|
||||
|
||||
- **`categories.json`** — failure classification. Always emit this when setting up a suite, with 1C-specific patterns: license pool exhaustion (`Не обнаружено свободной лицензии`), 1C application errors (`ВызватьИсключение|Произошла ошибка|…`), navigation/element lookup misses, runner timeouts, assertion failures.
|
||||
- **`environment.properties`** — `key=value` lines for the Environment widget. Useful when the suite runs across builds/branches (URL, 1C platform version, git branch, configuration version). Often emitted dynamically by `prepare()` rather than committed as a static file.
|
||||
- **`executor.json`** — CI metadata (Jenkins URL, GitHub run ID, etc.). Only relevant when the suite runs on a CI server; for local runs, skip it.
|
||||
|
||||
Discovery skips the underscored directory, so it never collides with tests.
|
||||
|
||||
## Severity guidance
|
||||
|
||||
When the user doesn't dictate, default to:
|
||||
|
||||
| Test kind | Severity |
|
||||
|-----------|----------|
|
||||
| Login + section navigation, basic CRUD on covered entities | `critical` (also tag `smoke`) |
|
||||
| Documents posting, report generation, end-to-end processes | `critical` |
|
||||
| Field-level edge cases, formatting, optional flows | `normal` |
|
||||
| Cosmetic / recording / non-functional | `minor` |
|
||||
| Reserved for show-stopper protections | `blocker` (use sparingly) |
|
||||
|
||||
Don't promote everything to `critical` — it loses signal in the Allure dashboard.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Sleeps as a substitute for assertions.** `wait(5)` after `openCommand` is fine; `wait(30)` because something flakes is a bug — find what state you can wait on with `getFormState` instead.
|
||||
- **Retry as a substitute for understanding.** "Not found" twice means the data isn't there or the label is wrong. Don't loop.
|
||||
- **Raw DOM via `getPage().$$(...)`.** Use `getFormState`, `readTable`, `readSpreadsheet`. Raw selectors break across 1C platform versions.
|
||||
- **`clickElement('×')` or `clickElement('Закрыть')`** to dismiss a form. Use `closeForm({ save: true|false })` — handles confirmation correctly.
|
||||
- **Position-based row identification** (`rows[0]`) when the DB has shared seed data. Filter by unique marker or label instead.
|
||||
- **Skipping recon** because "I know what this catalog looks like." You don't — the project's customisation almost certainly differs from a stock config.
|
||||
- **`tags: ['smoke']` on a 90-second test.** Smoke means fast.
|
||||
- **Hand-writing reset code** in `afterEach`. The runner already closes forms and dismisses errors.
|
||||
- **Cross-test state assumptions.** Each test must start from desktop and seed its own data. Order-of-execution coupling is a regression-suite trap.
|
||||
|
||||
## After a run — failure triage
|
||||
|
||||
1. Scan the JSON or Allure summary for `failed`.
|
||||
2. For each failure, read `error.message` + `error.step` + screenshot (saved next to the report).
|
||||
3. If `error.onecError.stack` is present — it's a 1C exception, look at the platform trace.
|
||||
4. Classify:
|
||||
- **Test bug** — selector wrong, expectation wrong, race with no anchor → fix the test.
|
||||
- **Application bug** — actual misbehaviour reproduced → report to the user with the failing step name and the platform stack.
|
||||
- **Stand flake** — Apache timeout, login form not loading, license shortage → fix the hook idempotency or session-cleanup logic, not the test.
|
||||
5. After fixes, re-run only the affected files (`node $RUN test tests/03-goods-receipt/`) before the full suite.
|
||||
|
||||
Report back to the user with the classification, not raw failure dumps.
|
||||
|
||||
## Reference
|
||||
|
||||
- Browser API: [SKILL.md](SKILL.md)
|
||||
- Video and narration: [recording.md](recording.md)
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test browser v1.9 — Playwright browser management for 1C web client
|
||||
// web-test browser v1.12 — Playwright browser management for 1C web client
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* Playwright browser management for 1C web client.
|
||||
@@ -37,6 +37,16 @@ let lastCaptions = []; // captions from the last completed recording (for addNar
|
||||
let lastRecordingDuration = null; // wall-clock duration of the last recording (seconds)
|
||||
let highlightMode = false;
|
||||
|
||||
// Multi-context registry: name → { context, page, sessionPrefix, seanceId, recorder, lastCaptions, lastRecordingDuration, highlightMode }
|
||||
// Populated by createContext(); module-level vars above mirror the active slot.
|
||||
// connect() does NOT use this Map — it preserves legacy single-session behavior for exec/run/start.
|
||||
const contexts = new Map();
|
||||
let activeContextName = null;
|
||||
// Isolation mode for the current cmdTest session — set by the first createContext call.
|
||||
// 'tab': all contexts share one persistent context (one window, multiple tabs, extension loads reliably).
|
||||
// 'window': each context gets its own BrowserContext (separate window per context, full cookie isolation, extension may not load).
|
||||
let activeMode = null;
|
||||
|
||||
const LOAD_TIMEOUT = 60000;
|
||||
const INIT_TIMEOUT = 60000;
|
||||
const ACTION_WAIT = 2000; // fallback minimum wait
|
||||
@@ -158,31 +168,51 @@ export async function connect(url, { extensionPath } = {}) {
|
||||
return await getPageState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort POST /e1cib/logout on a slot to release the 1C session license.
|
||||
* Silent — if page is closed or session info missing, just returns.
|
||||
* @param {object} slot { page, sessionPrefix, seanceId } from contexts Map
|
||||
* @param {number} [waitMs=500] pause after logout fetch (gives 1C time to process)
|
||||
*/
|
||||
async function _logoutSlot(slot, waitMs = 500) {
|
||||
if (!slot?.page || slot.page.isClosed() || !slot.seanceId || !slot.sessionPrefix) return;
|
||||
try {
|
||||
const logoutUrl = `${slot.sessionPrefix}/e1cib/logout?seanceId=${slot.seanceId}`;
|
||||
await slot.page.evaluate(async (url) => {
|
||||
await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{"root":{}}' });
|
||||
}, logoutUrl);
|
||||
await slot.page.waitForTimeout(waitMs);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully terminate the 1C session and close the browser.
|
||||
* Sends POST /e1cib/logout to release the license before closing.
|
||||
*/
|
||||
export async function disconnect() {
|
||||
// Auto-stop recording if active (prevents orphaned ffmpeg)
|
||||
// Multi-context path: stop recording + logout each slot before closing browser
|
||||
if (contexts.size > 0) {
|
||||
_saveActiveSlot();
|
||||
// Recorder is global — one stop covers all contexts
|
||||
if (recorder) {
|
||||
try { await stopRecording(); } catch {}
|
||||
}
|
||||
for (const [, slot] of contexts.entries()) {
|
||||
await _logoutSlot(slot);
|
||||
}
|
||||
contexts.clear();
|
||||
activeContextName = null;
|
||||
activeMode = null;
|
||||
}
|
||||
|
||||
// Single-session path (connect): auto-stop recording if active
|
||||
if (recorder) {
|
||||
try { await stopRecording(); } catch {}
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
// Graceful logout — release the 1C license
|
||||
if (page && !page.isClosed() && seanceId && sessionPrefix) {
|
||||
try {
|
||||
const logoutUrl = `${sessionPrefix}/e1cib/logout?seanceId=${seanceId}`;
|
||||
await page.evaluate(async (url) => {
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{"root":{}}'
|
||||
});
|
||||
}, logoutUrl);
|
||||
await page.waitForTimeout(1000);
|
||||
} catch {}
|
||||
}
|
||||
// Graceful logout — release the 1C license (single-session connect path)
|
||||
await _logoutSlot({ page, sessionPrefix, seanceId }, 1000);
|
||||
await browser.close().catch(() => {});
|
||||
browser = null;
|
||||
page = null;
|
||||
@@ -228,6 +258,203 @@ export function getSession() {
|
||||
return { sessionPrefix, seanceId };
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Multi-context support (used by run.mjs cmdTest only)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Save current module-level state into the active slot before switching.
|
||||
* No-op if no active slot.
|
||||
*/
|
||||
function _saveActiveSlot() {
|
||||
if (!activeContextName) return;
|
||||
const slot = contexts.get(activeContextName);
|
||||
if (!slot) return;
|
||||
slot.page = page;
|
||||
slot.sessionPrefix = sessionPrefix;
|
||||
slot.seanceId = seanceId;
|
||||
slot.highlightMode = highlightMode;
|
||||
// Note: `recorder`, `lastCaptions`, `lastRecordingDuration` are intentionally NOT
|
||||
// mirrored per-slot. A multi-context recording produces one continuous output file —
|
||||
// the recorder follows the active page via recorder._attachPage(), not per-slot state.
|
||||
}
|
||||
|
||||
/** Load a slot's state into module-level vars and mark it active. */
|
||||
function _activateSlot(name) {
|
||||
const slot = contexts.get(name);
|
||||
if (!slot) throw new Error(`Context "${name}" not found. Create it via createContext() first.`);
|
||||
page = slot.page;
|
||||
sessionPrefix = slot.sessionPrefix;
|
||||
seanceId = slot.seanceId;
|
||||
highlightMode = slot.highlightMode || false;
|
||||
activeContextName = name;
|
||||
}
|
||||
|
||||
/** Attach 1C session listeners to a page, writing into the given slot. */
|
||||
function _attachSessionListeners(pg, slot, name) {
|
||||
pg.on('dialog', dialog => dialog.accept().catch(() => {}));
|
||||
pg.on('request', req => {
|
||||
if (slot.seanceId) return;
|
||||
const m = req.url().match(/^(https?:\/\/[^/]+\/[^/]+\/[^/]+)\/e1cib\/.+[?&]seanceId=([^&]+)/);
|
||||
if (m) {
|
||||
slot.sessionPrefix = m[1];
|
||||
slot.seanceId = m[2];
|
||||
if (activeContextName === name) {
|
||||
sessionPrefix = m[1];
|
||||
seanceId = m[2];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create (or navigate) a named browser context.
|
||||
* First call launches Chromium via chromium.launch() (NOT launchPersistentContext) so that
|
||||
* subsequent calls can create additional isolated BrowserContexts in the same process.
|
||||
* Trade-off: 1C browser extension is loaded via --load-extension (process-level) rather than
|
||||
* persistent profile.
|
||||
*
|
||||
* Use this from run.mjs cmdTest only — exec/run/start use connect() and stay on the
|
||||
* legacy persistent-context path.
|
||||
*/
|
||||
export async function createContext(name, url, { extensionPath, isolation = 'tab' } = {}) {
|
||||
if (contexts.has(name)) {
|
||||
await setActiveContext(name);
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: LOAD_TIMEOUT });
|
||||
try { await page.waitForSelector('#themesCell_theme_0', { timeout: INIT_TIMEOUT }); }
|
||||
catch { await page.waitForTimeout(5000); }
|
||||
await closeModals();
|
||||
return await getPageState();
|
||||
}
|
||||
|
||||
if (!['tab', 'window'].includes(isolation)) {
|
||||
throw new Error(`createContext: invalid isolation "${isolation}", expected 'tab' or 'window'`);
|
||||
}
|
||||
if (activeMode && activeMode !== isolation) {
|
||||
throw new Error(`createContext: cannot mix isolation modes — first context used "${activeMode}", "${name}" requested "${isolation}". Use the same mode for all contexts in one run.`);
|
||||
}
|
||||
|
||||
// First context: launch browser. Subsequent: reuse existing.
|
||||
let isFirstContext = !browser;
|
||||
if (isFirstContext) {
|
||||
const extPath = findExtension(extensionPath);
|
||||
const launchArgs = ['--start-maximized'];
|
||||
if (extPath) {
|
||||
launchArgs.push('--disable-extensions-except=' + extPath, '--load-extension=' + extPath);
|
||||
}
|
||||
if (isolation === 'tab') {
|
||||
// Persistent context: extension loads reliably, one window with tabs per context
|
||||
persistentUserDataDir = pathJoin(tmpdir(), 'pw-1c-test-' + Date.now());
|
||||
mkdirSync(persistentUserDataDir, { recursive: true });
|
||||
browser = await chromium.launchPersistentContext(persistentUserDataDir, {
|
||||
headless: false,
|
||||
args: launchArgs,
|
||||
viewport: null,
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
});
|
||||
} else {
|
||||
// Window mode: separate BrowserContext per slot, full cookie isolation
|
||||
browser = await chromium.launch({ headless: false, args: launchArgs });
|
||||
}
|
||||
activeMode = isolation;
|
||||
}
|
||||
|
||||
// Save current active before switching
|
||||
_saveActiveSlot();
|
||||
|
||||
// Create slot — page differs by mode
|
||||
let newCtx, newPage;
|
||||
if (activeMode === 'tab') {
|
||||
// Reuse the persistent context for all slots; each slot gets its own page (tab)
|
||||
newCtx = browser;
|
||||
if (isFirstContext) {
|
||||
newPage = browser.pages()[0] || await browser.newPage();
|
||||
} else {
|
||||
newPage = await browser.newPage();
|
||||
}
|
||||
} else {
|
||||
// Window mode: each slot owns its BrowserContext + page
|
||||
newCtx = await browser.newContext({
|
||||
viewport: null,
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
});
|
||||
newPage = await newCtx.newPage();
|
||||
}
|
||||
|
||||
const slot = {
|
||||
context: newCtx,
|
||||
page: newPage,
|
||||
sessionPrefix: null,
|
||||
seanceId: null,
|
||||
highlightMode: false,
|
||||
};
|
||||
contexts.set(name, slot);
|
||||
|
||||
_attachSessionListeners(newPage, slot, name);
|
||||
_activateSlot(name);
|
||||
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: LOAD_TIMEOUT });
|
||||
try { await page.waitForSelector('#themesCell_theme_0', { timeout: INIT_TIMEOUT }); }
|
||||
catch { await page.waitForTimeout(5000); }
|
||||
await closeModals();
|
||||
|
||||
return await getPageState();
|
||||
}
|
||||
|
||||
/** Switch the active context. Subsequent browser API calls operate on this context's page. */
|
||||
export async function setActiveContext(name) {
|
||||
if (activeContextName === name) return;
|
||||
if (!contexts.has(name)) throw new Error(`Context "${name}" not found. Available: [${[...contexts.keys()].join(', ')}]`);
|
||||
// If a recording is active, flush the outgoing page's last frame so the gap is filled
|
||||
// up to the moment of the switch (avoids a "jump" in video time).
|
||||
if (recorder && recorder._flushFrames) recorder._flushFrames();
|
||||
_saveActiveSlot();
|
||||
_activateSlot(name);
|
||||
// If the recording is still alive (it lives across slots — we keep the same ffmpeg/output),
|
||||
// re-attach its screencast to the newly active page.
|
||||
if (recorder && recorder._attachPage) {
|
||||
await recorder._attachPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
export function listContexts() {
|
||||
return [...contexts.keys()];
|
||||
}
|
||||
|
||||
export function getActiveContext() {
|
||||
return activeContextName;
|
||||
}
|
||||
|
||||
export function hasContext(name) {
|
||||
return contexts.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a named context: logout, close its page (tab mode) or BrowserContext
|
||||
* (window mode), remove from registry. Cannot close the currently active
|
||||
* context — caller must setActiveContext to another first. This keeps the
|
||||
* recorder/page invariants simple: recorder is always attached to the
|
||||
* active slot, which closeContext never touches.
|
||||
*
|
||||
* @throws if name is not registered or equals the active context.
|
||||
*/
|
||||
export async function closeContext(name) {
|
||||
if (!contexts.has(name)) {
|
||||
throw new Error(`Context "${name}" not found. Available: [${[...contexts.keys()].join(', ')}]`);
|
||||
}
|
||||
if (name === activeContextName) {
|
||||
throw new Error(`closeContext: cannot close the active context "${name}". setActiveContext to another context first.`);
|
||||
}
|
||||
const slot = contexts.get(name);
|
||||
await _logoutSlot(slot);
|
||||
if (activeMode === 'tab') {
|
||||
try { await slot.page.close(); } catch {}
|
||||
} else {
|
||||
try { await slot.context.close(); } catch {}
|
||||
}
|
||||
contexts.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close startup modals and guide tabs.
|
||||
* Strategy: Escape → click default buttons → close extra tabs → repeat.
|
||||
@@ -4861,10 +5088,7 @@ export async function startRecording(outputPath, opts = {}) {
|
||||
const resolvedPath = resolveProjectPath(outputPath);
|
||||
mkdirSync(dirname(resolvedPath), { recursive: true });
|
||||
|
||||
// Create CDP session for screencast
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
|
||||
// Spawn ffmpeg process
|
||||
// Spawn ffmpeg process — single output file across context switches
|
||||
const ffmpeg = spawn(ffmpegPath, [
|
||||
'-y', // overwrite output
|
||||
'-f', 'image2pipe', // input: piped images
|
||||
@@ -4880,71 +5104,86 @@ export async function startRecording(outputPath, opts = {}) {
|
||||
resolvedPath
|
||||
], { stdio: ['pipe', 'ignore', 'pipe'] });
|
||||
|
||||
let ffmpegError = '';
|
||||
ffmpeg.stderr.on('data', d => { ffmpegError += d.toString(); });
|
||||
ffmpeg.on('error', err => { ffmpegError += err.message; });
|
||||
ffmpeg.on('error', err => { if (recorder) recorder.ffmpegError += err.message; });
|
||||
|
||||
// Listen for screencast frames and pipe to ffmpeg
|
||||
// CDP sends frames only on screen changes, so we duplicate frames
|
||||
// to fill gaps and maintain real-time playback speed
|
||||
const frameDuration = 1000 / fps;
|
||||
let lastFrameTime = null;
|
||||
let lastFrameBuf = null;
|
||||
const speechRate = opts.speechRate || 70; // ms per character for smart TTS wait
|
||||
|
||||
cdp.on('Page.screencastFrame', async ({ data, sessionId }) => {
|
||||
// Frame handler shared across CDP sessions (lives in recorder, not closure):
|
||||
// when the active context switches, we attach a new CDP session and route its
|
||||
// frames to the same ffmpeg pipe — preserving a single continuous timeline.
|
||||
const frameHandler = async ({ data, sessionId }, cdp) => {
|
||||
if (!recorder) return;
|
||||
const buf = Buffer.from(data, 'base64');
|
||||
const now = Date.now();
|
||||
|
||||
if (!ffmpeg.stdin.destroyed) {
|
||||
let framesWritten = 0;
|
||||
if (lastFrameTime && lastFrameBuf) {
|
||||
// Fill the gap with duplicates of the previous frame
|
||||
const gap = now - lastFrameTime;
|
||||
if (recorder.lastFrameTime && recorder.lastFrameBuf) {
|
||||
const gap = now - recorder.lastFrameTime;
|
||||
const dupes = Math.round(gap / frameDuration) - 1;
|
||||
for (let i = 0; i < dupes && i < fps * 30; i++) {
|
||||
ffmpeg.stdin.write(lastFrameBuf);
|
||||
ffmpeg.stdin.write(recorder.lastFrameBuf);
|
||||
framesWritten++;
|
||||
}
|
||||
}
|
||||
ffmpeg.stdin.write(buf);
|
||||
framesWritten++;
|
||||
// Track actual video timeline position (accounts for frame duplication)
|
||||
if (recorder) recorder.videoTimeMs += framesWritten * frameDuration;
|
||||
recorder.videoTimeMs += framesWritten * frameDuration;
|
||||
}
|
||||
|
||||
lastFrameTime = now;
|
||||
lastFrameBuf = buf;
|
||||
recorder.lastFrameTime = now;
|
||||
recorder.lastFrameBuf = buf;
|
||||
try { await cdp.send('Page.screencastFrameAck', { sessionId }); } catch {}
|
||||
});
|
||||
|
||||
// Start the screencast
|
||||
await cdp.send('Page.startScreencast', {
|
||||
format: 'jpeg',
|
||||
quality,
|
||||
everyNthFrame: 1
|
||||
});
|
||||
|
||||
// Expose a frame-writing helper on the recorder object.
|
||||
// During static periods (e.g. smart TTS pauses), CDP may not send screencast
|
||||
// frames. Call _flushFrames() to fill the gap with duplicates of the last frame,
|
||||
// keeping video timeline in sync with wall-clock time.
|
||||
const _flushFrames = () => {
|
||||
if (!lastFrameBuf || !lastFrameTime || ffmpeg.stdin.destroyed) return;
|
||||
const now = Date.now();
|
||||
const gap = now - lastFrameTime;
|
||||
const dupes = Math.round(gap / frameDuration);
|
||||
for (let i = 0; i < dupes; i++) {
|
||||
ffmpeg.stdin.write(lastFrameBuf);
|
||||
if (recorder) recorder.videoTimeMs += frameDuration;
|
||||
}
|
||||
if (dupes > 0) lastFrameTime = now;
|
||||
};
|
||||
|
||||
const speechRate = opts.speechRate || 70; // ms per character for smart TTS wait
|
||||
recorder = { cdp, ffmpeg, startTime: Date.now(), outputPath: resolvedPath, ffmpegError: '', captions: [], videoTimeMs: 0, _flushFrames, speechRate };
|
||||
// Redirect stderr accumulation to the recorder object
|
||||
ffmpeg.stderr.removeAllListeners('data');
|
||||
// Duplicate the last frame to fill wall-clock gaps (static periods, context switches).
|
||||
const _flushFrames = () => {
|
||||
if (!recorder || !recorder.lastFrameBuf || !recorder.lastFrameTime || ffmpeg.stdin.destroyed) return;
|
||||
const now = Date.now();
|
||||
const gap = now - recorder.lastFrameTime;
|
||||
const dupes = Math.round(gap / frameDuration);
|
||||
for (let i = 0; i < dupes; i++) {
|
||||
ffmpeg.stdin.write(recorder.lastFrameBuf);
|
||||
recorder.videoTimeMs += frameDuration;
|
||||
}
|
||||
if (dupes > 0) recorder.lastFrameTime = now;
|
||||
};
|
||||
|
||||
// Attach screencast to a specific page. Stops the old CDP first (if any).
|
||||
// Called by startRecording for the initial page, and by setActiveContext when
|
||||
// the active context changes mid-recording.
|
||||
const _attachPage = async (targetPage) => {
|
||||
if (recorder.cdp) {
|
||||
_flushFrames(); // freeze the last frame of the outgoing page up to "now"
|
||||
try { await recorder.cdp.send('Page.stopScreencast'); } catch {}
|
||||
try { await recorder.cdp.detach(); } catch {}
|
||||
recorder.cdp = null;
|
||||
}
|
||||
const cdp = await targetPage.context().newCDPSession(targetPage);
|
||||
cdp.on('Page.screencastFrame', (ev) => frameHandler(ev, cdp));
|
||||
await cdp.send('Page.startScreencast', { format: 'jpeg', quality, everyNthFrame: 1 });
|
||||
recorder.cdp = cdp;
|
||||
recorder.activePage = targetPage;
|
||||
};
|
||||
|
||||
recorder = {
|
||||
cdp: null,
|
||||
activePage: null,
|
||||
ffmpeg,
|
||||
startTime: Date.now(),
|
||||
outputPath: resolvedPath,
|
||||
ffmpegError: '',
|
||||
captions: [],
|
||||
videoTimeMs: 0,
|
||||
frameDuration,
|
||||
lastFrameTime: null,
|
||||
lastFrameBuf: null,
|
||||
_flushFrames,
|
||||
_attachPage,
|
||||
speechRate,
|
||||
};
|
||||
ffmpeg.stderr.on('data', d => { recorder.ffmpegError += d.toString(); });
|
||||
|
||||
await _attachPage(page);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
||||
"name": "{{PLUGIN_NAME}}",
|
||||
"description": "[Python] Полный цикл разработки на платформе 1С:Предприятие 8.3 — от создания конфигураций, расширений, внешних обработок и отчётов до загрузки изменений в информационную базу, обновления, запуска, публикации на веб-сервере, тестирования через веб-клиент и записи видеоинструкций.",
|
||||
"description": "[Python] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент. Linux/Mac или когда PowerShell недоступен.",
|
||||
"author": {
|
||||
"name": "Nikolay Shirokov"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "{{PLUGIN_NAME}}",
|
||||
"version": "{{VERSION}}",
|
||||
"description": "[{{RUNTIME_LABEL}}] Полный цикл разработки на платформе 1С:Предприятие 8.3 — от создания конфигураций, расширений, внешних обработок и отчётов до загрузки изменений в информационную базу, обновления, запуска, публикации на веб-сервере, тестирования через веб-клиент и записи видеоинструкций.",
|
||||
"description": "[{{RUNTIME_LABEL}}] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
||||
"author": {
|
||||
"name": "Nikolay Shirokov"
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ python tools/cc-1c-skills/scripts/switch.py
|
||||
| Веб-публикация (Web) | 4 навыка `/web-*` | Публикация баз через Apache, статус, остановка, удаление публикаций | [Подробнее](docs/web-guide.md) |
|
||||
| Тестирование (Web) | `/web-test` | Взаимодействие с веб-клиентом 1С — навигация, формы, таблицы, отчёты, тестирование | [Подробнее](docs/web-test-guide.md) |
|
||||
| Запись видео (Web) | `/web-test` | Запись видеоинструкций с субтитрами, подсветкой и TTS-озвучкой | [Подробнее](docs/web-test-recording-guide.md) |
|
||||
| Регресс прикладного решения (Web) | `/web-test` | Автоматический регресс конфигурации: тесты, проверки, отчёты, прогон после правок | [Подробнее](docs/web-test-regression-guide.md) |
|
||||
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
|
||||
|
||||
## Требования
|
||||
@@ -255,6 +256,7 @@ docs/
|
||||
├── web-guide.md # Гайд: веб-публикация через Apache
|
||||
├── web-test-guide.md # Гайд: тестирование через веб-клиент
|
||||
├── web-test-recording-guide.md # Гайд: запись видеоинструкций
|
||||
├── web-test-regression-guide.md # Гайд: регресс прикладного решения
|
||||
├── 1c-epf-spec.md # Спецификация XML-формата (EPF)
|
||||
├── 1c-erf-spec.md # Спецификация XML-формата (ERF)
|
||||
├── 1c-form-spec.md # Спецификация управляемых форм
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
# Регрессионное тестирование прикладного решения
|
||||
|
||||
Навык `/web-test` умеет не только разово выполнить сценарий в браузере, но и сопровождать прикладное решение полноценным набором автотестов: каждый тест — отдельный файл, с шагами, проверками, тегами, отчётом и видеозаписью падений. После каждой правки конфигурации модель прогоняет весь набор и показывает, что ожидаемо ведёт себя как раньше, а что сломалось.
|
||||
|
||||
```
|
||||
правка конфигурации → загрузка → обновление → публикация → прогон тестов → отчёт
|
||||
```
|
||||
|
||||
Это про прикладное решение в целом, не про разовую проверку одной формы. Для разовых сценариев («открой накладную, проверь сумму») по-прежнему удобнее интерактивный режим из [web-test-guide.md](web-test-guide.md).
|
||||
|
||||
## Предусловия
|
||||
|
||||
- База опубликована через Apache (`/web-publish`).
|
||||
- Установлен Node.js 18+, зависимости подняты: `cd .claude/skills/web-test/scripts && npm install`.
|
||||
- ffmpeg — нужен только если хотите видеозапись прогона как доказательство падения. Без него падения фиксируются скриншотами. Установка описана в [web-test-recording-guide.md](web-test-recording-guide.md).
|
||||
|
||||
## Как это устроено
|
||||
|
||||
Набор тестов живёт в каталоге `tests/` вашего проекта. Каждое прикладное решение — отдельная подпапка. Внутри подпапки:
|
||||
|
||||
- `_hooks.mjs` — подготовка стенда (восстановление базы, публикация) и общая очистка после прогона. Необязателен.
|
||||
- `webtest.config.mjs` — адрес базы и набор пользователей (например, кладовщик и менеджер для процессов согласования). Необязателен — если в проекте один пользователь и один URL, можно обойтись без него.
|
||||
- Сами тесты — файлы `*.test.mjs`, сгруппированные по функциональным папкам.
|
||||
|
||||
```
|
||||
tests/
|
||||
моя-конфигурация/
|
||||
_hooks.mjs
|
||||
webtest.config.mjs
|
||||
01-вход/
|
||||
01-открытие-базы.test.mjs
|
||||
02-контрагенты/
|
||||
01-создание.test.mjs
|
||||
02-правка-телефона.test.mjs
|
||||
03-поступление-товаров/
|
||||
01-оформление.test.mjs
|
||||
02-проведение.test.mjs
|
||||
04-отчёт-остатки/
|
||||
01-формирование.test.mjs
|
||||
05-согласование/
|
||||
01-полный-цикл.test.mjs
|
||||
```
|
||||
|
||||
Порядок выполнения — по алфавиту, поэтому удобно префиксовать папки и файлы номерами. Это даёт предсказуемый сценарий: сначала вход, потом справочники, потом документы, потом отчёты, в конце — процессы с несколькими пользователями.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Самый короткий путь от нуля до зелёного теста — попросить модель пройти ваш сценарий руками и зафиксировать его как тест:
|
||||
|
||||
```
|
||||
> Покрой регрессом справочник Контрагенты в моей конфигурации.
|
||||
> Нужны проверки: создание, правка телефона, удаление.
|
||||
```
|
||||
|
||||
Что сделает модель:
|
||||
|
||||
1. Соберёт информацию о справочнике через `/meta-info` и `/form-info` — посмотрит реквизиты и форму элемента, чтобы знать правильные имена полей.
|
||||
2. Подключится к опубликованной базе в интерактивном режиме и **руками пройдёт** каждый сценарий — создание, правка, удаление. Это нужно, чтобы зафиксировать настоящие имена кнопок, увидеть, какие диалоги показывает 1С, понять, требуется ли подтверждение сохранения.
|
||||
3. Зафиксирует пройденный сценарий как файл `tests/<ваша-конфигурация>/02-контрагенты/01-создание.test.mjs`.
|
||||
4. Запустит его и покажет результат.
|
||||
|
||||
При следующих прогонах ничего этого делать не нужно — модель просто запустит готовый набор.
|
||||
|
||||
## Сценарии работы с моделью
|
||||
|
||||
### Покрытие регрессом доработанного объекта
|
||||
|
||||
```
|
||||
> Я добавил в справочник Номенклатура реквизит "Цена" и "Активен".
|
||||
> Покрой это регрессом — создание, редактирование, фильтрация по активности
|
||||
```
|
||||
|
||||
Модель:
|
||||
- посмотрит структуру справочника и формы (через `/meta-info`, `/form-info`);
|
||||
- интерактивно проверит, как ведут себя новые поля в браузере;
|
||||
- сгенерирует 2-3 тестовых файла под папкой `02-номенклатура/`;
|
||||
- прогонит — покажет, что зелёное, что красное.
|
||||
|
||||
### Тест процесса с несколькими пользователями
|
||||
|
||||
```
|
||||
> Сделай тест для процесса согласования приходных накладных.
|
||||
> Кладовщик создаёт накладную, менеджер утверждает,
|
||||
> кладовщик видит обновлённый статус
|
||||
```
|
||||
|
||||
Модель настроит в `webtest.config.mjs` двух пользователей (с разными URL базы — например, `app-clerk` и `app-manager`), напишет тест, который оркестрирует переключение между ними, и положит его в `05-согласование/`.
|
||||
|
||||
```js
|
||||
export const contexts = ['кладовщик', 'менеджер'];
|
||||
|
||||
export default async function({ кладовщик, менеджер, step, assert }) {
|
||||
await step('Кладовщик создаёт накладную', async () => {
|
||||
await кладовщик.navigateSection('Склад');
|
||||
await кладовщик.openCommand('Приходные накладные');
|
||||
await кладовщик.clickElement('Создать');
|
||||
// ...
|
||||
});
|
||||
await step('Менеджер утверждает', async () => {
|
||||
await менеджер.navigateSection('Согласование');
|
||||
// ...
|
||||
});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Учтите ограничение по лицензиям 1С: каждый одновременно открытый пользователь — это занятая клиентская лицензия. Если в наборе много многопользовательских тестов, а на стенде лицензий впритык, прогоны начнут спотыкаться на «свободных лицензий не осталось». Модель освобождает сессии между тестами автоматически (закрывает контексты после процессного теста), но если стенд ограничен — закладывайте это в планирование набора: один-два многопользовательских сценария вместо десяти.
|
||||
|
||||
### Воспроизведение ошибки тестом
|
||||
|
||||
```
|
||||
> При проведении накладной без заполненного контрагента у нас не появляется
|
||||
> ошибка валидации, документ просто проводится с пустым контрагентом — это баг.
|
||||
> Зафиксируй это падающим тестом
|
||||
```
|
||||
|
||||
Модель воспроизведёт сценарий, напишет тест с проверкой «должна быть ошибка», получит красный — потом, когда вы поправите конфигурацию и попросите перепрогнать, тест станет зелёным. Это документирует ожидаемое поведение в виде кода.
|
||||
|
||||
### Прогон регресса после изменений
|
||||
|
||||
```
|
||||
> Я обновил расширение, накатил в базу. Прогони регресс
|
||||
```
|
||||
|
||||
Модель запустит весь набор, дождётся завершения и расскажет:
|
||||
- сколько тестов прошло, сколько упало, сколько пропущено;
|
||||
- по каждому упавшему — что именно сломалось (название шага, сообщение об ошибке, ссылка на скриншот);
|
||||
- классифицирует падения: это ошибка в самом тесте (нужно поправить тест), ошибка в приложении (баг, который вы внесли изменением), или нестабильность стенда (Apache не ответил вовремя, лицензия не освободилась).
|
||||
|
||||
```
|
||||
> Прогони только тесты по контрагентам с подробным отчётом
|
||||
```
|
||||
|
||||
Запустит подмножество — фильтр по тегу или папке, с записью JSON-отчёта.
|
||||
|
||||
### Подготовка автономного стенда
|
||||
|
||||
Если вы хотите, чтобы регресс можно было запустить «с нуля» — даже на чистой машине без подготовленной базы, — модель настроит автоматическую подготовку стенда:
|
||||
|
||||
```
|
||||
> Сделай, чтобы перед прогоном тестов база восстанавливалась из эталона,
|
||||
> а после прогона публикация снималась
|
||||
```
|
||||
|
||||
Это пишется один раз в файле `_hooks.mjs`: при запуске тестов запускается подготовка (через навыки `/db-create`, `/db-load-xml`, `/web-publish`), а после — очистка. Внутри предусмотрено кэширование: если ничего не менялось со прошлого прогона, повторная подготовка занимает доли секунды.
|
||||
|
||||
## Пример организации покрытия
|
||||
|
||||
Допустим, у нас условное прикладное решение «Учёт поступлений товаров» — справочники контрагентов и номенклатуры, документ приходной накладной, отчёт остатков, процесс согласования с двумя пользователями. Логично организовать набор так:
|
||||
|
||||
```
|
||||
tests/учёт-поступлений/
|
||||
_hooks.mjs # подготовка: восстановление базы + публикация
|
||||
webtest.config.mjs # URL базы, контексты кладовщика и менеджера
|
||||
01-вход/
|
||||
01-открытие-базы.test.mjs # базовая работоспособность: вход проходит, разделы видны
|
||||
02-навигация-по-разделам.test.mjs # обход всех разделов конфигурации
|
||||
02-контрагенты/
|
||||
01-создание.test.mjs # создание, проверка появления в списке
|
||||
02-редактирование.test.mjs # правка реквизита, проверка сохранения
|
||||
03-удаление.test.mjs # удаление с подтверждением
|
||||
03-номенклатура/
|
||||
01-создание.test.mjs
|
||||
02-фильтр-по-активности.test.mjs # быстрая фильтрация списка
|
||||
04-поступление-товаров/
|
||||
01-оформление.test.mjs # заполнение шапки и табличной части
|
||||
02-проведение.test.mjs # проведение документа, проверка движений
|
||||
03-отмена-проведения.test.mjs
|
||||
04-валидация-обязательных.test.mjs # негативный тест: пустой контрагент → ошибка
|
||||
05-отчёт-остатки/
|
||||
01-формирование.test.mjs
|
||||
02-отбор-по-складу.test.mjs
|
||||
03-расшифровка.test.mjs # переход из ячейки отчёта в исходный документ
|
||||
06-согласование/
|
||||
01-полный-цикл.test.mjs # многопользовательский тест
|
||||
```
|
||||
|
||||
Принципы:
|
||||
|
||||
- **Папки — по бизнес-функции**, не по типу метаданных. Лучше `04-поступление-товаров/` (что делает пользователь), чем `документы/` (что лежит в дереве конфигурации).
|
||||
- **Цифровые префиксы** — на папке и на файле. Гарантируют, что сначала отработают базовые проверки (вход, справочники), потом сложные (документы, отчёты, процессы). При падении базы остальное и так не пройдёт — нет смысла занимать стенд получасом.
|
||||
- **Один файл — одна логически связанная история.** Не «всё про контрагентов в одном файле», а «отдельно создание, отдельно правка, отдельно удаление». Когда падает — сразу видно, какой именно сценарий сломан.
|
||||
- **Негативные тесты тоже есть.** «Документ без контрагента не проводится» — такой же важный регресс, как и позитивный сценарий, особенно после правок в обработчиках проверки заполнения.
|
||||
- **Процессные тесты — в конце.** Они самые хрупкие (зависят от двух сессий, лицензий, синхронизации) и самые длинные. Если упадут — у вас уже есть данные от предыдущих тестов.
|
||||
|
||||
## Анатомия одного теста
|
||||
|
||||
Пользователь, как правило, тест не пишет — генерирует модель. Но прочитать и поправить полезно уметь. Стандартный файл выглядит так:
|
||||
|
||||
```js
|
||||
export const name = 'Создание контрагента';
|
||||
export const tags = ['контрагенты', 'базовая-проверка'];
|
||||
export const timeout = 60000;
|
||||
|
||||
export default async function({
|
||||
navigateSection, openCommand, clickElement, fillFields,
|
||||
readTable, closeForm, assert, step
|
||||
}) {
|
||||
await step('Открыть список контрагентов', async () => {
|
||||
await navigateSection('Продажи');
|
||||
await openCommand('Контрагенты');
|
||||
});
|
||||
|
||||
await step('Создать нового контрагента', async () => {
|
||||
await clickElement('Создать');
|
||||
await fillFields({ 'Наименование': 'ТД Тест', 'ИНН': '7707083893' });
|
||||
await clickElement('Записать и закрыть');
|
||||
});
|
||||
|
||||
await step('Убедиться, что элемент появился в списке', async () => {
|
||||
const t = await readTable();
|
||||
assert.tableHasRow(t, r => r['Наименование'] === 'ТД Тест');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Что здесь есть:
|
||||
|
||||
- **`name`** — человекочитаемое имя теста. Появится в отчёте.
|
||||
- **`tags`** — теги для фильтрации. Можно прогонять не весь набор, а только нужные: `--tags=контрагенты`.
|
||||
- **`timeout`** — сколько максимум тест может идти. По умолчанию 30 секунд, для длинных сценариев увеличиваем.
|
||||
- **Тело теста** — функция, которая получает API браузера (см. [SKILL.md](../.claude/skills/web-test/SKILL.md)) плюс `assert` и `step`.
|
||||
- **`step('имя', async () => {...})`** — обёртка шага. Имена шагов попадают в отчёт, при падении видно, какой именно шаг сломался.
|
||||
- **`assert.*`** — проверки. `assert.tableHasRow`, `assert.equal`, `assert.ok` и т.д. Если проверка не выполнилась — тест считается упавшим.
|
||||
|
||||
Имена шагов и теста — по-русски, описательные. Они показываются и в консоли, и в отчётах.
|
||||
|
||||
## Запуск и отчёты
|
||||
|
||||
### Простой прогон
|
||||
|
||||
```
|
||||
> Прогони регресс
|
||||
```
|
||||
|
||||
Модель запустит весь набор, дождётся, покажет сводку:
|
||||
|
||||
```
|
||||
✓ Открытие базы (2.1s)
|
||||
✓ Создание контрагента (8.4s)
|
||||
✗ Проведение приходной накладной (12.7s)
|
||||
└ Заполнить табличную часть (5.2s)
|
||||
Не найден столбец "Цена" в табличной части "Товары"
|
||||
скриншот: tests/учёт-поступлений/error-shot.png
|
||||
|
||||
23 пройдено, 1 упал, 0 пропущено (3 мин 42 с)
|
||||
```
|
||||
|
||||
### Подробный отчёт
|
||||
|
||||
```
|
||||
> Прогони регресс и сохрани подробный отчёт
|
||||
```
|
||||
|
||||
Модель добавит флаг записи отчёта (JSON или Allure) — потом по нему можно листать историю прогонов, видеть длительности шагов, открывать прикреплённые скриншоты.
|
||||
|
||||
Allure — стандартный визуальный отчёт с категориями падений, графиками, таймлайном. Чтобы посмотреть отчёт после прогона:
|
||||
|
||||
```bash
|
||||
# Allure CLI устанавливается отдельно (npm install -g allure-commandline)
|
||||
allure serve allure-results
|
||||
```
|
||||
|
||||
### Категории падений в Allure
|
||||
|
||||
Без дополнительной настройки Allure складывает все упавшие тесты в один общий список «Defects». Если в прогоне упало 15 тестов, не сразу понятно, что из этого — пятнадцать разных проблем или одна и та же ошибка (например, нехватка лицензии на стенде), которая зацепила пятнадцать тестов подряд.
|
||||
|
||||
Чтобы Allure группировал падения по причинам, рядом с тестами кладётся каталог `_allure/` с файлом `categories.json`. Подчёркивание в имени каталога — чтобы он не воспринимался как папка с тестами; раннер копирует его содержимое в отчёт.
|
||||
|
||||
```
|
||||
tests/моя-конфигурация/
|
||||
_allure/
|
||||
categories.json # классификация падений
|
||||
environment.properties # необязательно: URL, версия 1С, ветка git
|
||||
executor.json # необязательно: метаданные сборки CI
|
||||
_hooks.mjs
|
||||
01-вход/
|
||||
...
|
||||
```
|
||||
|
||||
`categories.json` — это список регулярных выражений, по которым ошибка теста относится к той или иной группе:
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "Нехватка лицензий 1С",
|
||||
"matchedStatuses": ["failed", "broken"],
|
||||
"messageRegex": ".*Не обнаружено свободной лицензии.*" },
|
||||
{ "name": "Ошибка приложения 1С",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*(ВызватьИсключение|В поле введены некорректные данные|Произошла ошибка).*" },
|
||||
{ "name": "Элемент не найден",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*(clickElement|fillFields|selectValue).*not found.*" },
|
||||
{ "name": "Превышен лимит времени теста",
|
||||
"matchedStatuses": ["failed", "broken"],
|
||||
"messageRegex": "Timeout \\(\\d+ms\\)" },
|
||||
{ "name": "Несовпадение ожидания и факта",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": "(Expected|AssertionError).*" }
|
||||
]
|
||||
```
|
||||
|
||||
Когда вы попросите модель в первый раз настроить регресс, она положит шаблонный `categories.json` со стандартными классами. По мере того как вы будете находить новые типичные причины падений (например, специфичные для вашего расширения тексты ошибок), категории дополняются.
|
||||
|
||||
В виджете «Categories» итогового отчёта вы увидите примерно так:
|
||||
|
||||
```
|
||||
Нехватка лицензий 1С — 12 падений
|
||||
Ошибка приложения 1С — 2 падения
|
||||
Несовпадение ожидания и факта — 1 падение
|
||||
```
|
||||
|
||||
— и сразу понятно, что 12 падений — это один стенд-баг, а двумя «ошибками приложения» нужно разобраться по существу.
|
||||
|
||||
Помимо `categories.json` в каталог `_allure/` можно положить ещё два стандартных файла:
|
||||
|
||||
- **`environment.properties`** — список `ключ=значение` (URL базы, версия платформы 1С, имя ветки git, номер сборки). Покажется в отчёте в виджете «Environment». Полезно, когда регресс гоняется на нескольких стендах или после каждого билда — видно, на чём именно был получен результат. Этот файл удобно генерировать прямо в подготовке стенда (`_hooks.mjs`), а не держать статичной копией.
|
||||
- **`executor.json`** — метаданные системы сборки: ссылка на Jenkins-задачу, идентификатор запуска GitHub Actions и т.д. Нужен только если регресс запускается на сервере сборки. При локальном прогоне ничего класть не надо.
|
||||
|
||||
### Прогон части набора
|
||||
|
||||
```
|
||||
> Прогони только тесты по поступлениям товаров
|
||||
> Прогони только базовые проверки
|
||||
> Прогони только упавший вчера тест с проведением накладной
|
||||
```
|
||||
|
||||
Модель выберет нужное подмножество — по папке, по тегу или по имени теста.
|
||||
|
||||
### Принудительная пересборка стенда
|
||||
|
||||
Если хотите, чтобы перед прогоном база восстановилась с нуля:
|
||||
|
||||
```
|
||||
> Прогони регресс с полной пересборкой стенда
|
||||
```
|
||||
|
||||
Это передаст в подготовку флаг типа `--rebuild-stand` — `_hooks.mjs` пересоздаст базу из эталона. Полезно после крупных правок или если подозреваете, что предыдущие прогоны загрязнили данные.
|
||||
|
||||
## Что делать, когда тест упал
|
||||
|
||||
Модель проанализирует падение и отнесёт его к одной из трёх категорий:
|
||||
|
||||
1. **Ошибка в самом тесте.** Например, переименовали реквизит — тест ищет старое имя поля. Решение: модель обновит тест.
|
||||
2. **Ошибка в приложении.** Это и есть то, ради чего регресс существует: что-то поменялось в конфигурации, и сценарий, который раньше работал, теперь не отрабатывает. Модель опишет, что именно произошло, со скриншотом и трассировкой стека 1С, если ошибка была серверной.
|
||||
3. **Нестабильность стенда.** Apache не ответил, не освободилась лицензия, база отвалилась. Это лечится не правкой теста, а починкой подготовки стенда в `_hooks.mjs` или, реже, повторным прогоном с одним повтором.
|
||||
|
||||
Просите модель не «исправь упавший тест», а «разберись с падением» — иначе она может молча подкрутить ожидание под текущее поведение, замаскировав настоящий баг.
|
||||
|
||||
## Полезные подробности
|
||||
|
||||
### Тестовые данные
|
||||
|
||||
В прикладном решении обычно нужны какие-то стартовые данные: пара контрагентов, номенклатура, заведённые организации. Их кладём не в каждый тест, а один раз в подготовку стенда (`_hooks.mjs`) — после восстановления базы загружаются эталонные данные, на которых работают все тесты.
|
||||
|
||||
Если конкретному тесту нужны свои данные (например, документ, который мы будем редактировать), он создаёт их сам в начале и убирает в конце.
|
||||
|
||||
### Имена документов и уникальность
|
||||
|
||||
Тесты прогоняются многократно. Если тест создаёт документ «Накладная-Тест», следующий прогон может натолкнуться на старую запись. Решение — добавлять к имени метку времени:
|
||||
|
||||
```js
|
||||
const метка = 'Тест-' + Date.now();
|
||||
await fillFields({ 'Комментарий': метка });
|
||||
// ...
|
||||
const t = await readTable();
|
||||
assert.tableHasRow(t, r => r['Комментарий'] === метка);
|
||||
```
|
||||
|
||||
Модель это делает автоматически, но если правите тест руками — держите в голове.
|
||||
|
||||
### Видео при падении
|
||||
|
||||
Можно включить запись видео всех тестов — тогда при падении прикладывается не только скриншот, но и MP4 со всей сессией:
|
||||
|
||||
```
|
||||
> Прогони регресс с записью видео
|
||||
```
|
||||
|
||||
Размер прогона при этом растёт (на 2-3 минутах теста выходит 5-10 МБ), но при отладке сложного падения видео экономит кучу времени.
|
||||
|
||||
### Многоязычные конфигурации
|
||||
|
||||
Если у вас есть конфигурация с командами и реквизитами на нескольких языках, тесты пишутся под один язык (как правило, тот, в котором ведётся работа в проде). При смене языка интерфейса в браузере тесты не пройдут — модель видит другие подписи кнопок.
|
||||
|
||||
## Где смотреть дальше
|
||||
|
||||
- API браузера, которое вызывают тесты — [SKILL.md](../.claude/skills/web-test/SKILL.md).
|
||||
- Подробная инструкция для модели по написанию тестов (на английском, технический документ) — [.claude/skills/web-test/regress.md](../.claude/skills/web-test/regress.md).
|
||||
- Интерактивный режим без тестов — [web-test-guide.md](web-test-guide.md).
|
||||
- Запись видеоинструкций — [web-test-recording-guide.md](web-test-recording-guide.md).
|
||||
+46
-11
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# switch.py v1.5 — Переключение навыков 1С между AI-платформами и рантаймами
|
||||
# switch.py v1.6 — Переключение навыков 1С между AI-платформами и рантаймами
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""
|
||||
Копирует (или создаёт ссылки на) навыки из .claude/skills/ на другие AI-платформы
|
||||
@@ -63,9 +63,35 @@ GITIGNORE_RECOMMENDATIONS = [
|
||||
# ---------------------------------------------------------------------------
|
||||
# Capture optional surrounding quote (group 'q') and bare path (group 'path').
|
||||
# Path matches non-whitespace non-quote chars to support ${CLAUDE_SKILL_DIR}/...
|
||||
RX_PS = re.compile(r'powershell\.exe\s+(?:-NoProfile\s+)?-File\s+(?P<q>["\']?)(?P<path>[^"\s]+?)\.ps1(?P=q)?')
|
||||
# Optional -ExecutionPolicy <value> between -NoProfile and -File (used for codex target).
|
||||
RX_PS = re.compile(
|
||||
r'powershell\.exe\s+'
|
||||
r'(?:-NoProfile\s+)?'
|
||||
r'(?:-ExecutionPolicy\s+\S+\s+)?'
|
||||
r'-File\s+(?P<q>["\']?)(?P<path>[^"\s]+?)\.ps1(?P=q)?'
|
||||
)
|
||||
RX_PY = re.compile(r"python\s+(?P<q>[\"']?)(?P<path>[^\"\s]+?)\.py(?P=q)?")
|
||||
|
||||
# Платформы, требующие -ExecutionPolicy Bypass (Codex запускает powershell как
|
||||
# login-shell, профиль грузится и упирается в Restricted policy).
|
||||
PS_BYPASS_PLATFORMS = {'codex'}
|
||||
|
||||
|
||||
def emit_ps_invocation(path, quote, platform):
|
||||
"""Build canonical powershell.exe invocation for a target platform."""
|
||||
ep = ' -ExecutionPolicy Bypass' if platform in PS_BYPASS_PLATFORMS else ''
|
||||
return f"powershell.exe -NoProfile{ep} -File {quote}{path}.ps1{quote}"
|
||||
|
||||
|
||||
def normalize_ps_invocation(content, platform):
|
||||
"""Re-emit existing powershell.exe ...ps1 invocations with platform flags.
|
||||
|
||||
Idempotent: matches both with and without -ExecutionPolicy in source.
|
||||
"""
|
||||
def repl(m):
|
||||
return emit_ps_invocation(m.group('path'), m.group('q'), platform)
|
||||
return RX_PS.sub(repl, content)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Junction / symlink helpers
|
||||
@@ -221,8 +247,12 @@ def rewrite_paths(content, platform, target_prefix, skill_name):
|
||||
return content
|
||||
|
||||
|
||||
def switch_runtime_content(content, target_runtime):
|
||||
"""Switch runtime invocations in .md content. Returns (new_content, switched)."""
|
||||
def switch_runtime_content(content, target_runtime, platform='claude-code'):
|
||||
"""Switch runtime invocations in .md content. Returns (new_content, switched).
|
||||
|
||||
Platform controls flags on emitted powershell invocations
|
||||
(codex requires -ExecutionPolicy Bypass).
|
||||
"""
|
||||
if target_runtime == 'python':
|
||||
def to_py(m):
|
||||
q = m.group('q')
|
||||
@@ -230,8 +260,7 @@ def switch_runtime_content(content, target_runtime):
|
||||
new = RX_PS.sub(to_py, content)
|
||||
elif target_runtime == 'powershell':
|
||||
def to_ps(m):
|
||||
q = m.group('q')
|
||||
return f"powershell.exe -NoProfile -File {q}{m.group('path')}.ps1{q}"
|
||||
return emit_ps_invocation(m.group('path'), m.group('q'), platform)
|
||||
new = RX_PY.sub(to_ps, content)
|
||||
else:
|
||||
return content, False
|
||||
@@ -354,9 +383,13 @@ def cmd_install(platform, runtime, project_dir):
|
||||
# where target runtime is not available)
|
||||
if not skip_runtime:
|
||||
if runtime == 'python':
|
||||
new_content, _ = switch_runtime_content(new_content, 'python')
|
||||
new_content, _ = switch_runtime_content(new_content, 'python', platform)
|
||||
elif runtime == 'powershell':
|
||||
new_content, _ = switch_runtime_content(new_content, 'powershell')
|
||||
new_content, _ = switch_runtime_content(new_content, 'powershell', platform)
|
||||
|
||||
# Normalize any remaining powershell invocations with platform flags
|
||||
# (covers skip_runtime=True case where source PS commands stayed)
|
||||
new_content = normalize_ps_invocation(new_content, platform)
|
||||
|
||||
if new_content != content:
|
||||
with open(md_path, 'w', encoding='utf-8') as f:
|
||||
@@ -491,14 +524,14 @@ def cmd_switch_runtime(runtime, project_dir):
|
||||
# Skip runtime conversion for single-runtime skills where
|
||||
# target files don't exist (e.g. img-grid has only .py)
|
||||
cur_rt = classify_skill_runtime(skill_path)
|
||||
missing = check_missing_files(skill_path, runtime, repo_root())
|
||||
missing = check_missing_files(skill_path, runtime, project_dir)
|
||||
skip_runtime = bool(missing) and (
|
||||
(runtime == 'python' and cur_rt in ('ps', 'none'))
|
||||
or (runtime == 'powershell' and cur_rt in ('py', 'none'))
|
||||
)
|
||||
|
||||
info, warnings = collect_runtime_messages(
|
||||
skill_name, skill_path, runtime, repo_root())
|
||||
skill_name, skill_path, runtime, project_dir)
|
||||
all_info.extend(info)
|
||||
all_warnings.extend(warnings)
|
||||
|
||||
@@ -509,7 +542,9 @@ def cmd_switch_runtime(runtime, project_dir):
|
||||
with open(md_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
new_content, changed = switch_runtime_content(content, runtime)
|
||||
new_content, _ = switch_runtime_content(content, runtime, platform_name)
|
||||
new_content = normalize_ps_invocation(new_content, platform_name)
|
||||
changed = new_content != content
|
||||
|
||||
if changed:
|
||||
with open(md_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env node
|
||||
// build-webtest-db v0.2 — Собирает синтетическую web-test конфигурацию в постоянные пути
|
||||
// и накатывает её в зарегистрированную базу `webtest` (см. .v8-project.json).
|
||||
//
|
||||
// Двойной режим:
|
||||
// - CLI: node tests/skills/build-webtest-db.mjs [--runtime ...] [--skip-platform]
|
||||
// - Module: import { runSteps, execSkill, getProjectInfo, ... } from './build-webtest-db.mjs'
|
||||
//
|
||||
// CLI:
|
||||
// node tests/skills/build-webtest-db.mjs # пересобрать с нуля
|
||||
// node tests/skills/build-webtest-db.mjs --runtime python
|
||||
// node tests/skills/build-webtest-db.mjs --skip-platform # только XML, без db-create/load/update
|
||||
//
|
||||
// После завершения база готова к /web-publish + web-test сессии.
|
||||
|
||||
import { execFile } from 'child_process';
|
||||
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join, resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const ROOT = dirname(__filename);
|
||||
const REPO_ROOT = resolve(ROOT, '../..');
|
||||
const SKILLS = resolve(REPO_ROOT, '.claude/skills');
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Reads .v8-project.json and locates webtest registration.
|
||||
* @returns {{ v8path: string, v8exe: string, webtestDb: object, configSrc: string, dbPath: string }}
|
||||
*/
|
||||
export function getProjectInfo() {
|
||||
const projectFile = join(REPO_ROOT, '.v8-project.json');
|
||||
if (!existsSync(projectFile)) throw new Error('.v8-project.json not found');
|
||||
const proj = JSON.parse(readFileSync(projectFile, 'utf8'));
|
||||
const webtestDb = proj.databases?.find(d => d.id === 'webtest');
|
||||
if (!webtestDb) throw new Error('Database "webtest" not registered in .v8-project.json');
|
||||
const v8path = proj.v8path;
|
||||
const v8exe = join(v8path, '1cv8.exe');
|
||||
const dbPath = webtestDb.path;
|
||||
const configSrc = resolve(REPO_ROOT, webtestDb.configSrc);
|
||||
return { v8path, v8exe, webtestDb, configSrc, dbPath };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a skill script path to an absolute file (chooses .ps1 or .py based on runtime).
|
||||
*/
|
||||
export function resolveScript(scriptRelPath, runtime = 'powershell') {
|
||||
const ext = runtime === 'python' ? '.py' : '.ps1';
|
||||
const full = join(SKILLS, scriptRelPath + ext);
|
||||
if (!existsSync(full)) throw new Error(`Script not found: ${full}`);
|
||||
return full;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single skill script with provided arguments.
|
||||
* @returns {Promise<string>} stdout
|
||||
*/
|
||||
export function execSkill(scriptPath, args, runtime = 'powershell') {
|
||||
return new Promise((res, rej) => {
|
||||
const cmd = runtime === 'python'
|
||||
? [process.env.PYTHON || 'python', [scriptPath, ...args]]
|
||||
: ['powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]];
|
||||
execFile(cmd[0], cmd[1], { encoding: 'utf8', timeout: 120_000, cwd: REPO_ROOT }, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
rej(new Error(stderr?.trim() || stdout?.trim() || err.message));
|
||||
} else {
|
||||
res(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces {workDir}/{v8path}/{dbPath} placeholders in a string value.
|
||||
*/
|
||||
export function replacePlaceholders(s, paths) {
|
||||
return String(s)
|
||||
.replace('{workDir}', paths.workDir ?? '')
|
||||
.replace('{v8path}', paths.v8path ?? '')
|
||||
.replace('{dbPath}', paths.dbPath ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an array of build steps.
|
||||
*
|
||||
* Each step: { name, script?, args?, input?, writeFile?, content? }
|
||||
* - writeFile: write content to a file (relative to workDir or absolute), skip script call
|
||||
* - script: relative path under .claude/skills (without extension)
|
||||
* - args: { '-Flag': value | true }, value may contain {workDir}/{v8path}/{dbPath}/{inputFile}
|
||||
* - input: JSON object written to __input.json (referenced by {inputFile} in args)
|
||||
*
|
||||
* @param {Array} steps
|
||||
* @param {{ workDir: string, v8path: string, dbPath: string }} paths
|
||||
* @param {string} runtime 'powershell' | 'python'
|
||||
* @param {(line: string) => void} log
|
||||
* @returns {Promise<{ ok: boolean, elapsed: number, failedAt?: number }>}
|
||||
*/
|
||||
export async function runSteps(steps, paths, runtime, log = console.log) {
|
||||
const t0 = Date.now();
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
const stepT0 = Date.now();
|
||||
|
||||
if (step.writeFile) {
|
||||
try {
|
||||
const target = replacePlaceholders(step.writeFile, paths);
|
||||
const abs = target.includes(':') || target.startsWith('/') ? target : join(paths.workDir, target);
|
||||
mkdirSync(dirname(abs), { recursive: true });
|
||||
writeFileSync(abs, step.content ?? '', 'utf8');
|
||||
const ms = Date.now() - stepT0;
|
||||
log(` [${i + 1}/${steps.length}] OK ${step.name} (${(ms / 1000).toFixed(1)}s)`);
|
||||
} catch (e) {
|
||||
log(` [${i + 1}/${steps.length}] FAIL ${step.name}: ${e.message}`);
|
||||
return { ok: false, elapsed: (Date.now() - t0) / 1000, failedAt: i };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let inputFile = null;
|
||||
if (step.input) {
|
||||
inputFile = join(paths.workDir, '__input.json');
|
||||
writeFileSync(inputFile, JSON.stringify(step.input, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
const script = resolveScript(step.script, runtime);
|
||||
const args = [];
|
||||
for (const [flag, value] of Object.entries(step.args || {})) {
|
||||
args.push(flag);
|
||||
if (value === true) continue;
|
||||
let v = String(value).replace('{inputFile}', inputFile || '');
|
||||
v = replacePlaceholders(v, paths);
|
||||
args.push(v);
|
||||
}
|
||||
|
||||
try {
|
||||
await execSkill(script, args, runtime);
|
||||
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
|
||||
const ms = Date.now() - stepT0;
|
||||
log(` [${i + 1}/${steps.length}] OK ${step.name} (${(ms / 1000).toFixed(1)}s)`);
|
||||
} catch (e) {
|
||||
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
|
||||
log(` [${i + 1}/${steps.length}] FAIL ${step.name}`);
|
||||
log(` ${e.message.split('\n').join('\n ').substring(0, 1500)}`);
|
||||
return { ok: false, elapsed: (Date.now() - t0) / 1000, failedAt: i };
|
||||
}
|
||||
}
|
||||
return { ok: true, elapsed: (Date.now() - t0) / 1000 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard platform load steps (db-create + db-load-xml + db-update).
|
||||
*/
|
||||
export function platformLoadSteps() {
|
||||
return [
|
||||
{
|
||||
name: 'db-create: создание файловой ИБ',
|
||||
script: 'db-create/scripts/db-create',
|
||||
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}' },
|
||||
},
|
||||
{
|
||||
name: 'db-load-xml: загрузка конфигурации',
|
||||
script: 'db-load-xml/scripts/db-load-xml',
|
||||
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}', '-ConfigDir': '{workDir}' },
|
||||
},
|
||||
{
|
||||
name: 'db-update: обновление БД',
|
||||
script: 'db-update/scripts/db-update',
|
||||
args: { '-V8Path': '{v8path}', '-InfoBasePath': '{dbPath}' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the build-webtest-config.test.mjs steps array.
|
||||
*/
|
||||
export async function loadBuildSteps() {
|
||||
const buildModule = await import(`file://${join(ROOT, 'integration/build-webtest-config.test.mjs').replace(/\\/g, '/')}`);
|
||||
return buildModule.steps;
|
||||
}
|
||||
|
||||
// ── CLI ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function runCli() {
|
||||
const argv = process.argv.slice(2);
|
||||
const opts = { runtime: 'powershell', skipPlatform: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '--runtime' && argv[i + 1]) { opts.runtime = argv[++i]; continue; }
|
||||
if (a === '--skip-platform') { opts.skipPlatform = true; continue; }
|
||||
if (a === '-h' || a === '--help') {
|
||||
console.log('Usage: build-webtest-db.mjs [--runtime powershell|python] [--skip-platform]');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
const { v8path, v8exe, configSrc, dbPath } = getProjectInfo();
|
||||
|
||||
if (!opts.skipPlatform && !existsSync(v8exe)) {
|
||||
console.error(`1cv8.exe not found at ${v8exe}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[build-webtest-db] configSrc: ${configSrc}`);
|
||||
console.log(`[build-webtest-db] dbPath: ${dbPath}`);
|
||||
console.log(`[build-webtest-db] runtime: ${opts.runtime}`);
|
||||
console.log('');
|
||||
|
||||
if (existsSync(configSrc)) {
|
||||
console.log(`Removing existing configSrc...`);
|
||||
rmSync(configSrc, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
}
|
||||
mkdirSync(configSrc, { recursive: true });
|
||||
|
||||
if (!opts.skipPlatform && existsSync(dbPath)) {
|
||||
console.log(`Removing existing IB...`);
|
||||
rmSync(dbPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
}
|
||||
|
||||
const buildSteps = await loadBuildSteps();
|
||||
const platformSteps = opts.skipPlatform ? [] : platformLoadSteps();
|
||||
const allSteps = [...buildSteps, ...platformSteps];
|
||||
|
||||
const paths = { workDir: configSrc, v8path, dbPath };
|
||||
const result = await runSteps(allSteps, paths, opts.runtime, console.log);
|
||||
|
||||
console.log('');
|
||||
if (!result.ok) {
|
||||
console.error(`Build FAILED after ${result.elapsed.toFixed(1)}s`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Build OK (${result.elapsed.toFixed(1)}s)`);
|
||||
console.log('');
|
||||
console.log(` configSrc: ${configSrc}`);
|
||||
if (!opts.skipPlatform) {
|
||||
console.log(` IB: ${dbPath}`);
|
||||
console.log('');
|
||||
console.log(` Next: /web-publish webtest → open in browser`);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI guard: run only when invoked directly, not when imported.
|
||||
const invokedDirectly = process.argv[1]
|
||||
? fileURLToPath(import.meta.url) === resolve(process.argv[1])
|
||||
: false;
|
||||
if (invokedDirectly) {
|
||||
runCli().catch(e => {
|
||||
console.error(e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,63 @@
|
||||
{
|
||||
"name": "Установить начальную страницу (template + left/right + visibility/roles)",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Catalog", "name": "Товары" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/Catalogs/Товары", "-FormName": "ФормаСписка", "-Purpose": "List" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/Catalogs/Товары", "-FormName": "ФормаЭлемента", "-Purpose": "Object" }
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Catalog", "name": "Контрагенты" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/Catalogs/Контрагенты", "-FormName": "ФормаСписка", "-Purpose": "List" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/Catalogs/Контрагенты", "-FormName": "ФормаОбъекта", "-Purpose": "Object" }
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "DataProcessor", "name": "Поиск" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/DataProcessors/Поиск", "-FormName": "ФормаПоиска", "-Purpose": "Object" }
|
||||
},
|
||||
{
|
||||
"script": "role-compile/scripts/role-compile",
|
||||
"input": { "name": "Оператор", "objects": [] },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "role-compile/scripts/role-compile",
|
||||
"input": { "name": "ПолныеПрава", "objects": [] },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
}
|
||||
],
|
||||
"input": [
|
||||
{
|
||||
"operation": "set-home-page",
|
||||
"value": {
|
||||
"template": "TwoColumnsVariableWidth",
|
||||
"left": [
|
||||
"CommonForm.НачалоРаботы",
|
||||
{ "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false },
|
||||
"Catalog.Товары.Form.ФормаСписка",
|
||||
{ "form": "Catalog.Товары.Form.ФормаЭлемента", "height": 100, "visibility": false },
|
||||
{ "form": "Справочник.Контрагенты.ФормаСписка", "height": 50 },
|
||||
{
|
||||
"form": "CommonForm.РабочийСтолОператора",
|
||||
"form": "Catalog.Контрагенты.Form.ФормаОбъекта",
|
||||
"visibility": false,
|
||||
"roles": { "Role.Оператор": true, "ПолныеПрава": false }
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Остающийся.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Удаляемый.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
<?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>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="PredefinedDataName">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Predefined">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Ref">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="DeletionMark">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="IsFolder">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Owner">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Parent">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Description">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Code">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<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>Catalog.Контрагенты.Form.ФормаОбъекта</DefaultObjectForm>
|
||||
<DefaultFolderForm />
|
||||
<DefaultListForm>Catalog.Контрагенты.Form.ФормаСписка</DefaultListForm>
|
||||
<DefaultChoiceForm />
|
||||
<DefaultFolderChoiceForm />
|
||||
<AuxiliaryObjectForm />
|
||||
<AuxiliaryFolderForm />
|
||||
<AuxiliaryListForm />
|
||||
<AuxiliaryChoiceForm />
|
||||
<AuxiliaryFolderChoiceForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn />
|
||||
<DataLockFields />
|
||||
<DataLockControlMode>Automatic</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation />
|
||||
<ExtendedObjectPresentation />
|
||||
<ListPresentation />
|
||||
<ExtendedListPresentation />
|
||||
<Explanation />
|
||||
<CreateOnInput>DontUse</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>ФормаСписка</Form>
|
||||
<Form>ФормаОбъекта</Form>
|
||||
</ChildObjects>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ФормаОбъекта</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ФормаОбъекта</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:CatalogObject.Контрагенты</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ФормаСписка</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ФормаСписка</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Список" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DynamicList</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<Settings xsi:type="DynamicList">
|
||||
<MainTable>Catalog.Контрагенты</MainTable>
|
||||
</Settings>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
@@ -0,0 +1,330 @@
|
||||
<?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>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="PredefinedDataName">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Predefined">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Ref">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="DeletionMark">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="IsFolder">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Owner">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Parent">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Description">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Code">
|
||||
<xr:LinkByType />
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true" />
|
||||
<xr:ToolTip />
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format />
|
||||
<xr:ChoiceForm />
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat />
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true" />
|
||||
<xr:Synonym />
|
||||
<xr:Comment />
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks />
|
||||
<xr:FillValue xsi:nil="true" />
|
||||
<xr:Mask />
|
||||
<xr:ChoiceParameters />
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<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>Catalog.Товары.Form.ФормаЭлемента</DefaultObjectForm>
|
||||
<DefaultFolderForm />
|
||||
<DefaultListForm>Catalog.Товары.Form.ФормаСписка</DefaultListForm>
|
||||
<DefaultChoiceForm />
|
||||
<DefaultFolderChoiceForm />
|
||||
<AuxiliaryObjectForm />
|
||||
<AuxiliaryFolderForm />
|
||||
<AuxiliaryListForm />
|
||||
<AuxiliaryChoiceForm />
|
||||
<AuxiliaryFolderChoiceForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn />
|
||||
<DataLockFields />
|
||||
<DataLockControlMode>Automatic</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation />
|
||||
<ExtendedObjectPresentation />
|
||||
<ListPresentation />
|
||||
<ExtendedListPresentation />
|
||||
<Explanation />
|
||||
<CreateOnInput>DontUse</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>ФормаСписка</Form>
|
||||
<Form>ФормаЭлемента</Form>
|
||||
</ChildObjects>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ФормаСписка</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ФормаСписка</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Список" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DynamicList</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<Settings xsi:type="DynamicList">
|
||||
<MainTable>Catalog.Товары</MainTable>
|
||||
</Settings>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ФормаЭлемента</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ФормаЭлемента</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:CatalogObject.Товары</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
@@ -246,6 +246,11 @@
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
<Catalog>Товары</Catalog>
|
||||
<Catalog>Контрагенты</Catalog>
|
||||
<DataProcessor>Поиск</DataProcessor>
|
||||
<Role>Оператор</Role>
|
||||
<Role>ПолныеПрава</Role>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<DataProcessor uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DataProcessorObject.Поиск" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DataProcessorManager.Поиск" category="Manager">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Поиск</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Поиск</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<UseStandardCommands>false</UseStandardCommands>
|
||||
<DefaultForm>DataProcessor.Поиск.Form.ФормаПоиска</DefaultForm>
|
||||
<AuxiliaryForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<ExtendedPresentation />
|
||||
<Explanation />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>ФормаПоиска</Form>
|
||||
</ChildObjects>
|
||||
</DataProcessor>
|
||||
</MetaDataObject>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ФормаПоиска</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ФормаПоиска</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DataProcessorObject.Поиск</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
@@ -3,14 +3,14 @@
|
||||
<WorkingAreaTemplate>TwoColumnsVariableWidth</WorkingAreaTemplate>
|
||||
<LeftColumn>
|
||||
<Item>
|
||||
<Form>CommonForm.НачалоРаботы</Form>
|
||||
<Form>Catalog.Товары.Form.ФормаСписка</Form>
|
||||
<Height>10</Height>
|
||||
<Visibility>
|
||||
<xr:Common>true</xr:Common>
|
||||
</Visibility>
|
||||
</Item>
|
||||
<Item>
|
||||
<Form>CommonForm.СписокЗадач</Form>
|
||||
<Form>Catalog.Товары.Form.ФормаЭлемента</Form>
|
||||
<Height>100</Height>
|
||||
<Visibility>
|
||||
<xr:Common>false</xr:Common>
|
||||
@@ -24,7 +24,7 @@
|
||||
</Visibility>
|
||||
</Item>
|
||||
<Item>
|
||||
<Form>CommonForm.РабочийСтолОператора</Form>
|
||||
<Form>Catalog.Контрагенты.Form.ФормаОбъекта</Form>
|
||||
<Height>10</Height>
|
||||
<Visibility>
|
||||
<xr:Common>false</xr:Common>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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">
|
||||
<Role uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Оператор</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Оператор</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
</Properties>
|
||||
</Role>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Rights xmlns="http://v8.1c.ru/8.2/roles"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="Rights" version="2.17">
|
||||
<setForNewObjects>false</setForNewObjects>
|
||||
<setForAttributesByDefault>true</setForAttributesByDefault>
|
||||
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
|
||||
</Rights>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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">
|
||||
<Role uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>ПолныеПрава</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>ПолныеПрава</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
</Properties>
|
||||
</Role>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Rights xmlns="http://v8.1c.ru/8.2/roles"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="Rights" version="2.17">
|
||||
<setForNewObjects>false</setForNewObjects>
|
||||
<setForAttributesByDefault>true</setForAttributesByDefault>
|
||||
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
|
||||
</Rights>
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics />
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Товары.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<Characteristics />
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>true</QuickChoice>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Контрагенты.StandardAttribute.Description</xr:Field>
|
||||
|
||||
@@ -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>
|
||||
<DataProcessor>Задачи</DataProcessor>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<DataProcessor uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DataProcessorObject.Задачи" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DataProcessorManager.Задачи" category="Manager">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Задачи</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Задачи</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<UseStandardCommands>false</UseStandardCommands>
|
||||
<DefaultForm>DataProcessor.Задачи.Form.Форма</DefaultForm>
|
||||
<AuxiliaryForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<ExtendedPresentation />
|
||||
<Explanation />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>Форма</Form>
|
||||
</ChildObjects>
|
||||
</DataProcessor>
|
||||
</MetaDataObject>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Форма</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Форма</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Список задач</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<AutoTitle>false</AutoTitle>
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
|
||||
<ChildItems>
|
||||
<Table name="Список" id="1">
|
||||
<DataPath>Список</DataPath>
|
||||
<ContextMenu name="СписокКонтекстноеМеню" id="2"/>
|
||||
<AutoCommandBar name="СписокКоманднаяПанель" id="3"/>
|
||||
<SearchStringAddition name="СписокСтрокаПоиска" id="4"/>
|
||||
<ViewStatusAddition name="СписокСостояниеПросмотра" id="5"/>
|
||||
<SearchControlAddition name="СписокУправлениеПоиском" id="6"/>
|
||||
<ChildItems>
|
||||
<InputField name="Наименование" id="7">
|
||||
<DataPath>Список.Наименование</DataPath>
|
||||
<ContextMenu name="НаименованиеКонтекстноеМеню" id="8"/>
|
||||
<ExtendedTooltip name="НаименованиеРасширеннаяПодсказка" id="9"/>
|
||||
</InputField>
|
||||
<ColumnGroup name="ГруппаСрок" id="10">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Срок</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<Group>Horizontal</Group>
|
||||
<ExtendedTooltip name="ГруппаСрокРасширеннаяПодсказка" id="11"/>
|
||||
<ChildItems>
|
||||
<InputField name="ДатаНачала" id="12">
|
||||
<DataPath>Список.ДатаНачала</DataPath>
|
||||
<ContextMenu name="ДатаНачалаКонтекстноеМеню" id="13"/>
|
||||
<ExtendedTooltip name="ДатаНачалаРасширеннаяПодсказка" id="14"/>
|
||||
</InputField>
|
||||
<InputField name="ДатаОкончания" id="15">
|
||||
<DataPath>Список.ДатаОкончания</DataPath>
|
||||
<ContextMenu name="ДатаОкончанияКонтекстноеМеню" id="16"/>
|
||||
<ExtendedTooltip name="ДатаОкончанияРасширеннаяПодсказка" id="17"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
</ColumnGroup>
|
||||
<ColumnGroup name="ГруппаИсполнитель" id="18">
|
||||
<Group>InCell</Group>
|
||||
<ShowInHeader>true</ShowInHeader>
|
||||
<ExtendedTooltip name="ГруппаИсполнительРасширеннаяПодсказка" id="19"/>
|
||||
<ChildItems>
|
||||
<InputField name="Исполнитель" id="20">
|
||||
<DataPath>Список.Исполнитель</DataPath>
|
||||
<ContextMenu name="ИсполнительКонтекстноеМеню" id="21"/>
|
||||
<ExtendedTooltip name="ИсполнительРасширеннаяПодсказка" id="22"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
</ColumnGroup>
|
||||
</ChildItems>
|
||||
</Table>
|
||||
</ChildItems>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="23">
|
||||
<Type>
|
||||
<v8:Type>cfg:DataProcessorObject.Задачи</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
<Attribute name="Список" id="24">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Список</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<Type>
|
||||
<v8:Type>v8:ValueTable</v8:Type>
|
||||
</Type>
|
||||
<Columns>
|
||||
<Column name="Наименование" id="25">
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>150</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
</Column>
|
||||
<Column name="ДатаНачала" id="26">
|
||||
<Type>
|
||||
<v8:Type>xs:dateTime</v8:Type>
|
||||
<v8:DateQualifiers>
|
||||
<v8:DateFractions>Date</v8:DateFractions>
|
||||
</v8:DateQualifiers>
|
||||
</Type>
|
||||
</Column>
|
||||
<Column name="ДатаОкончания" id="27">
|
||||
<Type>
|
||||
<v8:Type>xs:dateTime</v8:Type>
|
||||
<v8:DateQualifiers>
|
||||
<v8:DateFractions>Date</v8:DateFractions>
|
||||
</v8:DateQualifiers>
|
||||
</Type>
|
||||
</Column>
|
||||
<Column name="Исполнитель" id="28">
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>100</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
</Column>
|
||||
</Columns>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
+18
@@ -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>
|
||||
@@ -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>
|
||||
<DataProcessor>ЗапретРучногоВвода</DataProcessor>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<DataProcessor uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DataProcessorObject.ЗапретРучногоВвода" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DataProcessorManager.ЗапретРучногоВвода" category="Manager">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>ЗапретРучногоВвода</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Запрет ручного ввода</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<UseStandardCommands>false</UseStandardCommands>
|
||||
<DefaultForm>DataProcessor.ЗапретРучногоВвода.Form.Форма</DefaultForm>
|
||||
<AuxiliaryForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<ExtendedPresentation />
|
||||
<Explanation />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>Форма</Form>
|
||||
</ChildObjects>
|
||||
</DataProcessor>
|
||||
</MetaDataObject>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?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">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Форма</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Форма</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" 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: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">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Запрет ручного ввода</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<AutoTitle>false</AutoTitle>
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
|
||||
<ChildItems>
|
||||
<InputField name="ОбычноеПоле" id="1">
|
||||
<DataPath>ОбычноеПоле</DataPath>
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Обычное поле</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<ContextMenu name="ОбычноеПолеКонтекстноеМеню" id="2"/>
|
||||
<ExtendedTooltip name="ОбычноеПолеРасширеннаяПодсказка" id="3"/>
|
||||
</InputField>
|
||||
<InputField name="ПолеБезРучногоВвода" id="4">
|
||||
<DataPath>ПолеБезРучногоВвода</DataPath>
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Только через выбор</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<TextEdit>false</TextEdit>
|
||||
<ContextMenu name="ПолеБезРучногоВводаКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="ПолеБезРучногоВводаРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="7">
|
||||
<Type>
|
||||
<v8:Type>cfg:DataProcessorObject.ЗапретРучногоВвода</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
<Attribute name="ОбычноеПоле" id="8">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Обычное поле</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>100</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
</Attribute>
|
||||
<Attribute name="ПолеБезРучногоВвода" id="9">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Поле без ручного ввода</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>100</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
+18
@@ -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>
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Поле ввода с textEdit:false (запрет ручного ввода)",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "DataProcessor", "name": "ЗапретРучногоВвода" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "form-add/scripts/form-add",
|
||||
"args": { "-ObjectPath": "{workDir}/DataProcessors/ЗапретРучногоВвода.xml", "-FormName": "Форма" }
|
||||
}
|
||||
],
|
||||
"params": { "outputPath": "DataProcessors/ЗапретРучногоВвода/Forms/Форма/Ext/Form.xml" },
|
||||
"validatePath": "DataProcessors/ЗапретРучногоВвода/Forms/Форма/Ext/Form.xml",
|
||||
"input": {
|
||||
"title": "Запрет ручного ввода",
|
||||
"elements": [
|
||||
{ "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле" },
|
||||
{ "input": "ПолеБезРучногоВвода", "path": "ПолеБезРучногоВвода", "textEdit": false, "title": "Только через выбор" }
|
||||
],
|
||||
"attributes": [
|
||||
{ "name": "Объект", "type": "DataProcessorObject.ЗапретРучногоВвода", "main": true },
|
||||
{ "name": "ОбычноеПоле", "type": "string(100)" },
|
||||
{ "name": "ПолеБезРучногоВвода", "type": "string(100)" }
|
||||
]
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user