Initial commit

This commit is contained in:
Richard Jolley committed 2026-01-14 00:54:08 +00:00
commit c909d539bc
7178 files changed
+756624

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class BillingAccount extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'contact_name',
'primary_parent_user_id',
'invoice_email',
'phone',
'address_line1',
'address_line2',
'town',
'postcode',
'country',
];
/**
* The primary parent user linked to this billing account.
*/
public function primaryParent()
{
return $this->belongsTo(User::class, 'primary_parent_user_id');
}
/**
* Students billed under this account.
*/
public function students()
{
return $this->hasMany(StudentProfile::class, 'billing_account_id');
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Booking extends Model
{
use HasFactory;
protected $fillable = [
'slot_id',
'date',
'student_name',
'booked_from_page',
'message',
'status',
];
public function slot()
{
return $this->belongsTo(Slot::class);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Chapter extends Model
{
protected $fillable = ['lesson_section_id', 'title', 'order'];
public function lessonSection()
{
return $this->belongsTo(LessonSection::class);
}
public function lessons()
{
return $this->hasMany(Lesson::class)->orderBy('order');
}
protected static function booted()
{
static::creating(function ($chapter) {
if (empty($chapter->slug)) {
$chapter->slug = Str::slug($chapter->title);
}
});
static::updating(function ($chapter) {
if ($chapter->isDirty('title')) {
$chapter->slug = Str::slug($chapter->title);
}
});
}
public function getRouteKeyName()
{
return 'slug';
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Course extends Model
{
use HasFactory;
protected $fillable = ['lead_teacher', 'description', 'subject_id', 'level_id'];
public function subject()
{
return $this->belongsTo(Subject::class);
}
public function level()
{
return $this->belongsTo(Level::class);
}
public function modules()
{
return $this->hasMany(Module::class)->orderBy('order');
}
public function lessonSections()
{
return $this->hasManyThrough(LessonSection::class, Module::class);
}
protected static function booted()
{
static::creating(function ($course) {
$subject = $course->subject?->name ?? 'unknown-subject';
$level = $course->level?->name ?? 'unknown-level';
$course->slug = \Illuminate\Support\Str::slug($subject . '-' . $level);
});
}
public function getRouteKeyName(): string
{
return 'slug';
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ExternalBusyWindow extends Model
{
protected $fillable = [
'external_calendar_id', 'starts_at', 'ends_at',
'transparency', 'last_seen_at'
];
protected $casts = [
'starts_at' => 'datetime',
'ends_at' => 'datetime',
'last_seen_at' => 'datetime',
];
public function calendar()
{
return $this->belongsTo(ExternalCalendar::class, 'external_calendar_id');
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ExternalCalendar extends Model
{
protected $fillable = ['name', 'source', 'url', 'is_enabled'];
public function busyWindows()
{
return $this->hasMany(ExternalBusyWindow::class);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Invoice extends Model
{
protected $fillable = [
'student_id',
'period_start',
'period_end',
'total',
'status',
'pdf_path',
];
/**
* The student this invoice is for.
*/
public function student(): BelongsTo
{
return $this->belongsTo(StudentProfile::class, 'student_id');
}
/**
* All invoice line items associated with this invoice.
*/
public function items(): HasMany
{
return $this->hasMany(InvoiceItem::class);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InvoiceItem extends Model
{
protected $fillable = [
'invoice_id',
'attendance_id',
'description',
'quantity',
'unit_price',
'amount',
];
/**
* The invoice this line item belongs to.
*/
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
/**
* The attendance record this line item is based on.
*/
public function attendance(): BelongsTo
{
return $this->belongsTo(LessonAttendance::class, 'attendance_id');
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Lesson extends Model
{
protected $fillable = ['chapter_id','lesson_section_id','title','description','order', 'slug'];
//protected $fillable = ['lesson_section_id', 'title', 'description', 'video_url', 'order'];
public function lessonSection()
{
return $this->belongsTo(LessonSection::class);
}
public function chapter()
{
return $this->belongsTo(Chapter::class);
}
public function videos()
{
return $this->hasMany(LessonVideo::class)->orderBy('order');
}
public function progress()
{
return $this->hasMany(LessonProgress::class);
}
public function questions()
{
return $this->hasMany(LessonQuestion::class);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class LessonAttendance extends Model
{
protected $fillable = [
'teaching_arrangement_id',
'student_teacher_subject_id',
'scheduled_start',
'scheduled_end',
'actual_start',
'actual_end',
'week_of_year',
'status',
'billable',
'payable',
'notes',
];
/**
* The scheduled arrangement this attendance record belongs to.
*/
public function arrangement(): BelongsTo
{
return $this->belongsTo(TeachingArrangement::class, 'teaching_arrangement_id');
}
/**
* The student–teacher–subject relationship for this attendance.
*/
public function studentTeacherSubject(): BelongsTo
{
return $this->belongsTo(StudentTeacherSubject::class, 'student_teacher_subject_id');
}
/**
* Invoice item generated from this attendance (if billed).
*/
public function invoiceItem(): HasOne
{
return $this->hasOne(InvoiceItem::class, 'attendance_id');
}
/**
* Teacher pay record generated from this attendance (if payable).
*/
public function payItem(): HasOne
{
return $this->hasOne(TeacherPayItem::class, 'attendance_id');
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LessonProgress extends Model
{
protected $fillable = ['user_id', 'lesson_id', 'completed', 'last_viewed_at'];
public function user() {
return $this->belongsTo(User::class);
}
public function lesson() {
return $this->belongsTo(Lesson::class);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LessonQuestion extends Model
{
use HasFactory;
protected $fillable = [
'lesson_id',
'user_id',
'question',
'answer',
'answered_by',
'answered_at',
'published',
];
public function lesson()
{
return $this->belongsTo(Lesson::class);
}
public function student()
{
return $this->belongsTo(User::class, 'user_id');
}
public function teacher()
{
return $this->belongsTo(User::class, 'answered_by');
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class LessonSection extends Model
{
protected $fillable = ['module_id', 'title', 'year', 'order', 'slug'];
public function module()
{
return $this->belongsTo(Module::class);
}
public function lessons()
{
return $this->hasMany(Lesson::class)->orderBy('order');
}
public function chapters()
{
return $this->hasMany(Chapter::class)->orderBy('order');
}
public function getRouteKeyName()
{
return 'slug';
}
protected static function boot()
{
parent::boot();
static::creating(function ($section) {
if (empty($section->slug)) {
// Use title + uniqid to avoid collisions
$section->slug = Str::slug($section->title . '-' . uniqid());
}
});
static::updating(function ($section) {
if ($section->isDirty('title')) {
// When renaming, regenerate slug based on title + id
$section->slug = Str::slug($section->title . '-' . $section->id);
}
});
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LessonVideo extends Model
{
protected $fillable = ['lesson_id', 'title', 'video_url', 'order'];
public function lesson()
{
return $this->belongsTo(Lesson::class);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Level extends Model
{
protected $fillable = ['name'];
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Module extends Model
{
protected $fillable = ['course_id', 'name', 'order'];
public function course()
{
return $this->belongsTo(Course::class);
}
public function lessonSections()
{
return $this->hasMany(LessonSection::class)->orderBy('order');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ParentProfile extends Model
{
use SoftDeletes;
protected $fillable = [
'user_id',
'phone',
'address_line1',
'address_line2',
'town',
'postcode',
'country',
'notes',
];
/**
* Link back to the user account for this parent.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Optional: link to billing accounts where this parent is primary.
*/
public function billingAccounts()
{
return $this->hasMany(BillingAccount::class, 'primary_parent_user_id', 'user_id');
}
public function students(): BelongsToMany
{
return $this->belongsToMany(
StudentProfile::class,
'parent_student',
'parent_profile_id', // this model's FK on the pivot
'student_profile_id' // related model's FK on the pivot
)
->withPivot('relationship', 'notes')
->withTimestamps();
}
public function parentProfile(): HasOne
{
return $this->hasOne(ParentProfile::class);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Slot extends Model
{
use HasFactory;
protected $fillable = ['weekday', 'time', 'is_active'];
public function bookings()
{
return $this->hasMany(Booking::class);
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace App\Models;
use App\Models\ParentProfile;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class StudentProfile extends Model
{
protected $fillable = [
'user_id',
'parent_id',
'date_of_birth',
'notes',
'sen_notes',
'year_group',
'has_dyslexia',
'has_dyspraxia',
'has_autism',
'has_adhd',
'has_speech_needs',
'student_type_id',
'billing_account_id', // will exist after next migration
];
protected $casts = [
'date_of_birth' => 'date',
];
/**
* The user account for this student
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Parent/guardian of this student
*/
public function parent(): BelongsTo
{
return $this->belongsTo(User::class, 'parent_id');
}
public function parents(): BelongsToMany
{
return $this->belongsToMany(
ParentProfile::class,
'parent_student',
'student_profile_id', // FK column pointing to THIS model
'parent_profile_id' // FK column pointing to ParentProfile
)
->withPivot('relationship', 'notes')
->withTimestamps();
}
/**
* Subjects (with teachers) that this student studies
*/
public function subjects(): HasMany
{
return $this->hasMany(StudentTeacherSubject::class, 'student_id');
}
/**
* Attendance records for this student
*/
public function attendance(): HasMany
{
return $this->hasMany(LessonAttendance::class, 'student_teacher_subject_id');
}
/**
* The type of student (online, centre, hybrid, etc)
*/
public function type(): BelongsTo
{
return $this->belongsTo(StudentType::class, 'student_type_id');
}
public function billingAccount()
{
return $this->belongsTo(BillingAccount::class);
}
public function students()
{
return $this->belongsToMany(StudentProfile::class, 'parent_student')
->withPivot('relationship', 'notes')
->withTimestamps();
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class StudentTeacherSubject extends Model
{
protected $fillable = [
'student_id',
'teacher_id',
'subject_id',
'rate_override',
'status',
'notes',
];
/**
* The student this relationship belongs to.
*/
public function student(): BelongsTo
{
return $this->belongsTo(StudentProfile::class, 'student_id');
}
/**
* The teacher assigned to this student for this subject.
*/
public function teacher(): BelongsTo
{
return $this->belongsTo(TeacherProfile::class, 'teacher_id');
}
/**
* The subject being taught.
*/
public function subject(): BelongsTo
{
return $this->belongsTo(Subject::class, 'subject_id');
}
/**
* Teaching arrangements (sessions/timetabled slots)
* linking this student–teacher subject pair to actual classes.
*/
public function teachingArrangements(): HasMany
{
return $this->hasMany(TeachingArrangementStudent::class);
}
/**
* Attendance records for this student–teacher–subject combination.
*/
public function attendance(): HasMany
{
return $this->hasMany(LessonAttendance::class);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class StudentType extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
];
/**
* Students who belong to this type.
*
* Assuming student_profiles has a student_type_id column.
*/
public function students()
{
return $this->hasMany(StudentProfile::class);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Subject extends Model
{
protected $fillable = ['name'];
protected static function booted()
{
static::creating(function ($subject) {
if (empty($subject->slug)) {
$subject->slug = Str::slug($subject->name);
}
});
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TeacherPayItem extends Model
{
protected $fillable = [
'teacher_pay_period_id',
'attendance_id',
'hours',
'rate',
'total',
];
/**
* The pay period this item belongs to.
*/
public function payPeriod(): BelongsTo
{
return $this->belongsTo(TeacherPayPeriod::class, 'teacher_pay_period_id');
}
/**
* The attendance record this payment item is based on.
*/
public function attendance(): BelongsTo
{
return $this->belongsTo(LessonAttendance::class, 'attendance_id');
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class TeacherPayPeriod extends Model
{
protected $fillable = [
'teacher_id',
'period_start',
'period_end',
'total',
'status',
];
/**
* The teacher this pay period belongs to.
*/
public function teacher(): BelongsTo
{
return $this->belongsTo(TeacherProfile::class, 'teacher_id');
}
/**
* Each payable lesson included in this period.
*/
public function items(): HasMany
{
return $this->hasMany(TeacherPayItem::class, 'teacher_pay_period_id');
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class TeacherProfile extends Model
{
// Which attributes can be mass-assigned (used in ::create([...]))
protected $fillable = [
'user_id',
'bio',
'picture',
'location',
'hourly_rate',
'qualifications',
'active',
];
/**
* Link back to the user this profile belongs to
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Subjects this teacher teaches
*/
public function subjects(): BelongsToMany
{
return $this->belongsToMany(Subject::class, 'level_subject_teacher')
->withPivot('level_id'); // since we’re also tracking level
}
/**
* Levels this teacher teaches
*/
public function levels(): BelongsToMany
{
return $this->belongsToMany(Level::class, 'level_subject_teacher')
->withPivot('subject_id');
}
public function teachingArrangements(): HasMany
{
return $this->hasMany(TeachingArrangement::class, 'teacher_id');
}
public function payPeriods(): HasMany
{
return $this->hasMany(TeacherPayPeriod::class, 'teacher_id');
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class TeachingArrangement extends Model
{
protected $fillable = [
'teacher_id',
'subject_id',
'weekday',
'start_time',
'duration_minutes',
'is_group',
'max_students',
'location_id',
'fee_policy',
'active',
];
/**
* The teacher running this arrangement.
*/
public function teacher(): BelongsTo
{
return $this->belongsTo(TeacherProfile::class, 'teacher_id');
}
/**
* The subject being taught.
*/
public function subject(): BelongsTo
{
return $this->belongsTo(Subject::class, 'subject_id');
}
/**
* Where the session takes place (room, online, etc.)
*/
public function location(): BelongsTo
{
return $this->belongsTo(Location::class, 'location_id');
}
/**
* Students assigned to this arrangement.
*/
public function students(): HasMany
{
return $this->hasMany(TeachingArrangementStudent::class, 'teaching_arrangement_id');
}
/**
* Attendance records generated from this arrangement.
*/
public function attendance(): HasMany
{
return $this->hasMany(LessonAttendance::class, 'teaching_arrangement_id');
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TeachingArrangementStudent extends Model
{
protected $fillable = [
'teaching_arrangement_id',
'student_teacher_subject_id',
];
/**
* The teaching arrangement (scheduled slot).
*/
public function arrangement(): BelongsTo
{
return $this->belongsTo(TeachingArrangement::class, 'teaching_arrangement_id');
}
/**
* The student–teacher–subject relationship linked to this slot.
*/
public function studentTeacherSubject(): BelongsTo
{
return $this->belongsTo(StudentTeacherSubject::class, 'student_teacher_subject_id');
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasRoles {
HasRoles::hasRole as spatieHasRole;
}
public function teacherProfile()
{
return $this->hasOne(TeacherProfile::class);
}
public function studentProfile()
{
return $this->hasOne(StudentProfile::class);
}
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'first_name',
'last_name',
'email',
'password',
'role',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function lessonProgress()
{
return $this->hasMany(LessonProgress::class);
}
public function courses()
{
return $this->belongsToMany(\App\Models\Course::class, 'course_user')
->withTimestamps();
}
public function hasRole($roles, $guard = null): bool
{
// God inherits all roles
if ($this->roles->pluck('name')->contains('god')) {
return true;
}
// Delegate to Spatie's HasRoles trait
return $this->spatieHasRole($roles, $guard);
}
public function getNameAttribute()
{
return trim($this->first_name . ' ' . $this->last_name);
}
}