Skip to content

feat(timetable): publish a generated scenario as draft lessons for its window - #798

Merged
rubenvdlinde merged 12 commits into
developmentfrom
feat/timetable-generator-publish
Sep 30, 2026
Merged

rubenvdlinde merged 12 commits into
developmentfrom
feat/timetable-generator-publish

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Timetable generator, section 8: publish a generated scenario as draft lessons

Base: development (19711e0). Sits on #797 (section 7, compare), which sits on #796, #795, #794, #793 and #791. Merge in that order, or merge this PR with a merge commit to land all seven.

Change: timetabling-generator (DECISIONS rows 17 and 21), section 8 of 9 (task 8.1). The change stays open; section 9 (docs, Newman) archives it.

What the user can now do

On a generated scenario that is done, an admin presses "Publish as draft lessons". Planninq writes every placement as a draft lesson for every week of the scenario's window, with source planninq-generator and external reference {scenario}:{lesson}:{date}, and marks the scenario published. The existing draft review then applies unchanged: teachers see their own drafts, an admin publishes them. Drafts of an earlier scenario for the same window are replaced. When lessons of that window from the generator are already published, nothing is written and the admin reads why. A scenario taken from the current timetable is not published again.

How

  • lib/Service/TimetableScenarioPublisher.php (design decision 7): turns each placement's period key and the grid's period times (TimetableGridService::periodTimes(), section 6) into startsAt/endsAt per week (a two-period lesson ends at the end of its second period), removes the generator's drafts of other scenarios in the window, and upserts through the existing TimetableSessionService::upsert (idempotent by source and external reference).
  • TimetableScenarioStore::lessonsFrom() and deleteLesson(); the window is re-checked in PHP.
  • POST /api/timetable/scenarios/{id}/publish-drafts (TimetableScenarioController::publishDrafts, admin: AuthorizedAdminSetting plus an admin check; 403 otherwise, 400 with the reason when refused).
  • Scenario page: the button and "Draft lessons written: {count}"; helper canPublishDrafts. 3 new strings in all 36 locales.

Tests

  • tests/unit/Service/TimetableScenarioPublisherTest.php (4), the real store, session service and grid over an in-memory ObjectService: every week of the window gets the pattern as drafts (4 created, a two-period Maths lesson 10:10 to 11:50), every saved payload validated against the real timetableSession fragment (Opis), the scenario marked published (scenario "Publish a scenario as drafts"); a second scenario replaces the first one's drafts; a window with a published generator lesson is refused and nothing changes; an imported scenario is refused.
  • tests/unit/Controller/TimetableScenarioControllerTest.php (+2): a non-admin is refused; an admin gets the counts, or 400 with the reason.
  • tests/vitest/timetableScenarios.spec.js (+1): the button shows only for a finished generated scenario with placements.

Red first: the publisher tests failed with the class missing, the controller tests on the unknown publisher argument, the vitest case on the missing helper, and the imported-scenario case before the publisher checked the source (lane log lane17/red-s8.log).

Verification

At head d1686d0 (development 19711e0 + #791 + #793 + #794 + #795 + #796 + #797 + this section): composer check:strict 1 (phpmd: a short method name and static date parsing in the publisher) then phpmd 0 on the file, phpcs, psalm, phpstan 0, PHPUnit 536 (7 skipped). npm lint, stylelint, check:l10n, check:l10n-js, check:schema-l10n, check:manifest, vitest (423 tests) 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 a generated scenario that is done for the window 2026-10-05 to 2026-10-16: open it, "Publish as draft lessons". The page reads "Draft lessons written: N" and the status "Published as draft lessons".
  2. As a teacher with lessons in it: the draft review shows your drafts for both weeks.
  3. Publish the drafts through the draft review. Then generate a second scenario for the same window and publish it: refused with "Lessons of this window are already published from the generator. Nothing was written."
  4. As a non-admin: POST /apps/planninq/api/timetable/scenarios/<id>/publish-drafts 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 @ 6d17a92

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 07:21 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit d1686d0 into development Sep 30, 2026
36 of 39 checks passed
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