{ "task_id": "CODE-DEDUP-ACTIONS-001", "summary": "Extract repeated action-layer patterns (audit metadata enrichment, grid user count calculation) into shared service classes to eliminate ~260 lines of duplication across 7+ action/data files.", "assumptions": [ "Audit metadata enrichment (created_by, modified_by labels) repeats identically across 4+ edit actions.", "Grid user count calculation repeats identically across 3 data endpoints (roles, departments, tenants).", "Both patterns are pure data transformation — suitable for service extraction without changing action flow." ], "scope": { "in": [ "New: lib/Service/Audit/AuditMetadataEnricher.php — extract audit label resolution", "New: lib/Service/Data/GridUserCountEnricher.php — extract user count aggregation for grid data endpoints", "pages/admin/tenants/edit($id).php — replace inline audit metadata with service call", "pages/admin/roles/edit($id).php — replace inline audit metadata with service call", "pages/admin/users/edit($tenantslug,$id).php — replace inline audit metadata with service call", "pages/admin/departments/edit($id).php — replace inline audit metadata with service call", "pages/admin/roles/data().php — replace inline user count logic with service call", "pages/admin/departments/data().php — replace inline user count logic with service call", "pages/admin/tenants/data().php — replace inline user count logic with service call", "Factory registrars for new services" ], "out": [ "View files (.phtml) — not touched", "Repository SQL queries — not touched", "Frontend JS/CSS — not touched", "Permission/authorization logic — not touched" ] }, "guardrails": { "required_guard_ids": [ "GR-CORE-001", "GR-CORE-002", "GR-TEST-001", "GR-TEST-002", "GR-LANG-001", "GR-LANG-002" ], "required_quality_gate_ids": [ "QG-001", "QG-002", "QG-003", "QG-006" ] }, "success_criteria": [ { "id": "SC-001", "criterion": "AuditMetadataEnricher exists in lib/Service/Audit/ and is used by all 4 edit actions instead of inline created_by/modified_by resolution." }, { "id": "SC-002", "criterion": "GridUserCountEnricher exists in lib/Service/Data/ and is used by all 3 data endpoints instead of inline count aggregation." }, { "id": "SC-003", "criterion": "No inline audit-label or user-count-aggregation logic remains in pages/ action files." }, { "id": "SC-004", "criterion": "PHPUnit, PHPStan, Architecture Contract, and CS checks pass green; zero new failures introduced by this task (pre-existing failures are out of scope)." } ], "implementation_steps": [ { "id": "S1", "title": "Create AuditMetadataEnricher service", "description": "Extract the repeated pattern of resolving created_by, modified_by, status_changed_by, active_changed_by into display labels (name or email fallback) and UUIDs. Accept entity array by reference, list of audit fields to resolve, and UserAccountService dependency.", "guard_refs": ["GR-CORE-001", "GR-TEST-002"] }, { "id": "S2", "title": "Create GridUserCountEnricher service", "description": "Extract repeated pattern of collecting entity IDs from grid rows, querying user counts (total + active), and enriching rows with count fields. Generic enough to work with roles, departments, and tenants.", "guard_refs": ["GR-CORE-001", "GR-TEST-002"] }, { "id": "S3", "title": "Write unit tests for both new services", "description": "AuditMetadataEnricher: test with present/missing/zero audit IDs, user not found fallback. GridUserCountEnricher: test with empty rows, mixed IDs, zero counts.", "guard_refs": ["GR-TEST-001"] }, { "id": "S4", "title": "Register new services in DI container", "description": "Add factory registrations in appropriate Container/Registrars/ files.", "guard_refs": ["GR-CORE-002"] }, { "id": "S5", "title": "Refactor edit actions to use AuditMetadataEnricher", "description": "Replace inline audit metadata resolution in all 4 edit actions with enricher service call.", "guard_refs": ["GR-CORE-001", "GR-CORE-002"] }, { "id": "S6", "title": "Refactor data endpoints to use GridUserCountEnricher", "description": "Replace inline user count aggregation in all 3 data endpoints with enricher service call.", "guard_refs": ["GR-CORE-001", "GR-CORE-002"] }, { "id": "S7", "title": "Run all quality gates", "description": "QG-001, QG-002, QG-003, QG-006.", "guard_refs": ["GR-LANG-002"] } ], "tests": [ "tests/Service/Audit/AuditMetadataEnricherTest.php — new, covers label resolution edge cases", "tests/Service/Data/GridUserCountEnricherTest.php — new, covers count aggregation edge cases", "tests/Architecture/CoreStarterkitContractTest.php — must stay green" ], "acceptance_checks": [ "SC-001: rg 'AuditMetadataEnricher' pages/admin/ shows usage in 4 edit action files", "SC-002: rg 'GridUserCountEnricher' pages/admin/ shows usage in 3 data endpoint files", "SC-003: rg 'created_by_label.*=.*trim' pages/ returns 0 matches (no inline resolution left)", "SC-004: QG-001 + QG-002 + QG-003 + QG-006 introduce zero new failures (pre-existing issues are out of scope)" ], "risks": [ { "risk": "Edit actions have slightly different audit fields (some have status_changed_by, others don't)", "mitigation": "Design AuditMetadataEnricher to accept a configurable list of field names to resolve. Each caller specifies which fields apply." }, { "risk": "Data endpoints have slightly different count repository interfaces", "mitigation": "Design GridUserCountEnricher to accept a callable or repository interface for count queries, keeping it generic." } ] }