A role-based course management platform that moves every course from a department head's draft through dean and rector approval to Student Affairs, with authorization enforced per role, per scope, and per form field.
Client context
A private university in Istanbul with a multi-faculty structure, where each department chair builds that department's course catalogue every semester.
Turkish universities run this cycle under a strict hierarchy. A course is not simply "created" — it is proposed by a department, reviewed at faculty level by the dean, approved at university level by the rector, and only then handed to Student Affairs for entry into the OBS student information system. Each step carries different authority over different parts of the same record.
The operational challenge
Semester planning of this shape breaks down in spreadsheets and email for reasons that have little to do with data volume:
- Authority is field-level, not record-level. A dean legitimately edits some attributes of a course a department proposed, but not others; a rector can touch everything; an administrator owns a third set. A spreadsheet cannot express that, so control collapses into "whoever has the file".
- Approval state is invisible, and returns are routine. Nobody can answer "what is waiting on me?" without asking — and a course sent back for correction must re-enter the pipeline at the right stage with the reason attached, not restart from scratch.
- Courses are shared across departments. One course frequently serves several departments at several class levels — a structure spreadsheet rows model badly, and that only dean- and rector-level staff should be able to change.
- The registry hand-off is a cliff edge, and teaching load is decided blind. Student Affairs must act on approved courses only and track what it has already entered into OBS; meanwhile, assigning an instructor without seeing their existing weekly hours produces unbalanced loads that surface only after term starts.
Product strategy and approach
The guiding decision was to treat the approval hierarchy as the product's core domain model rather than a permissions afterthought. Two consequences followed.
First, approval state is a first-class lifecycle, not a status label — seven distinct states including separate dean-return and rector-return states, so a returned course holds a real position in the workflow with its own permitted transitions.
Second, each role gets its own operating surface rather than one screen with buttons hidden. Signing in routes each role to the workspace matching its job: department head to their catalogue, dean to the faculty-wide pool, rector to the university-wide pool, Student Affairs to the OBS panel. Navigation is filtered by role — Student Affairs sees a two-item menu where an administrator sees the full management tree.
The core solution
Course authoring with structural depth. A course record carries roughly forty planning attributes — credits, ECTS, theoretical and practical hours, language, delivery mode, classroom type, in- and out-of-department quotas, and shared-department configuration.
Multi-branch sections with instructor-per-branch. Each course splits into 1–10 branches, each with its own instructor and weekly hours, enforced by a uniqueness constraint on (course, branch number). Assignment happens through a dialog showing each candidate's confirmed hours, pending hours, minimum load, and expertise areas — so load balancing is decided with the data in view.
Shared courses as structured data. A shared course stores explicit department-plus-class-level combinations, validated and de-duplicated on save, re-validated before approval, and editable only at dean and rector level.
Approval with authority checked at the moment of writing. Approving or returning runs inside a database transaction that re-reads both actor and course, confirms the actor is still active and the course is still in the state the request assumed, re-checks authority, then writes the new state, the approval record, and the notifications together. A course whose state moved under a reviewer's feet fails with "the course status has changed, the operation was cancelled" rather than silently overwriting someone else's decision.
Governed hand-off to Student Affairs. Fully approved courses appear in a dedicated OBS panel with completion metrics, per-course and bulk "processed" marking that records who processed it and when, and an Excel export shaped for OBS entry.
Operational reporting and bulk onboarding. Faculty, university, department, and activity reports run off the same records — approval rate, average approval duration, per-department comparison — plus a rector-only report on instructor workload. Academic staff import from Excel through a four-step wizard against a documented ten-column contract, with institutional emails generated automatically and records committed in batches of 25 so a large import cannot exceed request limits.
Primary workflows
- Plan → submit: department head drafts courses, sets branch count, assigns an instructor per branch against live load, submits for dean approval.
- Review → approve or return: dean works the faculty pool, approving or returning with a reason, individually or in bulk; returns route back to the department with the reason attached.
- Final approval → hand-off: rector approves across the university-wide pool; Student Affairs then works the approved-only queue, marks courses OBS-processed, and exports the workbook.
- Throughout: in-app and email notifications reach the next actor at each transition, and every field edit and state change is written to an audit log.
My role
Sole developer. Repository history records 92 commits by a single author over roughly six weeks, covering the data model, approval engine, authorization layer, all four role workspaces, the reporting suite, the notification and email subsystem, and the Excel tooling.
Technical approach
Next.js 15, React 19, TypeScript, PostgreSQL via Prisma, NextAuth, Tailwind, Nodemailer, SheetJS. Decisions a buyer should care about:
- Authorization as a shared layer — role hierarchy, field-level edit rights, scope visibility, and transition rules live in one module used by both API routes and UI, so screens and endpoints cannot disagree.
- Race-safe state transitions — authority and state re-validated inside the writing transaction.
- Field-level edits with audit — a per-field endpoint checks that this role may edit this field on this course, then records the change.
- Notifications that cannot block work — email dispatch runs after the transaction and is logged on failure, so a mail outage never stalls an approval.
- Read paths shaped for institution-scale lists — twelve composite indexes on the course table alone, with virtualized rendering on large tables.
Outcomes and evidence
Verified by exercising the running application across all four roles against a seeded database, plus source review:
- A working seven-state approval pipeline with functioning return paths and bulk approve/return at dean and rector level.
- Enforced role scoping, demonstrated concretely: Student Affairs' queue contained only the fully approved courses, and its navigation collapsed to the OBS panel alone.
- Approval-cycle instrumentation the institution can manage by — approval rate, average approval duration, per-department comparison, and pipeline-stage distribution, computed from live records.
No usage, adoption, time-saved, or cost figures are claimed: this analysis had no access to production data. No AI capability is claimed either — the forecasting in the performance report is simple arithmetic extrapolation, and is described as such.
Confidentiality
The institution is not named, and all screenshots come from a local development instance running synthetic seed data. No real staff, student, or course records were accessed.
Need a similar product or workflow? If your approvals run on spreadsheets and email — authority differing by role and by field, work returned as often as approved, a downstream team needing a clean hand-off — this is the shape of system that fixes it.











