Succession Plan — technical guide
This implementation reference is for maintainers and authorized administrators. The operational journey is in Functional; tenant and questionnaire preparation are in Configuration. Paths below are relative to the named local repositories, not public service URLs.
Architecture and responsibilities
Verified in code: legacy succession is a People Review feature built with Symfony controllers, Twig, jQuery AJAX and Doctrine. It stores nominations as ReviewSuccessor, separate from generic questionnaire answers. The standalone redesigned Vue module is a separate, incomplete implementation in the inspected checkout.
| Component | Responsibility |
|---|---|
| Legacy AssessmentBundle | Configures question types, titles and ordered range bounds. |
Legacy ReviewBundle DefaultController | Creates/configures the campaign, population, reviewers and questionnaire. |
ReviewAssessmentController | Renders individual/collective forms; searches employees; saves, moves, deletes and completes. |
SuccessionQuestion.hml.twig | Search modal, required-period check, AJAX save and successor card rendering. The filename really contains hml. |
successionCollectiveQuestion.html.twig | Campaign-wide board, period columns, drag/drop and delete confirmation. |
ReviewPDFController and PDF templates | Render succession content in individual/collective PDF paths; generated PDFs were not checked live. |
Nextgen ModuleConfig/SuccessionPlan | Mock list, client-side filtering/sorting/pagination and basic-information modal. |
Request and data flow
Verified in code: config/routes.yaml mounts legacy ReviewBundle under /company/review. Resources/config/routing.yml provides the routes below. These are implementation contracts, not a recommendation to bypass UI access controls.
Route suffix under /company/review | Handler / input / result |
|---|---|
/r/assessmentView/{id} | indexAction; ID is a ReviewUser, not a campaign ID; renders individual form and its saved nominations. |
/r/succession/assessmentfindcollab | findUserAction; AJAX POST search; returns up to 15 employee id/name suggestions using the current tenant's schools. Empty search results use a different message-shaped JSON response. |
/r/succession/saveSuccessor | saveSuccessorAction; AJAX POST collab, period, review, reviewUser; resolves successor by username and campaign tenant; upserts nomination. |
/r/collectivereview/{id} | assessmentcollectiveAction; campaign ID; renders collective questions and nominations. |
/actions/succession/moveuser | moveSuccessionUserAction; AJAX POST successor, reviewUser, period; updates the nomination's target employee and period. successor is the nomination ID here. |
/actions/succession/deleteuser | deleteCollectiveSuccesseurAction; AJAX POST successor nomination ID; removes the Doctrine entity. |
/r/review/saveDone | assessmentDoneAction; AJAX POST reviewuser; sets that employee review's done flag. |
/actions/succession/collectiveEnd | EndCollectiveAction; AJAX POST review campaign ID; sets collective-done flag, completion date and status terminée. |
A sanitized save request uses form fields, not JSON:
collab=<selected employee username>
period=0-2
review=<campaign id>
reviewUser=<reviewed employee row id>
Success returns an object with response: "success", an HTML fragment and period. An unresolved username returns response: "response". The client appends the returned card to its period column. This HTML-returning contract differs from a resource-oriented nextgen API. Do not log the HTML or employee search payloads into general-purpose diagnostics.
Observed in application: selecting Jim in a documentation-only fixture, choosing 0–2 years and saving produced Successor taken into account. Reopening the individual form and opening the collective view displayed the stored nomination. The test used self-selection solely to verify mechanics; no eligibility or staffing assessment was performed.
Data model and lifecycle
Verified in code: src/Main/ConfigBundle/Resources/config/doctrine/ReviewSuccessor.orm.yml maps:
| Field / relationship | Meaning |
|---|---|
id | Generated integer nomination ID. |
period | Required string, length 255; rendered range label such as 0-2. |
review → Review | Campaign. |
reviewuser → ReviewUser | Employee review row being succeeded. |
successor → User | Candidate employee. |
The save handler searches for an existing tuple (review, reviewuser, successor) and updates its period, otherwise inserts a record. This is application-level reuse; a database uniqueness guarantee or concurrency protection was not established. There is no question ID on the nomination entity. Multiple individual succession questions in one questionnaire are therefore not established as isolated nomination sets.
Range configuration is AssessmentQuestion.options.choices: ordered lower/upper bounds stored in a flat array. Twig joins every pair with a hyphen. The collective controller finds an individual question with type succession from the same assessment; collective questions use type succession-collective. It loads nominations for the campaign, builds counts per successor, and matches period strings to columns. Changing labels does not migrate stored strings.
Individual completion and collective completion are separate writes. Individual completion sets ReviewUser.done; collective completion updates Review.iscollectivedone, datecollectivedone and status. Saving a nomination is neither of these completion actions. The save handler does not provision a job assignment, send an offer or call a development-plan creation service.
Access, configuration and privacy
Verified in code: individual rendering checks for a company School and matching tenant, then applies relationships/reviewer/admin/global-right conditions, including review_all and can_review_himself. Collective rendering accepts master admin, campaign admin or review_all. Questionnaire type availability uses module_review_bo; campaign editing checks review_create.
The save/move/delete methods shown above do not independently repeat all those rendering checks in their method bodies. This review did not establish the complete firewall, CSRF and tenant-authorization chain for each write. Page-level access checks must not be described as proof that every write endpoint enforces equivalent authorization. Validate those boundaries separately when maintaining these endpoints; no cross-tenant probes were performed.
Nominations are personnel planning records. Keep access within the authorized review population; avoid storing raw employee search responses, returned HTML, private profile details or authentication material in documentation and logs. Screenshots in these guides use the named test fixture. No public export/share setting was established.
Legacy and nextgen coexistence
Verified in code: smartmobility-nextgen-front/src/constants/app-route.ts defines SUCCESSION_PLAN as /:space(admin)/modules/succession-plan; router/private-routes.ts loads SuccessionPlanModule.vue with requiresAuth: true. Navigation maps the entry to read-succession-plan in NavigationModule.
In ModuleConfig/SuccessionPlan/:
components/SuccessionPlanList.vueusesmockSuccessionPlans; search filters name/position, status filters match mock values, and sorting/pagination run locally. The view action only shows an info toast; detail navigation is commented out. Deletion splices the local mock array.store/successionPlan.store.tsholds search/status/page/sort state; this is not evidence of server persistence.store/createSuccessionPlan.store.tshas one basic-information step, name/description fields and empty defaults.createSuccessionPlan.composables.tsrejects an empty/whitespace-only name.handleNextcontainsTODO: Call API to create succession plan, then displays success, resets the form and closes the modal. It does not save a plan.
The inspected nextgen backend includes SUCCESSION_CANDIDATE as a People Review matrix third-criterion option (Domain/V2/PeopleReviewCampaign/DTO/PeopleReviewCampaignMatrixDTO.php). That criterion does not establish a standalone Succession Plan CRUD API or migration from ReviewSuccessor. No dedicated successor persistence bridge, job/event flow, automatic recommendation call or AIHub call was found in the traced succession save/list path. Focused searches in AIHub entity/matching code found no direct succession integration; this is not a claim that all generic matching services are irrelevant to future succession features.
Failure modes and observability
| Failure | Behavior / diagnostic |
|---|---|
| Missing readiness period | Observed: inline Please select a period to continue; successor save must not be inferred from generic answer autosave messages. |
| Username not resolved | Verified: non-success response token; verify selected suggestion and tenant membership. |
| AJAX failure | Verified: client displays the translated AJAX error; inspect request outcome and returned token without logging personnel payloads. |
| Missing move/delete entity | Verified: response: "error". Some methods otherwise return success even when no state changed; check persisted state. |
| Incomplete or changed range pairs | Verified: rendering depends on pair order and period strings; no migration or semantic range validator was established. |
| Collective completion | Verified: campaign status changes to completed; this is broader than saving a successor. |
| Nextgen success toast | Verified: placeholder success does not mean API persistence. |
Runtime dependencies and extension points
Legacy requires the authenticated company session, tenant context, populated employee relationships, Doctrine persistence, Twig and the included jQuery/AJAX/sortable assets. PDF generation has its own controller/template path. No succession-specific worker, scheduler or AI dependency was established. Nextgen requires the Vue/Pinia app and auth/navigation context; deploying those assets alone does not complete its missing persistence layer.
For a future persistent standalone module, introduce explicit API contracts and tenant/permission validation, connect the list and create actions to them, and decide how candidate/readiness records map to legacy nominations. Specify migration, uniqueness, range compatibility and completion semantics before claiming parity. These are extension considerations, not capabilities of the inspected version.
Evidence and limitations
Observed in application: 25 October 2026, PS Testing 2026, Jim Carter only. Test campaign 3518, questionnaire 10061, individual row 59233. Five-step configuration, validation, successor save/reopen, collective display and individual completion were exercised. Individual completion returned to the reviewer table with 1 Reviews validated on 1, 100% and COMPLETED. No email was scheduled.
Verified in code: legacy checkout 9ecdb0db43; nextgen front d14017a5; nextgen backend 00beda02c; AIHub 757dd2678. These identify local checkouts, not the deployed release. Principal sources: legacy config/routes.yaml, ReviewBundle Resources/config/routing.yml, Controller/{DefaultController,ReviewAssessmentController,ReviewPDFController}.php, assessment and PDF templates, AssessmentBundle controller/editor, ReviewSuccessor.orm.yml; nextgen route/navigation and succession module paths above.
Not verified: live drag/drop, deletion, PDF generation, development-plan navigation, notification delivery, other-account access, complete write-endpoint security chain, standalone nextgen persistence or data migration. Collective completion was also observed: after I finish → Yes, I validate, the campaign summary showed Completed, Terminée, 100% and one finished review.
Functional-guide evidence
These implementation and product references support the functional guide. Local source behavior does not establish which revision or optional features are deployed. Application observations remain distinct from code and documentary evidence.
Verified in code: legacy src/Company/ReviewBundle/Controller/ReviewAssessmentController.php, Resources/views/assessment/SuccessionQuestion.hml.twig, successionCollectiveQuestion.html.twig; nextgen front src/modules/ModuleConfig/SuccessionPlan/. Repository roots are smartmobility-legacy and smartmobility-nextgen-front.
Last updated on 3 November 2026