360 feedback — technical guide
For maintainers tracing campaign membership, questionnaire answers and contributor completion. Implementation details below describe the legacy source; deployed availability depends on tenant configuration. See Functional for user workflows and Configuration for administrative responsibilities. The screenshot links the campaign summary to the lifecycle described here.
Architecture and responsibilities
The traced implementation is in smartmobility-legacy, using Symfony controllers, Twig, jQuery/Ajax and Doctrine. All paths below are relative to that repository unless another repository is named.
Manager campaign configuration
→ FeedbackInterview + Assessment
→ FeedbackInterviewUser (evaluated person)
→ ManagerReviewersService
→ FeedbackInterviewReviewer + AssessmentUser (one contribution)
Employee questionnaire → AssessmentAnswer / AssessmentQuestionItemAnswer
→ completion updates reviewer + AssessmentUser
→ manager consultation / employee received-feedback rendering
| Source | Responsibility |
|---|---|
config/routes.yaml | Mounts company bundle at /feedback and employee bundle at /plateforme/feedback. |
src/Company/FeedbackBundle/Resources/config/routing.yml | Campaign setup, participant/reviewer actions, consultation and reminder routes. |
src/Company/FeedbackBundle/Controller/DefaultController.php | Campaign writes, population loading, detail rendering, scheduled email persistence. |
src/Company/FeedbackBundle/Service/ManagerReviewersService.php | Assignment deduplication, AssessmentUser creation, disabled-reviewer cleanup, notification template selection. |
src/Customer/FeedbackBundle/Controller/DefaultController.php | Contributor list, assigned questionnaire, answer persistence and completion. |
src/Main/ConfigBundle/Entity/FeedbackInterview{,User,Reviewer}.php | Campaign, evaluated-person membership and contributor assignment. |
src/Main/ConfigBundle/Resources/config/doctrine/FeedbackInterview*.orm.yml | ORM fields, relations and cascade declarations. |
src/Command/FeedBack/{AddReviewerManagerCommand,SendMailReviewerCommand}.php | Explicit manager-assignment and notification command paths. |
Data model and lifecycle
FeedbackInterview links tenant Setting, owning School/manager context, one Assessment, dates and participant collection. Entity constants define only brouillon / publiée and campaign types employee / manager. UI tabs such as Completed are computed presentation groups, not additional persisted enum states.
FeedbackInterviewUser links campaign, evaluated User, optional delegate School, update timestamp and canAdd. It owns reviewer assignments. FeedbackInterviewReviewer links campaign, evaluated-person membership, reviewer User and one AssessmentUser, plus done and updated. Questionnaire answers refer to that AssessmentUser through AssessmentAnswer, with choice items in AssessmentQuestionItemAnswer.
ORM defaults for canAdd and addManager are false. New participant records copy campaign canAdd; per-participant updates may later differ. Parent collections declare cascade-all, and the reviewer/AssessmentUser relation also declares cascading. Evaluate ORM removal behavior before altering membership; no soft-delete or answer recovery guarantee was established.
Creation and assignment flow
newAction()persists a draft, default name, current time, School and Setting, flushes, then redirects to parameters. A GET-like navigation to/feedback/newhas a write side effect.updateAction()parses dates withd/m/Y H:i, appending start00:00and end23:59in the default server timezone. It writes name/description/status and flushes. The header action serializes the chosen status; it is not a separate publication state machine.assessmentAction()links an Assessment. The picker ininterviewIndicatorsAction()restricts offered templates by tenant and type360; this picker restriction is not itself a write-endpoint invariant.updateusersAction()treats submitted participant IDs as a replacement set. Omitted participants are removed, and an empty set removes all. New members getcanAdd; conditionaladdManagercreates a direct-manager assignment.- For manager campaigns it queries enabled Users by each evaluated manager's School, calls
addReviewers(..., true)and removes disabled reviewer assignments. It adds missing assignments but does not establish removal of every enabled reviewer who has changed reporting line. ManagerReviewersService::addReviewers()checks participant+reviewer existence, creates an AssessmentUser and reviewer record when absent, and flushes in batches of ten and at the end. This is application-level duplicate avoidance; a unique database constraint was not established.
No continuous hierarchy synchronization guarantee follows from this save flow. The explicit add-reviewers command exists, but its deployment scheduling was not verified.
The popup initially marks Manager active in Resources/views/partials/typeSelect.html.twig; this is a UI default, distinct from a persisted campaign type. updateusersAction() also contains an expired-end-date guard returning Impossible to add new collaborator; its DateTime/string comparison was not validated for every runtime case.
The new/edit email templates hide the Conditions group using display: none. New condition defaults to Non; both new itemDone options use managers, whereas the edit options use 1/0. Location targeting is gated by send_targeted_email. Any extension must resolve these contract differences before relying on conditional notifications.
HTTP contracts
These are internal legacy browser contracts, not a supported public integration API. Route YAML does not constrain HTTP verbs for the listed actions; POST below is the verified browser caller's method, not an exclusive server method restriction. Parameter names come from controller reads and Ajax callers. Examples use fictional IDs and must not be executed against production as documentation checks.
| Caller / route | Parameters and behavior |
|---|---|
GET navigation /feedback or /feedback/{id} | Campaign list or tenant-bound summary lookup. Repository list orders by end date descending. |
Navigation /feedback/new | Immediately persists draft, then redirects; unsafe as a read-only probe. |
POST /feedback/update | interview, name, description, status, startdate, enddate; JSON response: success after flush. |
POST /feedback/update/type | id, type; writes campaign type. |
POST /feedback/assessment | interview, assessment; links questionnaire. |
POST /feedback/userupdate | interview, collaborateur array; replaces participant set, synchronizes manager contributors through service. |
POST /feedback/addreviewers | Ajax request; interviewuser, reviewers entries with id; creates assignments and may send invitations. |
POST /feedback/canadd | Ajax request; interviewuser, canadd; writes the participant option. |
Navigation /feedback/questionnaire/{id} | Reviewer-assignment ID; renders company-side read-only questionnaire. |
GET /plateforme/feedback/ | Current user's reviewer assignments and received-feedback memberships. |
GET /plateforme/feedback/{id} | Reviewer-assignment ID; rejects missing assignments or those assigned to another current user with not-found. |
POST /plateforme/feedback/save | Ajax; review, assessment, name_<questionId>, comment_<questionId> and item-specific names. Persists answers; success JSON. |
POST /plateforme/feedback/done | Ajax; review; marks reviewer and AssessmentUser done and sets finished timestamp when assessment linkage exists. |
Read-only navigation example:
GET /plateforme/feedback/ HTTP/1.1
Accept: text/html
The employee save handler returns 400 for non-Ajax requests, 404 if Assessment or reviewer cannot be loaded, and success after processing. Completion uses a different error envelope: a missing review returns response: error without the same explicit 404. Do not assume one uniform error schema.
Answer persistence and submission
Customer\FeedbackBundle\Controller\DefaultController::saveQuestionnaireAction() iterates the requested Assessment's questions, finds or creates AssessmentAnswer by assessment user and question, records current user on creation, and processes scalar, selection and item answers. Array submissions remove prior item rows before rebuilding choices. Null/empty clearing differs by type; for select it clears response and selected item. A blank submitted comment does not universally erase a stored comment because comment assignment is conditional on non-empty input.
questionnaireDoneAction() marks both completion fields and AssessmentUser.finished. The questionnaire renderer uses reviewer.done as read-only state; company consultation passes readOnly: true. These are rendering rules, not proof of an immutable server record after completion. Save and done are separate requests; do not infer atomic answer-save-and-submit behavior or complete required-field validation on the server.
Permissions, eligibility and privacy boundaries
Company read actions commonly resolve the current School and tenant; several detail/configuration reads explicitly bind campaign Setting. Twig list/detail visibility additionally checks interview_all, management feedback visibility, direct manager or delegate depending on type/view. Creation link visibility checks interview_create. Exact predicates differ across tabs and detail views; the all-tenant repository list is later narrowed by templates.
Employee indexAction() queries reviewer=current user, keeps published campaigns and then includes completed assignments or unfinished assignments inside [startdate, enddate). It separately loads evaluated memberships by current user for received feedback; those branches do not share identical publication/date filtering. A received-view derived done calculation mixes formatted time and a timestamp in the inspected code, so it must not be used to claim reliable deadline enforcement.
Employee questionnaire GET explicitly verifies reviewer identity. The save/done controller bodies do not visibly repeat that same actor check or fully validate campaign dates, completed-state immutability and assessment-to-assignment consistency. Several company mutations also resolve entities directly by ID without the tenant predicate used in read actions. Global security/middleware coverage was not fully traced. Therefore this guide does not assert complete endpoint isolation from menu visibility or the GET check. Maintainers changing these paths should verify operation-specific authorization and resource binding before exposing new callers.
Answers and reviewer identities are HR data. The received-feedback template renders named contributors; the current anonymity FAQ confirms non-anonymous behavior (Documented in Confluence). Do not log answer contents or treat a campaign/list flag as anonymization.
Notifications and runtime dependencies
ManagerReviewersService uses Doctrine, Swift Mailer, translated NotificationEmail records and the tenant email base. addReviewers() sends only when requested, campaign is published and the reviewer has a syntactically valid address. listReviewers() additionally excludes completed assignments for its send branch. sendMail() selects employee/manager notification type and reviewer locale, falling back to the first translation. Missing templates can return without sending; complete handling of an empty translation collection was not validated.
Changing status to Published for a manager campaign starts app:feedback:send-mail-reviewers asynchronously through the configured PHP executable and application console. This is process execution, not a verified queue-backed delivery guarantee. AddReviewerManagerCommand separately adds manager reviewers. Both command names are implementation references, not production run instructions.
interviewNotificationAction() stores scheduled Email records with entity FeedbackInterview, recipient category, date/time, location and completion conditions. FeedbackInterviewUserRepository::getInterviewUser() narrows by configured location through customer.entity. The complete scheduler, condition evaluation and delivery/retry chain was not traced. Publication mail, reviewer-add mail, manual reminders and planned messages must be investigated separately.
Generation boundaries and extension points
Targeted searches of nextgen frontend routes, backend API routing and 360-specific terms did not establish a nextgen 360 campaign resource/wizard. No automatic migration or shared nextgen persistence contract was verified. AIHub src/data_exchange/feedback/router.py imports job-offer matching feedback flows; matching/chatbot feedback should not be mistaken for 360 questionnaires. No AIHub call was traced from the campaign controllers.
Feature declarations live in fixtures/FeatureSwitchFixtures.php and config/configuration/feature_module.yml; employee navigation maps module_360feedback in src/Main/SitemapBundle/Enum/Customer/NavigationEnum.php. A shell or navigation change alone does not replace the legacy controller.
For extensions, trace template controls → route → controller/resource binding → membership/AssessmentUser → answer records, and both company and employee renderers. New question behavior must preserve item clearing, comments, read-only rendering and form validation. Notification additions must cover template type, locale fallback and send trigger independently. Reassess authorization on every new read/write path.
Failure modes and evidence
| Symptom | Inspection point |
|---|---|
| Empty contributor list | Assignment identity, published status, start/end dates and done state in employee indexAction. |
| Missing form | Campaign Assessment link, reviewer AssessmentUser and template type/tenant in picker. |
| Wrong manager contributors | Current School attachments, participant-save synchronization, disabled-reviewer cleanup and explicit command invocation. |
| Different counts | Tab-specific template scope, enabled reviewers and AssessmentUser.done versus reviewer.done. |
| Save/submit inconsistency | Separate handlers, Ajax requirement, resource linkage, completion fields and question-specific clearing. |
| No mail | Trigger branch, valid address, configured template/translation, asynchronous process and mail transport. |
No production log signature, alert, execution schedule, retry policy, delivery timing or deployed revision was verified. Use operational status and sanitized correlation identifiers; exclude answers and personal data from diagnostic output.
Product references: Feedback 360, v4, 27 November 2024, describes campaign/participant/reviewer/AssessmentUser relationships; 360 playbook, v1, 13 September 2023, covers setup and direct-report contributions; anonymity FAQ, v4, 22 June 2026, explains that feedback is named. Historic date/time instructions differ from the implementation's day-boundary normalization. Customer-specific reporting queries and sample account data do not belong in this guide.
Middleware/security coverage, production notification processing, migration and recovery after membership deletion are deployment-specific. The HTTP paths in this guide are internal legacy browser contracts, not a supported public API.
Last updated on 3 November 2026