--- phase: 16-report-consolidation-toggle plan: "01" subsystem: export tags: [csv-export, consolidation, viewmodel, xaml, localization, tdd] dependency-graph: requires: [15-01, 15-02] provides: [MergePermissions toggle UI, consolidated CSV export path] affects: [UserAccessAuditViewModel, PermissionsViewModel, UserAccessCsvExportService] tech-stack: added: [] patterns: [ObservableProperty, XAML GroupBox binding, optional parameter default, early-return branch, TDD red-green] key-files: created: [] 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 - SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs decisions: - "Consolidated branch uses early-return pattern inside WriteSingleFileAsync to leave existing code path completely untouched" - "PermissionsViewModel gets MergePermissions as a no-op placeholder — PermissionsViewModel uses a different export service (CsvExportService, not UserAccessCsvExportService) so the property is reserved for future use" - "Export Options GroupBox placed after Scan Options in UserAccessAuditView and after Display Options in PermissionsView — keeps related configuration grouped" metrics: duration: ~15min completed: 2026-04-09 tasks: 3 files-modified: 8 --- # Phase 16 Plan 01: MergePermissions Toggle UI + Consolidated CSV Export Summary MergePermissions ObservableProperty on both ViewModels, Export Options GroupBox in both XAML tabs, localized EN/FR strings, and consolidated CSV export path using PermissionConsolidator. ## Tasks Completed | Task | Name | Commit | Files | |------|------|--------|-------| | 1 | Add MergePermissions property to both ViewModels and localization keys | ed9f149 | UserAccessAuditViewModel.cs, PermissionsViewModel.cs, Strings.resx, Strings.fr.resx | | 2 | Add Export Options GroupBox to both XAML views | db42047 | UserAccessAuditView.xaml, PermissionsView.xaml | | 3 (RED) | Add failing tests for RPT-03-f and RPT-03-g | 4f7a6e3 | UserAccessCsvExportServiceTests.cs | | 3 (GREEN) | Implement consolidated CSV export path and wire ViewModel call site | 28714fb | UserAccessCsvExportService.cs, UserAccessAuditViewModel.cs | ## What Was Built ### MergePermissions Property - `UserAccessAuditViewModel`: `[ObservableProperty] private bool _mergePermissions;` — defaults false, no partial handler needed - `PermissionsViewModel`: same property as a no-op placeholder (PermissionsViewModel uses `CsvExportService`, not `UserAccessCsvExportService`) ### Localization - `audit.grp.export` — "Export Options" / "Options d'exportation" - `chk.merge.permissions` — "Merge duplicate permissions" / "Fusionner les permissions en double" ### XAML GroupBoxes Both views received an "Export Options" GroupBox with a single checkbox bound to `MergePermissions`. The groupbox is always visible (never conditionally hidden). Existing XAML elements are untouched. ### Consolidated CSV Export `WriteSingleFileAsync` now accepts `bool mergePermissions = false`. When true: 1. Calls `PermissionConsolidator.Consolidate(entries)` to merge rows sharing the same (UserLogin, PermissionLevel, AccessType, GrantedThrough) key 2. Writes a consolidated CSV with header: `"User","User Login","Permission Level","Access Type","Granted Through","Locations","Location Count"` 3. Locations column = semicolon-separated `SiteTitle` values from all merged locations 4. Returns early — existing code path below is completely untouched `UserAccessAuditViewModel.ExportCsvAsync` now passes `MergePermissions` to the service. ### Tests (TDD) 3 new test methods covering RPT-03-f and RPT-03-g: - `WriteSingleFileAsync_mergePermissionsfalse_produces_identical_output` — byte-identical to default call - `WriteSingleFileAsync_mergePermissionstrue_writes_consolidated_rows` — consolidated header + merged rows - `WriteSingleFileAsync_mergePermissionstrue_singleLocation_noSemicolon` — single location has LocationCount=1 All 8 UserAccessCsvExportServiceTests pass. ## Decisions Made 1. **Early-return consolidated branch** — Plan specified leaving existing code path untouched. Added `if (mergePermissions) { ... return; }` before the existing `var sb = new StringBuilder()` block. The existing block is wrapped in an anonymous `{}` scope for clarity but behavior is unchanged. 2. **PermissionsViewModel as no-op placeholder** — The Permissions tab uses `CsvExportService` (not `UserAccessCsvExportService`), so `MergePermissions` cannot be wired to export logic in this plan. The property is added for XAML binding completeness and future implementation. 3. **GroupBox placement** — Export Options placed immediately before the Run/Export buttons in UserAccessAuditView, and before Action buttons in PermissionsView. This keeps export-related options adjacent to export buttons. ## Verification Results - `dotnet build` — 0 errors, 0 warnings - `dotnet test --filter "FullyQualifiedName~UserAccessCsvExportServiceTests"` — 8/8 passed - `dotnet test` full suite — 296 passed, 26 skipped, 2 flaky timing failures (pre-existing debounce tests, pass when run individually) ## Deviations from Plan None — plan executed exactly as written. ## Self-Check: PASSED Files exist: - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs — contains `_mergePermissions` - SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs — contains `_mergePermissions` - SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml — contains `Export Options` - SharepointToolbox/Views/Tabs/PermissionsView.xaml — contains `Export Options` - SharepointToolbox/Services/Export/UserAccessCsvExportService.cs — contains `mergePermissions` - SharepointToolbox.Tests/Services/Export/UserAccessCsvExportServiceTests.cs — 3 new test methods Commits exist: ed9f149, db42047, 4f7a6e3, 28714fb