diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index d337b23b..16259fec 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.103 — Compile 1C managed form from JSON or object metadata +# form-compile v1.104 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2168,6 +2168,7 @@ function Emit-SingleType { "d5p1:TextDocument" = "http://v8.1c.ru/8.1/data/txtedt" "d5p1:Chart" = "http://v8.1c.ru/8.2/data/chart" "d5p1:GanttChart" = "http://v8.1c.ru/8.2/data/chart" + "d5p1:Dendrogram" = "http://v8.1c.ru/8.2/data/chart" "d5p1:FlowchartContextType" = "http://v8.1c.ru/8.2/data/graphscheme" "d5p1:DataAnalysisTimeIntervalUnitType" = "http://v8.1c.ru/8.2/data/data-analysis" "d5p1:GeographicalSchema" = "http://v8.1c.ru/8.2/data/geo" @@ -3022,6 +3023,164 @@ function Emit-BorderTag { X "$indent" } +# ───────────────────────────────────────────────────────────────────────────── +# Planner design-time — встроенный конфиг планировщика +# на реквизите planner-типа. Структурный DSL: items[] + appearance/поведение-скаляры + +# timeScale (уровни шкалы времени) + period. Каждое присутствующее поле → каноничный +# порядок; пропущенное → дефолт (планировщик всегда несёт полный блок). Декомпилятор +# делает полный захват → раундтрип бит-в-бит; ручной авторинг может быть кратким. +$script:PLANNER_NS = 'http://v8.1c.ru/8.3/data/planner' +$script:CHART_NS = 'http://v8.1c.ru/8.2/data/chart' + +function PL-Get { + param($o, [string]$k, $def = $null) + if ($null -ne $o -and $o.PSObject.Properties[$k] -and $null -ne $o.$k) { return $o.$k } + return $def +} +function PL-Bool { + param($v) + if ($v -is [bool]) { if ($v) { 'true' } else { 'false' } } + elseif ("$v" -eq 'True') { 'true' } + elseif ("$v" -eq 'False') { 'false' } + else { "$v" } +} +function Emit-PlannerColor { + param([string]$tag, $o, [string]$key, [string]$ind) + X "$ind$(Esc-Xml "$(PL-Get $o $key 'auto')")" +} +function Emit-PlannerFont { + param($o, [string]$ind) + $f = PL-Get $o 'font' $null + if ($null -eq $f) { X "$ind"; return } + Emit-FontTag -tag 'pl:font' -val $f -indent $ind +} +function Emit-PlannerBorder { + param($o, [string]$ind, [string]$key = 'border') + $b = PL-Get $o $key $null + $bw = if ($b) { PL-Get $b 'width' 1 } else { 1 } + $bs = if ($b) { PL-Get $b 'style' 'Single' } else { 'Single' } + X "$ind" + X "$ind`t$(Esc-Xml "$bs")" + X "$ind" +} +function Emit-PlannerLevel { + param($lv, [string]$cns, [string]$ind) + $li = "$ind`t" + X "$ind" + X "$li$(Esc-Xml "$(PL-Get $lv 'measure' 'Hour')")" + X "$li$(PL-Get $lv 'interval' 1)" + X "$li$(PL-Bool (PL-Get $lv 'show' $true))" + $line = PL-Get $lv 'line' $null + $lw = if ($line) { PL-Get $line 'width' 1 } else { 1 } + $lg = if ($line) { PL-Get $line 'gap' $false } else { $false } + $lst = if ($line) { PL-Get $line 'style' 'Solid' } else { 'Solid' } + X "$li" + X "$li`t$(Esc-Xml "$lst")" + X "$li" + X "$li$(Esc-Xml "$(PL-Get $lv 'scaleColor' 'auto')")" + X "$li$(Esc-Xml "$(PL-Get $lv 'dayFormatRule' 'MonthDayWeekDay')")" + $fmt = PL-Get $lv 'format' $null + if ($null -eq $fmt) { $fmt = [ordered]@{ '#' = 'DF="HH:mm"'; 'ru' = 'DF="HH:mm"' } } + X "$li" + Emit-MLItems -val $fmt -indent "$li`t" + X "$li" + $labels = PL-Get $lv 'labels' $null + $ticks = if ($labels) { PL-Get $labels 'ticks' 0 } else { 0 } + X "$li" + X "$li`t$ticks" + X "$li" + X "$li$(Esc-Xml "$(PL-Get $lv 'backColor' 'auto')")" + X "$li$(Esc-Xml "$(PL-Get $lv 'textColor' 'auto')")" + X "$li$(PL-Bool (PL-Get $lv 'showPereodicalLabels' $true))" + X "$ind" +} +function Emit-PlannerTimeScale { + param($ts, [string]$ind) + $cns = $script:CHART_NS + $ci = "$ind`t" + X "$ind" + X "$ci$(Esc-Xml "$(if ($ts) { PL-Get $ts 'placement' 'Left' } else { 'Left' })")" + $levels = if ($ts) { @(PL-Get $ts 'levels' @()) } else { @() } + if (@($levels).Count -eq 0) { $levels = @($null) } # один уровень-дефолт + foreach ($lv in $levels) { Emit-PlannerLevel $lv $cns $ci } + $transp = if ($ts) { PL-Get $ts 'transparent' $false } else { $false } + X "$ci$(PL-Bool $transp)" + X "$ci$(Esc-Xml "$(if ($ts) { PL-Get $ts 'backColor' 'auto' } else { 'auto' })")" + X "$ci$(Esc-Xml "$(if ($ts) { PL-Get $ts 'textColor' 'auto' } else { 'auto' })")" + X "$ci$(if ($ts) { PL-Get $ts 'currentLevel' 0 } else { 0 })" + X "$ind" +} +function Emit-PlannerItem { + param($it, [string]$ind) + X "$ind" + $ii = "$ind`t" + $val = PL-Get $it 'value' $null + if ($null -eq $val) { X "$ii" } + else { X "$ii$(Esc-Xml "$val")" } + X "$ii$(Esc-Xml "$(PL-Get $it 'text' '')")" + $tt = PL-Get $it 'tooltip' '' + if ("$tt" -eq '') { X "$ii" } else { X "$ii$(Esc-Xml "$tt")" } + X "$ii$(PL-Get $it 'begin' '0001-01-01T00:00:00')" + X "$ii$(PL-Get $it 'end' '0001-01-01T00:00:00')" + Emit-PlannerColor 'borderColor' $it 'borderColor' $ii + Emit-PlannerColor 'backColor' $it 'backColor' $ii + Emit-PlannerColor 'textColor' $it 'textColor' $ii + Emit-PlannerFont $it $ii + X "$ii" + X "$ii$(PL-Get $it 'replacementDate' '0001-01-01T00:00:00')" + X "$ii$(PL-Bool (PL-Get $it 'deleted' $false))" + $id = PL-Get $it 'id' $null + if ($null -eq $id) { $id = [guid]::NewGuid().ToString() } + X "$ii$id" + X "$ii$(PL-Bool (PL-Get $it 'textFormatted' $false))" + Emit-PlannerBorder $it $ii 'border' + X "$ii$(Esc-Xml "$(PL-Get $it 'editMode' 'EnableEdit')")" + X "$ind" +} +function Emit-PlannerSettings { + param($pl, [string]$ind) + X "$ind" + $si = "$ind`t" + foreach ($it in @(PL-Get $pl 'items' @())) { Emit-PlannerItem $it $si } + Emit-PlannerColor 'borderColor' $pl 'borderColor' $si + Emit-PlannerColor 'backColor' $pl 'backColor' $si + Emit-PlannerColor 'textColor' $pl 'textColor' $si + Emit-PlannerColor 'lineColor' $pl 'lineColor' $si + Emit-PlannerFont $pl $si + X "$si$(PL-Get $pl 'beginOfRepresentationPeriod' '0001-01-01T00:00:00')" + X "$si$(PL-Get $pl 'endOfRepresentationPeriod' '0001-01-01T00:00:00')" + X "$si$(PL-Bool (PL-Get $pl 'alignElementsOfTimeScale' $true))" + X "$si$(PL-Bool (PL-Get $pl 'displayTimeScaleWrapHeaders' $true))" + X "$si$(PL-Bool (PL-Get $pl 'displayWrapHeaders' $true))" + $wfmt = PL-Get $pl 'timeScaleWrapHeadersFormat' $null + if ($null -eq $wfmt) { $wfmt = [ordered]@{ '#' = 'DLF="DD"'; 'ru' = 'DLF="DD"' } } + Emit-MLText -tag 'pl:timeScaleWrapHeadersFormat' -text $wfmt -indent $si + X "$si$(Esc-Xml "$(PL-Get $pl 'periodicVariantUnit' 'Day')")" + X "$si$(PL-Get $pl 'periodicVariantRepetition' 1)" + X "$si$(PL-Get $pl 'timeScaleWrapBeginIndent' 0)" + X "$si$(PL-Get $pl 'timeScaleWrapEndIndent' 0)" + Emit-PlannerTimeScale (PL-Get $pl 'timeScale' $null) $si + $period = PL-Get $pl 'period' $null + if ($period) { + X "$si" + X "$si`t$(PL-Get $period 'begin' '0001-01-01T00:00:00')" + X "$si`t$(PL-Get $period 'end' '0001-01-01T00:00:00')" + X "$si" + } + X "$si$(PL-Bool (PL-Get $pl 'displayCurrentDate' $true))" + X "$si$(Esc-Xml "$(PL-Get $pl 'itemsTimeRepresentation' 'BeginTime')")" + X "$si$(Esc-Xml "$(PL-Get $pl 'itemsBehaviorWhenSpaceInsufficient' 'CollapseItems')")" + X "$si$(PL-Bool (PL-Get $pl 'autoMinColumnWidth' $true))" + X "$si$(PL-Bool (PL-Get $pl 'autoMinRowHeight' $true))" + X "$si$(PL-Get $pl 'minColumnWidth' 0)" + X "$si$(PL-Get $pl 'minRowHeight' 0)" + X "$si$(Esc-Xml "$(PL-Get $pl 'fixDimensionsHeader' 'auto')")" + X "$si$(Esc-Xml "$(PL-Get $pl 'fixTimeScaleHeader' 'auto')")" + Emit-PlannerBorder $pl $si 'border' + X "$si$(Esc-Xml "$(PL-Get $pl 'newItemsTextType' 'String')")" + X "$ind" +} + function Emit-Appearance { param($el, [string]$indent, [string]$profile = 'field') if ($null -eq $el) { return } @@ -4766,6 +4925,11 @@ function Emit-Attributes { if ($hasVt) { Emit-Type -typeStr "$vtSpec" -indent $inner -tag "Settings" -tagAttrs ' xsi:type="v8:TypeDescription"' } + # Planner design-time (встроенный конфиг планировщика). + # Идёт сразу после (как valueType/DynamicList Settings — взаимоисключающи). + if ($attr.PSObject.Properties['planner'] -and $null -ne $attr.planner) { + Emit-PlannerSettings -pl $attr.planner -ind $inner + } if ($attr.main -eq $true) { X "$innertrue" diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 35fee7c8..d2089903 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.103 — Compile 1C managed form from JSON or object metadata +# form-compile v1.104 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -2714,6 +2714,181 @@ def emit_border_tag(lines, val, indent): lines.append(f'{indent}') +# ───────────────────────────────────────────────────────────────────────────── +# Planner design-time — зеркало Emit-PlannerSettings (ps1). +PLANNER_NS = 'http://v8.1c.ru/8.3/data/planner' +CHART_NS = 'http://v8.1c.ru/8.2/data/chart' + + +def _pl_get(o, k, default=None): + if isinstance(o, dict) and o.get(k) is not None: + return o[k] + return default + + +def _pl_bool(v): + if isinstance(v, bool): + return 'true' if v else 'false' + if str(v) == 'True': + return 'true' + if str(v) == 'False': + return 'false' + return str(v) + + +def emit_planner_color(lines, tag, o, key, ind): + lines.append(f'{ind}{esc_xml(str(_pl_get(o, key, "auto")))}') + + +def emit_planner_font(lines, o, ind): + f = _pl_get(o, 'font') + if f is None: + lines.append(f'{ind}') + return + emit_font_tag(lines, 'pl:font', f, ind) + + +def emit_planner_border(lines, o, ind, key='border'): + b = _pl_get(o, key) + bw = _pl_get(b, 'width', 1) if b else 1 + bs = _pl_get(b, 'style', 'Single') if b else 'Single' + lines.append(f'{ind}') + lines.append(f'{ind}\t{esc_xml(str(bs))}') + lines.append(f'{ind}') + + +def emit_planner_level(lines, lv, cns, ind): + li = f'{ind}\t' + lines.append(f'{ind}') + lines.append(f'{li}{esc_xml(str(_pl_get(lv, "measure", "Hour")))}') + lines.append(f'{li}{_pl_get(lv, "interval", 1)}') + lines.append(f'{li}{_pl_bool(_pl_get(lv, "show", True))}') + line = _pl_get(lv, 'line') + lw = _pl_get(line, 'width', 1) if line else 1 + lg = _pl_get(line, 'gap', False) if line else False + lst = _pl_get(line, 'style', 'Solid') if line else 'Solid' + lines.append(f'{li}') + lines.append(f'{li}\t{esc_xml(str(lst))}') + lines.append(f'{li}') + lines.append(f'{li}{esc_xml(str(_pl_get(lv, "scaleColor", "auto")))}') + lines.append(f'{li}{esc_xml(str(_pl_get(lv, "dayFormatRule", "MonthDayWeekDay")))}') + fmt = _pl_get(lv, 'format') + if fmt is None: + fmt = {'#': 'DF="HH:mm"', 'ru': 'DF="HH:mm"'} + lines.append(f'{li}') + emit_ml_items(lines, f'{li}\t', fmt) + lines.append(f'{li}') + labels = _pl_get(lv, 'labels') + ticks = _pl_get(labels, 'ticks', 0) if labels else 0 + lines.append(f'{li}') + lines.append(f'{li}\t{ticks}') + lines.append(f'{li}') + lines.append(f'{li}{esc_xml(str(_pl_get(lv, "backColor", "auto")))}') + lines.append(f'{li}{esc_xml(str(_pl_get(lv, "textColor", "auto")))}') + lines.append(f'{li}{_pl_bool(_pl_get(lv, "showPereodicalLabels", True))}') + lines.append(f'{ind}') + + +def emit_planner_timescale(lines, ts, ind): + cns = CHART_NS + ci = f'{ind}\t' + lines.append(f'{ind}') + placement = _pl_get(ts, 'placement', 'Left') if ts else 'Left' + lines.append(f'{ci}{esc_xml(str(placement))}') + levels = _pl_get(ts, 'levels', []) if ts else [] + if not levels: + levels = [None] + for lv in levels: + emit_planner_level(lines, lv, cns, ci) + transp = _pl_get(ts, 'transparent', False) if ts else False + lines.append(f'{ci}{_pl_bool(transp)}') + tbc = _pl_get(ts, 'backColor', 'auto') if ts else 'auto' + ttc = _pl_get(ts, 'textColor', 'auto') if ts else 'auto' + tcl = _pl_get(ts, 'currentLevel', 0) if ts else 0 + lines.append(f'{ci}{esc_xml(str(tbc))}') + lines.append(f'{ci}{esc_xml(str(ttc))}') + lines.append(f'{ci}{tcl}') + lines.append(f'{ind}') + + +def emit_planner_item(lines, it, ind): + lines.append(f'{ind}') + ii = f'{ind}\t' + val = _pl_get(it, 'value') + if val is None: + lines.append(f'{ii}') + else: + lines.append(f'{ii}{esc_xml(str(val))}') + lines.append(f'{ii}{esc_xml(str(_pl_get(it, "text", "")))}') + tt = _pl_get(it, 'tooltip', '') + if str(tt) == '': + lines.append(f'{ii}') + else: + lines.append(f'{ii}{esc_xml(str(tt))}') + lines.append(f'{ii}{_pl_get(it, "begin", "0001-01-01T00:00:00")}') + lines.append(f'{ii}{_pl_get(it, "end", "0001-01-01T00:00:00")}') + emit_planner_color(lines, 'borderColor', it, 'borderColor', ii) + emit_planner_color(lines, 'backColor', it, 'backColor', ii) + emit_planner_color(lines, 'textColor', it, 'textColor', ii) + emit_planner_font(lines, it, ii) + lines.append(f'{ii}') + lines.append(f'{ii}{_pl_get(it, "replacementDate", "0001-01-01T00:00:00")}') + lines.append(f'{ii}{_pl_bool(_pl_get(it, "deleted", False))}') + iid = _pl_get(it, 'id') + if iid is None: + import uuid + iid = str(uuid.uuid4()) + lines.append(f'{ii}{iid}') + lines.append(f'{ii}{_pl_bool(_pl_get(it, "textFormatted", False))}') + emit_planner_border(lines, it, ii, 'border') + lines.append(f'{ii}{esc_xml(str(_pl_get(it, "editMode", "EnableEdit")))}') + lines.append(f'{ind}') + + +def emit_planner_settings(lines, pl, ind): + lines.append(f'{ind}') + si = f'{ind}\t' + for it in _pl_get(pl, 'items', []): + emit_planner_item(lines, it, si) + emit_planner_color(lines, 'borderColor', pl, 'borderColor', si) + emit_planner_color(lines, 'backColor', pl, 'backColor', si) + emit_planner_color(lines, 'textColor', pl, 'textColor', si) + emit_planner_color(lines, 'lineColor', pl, 'lineColor', si) + emit_planner_font(lines, pl, si) + lines.append(f'{si}{_pl_get(pl, "beginOfRepresentationPeriod", "0001-01-01T00:00:00")}') + lines.append(f'{si}{_pl_get(pl, "endOfRepresentationPeriod", "0001-01-01T00:00:00")}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "alignElementsOfTimeScale", True))}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "displayTimeScaleWrapHeaders", True))}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "displayWrapHeaders", True))}') + wfmt = _pl_get(pl, 'timeScaleWrapHeadersFormat') + if wfmt is None: + wfmt = {'#': 'DLF="DD"', 'ru': 'DLF="DD"'} + emit_mltext(lines, si, 'pl:timeScaleWrapHeadersFormat', wfmt) + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "periodicVariantUnit", "Day")))}') + lines.append(f'{si}{_pl_get(pl, "periodicVariantRepetition", 1)}') + lines.append(f'{si}{_pl_get(pl, "timeScaleWrapBeginIndent", 0)}') + lines.append(f'{si}{_pl_get(pl, "timeScaleWrapEndIndent", 0)}') + emit_planner_timescale(lines, _pl_get(pl, 'timeScale'), si) + period = _pl_get(pl, 'period') + if period: + lines.append(f'{si}') + lines.append(f'{si}\t{_pl_get(period, "begin", "0001-01-01T00:00:00")}') + lines.append(f'{si}\t{_pl_get(period, "end", "0001-01-01T00:00:00")}') + lines.append(f'{si}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "displayCurrentDate", True))}') + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "itemsTimeRepresentation", "BeginTime")))}') + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "itemsBehaviorWhenSpaceInsufficient", "CollapseItems")))}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "autoMinColumnWidth", True))}') + lines.append(f'{si}{_pl_bool(_pl_get(pl, "autoMinRowHeight", True))}') + lines.append(f'{si}{_pl_get(pl, "minColumnWidth", 0)}') + lines.append(f'{si}{_pl_get(pl, "minRowHeight", 0)}') + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "fixDimensionsHeader", "auto")))}') + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "fixTimeScaleHeader", "auto")))}') + emit_planner_border(lines, pl, si, 'border') + lines.append(f'{si}{esc_xml(str(_pl_get(pl, "newItemsTextType", "String")))}') + lines.append(f'{ind}') + + def emit_appearance(lines, el, indent, profile='field'): if not isinstance(el, dict): return @@ -3063,6 +3238,7 @@ def emit_single_type(lines, type_str, indent): "d5p1:TextDocument": "http://v8.1c.ru/8.1/data/txtedt", "d5p1:Chart": "http://v8.1c.ru/8.2/data/chart", "d5p1:GanttChart": "http://v8.1c.ru/8.2/data/chart", + "d5p1:Dendrogram": "http://v8.1c.ru/8.2/data/chart", "d5p1:FlowchartContextType": "http://v8.1c.ru/8.2/data/graphscheme", "d5p1:DataAnalysisTimeIntervalUnitType": "http://v8.1c.ru/8.2/data/data-analysis", "d5p1:GeographicalSchema": "http://v8.1c.ru/8.2/data/geo", @@ -4480,6 +4656,9 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None): break if has_vt: emit_type(lines, '' if vt_spec is None else str(vt_spec), inner, tag="Settings", tag_attrs=' xsi:type="v8:TypeDescription"') + # Planner design-time (встроенный конфиг планировщика). + if attr.get('planner') is not None: + emit_planner_settings(lines, attr['planner'], inner) if attr.get('main') is True: lines.append(f'{inner}true') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 496979ac..545309d8 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.79 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.80 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -152,7 +152,7 @@ foreach ($el in $xmlDoc.SelectNodes("//*[local-name()='ConditionalAppearance']/* # (встроенная конфигурация диаграммы) пока НЕ воспроизводим → честный скип, чтобы не потерять молча. foreach ($s in $xmlDoc.SelectNodes("//*[local-name()='Attribute']/*[local-name()='Settings']")) { $st = $s.GetAttribute("type", $NS_XSI) - if ($st -and $st -notmatch 'TypeDescription$' -and $st -notmatch 'DynamicList$') { + if ($st -and $st -notmatch 'TypeDescription$' -and $st -notmatch 'DynamicList$' -and $st -notmatch 'Planner$') { Fail-Ring3 -kind "Attribute>Settings типа '$st' (design-time конфигурация, напр. диаграмма)" -loc "Attribute/Settings" } } @@ -1833,6 +1833,131 @@ function Decompile-Element { return $obj } +# ───────────────────────────────────────────────────────────────────────────── +# Planner design-time → объект planner на реквизите. +# Полный захват каждого поля (раундтрип бит-в-бит); зеркало Emit-PlannerSettings. +function PLD-Bool { param($v) if ($null -eq $v) { return $null } return ($v -eq 'true') } +function PLD-Int { param($v) if ($null -eq $v) { return $null } return [int]$v } +function Build-PlannerFont { + param($node) + if (-not $node) { return $null } + $o = [ordered]@{} + foreach ($a in @('ref','faceName','height','bold','italic','underline','strikeout','kind','scale')) { + $av = $node.GetAttribute($a); if ($av -ne '') { $o[$a] = $av } + } + if ($o.Count -eq 0) { return $null } + return $o +} +function Build-PlannerBorder { + param($node) + if (-not $node) { return $null } + $o = [ordered]@{} + $w = $node.GetAttribute('width'); if ($w -ne '') { $o['width'] = [int]$w } + $st = $node.SelectSingleNode("*[local-name()='style']"); if ($st) { $o['style'] = $st.InnerText } + return $o +} +function Build-PlannerItem { + param($itn) + $o = [ordered]@{} + $valNode = $itn.SelectSingleNode("*[local-name()='value']") + if ($valNode -and $valNode.GetAttribute('nil', $NS_XSI) -ne 'true' -and $valNode.InnerText) { $o['value'] = $valNode.InnerText } + $o['text'] = (Get-Child $itn 'text') + $tt = Get-Child $itn 'tooltip'; if ($tt) { $o['tooltip'] = $tt } + $o['begin'] = (Get-Child $itn 'begin') + $o['end'] = (Get-Child $itn 'end') + $o['borderColor'] = (Get-Child $itn 'borderColor') + $o['backColor'] = (Get-Child $itn 'backColor') + $o['textColor'] = (Get-Child $itn 'textColor') + $fnt = Build-PlannerFont ($itn.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $o['font'] = $fnt } + $o['replacementDate'] = (Get-Child $itn 'replacementDate') + $o['deleted'] = (PLD-Bool (Get-Child $itn 'deleted')) + $o['id'] = (Get-Child $itn 'id') + $o['textFormatted'] = (PLD-Bool (Get-Child $itn 'textFormatted')) + $brd = Build-PlannerBorder ($itn.SelectSingleNode("*[local-name()='border']")); if ($brd) { $o['border'] = $brd } + $o['editMode'] = (Get-Child $itn 'editMode') + return $o +} +function Build-PlannerLevel { + param($lvn) + $o = [ordered]@{} + $o['measure'] = (Get-Child $lvn 'measure') + $o['interval'] = (PLD-Int (Get-Child $lvn 'interval')) + $o['show'] = (PLD-Bool (Get-Child $lvn 'show')) + $lineNode = $lvn.SelectSingleNode("*[local-name()='line']") + if ($lineNode) { + $ln = [ordered]@{} + $w = $lineNode.GetAttribute('width'); if ($w -ne '') { $ln['width'] = [int]$w } + $g = $lineNode.GetAttribute('gap'); if ($g -ne '') { $ln['gap'] = ($g -eq 'true') } + $st = $lineNode.SelectSingleNode("*[local-name()='style']"); if ($st) { $ln['style'] = $st.InnerText } + $o['line'] = $ln + } + $o['scaleColor'] = (Get-Child $lvn 'scaleColor') + $o['dayFormatRule'] = (Get-Child $lvn 'dayFormatRule') + $fmtNode = $lvn.SelectSingleNode("*[local-name()='format']") + if ($fmtNode) { $f = Get-LangText $fmtNode; if ($null -ne $f) { $o['format'] = $f } } + $labelsNode = $lvn.SelectSingleNode("*[local-name()='labels']") + if ($labelsNode) { $o['labels'] = [ordered]@{ ticks = (PLD-Int (Get-Child $labelsNode 'ticks')) } } + $o['backColor'] = (Get-Child $lvn 'backColor') + $o['textColor'] = (Get-Child $lvn 'textColor') + $o['showPereodicalLabels'] = (PLD-Bool (Get-Child $lvn 'showPereodicalLabels')) + return $o +} +function Build-PlannerTimeScale { + param($tsn) + $o = [ordered]@{} + $o['placement'] = (Get-Child $tsn 'placement') + $levels = New-Object System.Collections.ArrayList + foreach ($lvn in @($tsn.SelectNodes("*[local-name()='level']"))) { [void]$levels.Add((Build-PlannerLevel $lvn)) } + $o['levels'] = @($levels) + $o['transparent'] = (PLD-Bool (Get-Child $tsn 'transparent')) + $o['backColor'] = (Get-Child $tsn 'backColor') + $o['textColor'] = (Get-Child $tsn 'textColor') + $o['currentLevel'] = (PLD-Int (Get-Child $tsn 'currentLevel')) + return $o +} +function Build-PlannerSettings { + param($setNode) + $pl = [ordered]@{} + $itemNodes = @($setNode.SelectNodes("*[local-name()='item']")) + if ($itemNodes.Count -gt 0) { + $items = New-Object System.Collections.ArrayList + foreach ($itn in $itemNodes) { [void]$items.Add((Build-PlannerItem $itn)) } + $pl['items'] = @($items) + } + $pl['borderColor'] = (Get-Child $setNode 'borderColor') + $pl['backColor'] = (Get-Child $setNode 'backColor') + $pl['textColor'] = (Get-Child $setNode 'textColor') + $pl['lineColor'] = (Get-Child $setNode 'lineColor') + $fnt = Build-PlannerFont ($setNode.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $pl['font'] = $fnt } + $pl['beginOfRepresentationPeriod'] = (Get-Child $setNode 'beginOfRepresentationPeriod') + $pl['endOfRepresentationPeriod'] = (Get-Child $setNode 'endOfRepresentationPeriod') + $pl['alignElementsOfTimeScale'] = (PLD-Bool (Get-Child $setNode 'alignElementsOfTimeScale')) + $pl['displayTimeScaleWrapHeaders'] = (PLD-Bool (Get-Child $setNode 'displayTimeScaleWrapHeaders')) + $pl['displayWrapHeaders'] = (PLD-Bool (Get-Child $setNode 'displayWrapHeaders')) + $wfNode = $setNode.SelectSingleNode("*[local-name()='timeScaleWrapHeadersFormat']") + if ($wfNode) { $wf = Get-LangText $wfNode; if ($null -ne $wf) { $pl['timeScaleWrapHeadersFormat'] = $wf } } + $pl['periodicVariantUnit'] = (Get-Child $setNode 'periodicVariantUnit') + $pl['periodicVariantRepetition'] = (PLD-Int (Get-Child $setNode 'periodicVariantRepetition')) + $pl['timeScaleWrapBeginIndent'] = (PLD-Int (Get-Child $setNode 'timeScaleWrapBeginIndent')) + $pl['timeScaleWrapEndIndent'] = (PLD-Int (Get-Child $setNode 'timeScaleWrapEndIndent')) + $tsNode = $setNode.SelectSingleNode("*[local-name()='timeScale']") + if ($tsNode) { $pl['timeScale'] = (Build-PlannerTimeScale $tsNode) } + $perNode = $setNode.SelectSingleNode("*[local-name()='period']") + if ($perNode) { $pl['period'] = [ordered]@{ begin = (Get-Child $perNode 'begin'); end = (Get-Child $perNode 'end') } } + $pl['displayCurrentDate'] = (PLD-Bool (Get-Child $setNode 'displayCurrentDate')) + $pl['itemsTimeRepresentation'] = (Get-Child $setNode 'itemsTimeRepresentation') + $pl['itemsBehaviorWhenSpaceInsufficient'] = (Get-Child $setNode 'itemsBehaviorWhenSpaceInsufficient') + $pl['autoMinColumnWidth'] = (PLD-Bool (Get-Child $setNode 'autoMinColumnWidth')) + $pl['autoMinRowHeight'] = (PLD-Bool (Get-Child $setNode 'autoMinRowHeight')) + $pl['minColumnWidth'] = (PLD-Int (Get-Child $setNode 'minColumnWidth')) + $pl['minRowHeight'] = (PLD-Int (Get-Child $setNode 'minRowHeight')) + $pl['fixDimensionsHeader'] = (Get-Child $setNode 'fixDimensionsHeader') + $pl['fixTimeScaleHeader'] = (Get-Child $setNode 'fixTimeScaleHeader') + $brd = Build-PlannerBorder ($setNode.SelectSingleNode("*[local-name()='border']")); if ($brd) { $pl['border'] = $brd } + $pl['newItemsTextType'] = (Get-Child $setNode 'newItemsTextType') + return $pl +} + # --- 5. Form-level assembly --- $dsl = [ordered]@{} @@ -1964,6 +2089,10 @@ if ($attrsNode) { $vt = Decompile-Type $setNode $ao['valueType'] = if ($vt) { $vt } else { '' } # пустой Settings → маркер "" } + # Planner design-time → объект planner (полный захват). + elseif ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'Planner$') { + $ao['planner'] = Build-PlannerSettings $setNode + } if ((Get-Child $a 'MainAttribute') -eq 'true') { $ao['main'] = $true } elseif ($suppressMainName -and $ao['name'] -eq $suppressMainName) { $ao['main'] = $false } $vw = Decompile-XrFlag $a 'View'; if ($null -ne $vw) { $ao['view'] = $vw } @@ -2053,9 +2182,9 @@ if ($attrsNode) { $ao['useAlways'] = @($shorts) } } - # Settings динамического списка + # Settings динамического списка (только xsi:type=DynamicList; Planner/TypeDescription — выше) $setNode = $a.SelectSingleNode("lf:Settings", $ns) - if ($setNode) { + if ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'DynamicList$') { $so = [ordered]@{} # AutoFillAvailableFields — дефолт true, платформа эмитит только отклонение (false). Захват «как есть». $afaf = Get-Child $setNode 'AutoFillAvailableFields'; if ($null -ne $afaf) { $so['autoFillAvailableFields'] = ($afaf -eq 'true') } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index d7d73b54..063e6b93 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -777,11 +777,11 @@ flags/layout/оформление/companions/события общий), плю Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и рус. (`ПолеТабличногоДокумента`, `ПолеИндикатора`, `ПолеДиаграммы`, `ПолеДиаграммыГанта`, …). Скаляры `output`/`protection`/… — generic pass-through; bool как `true`/`false`, enum verbatim. -> **Ограничение — design-time конфигурация диаграмм/планировщика.** Реквизит chart-типа может нести -> `` — встроенный конфиг диаграммы (серии/оси/цвета/планировщик). -> Planner несёт Settings ВСЕГДА (даже дефолтный), Chart/Gantt — при design-time настройке. Этот блок пока НЕ -> воспроизводится → декомпилятор делает честный fail-ring3 (не теряет молча). Поля без Settings (диаграмма, -> заполняемая в коде; график-схема; период) роундтрипятся полностью. +> **Design-time конфигурация диаграмм/планировщика.** Реквизит chart-типа может нести +> `` — встроенный конфиг (серии/оси/цвета/планировщик). +> **Planner** (`pl:Planner`) — поддержан, см. ключ `planner` ниже. **Chart/GanttChart** (`d4p1:Chart`/`GanttChart`) +> при design-time настройке — пока НЕ воспроизводятся → декомпилятор делает честный fail-ring3 (не теряет молча). +> Поля без Settings (диаграмма, заполняемая в коде; график-схема; период; дендрограмма) роундтрипятся полностью. #### autoCmdBar — командная панель формы @@ -833,6 +833,40 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и | `columns` | array | Колонки для ValueTable/ValueTree (`{ name, type, title?, functionalOptions?, useAlways? }`) | | `additionalColumns` | array | Доп. колонки табличных частей объекта: `[{ table: "Объект.ТабЧасть", columns: [] }]`. У главного реквизита-объекта; `` — та же грамматика, что у `columns`. Эмитятся в `` после прямых колонок | | `settings` | object | Настройки динамического списка (только `type: "DynamicList"`) | +| `planner` | object | Design-time конфигурация планировщика (только `type: "pl:Planner"`, ``). См. ниже | + +### planner — design-time конфигурация планировщика + +Для реквизита `type: "pl:Planner"` объект `planner` описывает встроенный конфиг поля-планировщика (элементы расписания + оформление/поведение + шкала времени). Платформа эмитит блок **всегда** (даже дефолтный); компилятор подставляет умолчания для пропущенных ключей — авторинг может быть кратким (`{ "items": [{ "text": "Встреча", "begin": "...", "end": "..." }] }`), декомпилятор делает полный захват (раундтрип бит-в-бит). Цвета verbatim (`auto`/`style:X`/`web:Red`/`#hex`); шрифт `{ kind: "AutoFont" }` или ref-строка; граница `{ width, style }`; ML-форматы — строка или `{ "#": ..., "ru": ... }`. + +```json +{ "name": "Планировщик", "type": "pl:Planner", "planner": { + "items": [{ "text": "Встреча", "begin": "2026-06-09T01:00:00", "end": "2026-06-09T04:00:00", + "borderColor": "auto", "backColor": "auto", "deleted": false, "editMode": "EnableEdit" }], + "period": { "begin": "2026-06-09T00:00:00", "end": "2026-06-09T23:59:59" }, + "displayCurrentDate": true, "itemsTimeRepresentation": "BeginTime", + "timeScale": { "placement": "Left", "levels": [{ "measure": "Hour", "interval": 1 }] } +} } +``` + +| Ключ planner | Тип | Назначение | +|---|---|---| +| `items` | array | Элементы планировщика (``): `value`(nil по умолч.)/`text`/`tooltip`/`begin`/`end`/`borderColor`/`backColor`/`textColor`/`font`/`replacementDate`/`deleted`/`id`(авто-GUID)/`textFormatted`/`border`/`editMode` | +| `borderColor`/`backColor`/`textColor`/`lineColor` | color | Цвета планировщика (умолч. `auto`) | +| `font` | font | Шрифт (умолч. `{kind:"AutoFont"}`) | +| `beginOfRepresentationPeriod`/`endOfRepresentationPeriod` | dateTime | Период представления | +| `alignElementsOfTimeScale`/`displayTimeScaleWrapHeaders`/`displayWrapHeaders`/`displayCurrentDate` | bool | Флаги отображения | +| `timeScaleWrapHeadersFormat` | ML | Формат перенесённых заголовков шкалы | +| `periodicVariantUnit`/`periodicVariantRepetition` | value/int | Единица/кратность периодического варианта | +| `timeScaleWrapBeginIndent`/`timeScaleWrapEndIndent` | int | Отступы переноса шкалы | +| `timeScale` | object | Шкала времени: `placement`, `levels:[{measure,interval,show,line:{width,gap,style},scaleColor,dayFormatRule,format(ML),labels:{ticks},backColor,textColor,showPereodicalLabels}]`, `transparent`, `backColor`, `textColor`, `currentLevel` | +| `period` | object | `{ begin, end }` — отображаемый период (опционально) | +| `itemsTimeRepresentation`/`itemsBehaviorWhenSpaceInsufficient`/`newItemsTextType`/`fixDimensionsHeader`/`fixTimeScaleHeader` | value | Поведение элементов/заголовков | +| `autoMinColumnWidth`/`autoMinRowHeight` | bool | Авто-минимум размеров | +| `minColumnWidth`/`minRowHeight` | int | Минимальные размеры | +| `border` | border | Рамка планировщика (`{ width, style }`) | + +> **Ограничение Phase 1:** `dimensions` (измерения планировщика) и `item.dimensionValues` пока всегда пустые (захват только пустого блока). Конфиг Chart/GanttChart (`d4p1:*`) — отдельная фаза. ### settings — динамический список diff --git a/tests/skills/cases/form-compile/chart-fields.json b/tests/skills/cases/form-compile/chart-fields.json index fccdcbb6..63a1bbcb 100644 --- a/tests/skills/cases/form-compile/chart-fields.json +++ b/tests/skills/cases/form-compile/chart-fields.json @@ -1,5 +1,5 @@ { - "name": "Форма с полями диаграмм (без design-time Settings)", + "name": "Форма с полями диаграмм + design-time Settings планировщика", "preRun": [ { "script": "meta-compile/scripts/meta-compile", @@ -20,14 +20,23 @@ { "graphicalSchema": "Схема", "path": "Схема", "titleLocation": "none", "edit": false }, { "periodField": "Период", "path": "Период", "titleLocation": "none" }, { "ganttChart": "Ганта", "path": "Ганта", "titleLocation": "none", - "ganttTable": { "table": "ТаблицаГанта", "path": "Ганта", "height": 3 } } + "ganttTable": { "table": "ТаблицаГанта", "path": "Ганта", "height": 3 } }, + { "dendrogram": "Дендро", "path": "Дендро", "titleLocation": "none" }, + { "planner": "Планировщик", "path": "Планировщик", "titleLocation": "none" } ], "attributes": [ { "name": "Объект", "type": "DataProcessorObject.Диаграммы", "main": true }, { "name": "Диаграмма", "type": "d5p1:Chart" }, { "name": "Схема", "type": "d5p1:FlowchartContextType" }, { "name": "Период", "type": "v8:StandardPeriod" }, - { "name": "Ганта", "type": "d5p1:GanttChart" } + { "name": "Ганта", "type": "d5p1:GanttChart" }, + { "name": "Дендро", "type": "d5p1:Dendrogram" }, + { "name": "Планировщик", "type": "pl:Planner", "planner": { + "items": [ + { "text": "Встреча", "begin": "2026-06-09T01:00:00", "end": "2026-06-09T04:00:00" } + ], + "period": { "begin": "2026-06-09T00:00:00", "end": "2026-06-09T23:59:59" } + } } ] } } diff --git a/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml index 5b97afc2..7769c5d6 100644 --- a/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml @@ -65,15 +65,27 @@ + + Дендро + None + + + + + Планировщик + None + + + - + cfg:DataProcessorObject.Диаграммы true - + <v8:item> <v8:lang>ru</v8:lang> @@ -84,7 +96,7 @@ <v8:Type xmlns:d5p1="http://v8.1c.ru/8.2/data/chart">d5p1:Chart</v8:Type> </Type> </Attribute> - <Attribute name="Схема" id="28"> + <Attribute name="Схема" id="34"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -95,7 +107,7 @@ <v8:Type xmlns:d5p1="http://v8.1c.ru/8.2/data/graphscheme">d5p1:FlowchartContextType</v8:Type> </Type> </Attribute> - <Attribute name="Период" id="29"> + <Attribute name="Период" id="35"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -106,7 +118,7 @@ <v8:Type>v8:StandardPeriod</v8:Type> </Type> </Attribute> - <Attribute name="Ганта" id="30"> + <Attribute name="Ганта" id="36"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -117,5 +129,123 @@ <v8:Type xmlns:d5p1="http://v8.1c.ru/8.2/data/chart">d5p1:GanttChart</v8:Type> </Type> </Attribute> + <Attribute name="Дендро" id="37"> + <Title> + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Дендро</v8:content> + </v8:item> + + + d5p1:Dendrogram + + + + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Планировщик</v8:content> + </v8:item> + + + pl:Planner + + + + + Встреча + + 2026-06-09T01:00:00 + 2026-06-09T04:00:00 + auto + auto + auto + + + 0001-01-01T00:00:00 + false + UUID-001 + false + + Single + + EnableEdit + + auto + auto + auto + auto + + 0001-01-01T00:00:00 + 0001-01-01T00:00:00 + true + true + true + + + # + DLF="DD" + + + ru + DLF="DD" + + + Day + 1 + 0 + 0 + + Left + + Hour + 1 + true + + Solid + + auto + MonthDayWeekDay + + + # + DF="HH:mm" + + + ru + DF="HH:mm" + + + + 0 + + auto + auto + true + + false + auto + auto + 0 + + + 2026-06-09T00:00:00 + 2026-06-09T23:59:59 + + true + BeginTime + CollapseItems + true + true + 0 + 0 + auto + auto + + Single + + String + +