Skip to content

feat(timetable): take the current timetable into a scenario, scored like a generated one - #796

Merged
rubenvdlinde merged 8 commits into
developmentfrom
feat/timetable-generator-import
Sep 30, 2026
Merged

rubenvdlinde merged 8 commits into
developmentfrom
feat/timetable-generator-import

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Timetable generator, section 6: take the current timetable into a scenario

Base: development (19711e0). Sits on #795 (section 5, the solver), which sits on #794, #793 and #791. Merge in that order, or merge this PR with a merge commit to land all five.

Change: timetabling-generator (DECISIONS rows 17 and 21), section 6 of 9 (task 6.1). The change stays open; tt-scenario-compare moves to built with section 7.

What the user can now do

An admin creates a scenario with source "imported" and a week (weekOf), opens it and presses "Take the current timetable". The scheduled lessons of that week become the scenario's lessons and placements, scored with the same scorer as a generated run. The page then shows how many lessons have a place, the lessons whose times are not a period of the week grid ("Not on a period of the week grid"), and, for an imported timetable only, a "Broken hard wishes" list: a breach is counted and shown, not hidden.

How

  • lib/Service/TimetableScenarioImporter.php: reads the scheduled timetableSession rows of the scenario's week, keys each lesson group:subject:n like the input builder, maps its start and end to a period key and a length on the grid (TimetableGridService::periodTimes(), new), scores the week with TimetableScorer, and stores the scenario done with input, placements, unplaced, brokenWishes and metrics. A generated or published scenario, and a week without lessons, are refused.
  • TimetableScenarioStore::scheduledLessons() (new) reads the window with RBAC off and re-checks status and start time in PHP, so the snapshot never depends on OpenRegister applying every filter.
  • TimetableInputBuilder::solverWish() (was private wish()): the importer shapes wishes exactly as a generated run does.
  • POST /api/timetable/scenarios/{id}/import (TimetableScenarioController::importCurrent, admin: AuthorizedAdminSetting plus an admin check; 403 otherwise, 400 with the reason for a refused scenario).
  • Scenario page (TimetableScenarioSections.vue): the button, the broken hard wishes list and the off-grid reason; helpers brokenHardRows, canTakeCurrentTimetable in src/utils/timetableScenarios.js. 4 new strings in all 36 locales.

Tests

  • tests/unit/Service/TimetableScenarioImporterTest.php (2), the real store, grid, input builder and scorer over an in-memory ObjectService: an imported timetable that breaks a hard wish shows hardWishesBroken 1 with the lesson named; a two-period lesson keeps length 2; a lesson off the period times is unplaced with offGrid; a lesson of the next week is left out; the stored payload validates against the real timetableScenario fragment (Opis). A generated scenario and an empty week are refused.
  • tests/unit/Controller/TimetableScenarioControllerTest.php (+2): a non-admin is refused; an admin gets the status and metrics, or 400 with the reason.
  • tests/vitest/timetableScenarios.spec.js (+3): broken hard wishes apart from soft ones, the off-grid reason, when the button shows.

Red first: the importer tests failed with the class missing, the controller tests on the unknown importer argument, the vitest cases on the missing helpers (lane log lane17/red-s6.log).

Verification

At head 6bc8cd5 (development 19711e0 + #791 + #793 + #794 + #795 + this section): composer check:strict 1 (phpcs, one 151-character line) then phpcs 0 on the changed files; phpmd, psalm, phpstan 0; PHPUnit 530 (7 skipped). npm lint, stylelint, check:l10n, check:l10n-js, check:schema-l10n, check:manifest, vitest and build all 0. CI-shaped Hydra gates (hydra-gates@main, full coverage, this body): ALL 82 applicable gates passed.

Live check

  1. As an admin, with scheduled lessons in the week of 5 October 2026 and a hard wish "klaas, not on Wednesday 5 to 8": Timetable, Scenarios, add "Current timetable", source imported, week 2026-10-05, window 2026-10-05 to 2026-10-30, status queued.
  2. Open it, "Take the current timetable". The page reads Done, "N of M lessons placed", and lists under "Broken hard wishes" klaas's Wednesday afternoon lessons.
  3. A generated scenario shows no "Take the current timetable" button. As a non-admin: POST /apps/planninq/api/timetable/scenarios/<id>/import answers 403.

Inherited: the non-required Newman, PHPUnit (pgsql) and Quality Report jobs have been red on every PR since #758.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/planninq @ 4399985

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
check-l10n-js ✅
check-schema-l10n ✅
composer ✅ ✅ 107/107
npm ✅ ✅ 646/646
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ❌
Newman ❌
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test.
Hydra gates ✅

Quality workflow — 2026-09-30 05:54 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant