- drop ClientPortal module, ClientTicket entity, ROLE_CLIENT and all couplings (Task, TaskDocument, User, Notification) back to an internal-only model
- migration drops client_ticket / user_allowed_projects / related FK columns and removes leftover external client accounts (would otherwise be promoted to ROLE_USER)
- remove client-portal frontend module, admin tickets tab, user portal section, portal nav item and portal/clientTicket i18n keys
- fix directory nav icon (invalid mdi:contact-multiple-outline -> mdi:card-account-details-outline)
- add 'make sync-permissions' target, wire it into install/db-reset and the prod deploy script
Findings from the post-migration code review. The arrival of ROLE_CLIENT
exposed internal endpoints still guarded only by IS_AUTHENTICATED_FULLY (or no
security), reachable by a client. Verified by re-running a multi-role smoke
test (client -> 403, internal roles -> 200).
Security (closed real client-isolation holes):
- TaskDocumentDownloadController: add ownership check (admin all / client only
own clientTicket docs / user only task-linked docs) — the custom download
bypassed the cloistered provider.
- Share browse/download/search/status controllers: IS_AUTHENTICATED_FULLY ->
ROLE_USER (SMB share is internal).
- User Get/GetCollection: add security ROLE_USER (was exposing the internal
directory to clients).
- BookStackLink GetCollection/Post/Delete: IS_AUTHENTICATED_FULLY -> ROLE_USER.
Contracts / robustness:
- TaskInterface gains getProject(): ?ProjectInterface; TimeTracking export
controller/service drop concrete cross-module entities for repo interfaces.
- Shared MCP Serializer signatures widened to the contracts (user/projectRef/
taskRef/tags/users); project()/userFull()/etc. kept concrete (use getters
outside the contracts).
- RecurrenceHandler: null-guard before findMaxNumberByProjectForUpdate().
180 tests green, cs-fixer clean, routes unchanged.
LST-66 (2.3) backend. Behaviour-preserving move of the absences domain into
src/Module/Absence/. API operations, securities, routes and the 10 MCP tool
names are unchanged.
- 3 entities + 3 enums moved to Domain/{Entity,Enum}; user relations stay on
UserInterface. 3 repositories split into Domain/Repository interfaces +
Doctrine impls (bound in services.yaml); find() kept off interfaces
(findById instead).
- Pure services (AbsenceDayCalculator, PublicHolidayProvider) -> Domain/Service;
AbsenceBalanceService -> Application/Service; State (5), controllers (5),
10 MCP tools and AccrueLeaveCommand -> Infrastructure/.
- New LeaveProfileInterface contract (Shared) exposes the HR getters used by
AbsenceBalanceService/AccrueLeaveCommand; User implements it -> Absence no
longer imports the concrete Core User. MCP tools/command inject
UserRepositoryInterface (findById) instead of the concrete repository.
- Timestampable/Blamable added to AbsenceBalance and AbsencePolicy (additive
migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
NULL + COMMENT). AbsenceRequest untouched (already has createdAt/reviewedAt).
- AbsenceModule registered (id absence, 4 RBAC perms, not re-wired); doctrine
mapping added; team-absences sidebar item gated by the module.
161 tests green, mapping valid, no API route regression, cs-fixer clean.