---
phase: 16-report-consolidation-toggle
plan: "01"
type: execute
wave: 1
depends_on: []
files_modified:
  - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs
  - SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs
  - SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml
  - SharepointToolbox/Views/Tabs/PermissionsView.xaml
  - SharepointToolbox/Localization/Strings.resx
  - SharepointToolbox/Localization/Strings.fr.resx
  - SharepointToolbox/Services/Export/UserAccessCsvExportService.cs
autonomous: true
requirements: [RPT-03]

must_haves:
  truths:
    - "MergePermissions property exists on UserAccessAuditViewModel and defaults to false"
    - "MergePermissions property exists on PermissionsViewModel and defaults to false (no-op placeholder)"
    - "Export Options GroupBox with 'Merge duplicate permissions' checkbox is visible in both audit tabs"
    - "CSV export with mergePermissions=false produces byte-identical output to current behavior"
    - "CSV export with mergePermissions=true writes consolidated rows with Locations column"
  artifacts:
    - path: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs"
      provides: "MergePermissions ObservableProperty + export call site wiring"
      contains: "_mergePermissions"
    - path: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
      provides: "MergePermissions ObservableProperty (no-op placeholder)"
      contains: "_mergePermissions"
    - path: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml"
      provides: "Export Options GroupBox with checkbox"
      contains: "Export Options"
    - path: "SharepointToolbox/Views/Tabs/PermissionsView.xaml"
      provides: "Export Options GroupBox with checkbox"
      contains: "Export Options"
    - path: "SharepointToolbox/Services/Export/UserAccessCsvExportService.cs"
      provides: "Consolidated CSV export path"
      contains: "mergePermissions"
  key_links:
    - from: "UserAccessAuditView.xaml"
      to: "UserAccessAuditViewModel.MergePermissions"
      via: "XAML Binding"
      pattern: "IsChecked.*Binding MergePermissions"
    - from: "UserAccessAuditViewModel.ExportCsvAsync"
      to: "UserAccessCsvExportService.WriteSingleFileAsync"
      via: "MergePermissions parameter passthrough"
      pattern: "WriteSingleFileAsync.*MergePermissions"
---

<objective>
Add the MergePermissions toggle property to both ViewModels, wire Export Options GroupBox in both XAML views, add localization keys, and implement the consolidated CSV export path.

Purpose: Establishes the user-facing toggle and the simpler CSV consolidation path, leaving the complex HTML rendering for Plan 02.
Output: Working toggle UI in both tabs, consolidated CSV export, non-consolidated paths unchanged.
</objective>

<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/16-report-consolidation-toggle/16-CONTEXT.md
@.planning/phases/16-report-consolidation-toggle/16-RESEARCH.md
@.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md

<interfaces>
<!-- Phase 15 consolidation API — use directly, do not re-implement -->

From SharepointToolbox/Core/Helpers/PermissionConsolidator.cs:
```csharp
public static class PermissionConsolidator
{
    internal static string MakeKey(UserAccessEntry e);
    public static IReadOnlyList<ConsolidatedPermissionEntry> Consolidate(IReadOnlyList<UserAccessEntry> entries);
}
```

From SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs:
```csharp
public record ConsolidatedPermissionEntry(
    string UserDisplayName, string UserLogin, string PermissionLevel,
    string AccessType, string GrantedThrough,
    bool IsExternalUser, bool IsHighPrivilege,
    IReadOnlyList<LocationInfo> Locations)
{
    public int LocationCount => Locations.Count;
}
```

From SharepointToolbox/Core/Models/LocationInfo.cs:
```csharp
public record LocationInfo(string SiteUrl, string SiteTitle, string ObjectTitle, string ObjectUrl, string ObjectType);
```

From SharepointToolbox/Services/Export/UserAccessCsvExportService.cs:
```csharp
public class UserAccessCsvExportService
{
    public string BuildCsv(string userDisplayName, string userLogin, IReadOnlyList<UserAccessEntry> entries);
    public async Task WriteAsync(IReadOnlyList<UserAccessEntry> entries, string outputDirectory, CancellationToken ct);
    public async Task WriteSingleFileAsync(IReadOnlyList<UserAccessEntry> entries, string filePath, CancellationToken ct);
}
```

From SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs (export call sites):
```csharp
// Line 495 — ExportCsvAsync:
await _csvExportService.WriteSingleFileAsync(Results, dialog.FileName, CancellationToken.None);
// Line 526 — ExportHtmlAsync:
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None, branding);
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add MergePermissions property to both ViewModels and localization keys</name>
  <files>
    SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs,
    SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs,
    SharepointToolbox/Localization/Strings.resx,
    SharepointToolbox/Localization/Strings.fr.resx
  </files>
  <action>
    1. In `UserAccessAuditViewModel.cs`, add a new `[ObservableProperty]` field after the existing observable properties block (around line 101):
       ```csharp
       [ObservableProperty]
       private bool _mergePermissions;
       ```
       No partial handler needed — the property defaults to `false` and has no side effects on change.

    2. In `PermissionsViewModel.cs`, add the same `[ObservableProperty]` field in the observable properties section:
       ```csharp
       [ObservableProperty]
       private bool _mergePermissions;
       ```
       This is a no-op placeholder — PermissionsViewModel does NOT use this value in any export logic.

    3. In `Strings.resx`, add two new entries following the existing naming convention (look at existing keys like `audit.grp.scanOptions`, `chk.includeInherited` etc. for the exact naming pattern):
       - Key: `audit.grp.export` — Value: `Export Options`
       - Key: `chk.merge.permissions` — Value: `Merge duplicate permissions`

    4. In `Strings.fr.resx`, add the same two keys:
       - Key: `audit.grp.export` — Value: `Options d'exportation`
       - Key: `chk.merge.permissions` — Value: `Fusionner les permissions en double`

    IMPORTANT: Both .resx files MUST have the keys added. Missing French keys cause empty strings in French locale.
  </action>
  <verify>
    <automated>cd "C:/Users/dev/Documents/projets/Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -v q 2>&1 | tail -5</automated>
  </verify>
  <done>Both ViewModels have MergePermissions property that defaults to false. Both .resx files have the two new localization keys. Solution builds without errors or warnings.</done>
</task>

<task type="auto">
  <name>Task 2: Add Export Options GroupBox to both XAML views</name>
  <files>
    SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml,
    SharepointToolbox/Views/Tabs/PermissionsView.xaml
  </files>
  <action>
    1. In `UserAccessAuditView.xaml`, locate the "Scan Options" GroupBox (around lines 199-210 — look for `GroupBox Header="{Binding [audit.grp.scanOptions]..."` or similar). Add a new GroupBox immediately AFTER the Scan Options GroupBox, within the same DockPanel, using the identical pattern:
       ```xml
       <GroupBox Header="{Binding [audit.grp.export], Source={x:Static loc:TranslationSource.Instance}}"
                 DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
         <StackPanel>
           <CheckBox Content="{Binding [chk.merge.permissions], Source={x:Static loc:TranslationSource.Instance}}"
                     IsChecked="{Binding MergePermissions}" />
         </StackPanel>
       </GroupBox>
       ```
       Match the exact `Source={x:Static loc:TranslationSource.Instance}` pattern used by the existing Scan Options GroupBox for localized headers and checkbox labels.

    2. In `PermissionsView.xaml`, locate the "Display Options" GroupBox in the left panel. Add the same Export Options GroupBox after it, using the same XAML pattern as above. The binding `{Binding MergePermissions}` will bind to `PermissionsViewModel.MergePermissions` (the no-op placeholder).

    IMPORTANT: Do NOT modify any existing XAML elements. Only ADD the new GroupBox. The GroupBox must be always visible (not conditionally hidden).
  </action>
  <verify>
    <automated>cd "C:/Users/dev/Documents/projets/Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -v q 2>&1 | tail -5</automated>
  </verify>
  <done>Both XAML views show an "Export Options" GroupBox with a "Merge duplicate permissions" checkbox bound to MergePermissions. Existing UI elements are unchanged.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: Implement consolidated CSV export path and wire ViewModel call site</name>
  <files>
    SharepointToolbox/Services/Export/UserAccessCsvExportService.cs,
    SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs,
    SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs
  </files>
  <behavior>
    - RPT-03-f: `WriteSingleFileAsync(entries, path, ct, mergePermissions: false)` produces byte-identical output to current `WriteSingleFileAsync(entries, path, ct)` — capture current output first, then verify no change
    - RPT-03-g: `WriteSingleFileAsync(entries, path, ct, mergePermissions: true)` writes consolidated CSV with header `"User","User Login","Permission Level","Access Type","Granted Through","Locations","Location Count"` and semicolon-separated site titles in Locations column
    - Edge case: single-location consolidated entry has LocationCount=1 and Locations=single site title (no semicolons)
  </behavior>
  <action>
    1. In `UserAccessCsvExportService.cs`, add `bool mergePermissions = false` parameter to `WriteSingleFileAsync`:
       ```csharp
       public async Task WriteSingleFileAsync(
           IReadOnlyList<UserAccessEntry> entries,
           string filePath,
           CancellationToken ct,
           bool mergePermissions = false)
       ```

    2. At the top of `WriteSingleFileAsync`, add an early-return consolidated branch:
       ```csharp
       if (mergePermissions)
       {
           var consolidated = PermissionConsolidator.Consolidate(entries);
           // Build consolidated CSV with distinct header and rows
           var sb = new StringBuilder();
           // Summary section (same pattern as existing)
           sb.AppendLine("\"User Access Audit Report (Consolidated)\"");
           sb.AppendLine($"\"Users Audited\",\"{consolidated.Select(e => e.UserLogin).Distinct().Count()}\"");
           sb.AppendLine($"\"Total Entries\",\"{consolidated.Count}\"");
           sb.AppendLine($"\"Generated\",\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\"");
           sb.AppendLine();
           sb.AppendLine("\"User\",\"User Login\",\"Permission Level\",\"Access Type\",\"Granted Through\",\"Locations\",\"Location Count\"");
           foreach (var entry in consolidated)
           {
               var locations = string.Join("; ", entry.Locations.Select(l => l.SiteTitle));
               sb.AppendLine(string.Join(",", new[]
               {
                   $"\"{entry.UserDisplayName}\"",
                   $"\"{entry.UserLogin}\"",
                   $"\"{entry.PermissionLevel}\"",
                   $"\"{entry.AccessType}\"",
                   $"\"{entry.GrantedThrough}\"",
                   $"\"{locations}\"",
                   $"\"{entry.LocationCount}\""
               }));
           }
           await File.WriteAllTextAsync(filePath, sb.ToString(), new UTF8Encoding(false), ct);
           return;
       }
       ```
       Leave the existing code path below this branch COMPLETELY UNTOUCHED.

    3. In `UserAccessAuditViewModel.cs`, update the `ExportCsvAsync` method call site (line ~495):
       Change: `await _csvExportService.WriteSingleFileAsync(Results, dialog.FileName, CancellationToken.None);`
       To: `await _csvExportService.WriteSingleFileAsync(Results, dialog.FileName, CancellationToken.None, MergePermissions);`

    4. Add test methods in `UserAccessCsvExportServiceTests.cs` for RPT-03-f (non-consolidated identical) and RPT-03-g (consolidated CSV format). Use test data with 2-3 UserAccessEntry rows where 2 share the same consolidation key.

    IMPORTANT: Do NOT modify `BuildCsv` — consolidation applies only at `WriteSingleFileAsync` level per RESEARCH.md Pitfall 4. Do NOT touch the existing code path below the `if (mergePermissions)` branch.
  </action>
  <verify>
    <automated>cd "C:/Users/dev/Documents/projets/Sharepoint" && dotnet test --filter "FullyQualifiedName~UserAccessCsvExportServiceTests" --no-restore -v q 2>&1 | tail -10</automated>
  </verify>
  <done>CSV export with mergePermissions=false is identical to pre-toggle output. CSV export with mergePermissions=true writes consolidated rows with Locations and LocationCount columns. ViewModel passes MergePermissions to the service.</done>
</task>

</tasks>

<verification>
1. `dotnet build` succeeds with 0 errors, 0 warnings
2. `dotnet test --filter "FullyQualifiedName~UserAccessCsvExportServiceTests"` — all tests pass including new consolidation tests
3. `dotnet test` — full suite green, no regressions
</verification>

<success_criteria>
- MergePermissions property exists on both ViewModels, defaults to false
- Export Options GroupBox visible in both XAML tabs with localized labels
- CSV consolidated path produces correct output with merged rows
- Non-consolidated CSV path is byte-identical to pre-Phase-16 output
- All existing tests pass without modification
</success_criteria>

<output>
After completion, create `.planning/phases/16-report-consolidation-toggle/16-01-SUMMARY.md`
</output>
