Compare commits

...

3 Commits

Author SHA1 Message Date
kawa 5c5e4b1415 Buttons size fix 2026-03-16 16:55:27 +01:00
kawa 086804edf9 Added functionnality : you can vcreate a whole folder tree by importing a CSV (see examples fodler) 2026-03-16 16:39:37 +01:00
kawa 9dc85c8057 Added sample CSV files for user/sites importation.
Release zip package / release (push) Successful in 1s
Fixed a few bugs.
2026-03-16 13:45:00 +01:00
5 changed files with 331 additions and 53 deletions
+283 -52
View File
@@ -2854,6 +2854,18 @@ $script:LangDefault = @{
"bulk.status.creating" = "Creating..." "bulk.status.creating" = "Creating..."
"bulk.status.ok" = "OK" "bulk.status.ok" = "OK"
"bulk.status.error" = "Error" "bulk.status.error" = "Error"
"tab.structure" = " Structure "
"grp.struct.csv" = "CSV Import"
"lbl.struct.desc" = "Import a CSV to create a folder tree. Each column represents a depth level."
"btn.struct.csv" = "Load CSV..."
"grp.struct.preview" = "Preview"
"grp.struct.target" = "Target"
"lbl.struct.library" = "Target library:"
"ph.struct.library" = "Shared Documents"
"btn.struct.create" = "Create Structure"
"btn.struct.clear" = "Clear"
"struct.col.path" = "Full Path"
"struct.col.depth" = "Depth"
} }
$script:Lang = $null # null = use LangDefault $script:Lang = $null # null = use LangDefault
@@ -3606,7 +3618,61 @@ $btnBulkCreate.Size = New-Object System.Drawing.Size(200, 34)
$tabBulk.Controls.AddRange(@($grpBulkList, $btnBulkCreate)) $tabBulk.Controls.AddRange(@($grpBulkList, $btnBulkCreate))
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk)) # ══════════════════════════════════════════════════════════════════════════════
# Tab 8 Structure (folder tree from CSV)
# ══════════════════════════════════════════════════════════════════════════════
$tabStruct = New-Object System.Windows.Forms.TabPage
$tabStruct.Text = T "tab.structure"
# ── CSV import + target (single row) ───────────────────────────────────────
$grpStructCsv = New-Group (T "grp.struct.csv") 10 4 620 52
$lblStructDesc = New-Object System.Windows.Forms.Label
$lblStructDesc.Text = T "lbl.struct.desc"
$lblStructDesc.Location = New-Object System.Drawing.Point(10, 20)
$lblStructDesc.Size = New-Object System.Drawing.Size(460, 20)
$btnStructCsv = New-Object System.Windows.Forms.Button
$btnStructCsv.Text = T "btn.struct.csv"
$btnStructCsv.Location = New-Object System.Drawing.Point(490, 18)
$btnStructCsv.Size = New-Object System.Drawing.Size(118, 26)
$grpStructCsv.Controls.AddRange(@($lblStructDesc, $btnStructCsv))
# ── Preview ────────────────────────────────────────────────────────────────
$grpStructPreview = New-Group (T "grp.struct.preview") 10 58 620 148
$tvStruct = New-Object System.Windows.Forms.TreeView
$tvStruct.Location = New-Object System.Drawing.Point(10, 18)
$tvStruct.Size = New-Object System.Drawing.Size(598, 120)
$tvStruct.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$tvStruct.ShowLines = $true
$tvStruct.ShowPlusMinus = $true
$grpStructPreview.Controls.Add($tvStruct)
# ── Target + Buttons (single row) ─────────────────────────────────────────
$lblStructLib = New-Object System.Windows.Forms.Label
$lblStructLib.Text = T "lbl.struct.library"
$lblStructLib.Location = New-Object System.Drawing.Point(12, 214)
$lblStructLib.Size = New-Object System.Drawing.Size(110, 20)
$txtStructLib = New-Object System.Windows.Forms.TextBox
$txtStructLib.Location = New-Object System.Drawing.Point(124, 212)
$txtStructLib.Size = New-Object System.Drawing.Size(200, 22)
$txtStructLib.PlaceholderText = T "ph.struct.library"
$btnStructCreate = New-ActionBtn (T "btn.struct.create") 340 208 ([System.Drawing.Color]::FromArgb(0, 120, 212))
$btnStructCreate.Size = New-Object System.Drawing.Size(180, 30)
$btnStructClear = New-Object System.Windows.Forms.Button
$btnStructClear.Text = T "btn.struct.clear"
$btnStructClear.Location = New-Object System.Drawing.Point(528, 208)
$btnStructClear.Size = New-Object System.Drawing.Size(90, 30)
$tabStruct.Controls.AddRange(@($grpStructCsv, $grpStructPreview, $lblStructLib, $txtStructLib, $btnStructCreate, $btnStructClear))
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk, $tabStruct))
# ── Progress bar ─────────────────────────────────────────────────────────────── # ── Progress bar ───────────────────────────────────────────────────────────────
$progressBar = New-Object System.Windows.Forms.ProgressBar $progressBar = New-Object System.Windows.Forms.ProgressBar
@@ -3793,6 +3859,11 @@ $_reg = {
& $_reg $script:i18nMap $btnBulkRemove "btn.bulk.remove" & $_reg $script:i18nMap $btnBulkRemove "btn.bulk.remove"
& $_reg $script:i18nMap $btnBulkClear "btn.bulk.clear" & $_reg $script:i18nMap $btnBulkClear "btn.bulk.clear"
& $_reg $script:i18nMap $btnBulkCreate "btn.bulk.create" & $_reg $script:i18nMap $btnBulkCreate "btn.bulk.create"
& $_reg $script:i18nMap $lblStructDesc "lbl.struct.desc"
& $_reg $script:i18nMap $btnStructCsv "btn.struct.csv"
& $_reg $script:i18nMap $lblStructLib "lbl.struct.library"
& $_reg $script:i18nMap $btnStructCreate "btn.struct.create"
& $_reg $script:i18nMap $btnStructClear "btn.struct.clear"
# Tab pages # Tab pages
& $_reg $script:i18nTabs $tabPerms "tab.perms" & $_reg $script:i18nTabs $tabPerms "tab.perms"
@@ -3802,6 +3873,7 @@ $_reg = {
& $_reg $script:i18nTabs $tabDupes "tab.dupes" & $_reg $script:i18nTabs $tabDupes "tab.dupes"
& $_reg $script:i18nTabs $tabTransfer "tab.transfer" & $_reg $script:i18nTabs $tabTransfer "tab.transfer"
& $_reg $script:i18nTabs $tabBulk "tab.bulk" & $_reg $script:i18nTabs $tabBulk "tab.bulk"
& $_reg $script:i18nTabs $tabStruct "tab.structure"
# Menu items # Menu items
& $_reg $script:i18nMenus $menuSettings "menu.settings" & $_reg $script:i18nMenus $menuSettings "menu.settings"
@@ -3816,6 +3888,7 @@ $script:i18nPlaceholders = [System.Collections.Generic.Dictionary[string,object]
& $_reg $script:i18nPlaceholders $txtSrchModBy "ph.modified.by" & $_reg $script:i18nPlaceholders $txtSrchModBy "ph.modified.by"
& $_reg $script:i18nPlaceholders $txtSrchLib "ph.library" & $_reg $script:i18nPlaceholders $txtSrchLib "ph.library"
& $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib" & $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib"
& $_reg $script:i18nPlaceholders $txtStructLib "ph.struct.library"
& $_reg $script:i18nPlaceholders $txtXferSrcSite "ph.xfer.site" & $_reg $script:i18nPlaceholders $txtXferSrcSite "ph.xfer.site"
& $_reg $script:i18nPlaceholders $txtXferSrcLib "ph.xfer.library" & $_reg $script:i18nPlaceholders $txtXferSrcLib "ph.xfer.library"
& $_reg $script:i18nPlaceholders $txtXferDstSite "ph.xfer.site" & $_reg $script:i18nPlaceholders $txtXferDstSite "ph.xfer.site"
@@ -5229,30 +5302,41 @@ $btnBulkCsv.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog $ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "CSV (*.csv)|*.csv|All (*.*)|*.*" $ofd.Filter = "CSV (*.csv)|*.csv|All (*.*)|*.*"
if ($ofd.ShowDialog($form) -ne "OK") { return } if ($ofd.ShowDialog($form) -ne "OK") { return }
$rows = Import-Csv $ofd.FileName # Try semicolon first (handles commas inside fields), fall back to comma
$content = Get-Content $ofd.FileName -Raw
if ($content -match ';') {
$rows = Import-Csv $ofd.FileName -Delimiter ';'
} else {
$rows = Import-Csv $ofd.FileName
}
$count = 0 $count = 0
foreach ($r in $rows) { foreach ($r in $rows) {
# Accepted column names (case-insensitive via PSObject) # Read columns via PSObject properties (case-insensitive)
$name = if ($r.Name) { $r.Name } elseif ($r.name) { $r.name } $props = @{}
elseif ($r.Title) { $r.Title } elseif ($r.title) { $r.title } else { "" } foreach ($p in $r.PSObject.Properties) { $props[$p.Name.ToLower()] = "$($p.Value)".Trim() }
$alias = if ($r.Alias) { $r.Alias } elseif ($r.alias) { $r.alias }
elseif ($r.URL) { $r.URL } elseif ($r.url) { $r.url } else { "" }
$type = if ($r.Type) { $r.Type } elseif ($r.type) { $r.type } else { "Team" }
$tpl = if ($r.Template) { $r.Template } elseif ($r.template) { $r.template } else { "" }
$own = if ($r.Owners) { $r.Owners } elseif ($r.owners) { $r.owners }
elseif ($r.Owner) { $r.Owner } elseif ($r.owner) { $r.owner } else { "" }
$mem = if ($r.Members) { $r.Members } elseif ($r.members) { $r.members } else { "" }
if (-not $name -or -not $alias) { continue } $name = if ($props['name']) { $props['name'] } elseif ($props['title']) { $props['title'] } else { "" }
$alias = if ($props['alias']) { $props['alias'] } elseif ($props['url']) { $props['url'] } else { "" }
$type = if ($props['type']) { $props['type'] } else { "Team" }
$tpl = if ($props['template']) { $props['template'] } else { "" }
$own = if ($props['owners']) { $props['owners'] } elseif ($props['owner']) { $props['owner'] } else { "" }
$mem = if ($props['members']) { $props['members'] } else { "" }
# Name is required; skip empty rows
if (-not $name) { continue }
# Auto-generate alias from name if not provided
if (-not $alias) {
$alias = $name.ToLower() -replace '[^a-z0-9\-]', '-' -replace '-+', '-' -replace '^-|-$', ''
}
# Normalize type # Normalize type
if ($type -match '^[Cc]omm') { $type = "Communication" } else { $type = "Team" } if ($type -match '^[Cc]omm') { $type = "Communication" } else { $type = "Team" }
Add-BulkListItem @{ Add-BulkListItem @{
Name = $name.Trim() Name = $name
Alias = $alias.Trim() Alias = $alias
Type = $type Type = $type
Template = $tpl.Trim() Template = $tpl
Owners = $own.Trim() Owners = $own
Members = $mem.Trim() Members = $mem
} }
$count++ $count++
} }
@@ -5340,32 +5424,83 @@ $btnBulkCreate.Add_Click({
$name = $entry.Name $name = $entry.Name
$alias = $entry.Alias $alias = $entry.Alias
$isTeam = $entry.Type -ne "Communication" $isTeam = $entry.Type -ne "Communication"
$owners = @($entry.Owners -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $ownerRaw = "$($entry.Owners)"
$members = @($entry.Members -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $memberRaw = "$($entry.Members)"
$owners = [string[]]@($ownerRaw -split '[,;\s]+' | Where-Object { $_ -match '\S' })
$members = [string[]]@($memberRaw -split '[,;\s]+' | Where-Object { $_ -match '\S' })
$tplName = $entry.Template $tplName = $entry.Template
BgLog "[$idx/$total] Creating '$name' (alias: $alias, type: $($entry.Type))..." "White" BgLog "[$idx/$total] Creating '$name' (alias: $alias, type: $($entry.Type))..." "White"
BgLog " DEBUG owners raw='$ownerRaw' parsed=[$($owners -join '|')] count=$($owners.Count)" "Gray"
BgLog " DEBUG members raw='$memberRaw' parsed=[$($members -join '|')] count=$($members.Count)" "Gray"
# TeamSite requires at least one owner
if ($isTeam -and $owners.Count -eq 0) {
BgLog " ERREUR : TeamSite requires at least one owner — skipping '$name'" "Red"
$Sync.Queue.Enqueue(@{ Text = "##STATUS##"; Index = ($idx - 1); Value = "Error: no owner" })
$Sync.ErrCount++
continue
}
# Update status # Update status
$Sync.Queue.Enqueue(@{ Text = "##STATUS##"; Index = ($idx - 1); Value = "Creating..." }) $Sync.Queue.Enqueue(@{ Text = "##STATUS##"; Index = ($idx - 1); Value = "Creating..." })
try { try {
# Create the site # Create the site WITHOUT owners/members (PnP bug: odata.bind empty array)
# Current user becomes default owner; we add owners/members after creation
Connect-PnPOnline -Url $adminUrl -Interactive -ClientId $Params.ClientId Connect-PnPOnline -Url $adminUrl -Interactive -ClientId $Params.ClientId
$newUrl = if ($isTeam) { if ($isTeam) {
if ($owners.Count -gt 0) { BgLog " Creating TeamSite '$alias' (owners/members added after)..." "DarkGray"
New-PnPSite -Type TeamSite -Title $name -Alias $alias -Owners $owners -Wait $newUrl = New-PnPSite -Type TeamSite -Title $name -Alias $alias -Wait
} else {
New-PnPSite -Type TeamSite -Title $name -Alias $alias -Wait
}
} else { } else {
New-PnPSite -Type CommunicationSite -Title $name -Url "$base/sites/$alias" -Wait BgLog " Creating CommunicationSite '$alias'..." "DarkGray"
$newUrl = New-PnPSite -Type CommunicationSite -Title $name -Url "$base/sites/$alias" -Wait
} }
BgLog " Site cree : $newUrl" "LightGreen" BgLog " Site cree : $newUrl" "LightGreen"
# Connect to the new site for template + members # Connect to the new site for owners/members/template
Connect-PnPOnline -Url $newUrl -Interactive -ClientId $Params.ClientId Connect-PnPOnline -Url $newUrl -Interactive -ClientId $Params.ClientId
# Assign owners & members post-creation
if ($isTeam) {
$groupId = $null
try { $groupId = (Get-PnPSite -Includes GroupId).GroupId.Guid } catch {}
if ($groupId) {
foreach ($o in $owners) {
try {
Add-PnPMicrosoft365GroupOwner -Identity $groupId -Users $o -ErrorAction Stop
BgLog " Owner added: $o" "Cyan"
} catch { BgLog " Warn owner '$o': $($_.Exception.Message)" "DarkYellow" }
}
foreach ($m in $members) {
try {
Add-PnPMicrosoft365GroupMember -Identity $groupId -Users $m -ErrorAction Stop
BgLog " Member added: $m" "Cyan"
} catch { BgLog " Warn member '$m': $($_.Exception.Message)" "DarkYellow" }
}
} else {
BgLog " Could not get M365 GroupId — owners/members not assigned" "DarkYellow"
}
} else {
# CommunicationSite — classic SharePoint groups
if ($owners.Count -gt 0) {
$ownerGrp = Get-PnPGroup | Where-Object { $_.Title -like "*Propri*" -or $_.Title -like "*Owner*" } | Select-Object -First 1
if ($ownerGrp) {
foreach ($o in $owners) {
try { Add-PnPGroupMember -LoginName $o -Group $ownerGrp.Title -ErrorAction SilentlyContinue } catch {}
}
}
}
if ($members.Count -gt 0) {
$memberGrp = Get-PnPGroup | Where-Object { $_.Title -like "*Membre*" -or $_.Title -like "*Member*" } | Select-Object -First 1
if ($memberGrp) {
foreach ($m in $members) {
try { Add-PnPGroupMember -LoginName $m -Group $memberGrp.Title -ErrorAction SilentlyContinue } catch {}
}
}
}
}
# Apply template if specified # Apply template if specified
if ($tplName -and $Params.Templates.ContainsKey($tplName)) { if ($tplName -and $Params.Templates.ContainsKey($tplName)) {
$tpl = $Params.Templates[$tplName] $tpl = $Params.Templates[$tplName]
@@ -5406,30 +5541,7 @@ $btnBulkCreate.Add_Click({
} }
} }
# Add members
if ($members.Count -gt 0) {
$memberGroup = Get-PnPGroup | Where-Object { $_.Title -like "*Membres*" -or $_.Title -like "*Members*" } | Select-Object -First 1
if ($memberGroup) {
foreach ($m in $members) {
try {
Add-PnPGroupMember -LoginName $m -Group $memberGroup.Title -ErrorAction SilentlyContinue
} catch {}
}
BgLog " $($members.Count) member(s) added." "Cyan"
}
}
# Add owners for Communication sites (TeamSite owners set at creation)
if (-not $isTeam -and $owners.Count -gt 0) {
$ownerGroup = Get-PnPGroup | Where-Object { $_.Title -like "*Propri*" -or $_.Title -like "*Owner*" } | Select-Object -First 1
if ($ownerGroup) {
foreach ($o in $owners) {
try {
Add-PnPGroupMember -LoginName $o -Group $ownerGroup.Title -ErrorAction SilentlyContinue
} catch {}
}
}
}
$Sync.Queue.Enqueue(@{ Text = "##STATUS##"; Index = ($idx - 1); Value = "OK" }) $Sync.Queue.Enqueue(@{ Text = "##STATUS##"; Index = ($idx - 1); Value = "OK" })
$Sync.CreatedSites.Add([PSCustomObject]@{ $Sync.CreatedSites.Add([PSCustomObject]@{
@@ -5539,6 +5651,125 @@ $btnBulkCreate.Add_Click({
#endregion #endregion
#region ===== Structure (folder tree from CSV) =====
# Store the parsed folder paths
$script:_StructPaths = @()
function Build-StructTree([string]$csvPath) {
# Auto-detect delimiter
$raw = Get-Content $csvPath -Raw
$delim = if ($raw -match ';') { ';' } else { ',' }
$rows = Import-Csv $csvPath -Delimiter $delim
$paths = [System.Collections.Generic.List[string]]::new()
foreach ($r in $rows) {
$cols = @($r.PSObject.Properties | ForEach-Object { "$($_.Value)".Trim() })
# Build path from non-empty columns
$parts = @($cols | Where-Object { $_ -ne '' })
if ($parts.Count -gt 0) {
# Add all intermediate paths to ensure parents exist
for ($i = 1; $i -le $parts.Count; $i++) {
$p = ($parts[0..($i-1)] -join '/')
if (-not $paths.Contains($p)) { $paths.Add($p) }
}
}
}
$script:_StructPaths = @($paths | Sort-Object)
return $script:_StructPaths
}
function Populate-StructTreeView([string[]]$paths) {
$tvStruct.Nodes.Clear()
$nodeMap = @{}
foreach ($p in $paths) {
$parts = $p -split '/'
$parentKey = if ($parts.Count -gt 1) { ($parts[0..($parts.Count - 2)] -join '/') } else { '' }
$name = $parts[-1]
$node = New-Object System.Windows.Forms.TreeNode($name)
$node.Tag = $p
if ($parentKey -and $nodeMap.ContainsKey($parentKey)) {
$nodeMap[$parentKey].Nodes.Add($node) | Out-Null
} else {
$tvStruct.Nodes.Add($node) | Out-Null
}
$nodeMap[$p] = $node
}
$tvStruct.ExpandAll()
}
$btnStructCsv.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "CSV (*.csv)|*.csv|All (*.*)|*.*"
if ($ofd.ShowDialog($form) -ne "OK") { return }
try {
$paths = Build-StructTree $ofd.FileName
Populate-StructTreeView $paths
Write-Log "$($paths.Count) folder(s) loaded from CSV." "LightGreen"
} catch {
Write-Log "CSV error: $($_.Exception.Message)" "Red"
}
})
$btnStructClear.Add_Click({
$tvStruct.Nodes.Clear()
$script:_StructPaths = @()
Write-Log "Structure cleared." "Gray"
})
$btnStructCreate.Add_Click({
$siteUrl = $txtSiteUrl.Text.Trim()
$clientId = $txtClientId.Text.Trim()
$library = $txtStructLib.Text.Trim()
if (-not $siteUrl) { Write-Log "Site URL required." "Red"; return }
if (-not $clientId) { Write-Log "Client ID required." "Red"; return }
if (-not $library) { Write-Log "Target library required." "Red"; return }
if ($script:_StructPaths.Count -eq 0) { Write-Log "No structure loaded. Load a CSV first." "Red"; return }
$btnStructCreate.Enabled = $false
$btnStructCsv.Enabled = $false
Start-ProgressAnim
Write-Log "=== CREATING FOLDER STRUCTURE ===" "White"
Write-Log "Target: $siteUrl / $library" "Gray"
Write-Log "Folders to create: $($script:_StructPaths.Count)" "Gray"
Write-Log ("-" * 52) "DarkGray"
try {
Connect-PnPOnline -Url $siteUrl -Interactive -ClientId $clientId
# Get the library root
$list = Get-PnPList -Identity $library -ErrorAction Stop
$rf = Get-PnPProperty -ClientObject $list -Property RootFolder
$base = $rf.ServerRelativeUrl.TrimEnd('/')
$ok = 0
$err = 0
$total = $script:_StructPaths.Count
foreach ($p in $script:_StructPaths) {
$folderPath = "$base/$p"
try {
# Resolve-PnPFolder creates the full path recursively
Resolve-PnPFolder -SiteRelativePath "$library/$p" -ErrorAction Stop | Out-Null
$ok++
Write-Log " OK: $p" "LightGreen"
} catch {
$err++
Write-Log " FAIL: $p$($_.Exception.Message)" "Red"
}
}
Write-Log "=== STRUCTURE COMPLETE: $ok OK, $err error(s) ===" "White"
} catch {
Write-Log "Error: $($_.Exception.Message)" "Red"
} finally {
$btnStructCreate.Enabled = $true
$btnStructCsv.Enabled = $true
Stop-ProgressAnim
}
})
#endregion
# ── Initialisation : chargement des settings ─────────────────────────────── # ── Initialisation : chargement des settings ───────────────────────────────
$_settings = Load-Settings $_settings = Load-Settings
$script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) { $script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) {
+8
View File
@@ -0,0 +1,8 @@
Email
user1@contoso.com
user2@contoso.com
user3@contoso.com
manager1@contoso.com
designer@contoso.com
analyste@contoso.com
stagiaire@contoso.com
1 Email
2 user1@contoso.com
3 user2@contoso.com
4 user3@contoso.com
5 manager1@contoso.com
6 designer@contoso.com
7 analyste@contoso.com
8 stagiaire@contoso.com
+6
View File
@@ -0,0 +1,6 @@
Name;Alias;Type;Template;Owners;Members
Projet Alpha;projet-alpha;Team;;admin@contoso.com;user1@contoso.com, user2@contoso.com
Projet Beta;;Team;;admin@contoso.com;user3@contoso.com, user4@contoso.com
Communication RH;;Communication;;rh-admin@contoso.com;manager1@contoso.com, manager2@contoso.com
Equipe Marketing;equipe-marketing;Team;;marketing-lead@contoso.com;designer@contoso.com, redacteur@contoso.com
Portail Intranet;;Communication;;it-admin@contoso.com;
1 Name Alias Type Template Owners Members
2 Projet Alpha projet-alpha Team admin@contoso.com user1@contoso.com, user2@contoso.com
3 Projet Beta Team admin@contoso.com user3@contoso.com, user4@contoso.com
4 Communication RH Communication rh-admin@contoso.com manager1@contoso.com, manager2@contoso.com
5 Equipe Marketing equipe-marketing Team marketing-lead@contoso.com designer@contoso.com, redacteur@contoso.com
6 Portail Intranet Communication it-admin@contoso.com
+20
View File
@@ -0,0 +1,20 @@
Level1;Level2;Level3;Level4
Administration;;;
Administration;Comptabilite;;
Administration;Comptabilite;Factures;
Administration;Comptabilite;Bilans;
Administration;Ressources Humaines;;
Administration;Ressources Humaines;Contrats;
Administration;Ressources Humaines;Fiches de paie;
Projets;;;
Projets;Projet Alpha;;
Projets;Projet Alpha;Documents;
Projets;Projet Alpha;Livrables;
Projets;Projet Beta;;
Projets;Projet Beta;Documents;
Communication;;;
Communication;Interne;;
Communication;Interne;Notes de service;
Communication;Externe;;
Communication;Externe;Communiques de presse;
Communication;Externe;Newsletter;
1 Level1 Level2 Level3 Level4
2 Administration
3 Administration Comptabilite
4 Administration Comptabilite Factures
5 Administration Comptabilite Bilans
6 Administration Ressources Humaines
7 Administration Ressources Humaines Contrats
8 Administration Ressources Humaines Fiches de paie
9 Projets
10 Projets Projet Alpha
11 Projets Projet Alpha Documents
12 Projets Projet Alpha Livrables
13 Projets Projet Beta
14 Projets Projet Beta Documents
15 Communication
16 Communication Interne
17 Communication Interne Notes de service
18 Communication Externe
19 Communication Externe Communiques de presse
20 Communication Externe Newsletter
+14 -1
View File
@@ -137,5 +137,18 @@
"bulk.status.pending": "En attente", "bulk.status.pending": "En attente",
"bulk.status.creating": "Création...", "bulk.status.creating": "Création...",
"bulk.status.ok": "OK", "bulk.status.ok": "OK",
"bulk.status.error": "Erreur" "bulk.status.error": "Erreur",
"tab.structure": " Structure ",
"grp.struct.csv": "Import CSV",
"lbl.struct.desc": "Importez un CSV pour créer une arborescence. Chaque colonne représente un niveau de profondeur.",
"btn.struct.csv": "Charger CSV...",
"grp.struct.preview": "Aperçu",
"grp.struct.target": "Cible",
"lbl.struct.library": "Bibliothèque cible :",
"ph.struct.library": "Documents partagés",
"btn.struct.create": "Créer l'arborescence",
"btn.struct.clear": "Effacer",
"struct.col.path": "Chemin complet",
"struct.col.depth": "Profondeur"
} }