diff --git a/provisioning/Export-ListSchema.ps1 b/provisioning/Export-ListSchema.ps1 new file mode 100644 index 0000000..c9571fb --- /dev/null +++ b/provisioning/Export-ListSchema.ps1 @@ -0,0 +1,258 @@ +<# +.SYNOPSIS + Экспортирует схему реальных SP-списков в манифесты provisioning.json (источник + правды по колонкам форм портала ct-forms-portal). + +.DESCRIPTION + Читает описание форм из sources.json и для каждой формы: + 1) подключается к ИСХОДНОМУ сайту формы (sourceSite); + 2) для каждого списка из формы читает пользовательские колонки + (кастомные, не системные) и их точные типы/варианты Choice/Lookup; + 3) применяет правило переименования (renameSuffix, кроме renameExclude) — + и к самому имени списка, и к целевым спискам Lookup-колонок; + 4) пишет манифест provisioning.json (мульти-список) в outFile формы. + + Так схема снимается 1:1 с боевых списков (без ручных догадок по типам), а затем + Provision-Lists.ps1 воспроизводит её на целевом сайте (/sites/sed). + + ВАЖНО: экспортируется только СХЕМА (списки + колонки). Данные справочников + (Поставщики, Курс Валют, Матрица согласования), настройка Syntex на библиотеке КП и + URL потоков Power Automate — НЕ входят в манифест и переносятся отдельно. + + Вход — по сертификату «от имени приложения» (app-only, право Sites.Selected). + Приложению нужен доступ КО ВСЕМ исходным сайтам форм (напр. и /sites/kps, и /sites/sed). + +.PARAMETER ClientId + Client Id приложения-провижинера (Sites.Selected). + +.PARAMETER Tenant + Тенант, например tnsukz.onmicrosoft.com + +.PARAMETER Thumbprint + Отпечаток сертификата в хранилище CurrentUser\My. + +.PARAMETER SourcesPath + Путь к sources.json. По умолчанию — рядом со скриптом. + +.PARAMETER Only + Экспортировать только указанные формы (по ключу form). По умолчанию — все. + +.EXAMPLE + ./Export-ListSchema.ps1 -ClientId "" -Tenant "tnsukz.onmicrosoft.com" -Thumbprint "" + +.EXAMPLE + # Только KPS + ./Export-ListSchema.ps1 -ClientId "" -Tenant "..." -Thumbprint "" -Only kps +#> +param( + [Parameter(Mandatory = $true)][string]$ClientId, + [Parameter(Mandatory = $true)][string]$Tenant, + [Parameter(Mandatory = $true)][string]$Thumbprint, + [string]$SourcesPath = "$PSScriptRoot/sources.json", + [string[]]$Only +) + +$ErrorActionPreference = 'Stop' + +# --- Переименование по правилу формы (суффикс, кроме исключений) --- +function Convert-ListTitle { + param([string]$Title, [string]$Suffix, [string[]]$Exclude) + + if ([string]::IsNullOrEmpty($Suffix)) { return $Title } + if ($Exclude -and ($Exclude -contains $Title)) { return $Title } + if ($Title.EndsWith($Suffix)) { return $Title } # идемпотентно — не удваиваем суффикс + return "$Title$Suffix" +} + +# --- SP-тип колонки → тип манифеста --- +# Возвращает hashtable колонки в формате манифеста или $null (колонку пропускаем). +function ConvertTo-ManifestColumn { + param($Field, [string]$Suffix, [string[]]$Exclude) + + $t = [string]$Field.TypeAsString + $col = [ordered]@{ + internalName = [string]$Field.InternalName + title = [string]$Field.Title + } + + switch ($t) { + 'Text' { $col.type = 'Text' } + 'Note' { $col.type = 'Note' } + 'Boolean' { $col.type = 'Boolean' } + 'Number' { + $col.type = 'Number' + $dec = Get-DecimalsFromSchema -SchemaXml $Field.SchemaXml + if ($null -ne $dec) { $col.decimals = $dec } + } + 'Currency' { + $col.type = 'Number' + $dec = Get-DecimalsFromSchema -SchemaXml $Field.SchemaXml + $col.decimals = if ($null -ne $dec) { $dec } else { 2 } + } + 'DateTime' { + $col.type = 'DateTime' + # DisplayFormat: 0 = DateOnly, 1 = DateTime. + if ([int]$Field.DisplayFormat -eq 0) { $col.dateOnly = $true } + } + 'Choice' { + $col.type = 'Choice' + $col.choices = @($Field.Choices) + if (Test-FillInChoice -SchemaXml $Field.SchemaXml) { $col.fillIn = $true } + } + 'MultiChoice' { + $col.type = 'Choice' + $col.multiChoice = $true + $col.choices = @($Field.Choices) + if (Test-FillInChoice -SchemaXml $Field.SchemaXml) { $col.fillIn = $true } + } + 'User' { + $col.type = 'User' + $col.userSelectionMode = if ([int]$Field.SelectionMode -eq 1) { 'PeopleAndGroups' } else { 'PeopleOnly' } + } + 'UserMulti' { + $col.type = 'User' + $col.allowMultiple = $true + $col.userSelectionMode = if ([int]$Field.SelectionMode -eq 1) { 'PeopleAndGroups' } else { 'PeopleOnly' } + } + 'Lookup' { + $col.type = 'Lookup' + $lk = Resolve-LookupTarget -Field $Field -Suffix $Suffix -Exclude $Exclude + if ($null -eq $lk) { return $null } + $col.lookupList = $lk.list + $col.lookupShowField = $lk.show + } + 'LookupMulti' { + $col.type = 'Lookup' + $col.allowMultiple = $true + $lk = Resolve-LookupTarget -Field $Field -Suffix $Suffix -Exclude $Exclude + if ($null -eq $lk) { return $null } + $col.lookupList = $lk.list + $col.lookupShowField = $lk.show + } + default { + Write-Warning " [skip] $($Field.InternalName): неизвестный/несоздаваемый тип '$t'" + return $null + } + } + + if ($Field.Required) { $col.required = $true } + return $col +} + +# Decimals из SchemaXml числовой колонки (атрибут Decimals='N'), иначе $null. +function Get-DecimalsFromSchema { + param([string]$SchemaXml) + if ([string]::IsNullOrEmpty($SchemaXml)) { return $null } + $m = [regex]::Match($SchemaXml, "Decimals=['""](?\d+)['""]") + if ($m.Success) { return [int]$m.Groups['d'].Value } + return $null +} + +# Разрешён ли ввод своих значений в Choice (атрибут FillInChoice='TRUE'). +function Test-FillInChoice { + param([string]$SchemaXml) + if ([string]::IsNullOrEmpty($SchemaXml)) { return $false } + return [regex]::IsMatch($SchemaXml, "FillInChoice=['""]TRUE['""]", [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) +} + +# Целевой список Lookup: GUID → Title (с применением rename), + show-поле. +function Resolve-LookupTarget { + param($Field, [string]$Suffix, [string[]]$Exclude) + + $guid = [string]$Field.LookupList + if ([string]::IsNullOrEmpty($guid)) { + Write-Warning " [skip lookup] $($Field.InternalName): пустой LookupList (зависимый lookup?)" + return $null + } + try { + $target = Get-PnPList -Identity $guid -ErrorAction Stop + $rawTitle = [string]$target.Title + $show = if ($Field.LookupField) { [string]$Field.LookupField } else { 'Title' } + return @{ list = (Convert-ListTitle -Title $rawTitle -Suffix $Suffix -Exclude $Exclude); show = $show } + } + catch { + Write-Warning " [skip lookup] $($Field.InternalName): не разрешил целевой список $guid — $($_.Exception.Message)" + return $null + } +} + +# Системные типы, которые не экспортируем даже если FromBaseType=false. +$SYSTEM_TYPES = @('Computed', 'Attachments', 'File', 'Counter', 'Guid', 'ContentTypeId', 'ModStat', 'WorkflowStatus', 'ThreadIndex') + +# --- Основной проход --- +$sources = Get-Content -Path $SourcesPath -Raw -Encoding UTF8 | ConvertFrom-Json +$forms = $sources.forms +if ($Only) { $forms = $forms | Where-Object { $Only -contains $_.form } } +if (-not $forms) { throw "Формы для экспорта не найдены (проверь sources.json / -Only)" } + +foreach ($f in $forms) { + Write-Host "`n=== Форма '$($f.form)' — экспорт с $($f.sourceSite) ===" -ForegroundColor Cyan + Connect-PnPOnline -Url $f.sourceSite -ClientId $ClientId -Tenant $Tenant -Thumbprint $Thumbprint + + $suffix = if ($f.PSObject.Properties.Name -contains 'renameSuffix') { [string]$f.renameSuffix } else { '' } + $exclude = if ($f.PSObject.Properties.Name -contains 'renameExclude') { @($f.renameExclude) } else { @() } + + $manifestLists = @() + foreach ($ls in $f.lists) { + $srcTitle = [string]$ls.title + $outTitle = Convert-ListTitle -Title $srcTitle -Suffix $suffix -Exclude $exclude + Write-Host " • список «$srcTitle» → «$outTitle»" -ForegroundColor White + + $list = Get-PnPList -Identity $srcTitle -ErrorAction SilentlyContinue + if (-not $list) { + Write-Warning " [!] нет списка «$srcTitle» на $($f.sourceSite) — пропуск" + continue + } + + $template = if ($ls.template) { [string]$ls.template } elseif ([int]$list.BaseType -eq 1) { 'DocumentLibrary' } else { 'GenericList' } + + # Отсекаем: скрытые/базовые/readonly/системные типы, а также по имени — + # 'Title' (базовая, всегда есть), '_*' (системные: _ModelId, _ExtendedDescription …) + # и 'Prime*' (колонки модели Syntex — появляются при настройке распознавания, руками не создаём). + $fields = Get-PnPField -List $srcTitle | Where-Object { + (-not $_.Hidden) -and (-not $_.FromBaseType) -and (-not $_.ReadOnlyField) ` + -and ($SYSTEM_TYPES -notcontains $_.TypeAsString) ` + -and ($_.InternalName -ne 'Title') ` + -and ($_.InternalName -notlike '_*') ` + -and ($_.InternalName -notlike 'Prime*') + } + + $columns = @() + foreach ($fld in $fields) { + $c = ConvertTo-ManifestColumn -Field $fld -Suffix $suffix -Exclude $exclude + if ($null -ne $c) { + $columns += $c + Write-Host " [+] $($c.internalName) [$($c.type)]" -ForegroundColor DarkGray + } + } + + $entry = [ordered]@{ + title = $outTitle + template = $template + columns = $columns + } + $manifestLists += $entry + } + + $manifest = [ordered]@{ + form = [string]$f.form + targetSite = [string]$f.targetSite + lists = $manifestLists + } + + $outPath = Join-Path $PSScriptRoot $f.outFile + $outDir = Split-Path -Parent $outPath + if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null } + $json = $manifest | ConvertTo-Json -Depth 8 + # ConvertTo-Json экранирует кириллицу в \uXXXX — декодируем ТОЛЬКО их (не трогая \" \\), + # чтобы манифест был читаемым UTF-8 и при этом остался валидным JSON. + $json = [regex]::Replace($json, '\\u(?[0-9a-fA-F]{4})', { + param($m) [string][char][int]::Parse($m.Groups['c'].Value, [System.Globalization.NumberStyles]::HexNumber) + }) + Set-Content -Path $outPath -Value $json -Encoding UTF8 + Write-Host " → записан манифест $outPath" -ForegroundColor Green + + Disconnect-PnPOnline +} + +Write-Host "`nЭкспорт завершён." -ForegroundColor Cyan diff --git a/provisioning/Provision-Lists.ps1 b/provisioning/Provision-Lists.ps1 new file mode 100644 index 0000000..9c4129a --- /dev/null +++ b/provisioning/Provision-Lists.ps1 @@ -0,0 +1,188 @@ +<# +.SYNOPSIS + Идемпотентно создаёт списки и библиотеки форм портала ct-forms-portal на целевом + сайте (/sites/sed) по манифестам provisioning.json. + +.DESCRIPTION + Читает ВСЕ манифесты provisioning/manifests/*.provisioning.json (снятые + Export-ListSchema.ps1) и в ДВА прохода: + Проход A — создаёт все списки/библиотеки (если ещё нет) по всем манифестам. + Проход B — обеспечивает колонки на каждом списке. + Два прохода нужны, чтобы к моменту создания Lookup-колонки её целевой список уже + существовал (даже если он объявлен в другом манифесте — напр. общий «Поставщики»). + + Литеральные имена: имя списка берётся как есть из манифеста (никаких конвенций + «— данные/— согласование», в отличие от request-portal). Поддержаны обычные списки + (GenericList) и библиотеки документов (DocumentLibrary). + + Колонки создаются прямо НА СПИСКЕ (без общих столбцов сайта) — у форм CT/OID/KPS + имена колонок совпадают между списками, а Choice-наборы могут отличаться, поэтому + общий эталон сайта тут только мешал бы. + + Повторный запуск безопасен: существующие списки/колонки не пересоздаются; для Choice + синхронизируются варианты. + + Вход — по сертификату «от имени приложения» (app-only, Sites.Selected на /sites/sed). + +.PARAMETER SiteUrl + Целевой сайт, например https://tnsukz.sharepoint.com/sites/sed + +.PARAMETER ClientId + Client Id приложения-провижинера (Sites.Selected). + +.PARAMETER Tenant + Тенант, например tnsukz.onmicrosoft.com + +.PARAMETER Thumbprint + Отпечаток сертификата в хранилище CurrentUser\My. + +.PARAMETER ManifestsDir + Папка с манифестами. По умолчанию — provisioning/manifests рядом со скриптом. + +.PARAMETER Only + Провижионить только манифесты указанных форм (по полю form). По умолчанию — все. + +.EXAMPLE + ./Provision-Lists.ps1 -SiteUrl "https://tnsukz.sharepoint.com/sites/sed" ` + -ClientId "" -Tenant "tnsukz.onmicrosoft.com" -Thumbprint "" +#> +param( + [Parameter(Mandatory = $true)][string]$SiteUrl, + [Parameter(Mandatory = $true)][string]$ClientId, + [Parameter(Mandatory = $true)][string]$Tenant, + [Parameter(Mandatory = $true)][string]$Thumbprint, + [string]$ManifestsDir = "$PSScriptRoot/manifests", + [string[]]$Only +) + +$ErrorActionPreference = 'Stop' + +function ConvertTo-XmlSafe { + param([string]$Value) + return $Value.Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace("'", ''').Replace('"', '"') +} + +function Test-FieldExists { + param([string]$List, [string]$InternalName) + return $null -ne (Get-PnPField -List $List -Identity $InternalName -ErrorAction SilentlyContinue) +} + +# Создаёт/обновляет колонку НА СПИСКЕ. Идемпотентно. Lookup/UserMulti/Number-с-decimals +# — через XML (иначе PnP-cmdlet не покрывает нюансы), остальное — обычным Add-PnPField. +function Add-ListColumn { + param([string]$List, $Col) + + if (Test-FieldExists -List $List -InternalName $Col.internalName) { + if ($Col.type -eq 'Choice' -and $Col.choices) { + try { + $vals = @{ Choices = [string[]]$Col.choices } + if ($null -ne $Col.fillIn) { $vals.FillInChoice = [bool]$Col.fillIn } + Set-PnPField -List $List -Identity $Col.internalName -Values $vals | Out-Null + Write-Host " [~] варианты обновлены: $($Col.internalName)" -ForegroundColor DarkGray + } + catch { Write-Warning " [!] $($Col.internalName): не удалось обновить варианты — $($_.Exception.Message)" } + } + else { Write-Host " [=] есть: $($Col.internalName)" -ForegroundColor DarkGray } + return + } + + try { + switch ($Col.type) { + 'Text' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Text -AddToDefaultView | Out-Null } + 'Note' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Note -AddToDefaultView | Out-Null } + 'Boolean' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Boolean -AddToDefaultView | Out-Null } + 'DateTime' { + Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type DateTime -AddToDefaultView | Out-Null + if ($Col.dateOnly) { Set-PnPField -List $List -Identity $Col.internalName -Values @{ DisplayFormat = 0; FriendlyDisplayFormat = 1 } | Out-Null } + } + 'Choice' { + $multi = [bool]$Col.multiChoice + $type = if ($multi) { 'MultiChoice' } else { 'Choice' } + Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type $type -Choices $Col.choices -AddToDefaultView | Out-Null + if ($Col.fillIn) { Set-PnPField -List $List -Identity $Col.internalName -Values @{ FillInChoice = $true } | Out-Null } + } + 'Number' { + if ($null -ne $Col.decimals) { + $xml = "" + Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null + } + else { + Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Number -AddToDefaultView | Out-Null + } + } + 'User' { + if ($Col.allowMultiple) { + $xml = "" + Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null + } + else { + Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type User -AddToDefaultView | Out-Null + } + if ($Col.userSelectionMode) { + $mode = if ($Col.userSelectionMode -eq 'PeopleAndGroups') { 1 } else { 0 } + Set-PnPField -List $List -Identity $Col.internalName -Values @{ SelectionMode = $mode } | Out-Null + } + } + 'Lookup' { + $src = Get-PnPList -Identity $Col.lookupList -ErrorAction Stop + $guid = $src.Id.ToString() + $show = if ($Col.lookupShowField) { $Col.lookupShowField } else { 'Title' } + $multAttr = if ($Col.allowMultiple) { " Mult='TRUE'" } else { '' } + $ftype = if ($Col.allowMultiple) { 'LookupMulti' } else { 'Lookup' } + $xml = "" + Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null + } + default { throw "Неизвестный тип колонки '$($Col.type)'" } + } + if ($Col.required -and $Col.type -ne 'Lookup') { + Set-PnPField -List $List -Identity $Col.internalName -Values @{ Required = $true } | Out-Null + } + Write-Host " [+] $($Col.internalName) — «$($Col.title)» [$($Col.type)]" -ForegroundColor Green + } + catch { Write-Warning " [!] $($Col.internalName): $($_.Exception.Message)" } +} + +Write-Host "Подключение к $SiteUrl (app-only) ..." -ForegroundColor Cyan +Connect-PnPOnline -Url $SiteUrl -ClientId $ClientId -Tenant $Tenant -Thumbprint $Thumbprint + +$files = Get-ChildItem -Path $ManifestsDir -Filter '*.provisioning.json' -ErrorAction SilentlyContinue +if (-not $files) { throw "Манифесты *.provisioning.json не найдены в $ManifestsDir" } + +$manifests = @() +foreach ($file in $files) { + $def = Get-Content -Path $file.FullName -Raw -Encoding UTF8 | ConvertFrom-Json + if ($Only -and ($Only -notcontains $def.form)) { continue } + $manifests += $def +} +if (-not $manifests) { throw "Нет манифестов после фильтра -Only" } + +# --- Проход A: создать все списки/библиотеки --- +Write-Host "`n=== Проход A: списки ===" -ForegroundColor Cyan +foreach ($def in $manifests) { + Write-Host "Форма '$($def.form)':" -ForegroundColor White + foreach ($ls in $def.lists) { + $tmpl = if ($ls.template -eq 'DocumentLibrary') { 'DocumentLibrary' } else { 'GenericList' } + if (Get-PnPList -Identity $ls.title -ErrorAction SilentlyContinue) { + Write-Host " [=] есть: «$($ls.title)»" -ForegroundColor DarkGray + } + else { + New-PnPList -Title $ls.title -Template $tmpl -EnableVersioning | Out-Null + Write-Host " [+] создан ($tmpl): «$($ls.title)»" -ForegroundColor Green + } + } +} + +# --- Проход B: колонки --- +Write-Host "`n=== Проход B: колонки ===" -ForegroundColor Cyan +foreach ($def in $manifests) { + Write-Host "Форма '$($def.form)':" -ForegroundColor White + foreach ($ls in $def.lists) { + Write-Host " список «$($ls.title)»:" -ForegroundColor White + foreach ($col in $ls.columns) { + Add-ListColumn -List $ls.title -Col $col + } + } +} + +Write-Host "`nГотово." -ForegroundColor Cyan +Disconnect-PnPOnline diff --git a/provisioning/README.md b/provisioning/README.md new file mode 100644 index 0000000..ad5342d --- /dev/null +++ b/provisioning/README.md @@ -0,0 +1,138 @@ +# Провижионинг списков ct-forms-portal + +Списки форм **CT / OID / KPS** воспроизводятся на боевом сайте портала +**`/sites/sed`** из манифестов `provisioning.json`. Манифесты **снимаются 1:1 с живых +списков** (Export-ListSchema.ps1), затем воспроизводятся на цели (Provision-Lists.ps1). +Так схема всегда «под рукой» и одинаково создаётся на тесте и в бою (по аналогии с +`request-portal/provisioning`). + +## Почему так, а не копия request-portal + +- Здесь **литеральные имена** списков (`Сравнительная таблица`, `Позиции СТ ОИД`…), а не + конвенция «Форма N — данные/согласование». Скрипт request-portal на таких именах падал бы. +- Поддержаны **библиотеки документов** (`Коммерческие предложения*`), а не только списки. +- Одна форма (KPS) = **много списков** → манифест мультисписковый. + +## Коллизия имён KPS ↔ CT (важно!) + +CT уже живёт на `/sites/sed` и владеет `Сравнительная таблица`, `Позиции СТ`, +`Оценка поставщиков`, `Критерии`, `Согласование СТ`, библиотекой `Коммерческие предложения`. +KPS раньше жил один на `/sites/kps` с **теми же именами**. При переезде на `/sites/sed` +data-списки KPS получают суффикс **` КПС`** (зеркально суффиксу ` ОИД` у OID): + +| Роль | Было (на /sites/kps) | Стало (на /sites/sed) | +| --- | --- | --- | +| шапка | Сравнительная таблица | **Сравнительная таблица КПС** | +| позиции | Позиции СТ | **Позиции СТ КПС** | +| офферы | Оценка поставщиков | **Оценка поставщиков КПС** | +| критерии | Критерии | **Критерии КПС** | +| согласование | Согласование СТ | **Согласование СТ КПС** | +| библиотека КП | Коммерческие предложения | **Коммерческие предложения КПС** | +| без КП | Не получены КП КПС | Не получены КП КПС (уже уникально) | +| матрица | Матрица согласования КПС | Матрица согласования КПС (уже уникально) | +| справочник | Поставщики | **Поставщики** (ОБЩИЙ с CT) | +| справочник | Курс Валют | **Курс Валют** (ОБЩИЙ с CT) | + +Суффикс применяет Export автоматически (`renameSuffix`/`renameExclude` в `sources.json`), +включая цели Lookup-колонок. Код уже приведён к новым именам: +- `forms/kps/services/KpsCtFormService.ts` — карта `LISTS`; +- `components/config/kpsDocumentConfig.ts` — `siteUrl` → `/sites/sed`, имена списков. + +> Общие справочники `Поставщики`, `Курс Валют` и `Матрица согласования СТ` создаёт +> манифест **CT**. OID тоже опирается на них (своей матрицы у OID нет). Поэтому +> провижионь **вместе с CT** (или все формы разом) — иначе Lookup'ы KPS/OID не найдут цель. + +## Что провижионинг НЕ делает (перенести отдельно!) + +- **Данные справочников** — содержимое `Поставщики`, `Курс Валют`, `Матрица согласования *`. + Создаются только пустые списки с колонками. +- **Настройка Syntex** на библиотеках `Коммерческие предложения*` (колонка `file_content` + создаётся, но модель распознавания настраивается вручную). +- **URL потоков Power Automate** (в коде сервисов) — указывают на прежнее окружение. +- **`Единицы измерения`** (OID) живёт на `/sites/stroy` — внешний справочник, не трогаем. + +## Разовая подготовка (app-only, Sites.Selected) + +Как в request-portal. Приложению нужен доступ **и к `/sites/sed`, и к `/sites/kps`** +(последнее — только для экспорта схемы KPS с текущего сайта): + +```powershell +# один раз (если приложения ещё нет) +Register-PnPEntraIDApp -ApplicationName "PnP-Provisioning-Scoped" -Tenant "tnsukz.onmicrosoft.com" -SharePointApplicationPermissions "Sites.Selected" -Store CurrentUser + +# админ выдаёт доступ к обоим сайтам +Grant-PnPAzureADAppSitePermission -AppId "" -DisplayName "PnP-Provisioning-Scoped" -Permissions FullControl -Site "https://tnsukz.sharepoint.com/sites/sed" +Grant-PnPAzureADAppSitePermission -AppId "" -DisplayName "PnP-Provisioning-Scoped" -Permissions Read -Site "https://tnsukz.sharepoint.com/sites/kps" +``` + +Отпечаток сертификата: +```powershell +Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Subject -like "*PnP-Provisioning-Scoped*" } | Select-Object Subject, Thumbprint +``` + +## Шаг 1. Снять схему с живых списков → манифесты + +```powershell +cd provisioning +./Export-ListSchema.ps1 ` + -ClientId "" ` + -Tenant "tnsukz.onmicrosoft.com" ` + -Thumbprint "<отпечаток>" +``` + +Создаст `manifests/kps.provisioning.json`, `manifests/ct.provisioning.json`, +`manifests/oid.provisioning.json`. Только одна форма — флаг `-Only kps`. + +**Проверь манифесты глазами** и закоммить в репозиторий — это и есть источник правды по схеме. + +## Шаг 2. Воспроизвести списки на `/sites/sed` + +Сначала на **тестовом** сайте, потом на бою. + +```powershell +./Provision-Lists.ps1 ` + -SiteUrl "https://tnsukz.sharepoint.com/sites/sed" ` + -ClientId "" ` + -Tenant "tnsukz.onmicrosoft.com" ` + -Thumbprint "<отпечаток>" +``` + +Идёт в два прохода: сначала все списки/библиотеки (чтобы Lookup нашли цель), потом +колонки. Идемпотентно — повторный запуск ничего не ломает. `-Only kps` провижионит +только KPS (но общие `Поставщики`/`Курс Валют` должны уже существовать). + +Лог: `[+]` создано, `[=]` уже было, `[~]` обновлено, `[!]` предупреждение. + +## Формат манифеста + +```jsonc +{ + "form": "kps", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "lists": [ + { + "title": "Сравнительная таблица КПС", + "template": "GenericList", // или "DocumentLibrary" + "shared": false, + "columns": [ + { "internalName": "number", "title": "Номер СТ", "type": "Number" }, + { "internalName": "status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "..."] }, + { "internalName": "winSupplier", "title": "Победитель", "type": "Lookup", "lookupList": "Поставщики", "lookupShowField": "Title" } + ] + } + ] +} +``` + +Типы колонок: `Text`, `Note`, `Number` (+`decimals`), `Choice` (+`choices`, +`multiChoice`), +`DateTime` (+`dateOnly`), `Boolean`, `User` (+`userSelectionMode`, +`allowMultiple`), +`Lookup` (+`lookupList`, +`lookupShowField`, +`allowMultiple`). + +## Порядок миграции KPS (cutover) + +1. Экспорт (Шаг 1) → манифесты в репозитории. +2. Провижн на `/sites/sed` (Шаг 2) → пустые списки `… КПС` + общие справочники. +3. Перенести данные (справочники, а при необходимости — рабочие СТ) на новые списки. +4. Задеплоить обновлённый `ct-forms-portal` (код уже смотрит на `… КПС` / `/sites/sed`). + +До шага 4 старый `kps-ct-form` на `/sites/kps` продолжает работать — он не тронут. diff --git a/provisioning/manifests/ct.provisioning.json b/provisioning/manifests/ct.provisioning.json new file mode 100644 index 0000000..b91ba0b --- /dev/null +++ b/provisioning/manifests/ct.provisioning.json @@ -0,0 +1,530 @@ +{ + "form": "ct", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "lists": [ + { + "title": "Сравнительная таблица", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "number", + "title": "Номер СТ", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "project", + "title": "Проект", + "type": "Text" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Note" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "Черновик", + "Согласовано", + "На согласовании" + ] + }, + { + "internalName": "winSupplier", + "title": "Выбранный поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "number0", + "title": "Номер СТ", + "type": "Number", + "decimals": 0 + } + ] + }, + { + "title": "Позиции СТ", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица", + "lookupShowField": "number" + }, + { + "internalName": "demandNumber", + "title": "Номер потребности", + "type": "Text" + }, + { + "internalName": "position", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "disciplineCode", + "title": "Марка", + "type": "Text" + }, + { + "internalName": "SMR", + "title": "Тип СМР", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "agskCode", + "title": "Код АГСК", + "type": "Text" + }, + { + "internalName": "uniqueCode", + "title": "Уникальный код", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "positionType", + "title": "Тип", + "type": "Choice", + "choices": [ + "ТМЦ", + "Дополнительные затраты" + ] + }, + { + "internalName": "deadline", + "title": "Срок поставки", + "type": "DateTime" + }, + { + "internalName": "CT0", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица", + "lookupShowField": "number" + }, + { + "internalName": "quantity0", + "title": "Количество", + "type": "Number", + "decimals": 3 + } + ] + }, + { + "title": "Оценка поставщиков", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "position", + "title": "Позиция", + "type": "Lookup", + "lookupList": "Позиции СТ", + "lookupShowField": "position" + }, + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "supplierPosition", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "price", + "title": "Цена", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summa", + "title": "Сумма", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "valuta", + "title": "Валюта", + "type": "Lookup", + "lookupList": "Курс Валют", + "lookupShowField": "code" + }, + { + "internalName": "priceTenge", + "title": "Цена в тенге", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summaTenge", + "title": "Сумма в тенге", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "offer", + "title": "КП", + "type": "Lookup", + "lookupList": "Коммерческие предложения", + "lookupShowField": "FileLeafRef" + }, + { + "internalName": "deliveryDistanceKm", + "title": "deliveryDistanceKm", + "type": "Number" + } + ] + }, + { + "title": "Критерии", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "deliveryType", + "title": "Условия поставки", + "type": "Choice", + "choices": [ + "DAP", + "FOB", + "DDP", + "EXW" + ] + }, + { + "internalName": "paymentType", + "title": "Условия оплаты", + "type": "Text" + }, + { + "internalName": "deadline", + "title": "Сроки изготовления и доставки", + "type": "Text" + }, + { + "internalName": "sertificates", + "title": "Сертификаты", + "type": "Choice", + "choices": [ + "Да", + "Нет" + ] + }, + { + "internalName": "comment", + "title": "Примечание", + "type": "Note" + }, + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица", + "lookupShowField": "number" + }, + { + "internalName": "supplier0", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + } + ] + }, + { + "title": "Согласование СТ", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "order0", + "title": "Порядок", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "aproover", + "title": "Согласующий", + "type": "User", + "userSelectionMode": "PeopleAndGroups" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "На согласовании", + "Согласовано", + "На исполнении", + "Выполнено", + "Рекомендую", + "Черновик", + "Отклонено" + ] + }, + { + "internalName": "aproovDate", + "title": "Дата", + "type": "DateTime" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "CT", + "title": "CT", + "type": "Lookup", + "lookupList": "Сравнительная таблица", + "lookupShowField": "Title" + }, + { + "internalName": "canAddApprover", + "title": "Добавить делеганта", + "type": "Boolean" + } + ] + }, + { + "title": "Не получены КП", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "CT", + "title": "CT", + "type": "Lookup", + "lookupList": "Сравнительная таблица", + "lookupShowField": "number" + } + ] + }, + { + "title": "Поставщики", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "name", + "title": "Наименование поставщика", + "type": "Text" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Text" + } + ] + }, + { + "title": "Курс Валют", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "code", + "title": "Код валюты", + "type": "Choice", + "choices": [ + "USD", + "RUB", + "CNY", + "EUR" + ] + }, + { + "internalName": "courseDate", + "title": "Дата курса", + "type": "DateTime" + }, + { + "internalName": "course", + "title": "Курс", + "type": "Number", + "decimals": 3 + } + ] + }, + { + "title": "Матрица согласования СТ", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "company", + "title": "Компания", + "type": "Choice", + "choices": [ + "ТОО Монтажник-Т", + "ТОО ГеоТрек", + "ТОО Selin Invest", + "ТОО ТехЛаб", + "ТОО TANSU Construction", + "ТОО АСТЕЛ-К", + "ТОО KazPromService", + "ТОО CL MANAGEMENT", + "ТОО QAZGEOMAX", + "ТОО AFL Engineering", + "ТОО СТ Global", + "ТОО ОСН Беркут" + ], + "required": true + }, + { + "internalName": "department", + "title": "Отдел", + "type": "Text" + }, + { + "internalName": "approover", + "title": "Согласующий", + "type": "User", + "userSelectionMode": "PeopleOnly" + }, + { + "internalName": "_x041f__x043e__x0440__x044f__x04", + "title": "Порядок", + "type": "Number" + }, + { + "internalName": "post", + "title": "Должность", + "type": "Text" + }, + { + "internalName": "canAddAproover", + "title": "добавление согласующих", + "type": "Boolean" + } + ] + }, + { + "title": "Коммерческие предложения", + "template": "DocumentLibrary", + "shared": false, + "columns": [ + { + "internalName": "Title", + "title": "Название", + "type": "Text" + }, + { + "internalName": "_ExtendedDescription", + "title": "Описание", + "type": "Note" + }, + { + "internalName": "customer", + "title": "Наименование поставщика", + "type": "Text" + }, + { + "internalName": "PrimeClassificationStatus", + "title": "Состояние обработки", + "type": "Text" + }, + { + "internalName": "PrimeClassificationStatusDetails", + "title": "Подробные сведения об обработке", + "type": "Note" + }, + { + "internalName": "PrimeLastClassified", + "title": "Обработано", + "type": "DateTime" + }, + { + "internalName": "PrimeCorrectedByUser", + "title": "Исправлен", + "type": "Boolean" + }, + { + "internalName": "deadline", + "title": "Срок действия КП", + "type": "DateTime", + "dateOnly": true + }, + { + "internalName": "PrimeConfidenceScore", + "title": "Степень достоверности", + "type": "Number", + "decimals": 4 + }, + { + "internalName": "PrimeModelVersion", + "title": "Версия модели", + "type": "Text" + }, + { + "internalName": "_ModelId", + "title": "_ModelId", + "type": "Text" + }, + { + "internalName": "file_content", + "title": "file_content", + "type": "Note" + }, + { + "internalName": "IssueDate", + "title": "Дата документа", + "type": "DateTime", + "dateOnly": true + }, + { + "internalName": "DocumentNumber", + "title": "Номер документа", + "type": "Text" + } + ] + } + ] +} diff --git a/provisioning/manifests/kps.provisioning.json b/provisioning/manifests/kps.provisioning.json new file mode 100644 index 0000000..7b170a9 --- /dev/null +++ b/provisioning/manifests/kps.provisioning.json @@ -0,0 +1,377 @@ +{ + "form": "kps", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "lists": [ + { + "title": "Сравнительная таблица КПС", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "number", + "title": "Номер СТ", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "project", + "title": "Проект", + "type": "Text" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "Черновик", + "Согласовано", + "На согласовании", + "Отклонено" + ] + }, + { + "internalName": "winSupplier", + "title": "Выбранный поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + } + ] + }, + { + "title": "Позиции СТ КПС", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица КПС", + "lookupShowField": "number" + }, + { + "internalName": "demandNumber", + "title": "Номер заявки на закуп", + "type": "Text" + }, + { + "internalName": "position", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "catalogNumber", + "title": "Кат. номер", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "uniqueCode", + "title": "Уникальный код", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "positionType", + "title": "Тип", + "type": "Choice", + "choices": [ + "ТМЦ", + "Дополнительные затраты" + ] + }, + { + "internalName": "deadline", + "title": "Срок поставки", + "type": "DateTime" + } + ] + }, + { + "title": "Оценка поставщиков КПС", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "position", + "title": "Позиция", + "type": "Lookup", + "lookupList": "Позиции СТ КПС", + "lookupShowField": "position" + }, + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "supplierPosition", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "price", + "title": "Цена", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summa", + "title": "Сумма", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "valuta", + "title": "Валюта", + "type": "Lookup", + "lookupList": "Курс Валют", + "lookupShowField": "code" + }, + { + "internalName": "priceTenge", + "title": "Цена в тенге", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summaTenge", + "title": "Сумма в тенге", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "offer", + "title": "КП", + "type": "Lookup", + "lookupList": "Коммерческие предложения КПС", + "lookupShowField": "FileLeafRef" + }, + { + "internalName": "deliveryDistanceKm", + "title": "deliveryDistanceKm", + "type": "Number" + } + ] + }, + { + "title": "Критерии КПС", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица КПС", + "lookupShowField": "number" + }, + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "deliveryType", + "title": "Условия поставки", + "type": "Choice", + "choices": [ + "DAP", + "FOB", + "DDP", + "EXW" + ] + }, + { + "internalName": "paymentType", + "title": "Условия оплаты", + "type": "Text" + }, + { + "internalName": "deadline", + "title": "Сроки изготовления и доставки", + "type": "Text" + }, + { + "internalName": "sertificates", + "title": "Сертификаты", + "type": "Choice", + "choices": [ + "Да", + "Нет" + ] + }, + { + "internalName": "comment", + "title": "Примечание", + "type": "Text" + } + ] + }, + { + "title": "Согласование СТ КПС", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица КПС", + "lookupShowField": "number" + }, + { + "internalName": "order0", + "title": "Порядок", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "aproover", + "title": "Согласующий", + "type": "User", + "userSelectionMode": "PeopleAndGroups" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "На согласовании", + "Согласовано", + "На исполнении", + "Выполнено" + ] + }, + { + "internalName": "aproovDate", + "title": "Дата", + "type": "DateTime" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + } + ] + }, + { + "title": "Не получены КП КПС", + "template": "GenericList", + "shared": true, + "columns": [ + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "CT", + "title": "CT", + "type": "Lookup", + "lookupList": "Сравнительная таблица КПС", + "lookupShowField": "Title" + } + ] + }, + { + "title": "Матрица согласования КПС", + "template": "GenericList", + "shared": true, + "columns": [ + { + "internalName": "company", + "title": "Компания", + "type": "Choice", + "choices": [ + "ТОО Монтажник-Т", + "ТОО ГеоТрек", + "ТОО Selin Invest", + "ТОО ТехЛаб", + "ТОО TANSU Construction", + "ТОО АСТЕЛ-К", + "ТОО KazPromService", + "ТОО CL MANAGEMENT", + "ТОО QAZGEOMAX", + "ТОО AFL Engineering", + "ТОО СТ Global", + "ТОО ОСН Беркут" + ] + }, + { + "internalName": "department", + "title": "Отдел", + "type": "Text" + }, + { + "internalName": "approover", + "title": "Согласующий", + "type": "User", + "userSelectionMode": "PeopleOnly" + }, + { + "internalName": "order0", + "title": "Порядок", + "type": "Number" + }, + { + "internalName": "post", + "title": "Должность", + "type": "Text" + } + ] + }, + { + "title": "Коммерческие предложения КПС", + "template": "DocumentLibrary", + "shared": false, + "columns": [ + { + "internalName": "Title", + "title": "Название", + "type": "Text" + }, + { + "internalName": "_ExtendedDescription", + "title": "Описание", + "type": "Note" + }, + { + "internalName": "file_content", + "title": "file_content", + "type": "Note" + } + ] + } + ] +} diff --git a/provisioning/manifests/oid.provisioning.json b/provisioning/manifests/oid.provisioning.json new file mode 100644 index 0000000..4f76625 --- /dev/null +++ b/provisioning/manifests/oid.provisioning.json @@ -0,0 +1,378 @@ +{ + "form": "oid", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "lists": [ + { + "title": "Сравнительная таблица ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "number", + "title": "Номер СТ", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "project", + "title": "Проект", + "type": "Text" + }, + { + "internalName": "workName", + "title": "Наименование работ", + "type": "Text" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "Черновик", + "Согласовано", + "На согласовании" + ] + }, + { + "internalName": "winSupplier", + "title": "Выигравший поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + } + ] + }, + { + "title": "Позиции СТ ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица ОИД", + "lookupShowField": "number" + }, + { + "internalName": "parentGroup", + "title": "Наименование группы позиции", + "type": "Text" + }, + { + "internalName": "position", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "positionType", + "title": "Тип", + "type": "Choice", + "choices": [ + "Дополнительные затраты", + "Работа" + ] + }, + { + "internalName": "priceSmeta", + "title": "Цена из сметы", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summaSmeta", + "title": "Сумма из сметы", + "type": "Number", + "decimals": 2 + } + ] + }, + { + "title": "Оценка поставщиков ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "position", + "title": "Позиция", + "type": "Lookup", + "lookupList": "Позиции СТ ОИД", + "lookupShowField": "position" + }, + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "supplierPosition", + "title": "Наименование позиции", + "type": "Text" + }, + { + "internalName": "unit", + "title": "Ед. изм", + "type": "Text" + }, + { + "internalName": "quantity", + "title": "Количество", + "type": "Number", + "decimals": 3 + }, + { + "internalName": "price", + "title": "Цена", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "summa", + "title": "Сумма", + "type": "Number", + "decimals": 2 + }, + { + "internalName": "offer", + "title": "КП", + "type": "Lookup", + "lookupList": "Коммерческие предложения", + "lookupShowField": "Title" + } + ] + }, + { + "title": "Критерии ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица ОИД", + "lookupShowField": "number" + }, + { + "internalName": "supplier", + "title": "Поставщик", + "type": "Lookup", + "lookupList": "Поставщики", + "lookupShowField": "name" + }, + { + "internalName": "deliveryType", + "title": "Условия поставки", + "type": "Choice", + "choices": [ + "DAP", + "FOB", + "DDP", + "EXW" + ] + }, + { + "internalName": "paymentType", + "title": "Условия оплаты", + "type": "Text" + }, + { + "internalName": "deadline", + "title": "Сроки изготовления и доставки", + "type": "Text" + }, + { + "internalName": "sertificates", + "title": "Сертификаты", + "type": "Choice", + "choices": [ + "Да", + "Нет" + ] + }, + { + "internalName": "comment", + "title": "Примечание", + "type": "Text" + }, + { + "internalName": "criteriaLabel", + "title": "Название доп.условия", + "type": "Text" + } + ] + }, + { + "title": "Согласование СТ ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица ОИД", + "lookupShowField": "number" + }, + { + "internalName": "order0", + "title": "Порядок", + "type": "Number", + "decimals": 0 + }, + { + "internalName": "aproover", + "title": "Согласующий", + "type": "User", + "userSelectionMode": "PeopleAndGroups" + }, + { + "internalName": "status", + "title": "Статус", + "type": "Choice", + "choices": [ + "На согласовании", + "Согласовано", + "На исполнении", + "Выполнено", + "Рекомендую", + "Черновик", + "Отклонено" + ] + }, + { + "internalName": "aproovDate", + "title": "Дата", + "type": "DateTime" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + }, + { + "internalName": "canAddAproover", + "title": "Добавить делеганта", + "type": "Boolean" + } + ] + }, + { + "title": "Не получены КП ОИД", + "template": "GenericList", + "shared": false, + "columns": [ + { + "internalName": "CT", + "title": "Номер СТ", + "type": "Lookup", + "lookupList": "Сравнительная таблица ОИД", + "lookupShowField": "number" + }, + { + "internalName": "comment", + "title": "Комментарий", + "type": "Text" + } + ] + }, + { + "title": "Коммерческие предложения ОИД", + "template": "DocumentLibrary", + "shared": false, + "columns": [ + { + "internalName": "Title", + "title": "Название", + "type": "Text" + }, + { + "internalName": "_ExtendedDescription", + "title": "Описание", + "type": "Note" + }, + { + "internalName": "file_content", + "title": "file_content", + "type": "Note" + }, + { + "internalName": "_ModelId0", + "title": "_ModelId", + "type": "Text" + }, + { + "internalName": "customer", + "title": "Наименование поставщика", + "type": "Text" + }, + { + "internalName": "DocumentNumber", + "title": "Номер документа", + "type": "Text" + }, + { + "internalName": "PrimeLastClassified0", + "title": "Обработано", + "type": "DateTime", + "dateOnly": true + }, + { + "internalName": "PrimeClassificationStatusDetails0", + "title": "Подробные сведения об обработке", + "type": "Note" + }, + { + "internalName": "PrimeClassificationStatus0", + "title": "Состояние обработки", + "type": "Text" + }, + { + "internalName": "deadline", + "title": "Срок действия КП", + "type": "DateTime", + "dateOnly": true + }, + { + "internalName": "PrimeConfidenceScore0", + "title": "Степень достоверности", + "type": "Number" + }, + { + "internalName": "PrimeModelVersion0", + "title": "Версия модели", + "type": "Text" + }, + { + "internalName": "IssueDate0", + "title": "Дата документа", + "type": "DateTime", + "dateOnly": true + }, + { + "internalName": "PrimeCorrectedByUser0", + "title": "Исправлен", + "type": "Boolean" + } + ] + } + ] +} diff --git a/provisioning/sources.json b/provisioning/sources.json new file mode 100644 index 0000000..7c4f5c5 --- /dev/null +++ b/provisioning/sources.json @@ -0,0 +1,60 @@ +{ + "_comment": "Что снимать Export-ListSchema.ps1. sourceSite — где списки живут СЕЙЧАС; targetSite — куда их воспроизводит Provision-Lists.ps1. Для KPS применяется renameSuffix ' КПС' ко всем спискам, КРОМЕ renameExclude (общие справочники и уже-уникальные имена). CT владеет общими 'Поставщики'/'Курс Валют'/'Матрица согласования СТ' — поэтому провижионь с CT (или все формы разом), иначе Lookup'ы KPS/OID не найдут цель.", + "forms": [ + { + "form": "kps", + "sourceSite": "https://tnsukz.sharepoint.com/sites/kps", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "outFile": "manifests/kps.provisioning.json", + "renameSuffix": " КПС", + "renameExclude": ["Поставщики", "Курс Валют", "Не получены КП КПС", "Матрица согласования КПС"], + "lists": [ + { "title": "Сравнительная таблица", "template": "GenericList" }, + { "title": "Позиции СТ", "template": "GenericList" }, + { "title": "Оценка поставщиков", "template": "GenericList" }, + { "title": "Критерии", "template": "GenericList" }, + { "title": "Согласование СТ", "template": "GenericList" }, + { "title": "Не получены КП КПС", "template": "GenericList" }, + { "title": "Матрица согласования КПС", "template": "GenericList" }, + { "title": "Коммерческие предложения", "template": "DocumentLibrary" } + ] + }, + { + "form": "ct", + "sourceSite": "https://tnsukz.sharepoint.com/sites/sed", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "outFile": "manifests/ct.provisioning.json", + "renameSuffix": "", + "renameExclude": [], + "lists": [ + { "title": "Сравнительная таблица", "template": "GenericList" }, + { "title": "Позиции СТ", "template": "GenericList" }, + { "title": "Оценка поставщиков", "template": "GenericList" }, + { "title": "Критерии", "template": "GenericList" }, + { "title": "Согласование СТ", "template": "GenericList" }, + { "title": "Не получены КП", "template": "GenericList" }, + { "title": "Поставщики", "template": "GenericList" }, + { "title": "Курс Валют", "template": "GenericList" }, + { "title": "Матрица согласования СТ", "template": "GenericList" }, + { "title": "Коммерческие предложения", "template": "DocumentLibrary" } + ] + }, + { + "form": "oid", + "sourceSite": "https://tnsukz.sharepoint.com/sites/sed", + "targetSite": "https://tnsukz.sharepoint.com/sites/sed", + "outFile": "manifests/oid.provisioning.json", + "renameSuffix": "", + "renameExclude": [], + "lists": [ + { "title": "Сравнительная таблица ОИД", "template": "GenericList" }, + { "title": "Позиции СТ ОИД", "template": "GenericList" }, + { "title": "Оценка поставщиков ОИД", "template": "GenericList" }, + { "title": "Критерии ОИД", "template": "GenericList" }, + { "title": "Согласование СТ ОИД", "template": "GenericList" }, + { "title": "Не получены КП ОИД", "template": "GenericList" }, + { "title": "Коммерческие предложения ОИД", "template": "DocumentLibrary" } + ] + } + ] +}