Skip to content

feat(enrolment): apply wedge spec via manifest + ADR-031 lifecycle handler (closes #23) - #31

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feature/wedge/enrolment
May 11, 2026
Merged

feat(enrolment): apply wedge spec via manifest + ADR-031 lifecycle handler (closes #23)#31
rubenvdlinde merged 1 commit into
developmentfrom
feature/wedge/enrolment

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

  • Enrolment schema added to lib/Settings/scholiq_register.json: full lifecycle (pending → active → completed | withdrawn | failed), relations to LearnerProfile + Course, calculations (isOverdue, daysRemaining, ragStatus), and declarative notifications (welcomeOnActivate, completionOnComplete, reminderT30/T7/T1 with idempotencyKey, managerAlertOnOverdue with HR-group fallback). No EnrolmentController, EnrolmentService, EnrolmentDueReminderJob, or app-local notification service — all replaced by x-openregister-* extensions per ADR-022 + ADR-031.
  • lib/Lifecycle/XapiCompletionHandler.php — the one ADR-031-legitimate PHP file: single handle() method; listens on XapiStatementReceivedEvent (openregister.audit.xapi.statement.received); guards on verb ∈ {completed, passed} + lesson.mandatoryTraining=true + lesson is the final published lesson of its course; calls ObjectService::transition('Enrolment', $enrolmentId, 'complete'). OR then emits the audit entry and completionOnComplete notification automatically.
  • lib/AppInfo/Application.php wires XapiCompletionHandler via IRegistrationContext::registerEventListener on XapiStatementReceivedEvent.
  • src/manifest.json extended with EnrolmentDetail (type=detail, register=scholiq, schema=Enrolment) and BulkEnrol (type=custom, component=BulkEnrolModal).
  • src/views/BulkEnrolModal.vue — 3-step modal: (1) audience picker via NC OCS /ocs/v2.php/cloud/groups or CSV upload, (2) Course picker + mandatory toggle + dueDate, (3) confirm then POST directly to POST /api/openregister/scholiq/Enrolment/batch with browser-generated bulkJobId; polls progress via GET /api/openregister/scholiq/Enrolment?bulkJobId=<uuid>.
  • src/main.js registers BulkEnrolModal in customComponents on CnAppRoot.

ADR-031 legitimacy

XapiCompletionHandler qualifies as a lifecycle guard exception: it is a cross-system bridge (xAPI LRS event → OR Enrolment transition) with conditional logic that cannot be expressed as a schema calculation. It has one method and delegates everything else to OR.

Test plan

  • Verify JSON validity of scholiq_register.json and manifest.json (python3 -c "import json; json.load(open(...))")
  • PHP syntax check: composer lint — all files pass
  • Manually POST a cmi5 completed statement to OR and verify the matching active Enrolment transitions to completed
  • Open /enrolments/bulk in the app shell and walk through all 3 modal steps
  • Seed an Enrolment with dueDate = today+30, trigger OR's notification tick, assert reminderT30 fires exactly once (idempotencyKey)
  • Seed an Enrolment with dueDate = yesterday, assert isOverdue = true and managerAlertOnOverdue fires

🤖 Generated with Claude Code

@rubenvdlinde
rubenvdlinde force-pushed the feature/wedge/enrolment branch from 69f463c to 1e642c5 Compare May 11, 2026 21:53
…ndler (closes #23)

- Add Enrolment schema to scholiq_register.json: lifecycle (pending → active → completed | withdrawn | failed), x-openregister-relations (learner + course), x-openregister-calculations (isOverdue, daysRemaining, ragStatus), x-openregister-notifications (welcomeOnActivate, completionOnComplete, reminderT30/T7/T1 with idempotencyKey, managerAlertOnOverdue with HR-group fallback)
- Create lib/Lifecycle/XapiCompletionHandler.php: single-method IEventListener; fires complete transition on active Enrolment when xAPI verb ∈ {completed,passed} AND lesson.mandatoryTraining=true AND lesson is final published lesson of course
- Register XapiCompletionHandler in Application.php via IRegistrationContext::registerEventListener on XapiStatementReceivedEvent (ADR-031 legitimate exception)
- Extend src/manifest.json with EnrolmentDetail (type=detail) and BulkEnrol (type=custom, component=BulkEnrolModal) pages
- Create src/views/BulkEnrolModal.vue: 3-step modal — audience picker (NC groups OCS API + CSV), section+config (Course picker, mandatory toggle, dueDate), confirm+submit (POSTs directly to OR batch endpoint, polls progress via bulkJobId)
- Register BulkEnrolModal in src/main.js via customComponents on CnAppRoot
- No EnrolmentController, EnrolmentService, BulkEnrolmentService, EnrolmentDueReminderJob, enrolmentStore.js, or custom router entries
@rubenvdlinde
rubenvdlinde force-pushed the feature/wedge/enrolment branch from 1e642c5 to a746261 Compare May 11, 2026 21:53
@rubenvdlinde
rubenvdlinde merged commit 71157bb into development May 11, 2026
19 of 28 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/scholiq @ 9d68e8d

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd
psalm
phpstan
phpmetrics ⏭️
eslint
stylelint ⏭️
composer ⏭️
npm
PHPUnit
Newman
Playwright

Quality workflow — 2026-05-11 21:54 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/scholiq @ 4dfb977

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-11 21:55 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde added a commit that referenced this pull request May 12, 2026
…m manifest to nc-vue v1.4.0; drop dead scaffold files (#40)

## Root cause

All 5 Wave-2 worktrees were branched off the same pre-Wave-2 base, so the sub-agents
worked blind to each other. The dashboard agent wrote intentional STUB Enrolment +
Regulation schemas ("declare a stub if it doesn't exist yet"). When the dashboard PR
was cherry-picked LAST onto a development that already had #31's full Enrolment and
#33's full Regulation, git's line-based JSON merge kept the stub (the schemas were
declared at different file positions, so no textual conflict — git appended both and
`json.load` resolved the duplicate key to the stub). The merge-resolver script was
only re-run on manifest.json, not scholiq_register.json.

## Fixes

- **Restore full Enrolment schema** (10 props + x-openregister + lifecycle pending→active→completed|withdrawn|failed + relations learner/course + calculations isOverdue/daysRemaining/ragStatus + notifications welcomeOnActivate/completionOnComplete/reminderT30/T7/T1/managerAlertOnOverdue) and merge in the dashboard's myMandatoryTraining widget.
- **Restore full Regulation schema** (12 props + x-openregister + lifecycle draft→published→archived + aggregations mandatoryEnrolledCount/mandatoryCompletedCount/attestationCount/validCredentialCount + calculations coveragePercent/ragStatus + notifications officerAlertOnCoverageDrop/onPublished) and merge widgets coverageGrid+boardProof+attestationCount.
- **Move `appendOnly: true` to top level** on Credential, XapiStatement, Attestation — OR's Schema::hydrate() only reads top-level `appendOnly` (it calls setAppendOnly()); when nested inside `x-openregister` the flag was silently dropped, so these schemas were NOT actually append-only.
- **Conform src/manifest.json to nc-vue manifest schema v1.4.0**: removed `visibleIf` from pages, `public` from CredentialVerify, fixed dashboard widgets to {id,title,type:'custom'}, stripped non-schema keys (href, visibleIf) from the viewInMydash action. `tests/validate-manifest.js` now PASSes (was FAIL with 14 errors).
- **Delete dead scaffold files**: src/router/index.js, src/views/Dashboard.vue, src/navigation/MainMenu.vue (none imported anywhere), lib/Controller/DashboardController.php (no route).

Follow-ups: nc-vue needs `visibleIf` page predicates, `public` pages, and `widget-ref` page-content (umbrella #200) before the dashboard can be fully declarative; until then the dashboard pages are conformant-but-minimal.
@rubenvdlinde
rubenvdlinde deleted the feature/wedge/enrolment branch August 14, 2026 09:49
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