Skip to main content

Jobs & skills framework — technical guide

This guide covers the catalogue domain across legacy, nextgen-back, nextgen-front and AIhub. It is intended for engineers and integration teams. All implementation claims below are Verified in code unless explicitly marked otherwise. Local sources establish contracts and responsibilities, not deployed topology. Illustrated usage and administrator procedures are in the functional and configuration guides.

Architecture and implementation boundary​

ComponentResponsibility and verified entry points
Legacy manager catalogueSymfony controllers and Twig/JavaScript under src/Company/{Job,Skill,Referential}Bundle; job list at /company/job, skill list at /company/skill.
Legacy employee explorersrc/Customer/InternalJobBundle/Controller/DefaultController.php::listingAction, SearchController::{homeAction,filterAction,suggestionsAction} and Services/SearchService.php; live navigation used /plateforme/internal-job/.
Nextgen backendV1 resource routing, DTO validation, company-scoped repositories, persistence listeners and consumers. config/routes.yaml mounts the V1 routes at /api/v1. “V1” here is the API namespace, not a claim that this is the legacy UI.
Nextgen frontendsrc/router/private-routes.ts and a bounded framework-related module search did not identify a dedicated framework editor. Skill-rendering components in reviews are not evidence of catalogue CRUD screen parity.
AIhubExplicit imports from SmartMobility source data and normalization pipelines; no direct catalogue-save → immediate AIhub update call chain was established.

Request flow, represented as text:

Manager catalogue -> legacy controller + permission/tenant checks
-> Twig filters + search script -> repository -> catalogue records
Employee explorer -> InternalJob SearchController -> SearchService -> company/locale/status query
API client -> /api/v1 framework route -> action permission -> DTO -> manager/repository -> persistence
JobSkill create -> listener -> SkillAttachedToJob event -> consumer -> missing user-skill attachment
AIhub import process -> source extraction -> normalization/standardization (separate integration)

Observed in application: the modern manager shell opened legacy catalogue routes. That observation is documented in the functional guide; it does not establish the deployed nextgen integration or shared database.

Domain and data model​

Entity / relationMeaning and source
JobTranslated role descriptions, code, company, active state, status and reference. Legacy Main/ConfigBundle entity/repository; nextgen Infrastructure/Persistence/Entity/Job/Job.php.
SkillTranslated titles/descriptions, mastery-level expectations, external code, group and reference. Nextgen Domain/V1/Skill/DTO/SkillDTO.php; mapping to Infrastructure/Persistence/Entity/Skill/Skill.php.
JobSkillUnique job + skill relation; required mastery maps to persisted note. Domain/V1/JobSkill/DTO/JobSkillDTO.php.
SkillGroupSkill classification/type and repository relationship; legacy form also exposes weight, measurable and position. Main/ConfigBundle/Form/Type/SkillGroupType.php.
ReferentialCompany-scoped repository, title/locale and main/public flags. Nextgen uses the ReferentialDTO spelling for its contract and Referentiel spelling in persistence.
User skillSeparate individual skill record. Its assessment is not the job's required mastery. Infrastructure/Service/User/UserSkillService.php.

Both repositories map familiar tables including job, skill, jobskill, referentiel and SkillGroup. This is evidence of compatible domain lineage, not verified proof of shared deployed storage, bidirectional synchronization or a migration strategy. Legacy's translated repository form and nextgen's title/locale DTO are different contracts.

A reference contains jobs, skills and groups. The nextgen referential repository's findUnusedReferential checks links to those objects. ReferentielListener::preDelete delegates to ReferentielManager::isDeletable and rejects deletion when not allowed. preCreate makes the first reference main; postCreateOrUpdate calls unsetMainReferential for a main record. A transaction/concurrency guarantee for simultaneous main-reference changes was not investigated.

Manager job entry is src/Company/JobBundle/Controller/DefaultController.php::indexAction, rendering Resources/views/Default/index.html.twig. web/assets_search/js/SearchCompanyJob.js::{getFilters,CallSearch} serializes search controls and pagination. The search route is defined in src/Main/ConfigBundle/Resources/config/routing/companyjob.yml; Controller/CompanyJobController.php::searchAction checks authenticated Ajax usage and the referential_token CSRF token, then chooses the manager/customer query from context.

src/Main/ConfigBundle/Repository/JobRepository.php::applyKeywordSearchConditions searches translated titles in the current locale and applies hidden-job behavior. Multiple consistency indicators are combined with Expr\Andx. Manager and customer methods are distinct. Do not transplant the older company customer-search query's public-reference predicate to every employee route.

The observed employee explorer uses Customer/InternalJobBundle/Services/SearchService.php::{jobs,findFilteredCustomerJob}. Its query scopes company and active jobs, joins the active translation locale, applies visible-status filtering when requested and handles hidden jobs specially: an empty search excludes hidden records, while an exact title can include a hidden record. Hidden is not an authorization boundary. Proximity filtering depends on matching configuration/data; current matching values and thresholds were not validated.

Customer/InternalJobBundle/Resources/views/Partials/scripts.search.html.twig::resetAll resets advanced fields and proximity and submits, but leaves the main search text (the line that would clear it is commented). resetElement clears that group's visible inputs and submits. By contrast, manager Skills web/assets_search/js/SearchActivities.js::clearSearch clears controls without calling search. These differences explain the functional guide's separate reset instructions.

Nextgen API contracts​

Definitions are in src/Http/Api/Routing/V1/{job,skill,skill_group,referential,job_skill}.yaml. All paths below have prefix /api/v1. CRUD means GET list/detail, POST create, PUT/PATCH update and DELETE, with numeric identifier requirements on detail routes.

Resource pathsAction permissionsList behavior / deletion configuration
/job, /job/{id}read-job, create-job, update-job, delete-jobTitle ASC; title/code filters with equal, contain, start, end. Soft deletion configured.
/skill, /skill/{id}read-skill, create-skill, update-skill, delete-skillTitle ASC; title/type/id filters. soft_deletable: false.
/skill-group, /skill-group/{id}read-skill-group, create-skill-group, update-skill-group, delete-skill-groupTitle/type/referential_id filters. Soft deletion configured.
/referential, /referential/{id}read-referential, create-referential, update-referential, delete-referentialTitle ASC; title and strict public/main equality filters. Hard deletion configured, subject to listener guard.
/job/{jobId}/skill, /job/{jobId}/skill/{skillId}read-job-skill, create-job-skill, update-job-skill, delete-job-skillRequired level ASC; detail identifier is the skill ID nested under the job. DELETE removes the relationship with hard deletion configuration.

These filter names/operators are verified route metadata. Complete filter-query encoding, pagination headers, authentication setup and all serializer groups are outside this feature contract; use the deployment's generated API documentation rather than inventing query syntax.

DTO validation and an illustrative request​

src/Domain/V1/Job/DTO/JobDTO.php requires translated titles with length constraints, validates optional unique codes and declares translated description sections. SkillDTO.php validates translated titles, optional unique external code, company-scoped group/reference IDs and mastery expectation labels 1–5. Type is a read-only hard/soft/other value, defaulting to other in the DTO; it is not a free writable group reassignment field.

ReferentialDTO.php requires a nonblank string title of at most 255 characters on write, a supported locale on creation, and non-null boolean main/public fields in write validation; the booleans are in the Detailed serialization group. First-reference main behavior is enforced afterwards by the listener. Do not infer legacy checkbox defaults from DTO requirements.

JobSkillDTO.php validates job and skill existence within the company on create and uniqueness of the pair. requiredLevel is a nullable integer mapped to note; this DTO does not impose a universal 1–4 constraint. The checked Behat feature deliberately uses 10, 20 and 30. Those are contract test values, not recommended business scales.

Illustrative attachment request, not executed; IDs are placeholders for authorized records and the scale must match the agreed integration:

POST /api/v1/job/123/skill
Content-Type: application/json

{
"skill_id": 456,
"required_level": 3
}

tests/Behat/Features/V1/job_skill/crud.feature specifies 201 on creation, 400 with This job already has this skill. for a duplicate, 200 on read/update, 204 on deletion and 404 on a subsequent read. Its Nested serialization scenario includes the related skill; Detailed adds timestamps. These tests were inspected, not executed. No credentials, live IDs or production writes are required to understand the contract.

Authorization and tenancy​

Legacy Company/JobBundle/Controller/DefaultController.php::grantOrDenyAccess checks profile permissions with AND by default; a false strict argument enables OR. The helper also bypasses its profile check when no profile is attached, which deserves attention in an authorization audit. indexAction uses referentiel_r; checked create/edit actions pass ['referentiel_c','job_c_d'] with OR semantics. Duplication paths differ, including job_duplicate; do not claim a single universal duplicate rule. Job skill updates and status actions use their own reference-write checks. Skill create/edit checks in Company/SkillBundle/Controller/DefaultController.php use reference-read in the inspected implementation; do not infer stronger write checks from the UI.

Nextgen action permissions are declared per route. Infrastructure/Persistence/Repository/Job/JobRepository.php::findJobs constrains company and active state; Skill/SkillRepository.php::findSkills and Referential/ReferentielRepository.php::findReferentials scope the company. A general catalogue skill query is not automatically an employee-public catalogue. DTO EntityExist(... hasCompany: true) adds reference validation; it does not replace route authorization.

Cross-company identifiers, absent permissions and resource visibility must be tested as separate concerns. The checked job-skill Behat features expect 403 without permission and 400 validation responses for invalid relations; those expectations are source evidence, not a live penetration test.

Events and employee-skill lifecycle​

  1. Infrastructure/Persistence/Listener/Job/JobSkillListener.php::postCreate publishes SkillAttachedToJob after relation creation.
  2. Command/Consumer/Job/SkillAttachedToJobConsumerCommand.php resolves job/skill and invokes UserSkillService::attachSkillFromJob.
  3. Command/Consumer/User/JobAssignedToUserConsumerCommand.php invokes UserSkillService::addSkillsToUser for job assignment.
  4. Infrastructure/Service/User/UserSkillService.php attaches missing skills, with batching of 100 in the inspected implementation. It does not establish that required job mastery replaces assessed individual mastery.

The consumers catch Throwable, log critical context through CatchEvent and return a reject result. Broker retries, dead-letter handling, consumer scheduling and production latency were not verified. Relation deletion must not be assumed to symmetrically remove employee skills or reverse assessments. A successful catalogue API response is not confirmation that every downstream employee record is already updated.

AIhub and external integration boundary​

aihub/src/entity/job/imports/data_sources/import_jobs_sm.py::import_jobs_from_sm extracts company job/skill relationships, descriptions, statuses and required levels, with default active/visible-status selection in the inspected import. create_job_description and create_skills_list normalize extracted content; src/entity/job/imports/import_jobs_process.py::standardize_job_batch invokes asynchronous standardization. Skill ingestion has a separate src/entity/skill/import_service.py::import_skills → process_all_skills chain.

This proves import/standardization lineage, not an immediate Save-triggered AIhub write or a customer-visible Lexi import button. Runtime data sources, schedules, model/provider activation, quotas and result feedback into catalogue editing were not verified. Avoid exposing connection details in logs or documentation.

Legacy JobBundle/Controller/DefaultController.php::jobgenerateAction is an ATS/Teamtailor integration path in the inspected source, not evidence of a general AI job-generation workflow. Export dispatch and PDF handling are separate paths; no export, external call or AI process was run.

Configuration, runtime and extension points​

The configuration guide lists owner and generation explicitly. Legacy feature keys affect direction/family/development/qualification/classification filters, auto codes, recommendations and export format. Effective tenant defaults were not retrieved. Job SmartEditor fields are context-specific (bo/fo); adding a filter requires verifying its schema, query handling and translation in both relevant routes.

Nextgen depends on its Symfony/API runtime, Doctrine persistence, company/security context and the event-consumer runtime for propagation. AIhub uses separate extraction/standardization processes. Shared table names do not prove shared deployment connectivity. To extend the domain, trace route → permission → DTO validation/mapping → scoped repository → listener/consumer, then review API and UI consumers before changing identifiers or scales.

Failure modes, observability and privacy​

SymptomVerified investigation point / limit
Manager sees more than employeeDifferent routes/queries, company, locale translation, active state, status and hidden handling. Do not diagnose solely from a repository checkbox.
API access deniedRoute action permission and current company. Behat contract covers 403.
Duplicate job skill / invalid relationDTO uniqueness and company-scoped existence; contract expects 400 with invalid-parameter information.
Reference cannot be deletedReferentielListener::preDelete, ReferentielManager::isDeletable, unused-reference query; error Cannot delete the referential.
Missing employee skills after attachmentEvent publication, consumer execution and critical/reject logs; no synchronous completion guarantee.
Empty results in one languageTranslation joins and actual locale content. Live cause was not diagnosed.
Import identity driftStable external codes, replacement mapping and separate employee assessments. No automatic migration recovery established.

Keep logs limited to necessary technical context; employee profiles, motivations and assessments are separate sensitive records. Never treat hidden/public presentation flags as substitutes for authorization. No account-switch address, secret, connection string or infrastructure identifier is needed in customer documentation.

Evidence, tests and limitations​

  • Verified in code: paths and symbols cited throughout; source review on 15 October 2026 across the four local repositories. No deployment version or shared-database topology inferred.
  • Test sources inspected, not run: nextgen tests/Behat/Features/V1/{job,skill,skill_group,referential,job_skill}/crud.feature and relevant constraints scenarios. No app source was modified and no API mutation, import, worker or external integration was executed.
  • Documented in Confluence: Jobs & Skills Framework playbook, v8, 8 August 2023; Lexi import guide, v4, 28 May 2026. They establish business/import intent, not deployed endpoints or numeric API constraints. Conflicts in older reference/group instructions are explained in configuration.
  • Supplied KBCT entry 2136702980 was unavailable. Supplied PS entries 2104033292 (Delivery Philosophy & Project Methodology, v7, 30 December 2025) and 2104983553 (Onboarding/learning and partner certification, v9, 18 February 2026) provide methodology context, not feature implementation evidence.
  • Not verified: nextgen editor parity, runtime routing between generations, shared storage/synchronization, live authorization exhaustiveness, full filter serialization, consumer retry policies, production latency, AIhub refresh schedules and destructive-migration recovery.

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.

  • Observed in application: smartmobility.io, 15 October 2026; existing authenticated test session, confirmed manager and personal workspaces; separately captured EN/FR interfaces. Browser chrome and personal header details were cropped from assets. A synthetic draft job and one synthetic job–skill relation were saved in the test tenant, with required mastery and importance set to 3. The employee Career planner loaded but returned no job cards, so employee visibility and detail are not confirmed. Two synthetic skills were imported into the test catalogue with the French company default language. No employee assessment was changed.
  • Verified in code: legacy src/Company/JobBundle/Controller/DefaultController.php, job list template, web/assets_search/js/SearchCompanyJob.js, SearchActivities.js; src/Customer/InternalJobBundle/Controller/{DefaultController,SearchController}.php, Services/SearchService.php, and Resources/views/Partials/scripts.search.html.twig. Nextgen APIs and bounded front-end findings are detailed in the technical guide.
  • Documented in Confluence: Creating a job description, v3, 10 November 2022, supports job content, attached skills and publication intent; Importer un référentiel Lexi dans SmartMobility, v4, 28 May 2026, distinguishes reference requirements from individual assessment and describes replacement mapping. These documentary workflows were not executed.
  • Not verified: saved-form validation, exports/imports, skill transfer/deletion, employee detail and personal-action results, automatic translation, a dedicated nextgen editor, production deployment equivalence. Missing corresponding screenshots are intentional rather than substitutes from another locale or generation. The supplied KBCT entry 2136702980 was unavailable; relevant accessible pages were used instead. The two supplied PS entry pages concern delivery/onboarding rather than framework UI rules.

Last updated on 3 November 2026