feat(form-decompile,form-compile): Planner design-time Settings из ring-3 (Фаза 1 кластера Chart-Settings)

Реквизит planner-типа несёт <Settings xsi:type="pl:Planner"> — встроенный конфиг
поля-планировщика (элементы расписания + оформление/поведение + шкала времени).
Раньше декомпилятор делал fail-ring3 (третий вид Settings после TypeDescription/
DynamicList). Корпус 8.3.24: Planner Settings = 1 реальная форма (КонтактныеЛица/
ФормаЛиды), всё chart-семейство = 38 форм. Решение (с пользователем): структурный
DSL ради возможности модели СОЗДАВАТЬ дашборды/планировщики, не только раундтрипить.

DSL: ключ planner:{…} на реквизите (docs/form-dsl-spec.md):
- items[] (элементы расписания) + appearance/поведение-скаляры + timeScale
  (placement/levels[]/colors) + period;
- цвета verbatim, шрифт {kind:AutoFont}/ref, граница {width,style}, ML-форматы;
- компилятор подставляет дефолты для пропущенных ключей (краткий авторинг),
  декомпилятор — полный захват (раундтрип бит-в-бит).

Снят fail-ring3 для pl:Planner (Chart/GanttChart остаются — Фазы 2/3).
Заодно фикс: d5p1:Dendrogram отсутствовал в specialTypeNs (эмитился без
xmlns-префикса) — добавлен в карту (ps1+py).

Раундтрип бит-в-бит: синтетика upload/epf/Диаграммы (с items+period) +
реальная ФормаЛиды (без items/period, иные значения скаляров). Зеркало py
(ps1==py байт-в-байт). Кейс chart-fields расширен (+planner +dendrogram),
сертифицирован загрузкой в 1С. Регресс 41/41 (ps1+py).

Ограничение Phase 1: dimensions/item.dimensionValues пока всегда пустые.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-10 13:35:10 +03:00
co-authored by Claude Opus 4.8
parent 29f288fe1d
commit f064d53eb2
6 changed files with 664 additions and 19 deletions
@@ -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</Border>"
}
# ─────────────────────────────────────────────────────────────────────────────
# Planner design-time <Settings xsi:type="pl:Planner"> — встроенный конфиг планировщика
# на реквизите 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<pl:$tag>$(Esc-Xml "$(PL-Get $o $key 'auto')")</pl:$tag>"
}
function Emit-PlannerFont {
param($o, [string]$ind)
$f = PL-Get $o 'font' $null
if ($null -eq $f) { X "$ind<pl:font kind=`"AutoFont`"/>"; 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<pl:border width=`"$bw`">"
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-Xml "$bs")</v8ui:style>"
X "$ind</pl:border>"
}
function Emit-PlannerLevel {
param($lv, [string]$cns, [string]$ind)
$li = "$ind`t"
X "$ind<level xmlns=`"$cns`">"
X "$li<measure>$(Esc-Xml "$(PL-Get $lv 'measure' 'Hour')")</measure>"
X "$li<interval>$(PL-Get $lv 'interval' 1)</interval>"
X "$li<show>$(PL-Bool (PL-Get $lv 'show' $true))</show>"
$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<line width=`"$lw`" gap=`"$(PL-Bool $lg)`">"
X "$li`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-Xml "$lst")</v8ui:style>"
X "$li</line>"
X "$li<scaleColor>$(Esc-Xml "$(PL-Get $lv 'scaleColor' 'auto')")</scaleColor>"
X "$li<dayFormatRule>$(Esc-Xml "$(PL-Get $lv 'dayFormatRule' 'MonthDayWeekDay')")</dayFormatRule>"
$fmt = PL-Get $lv 'format' $null
if ($null -eq $fmt) { $fmt = [ordered]@{ '#' = 'DF="HH:mm"'; 'ru' = 'DF="HH:mm"' } }
X "$li<format>"
Emit-MLItems -val $fmt -indent "$li`t"
X "$li</format>"
$labels = PL-Get $lv 'labels' $null
$ticks = if ($labels) { PL-Get $labels 'ticks' 0 } else { 0 }
X "$li<labels>"
X "$li`t<ticks>$ticks</ticks>"
X "$li</labels>"
X "$li<backColor>$(Esc-Xml "$(PL-Get $lv 'backColor' 'auto')")</backColor>"
X "$li<textColor>$(Esc-Xml "$(PL-Get $lv 'textColor' 'auto')")</textColor>"
X "$li<showPereodicalLabels>$(PL-Bool (PL-Get $lv 'showPereodicalLabels' $true))</showPereodicalLabels>"
X "$ind</level>"
}
function Emit-PlannerTimeScale {
param($ts, [string]$ind)
$cns = $script:CHART_NS
$ci = "$ind`t"
X "$ind<pl:timeScale>"
X "$ci<placement xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'placement' 'Left' } else { 'Left' })")</placement>"
$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<transparent xmlns=`"$cns`">$(PL-Bool $transp)</transparent>"
X "$ci<backColor xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'backColor' 'auto' } else { 'auto' })")</backColor>"
X "$ci<textColor xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'textColor' 'auto' } else { 'auto' })")</textColor>"
X "$ci<currentLevel xmlns=`"$cns`">$(if ($ts) { PL-Get $ts 'currentLevel' 0 } else { 0 })</currentLevel>"
X "$ind</pl:timeScale>"
}
function Emit-PlannerItem {
param($it, [string]$ind)
X "$ind<pl:item>"
$ii = "$ind`t"
$val = PL-Get $it 'value' $null
if ($null -eq $val) { X "$ii<pl:value xsi:nil=`"true`"/>" }
else { X "$ii<pl:value xsi:type=`"xs:string`">$(Esc-Xml "$val")</pl:value>" }
X "$ii<pl:text>$(Esc-Xml "$(PL-Get $it 'text' '')")</pl:text>"
$tt = PL-Get $it 'tooltip' ''
if ("$tt" -eq '') { X "$ii<pl:tooltip/>" } else { X "$ii<pl:tooltip>$(Esc-Xml "$tt")</pl:tooltip>" }
X "$ii<pl:begin>$(PL-Get $it 'begin' '0001-01-01T00:00:00')</pl:begin>"
X "$ii<pl:end>$(PL-Get $it 'end' '0001-01-01T00:00:00')</pl:end>"
Emit-PlannerColor 'borderColor' $it 'borderColor' $ii
Emit-PlannerColor 'backColor' $it 'backColor' $ii
Emit-PlannerColor 'textColor' $it 'textColor' $ii
Emit-PlannerFont $it $ii
X "$ii<pl:dimensionValues/>"
X "$ii<pl:replacementDate>$(PL-Get $it 'replacementDate' '0001-01-01T00:00:00')</pl:replacementDate>"
X "$ii<pl:deleted>$(PL-Bool (PL-Get $it 'deleted' $false))</pl:deleted>"
$id = PL-Get $it 'id' $null
if ($null -eq $id) { $id = [guid]::NewGuid().ToString() }
X "$ii<pl:id>$id</pl:id>"
X "$ii<pl:textFormatted>$(PL-Bool (PL-Get $it 'textFormatted' $false))</pl:textFormatted>"
Emit-PlannerBorder $it $ii 'border'
X "$ii<pl:editMode>$(Esc-Xml "$(PL-Get $it 'editMode' 'EnableEdit')")</pl:editMode>"
X "$ind</pl:item>"
}
function Emit-PlannerSettings {
param($pl, [string]$ind)
X "$ind<Settings xmlns:pl=`"$($script:PLANNER_NS)`" xsi:type=`"pl:Planner`">"
$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:beginOfRepresentationPeriod>$(PL-Get $pl 'beginOfRepresentationPeriod' '0001-01-01T00:00:00')</pl:beginOfRepresentationPeriod>"
X "$si<pl:endOfRepresentationPeriod>$(PL-Get $pl 'endOfRepresentationPeriod' '0001-01-01T00:00:00')</pl:endOfRepresentationPeriod>"
X "$si<pl:alignElementsOfTimeScale>$(PL-Bool (PL-Get $pl 'alignElementsOfTimeScale' $true))</pl:alignElementsOfTimeScale>"
X "$si<pl:displayTimeScaleWrapHeaders>$(PL-Bool (PL-Get $pl 'displayTimeScaleWrapHeaders' $true))</pl:displayTimeScaleWrapHeaders>"
X "$si<pl:displayWrapHeaders>$(PL-Bool (PL-Get $pl 'displayWrapHeaders' $true))</pl:displayWrapHeaders>"
$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<pl:periodicVariantUnit>$(Esc-Xml "$(PL-Get $pl 'periodicVariantUnit' 'Day')")</pl:periodicVariantUnit>"
X "$si<pl:periodicVariantRepetition>$(PL-Get $pl 'periodicVariantRepetition' 1)</pl:periodicVariantRepetition>"
X "$si<pl:timeScaleWrapBeginIndent>$(PL-Get $pl 'timeScaleWrapBeginIndent' 0)</pl:timeScaleWrapBeginIndent>"
X "$si<pl:timeScaleWrapEndIndent>$(PL-Get $pl 'timeScaleWrapEndIndent' 0)</pl:timeScaleWrapEndIndent>"
Emit-PlannerTimeScale (PL-Get $pl 'timeScale' $null) $si
$period = PL-Get $pl 'period' $null
if ($period) {
X "$si<pl:period>"
X "$si`t<pl:begin>$(PL-Get $period 'begin' '0001-01-01T00:00:00')</pl:begin>"
X "$si`t<pl:end>$(PL-Get $period 'end' '0001-01-01T00:00:00')</pl:end>"
X "$si</pl:period>"
}
X "$si<pl:displayCurrentDate>$(PL-Bool (PL-Get $pl 'displayCurrentDate' $true))</pl:displayCurrentDate>"
X "$si<pl:itemsTimeRepresentation>$(Esc-Xml "$(PL-Get $pl 'itemsTimeRepresentation' 'BeginTime')")</pl:itemsTimeRepresentation>"
X "$si<pl:itemsBehaviorWhenSpaceInsufficient>$(Esc-Xml "$(PL-Get $pl 'itemsBehaviorWhenSpaceInsufficient' 'CollapseItems')")</pl:itemsBehaviorWhenSpaceInsufficient>"
X "$si<pl:autoMinColumnWidth>$(PL-Bool (PL-Get $pl 'autoMinColumnWidth' $true))</pl:autoMinColumnWidth>"
X "$si<pl:autoMinRowHeight>$(PL-Bool (PL-Get $pl 'autoMinRowHeight' $true))</pl:autoMinRowHeight>"
X "$si<pl:minColumnWidth>$(PL-Get $pl 'minColumnWidth' 0)</pl:minColumnWidth>"
X "$si<pl:minRowHeight>$(PL-Get $pl 'minRowHeight' 0)</pl:minRowHeight>"
X "$si<pl:fixDimensionsHeader>$(Esc-Xml "$(PL-Get $pl 'fixDimensionsHeader' 'auto')")</pl:fixDimensionsHeader>"
X "$si<pl:fixTimeScaleHeader>$(Esc-Xml "$(PL-Get $pl 'fixTimeScaleHeader' 'auto')")</pl:fixTimeScaleHeader>"
Emit-PlannerBorder $pl $si 'border'
X "$si<pl:newItemsTextType>$(Esc-Xml "$(PL-Get $pl 'newItemsTextType' 'String')")</pl:newItemsTextType>"
X "$ind</Settings>"
}
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 <Settings xsi:type="pl:Planner"> (встроенный конфиг планировщика).
# Идёт сразу после <Type> (как 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 "$inner<MainAttribute>true</MainAttribute>"