Passed
Push — master ( 8c9d15...53e72a )
by Thomas
07:13
created

Course::skill_evaluations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
3
namespace App\Models;
4
5
use App\Events\CourseUpdated;
6
use App\Models\Skills\Skill;
7
use App\Models\Skills\SkillEvaluation;
8
use Backpack\CRUD\app\Models\Traits\CrudTrait;
9
use Carbon\Carbon;
10
use Illuminate\Database\Eloquent\Model;
11
use Spatie\Activitylog\Traits\LogsActivity;
12
13
class Course extends Model
14
{
15
    use CrudTrait;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Models\Traits\CrudTrait requires some properties which are not provided by App\Models\Course: $fakeColumns, $identifiableAttribute, $Type
Loading history...
16
    use LogsActivity;
17
18
    protected $dispatchesEvents = [
19
        'updated' => CourseUpdated::class,
20
    ];
21
22
    /*
23
    |--------------------------------------------------------------------------
24
    | GLOBAL VARIABLES
25
    |--------------------------------------------------------------------------
26
    */
27
    // protected $primaryKey = 'id';
28
    public $timestamps = true;
29
    protected $guarded = ['id'];
30
    //protected $fillable = [];
31
    // protected $hidden = [];
32
    protected $dates = ['start_date', 'end_date'];
33
    //protected $with = ['enrollments'];
34
    protected $appends = ['course_times', 'course_teacher_name', 'course_period_name', 'course_enrollments_count', 'sortable_id'];
35
    protected static $logUnguarded = true;
36
37
    /*
38
    |--------------------------------------------------------------------------
39
    | SCOPES
40
    |--------------------------------------------------------------------------
41
    */
42
43
    /** filter only the courses that have no parent */
44
    public function scopeParent($query)
45
    {
46
        return $query->where('parent_course_id', null);
47
    }
48
49
    public function scopeChildren($query)
50
    {
51
        return $query->where('parent_course_id', '!=', null);
52
    }
53
54
    /** unused, needed because some reference might still be present in other modules */
55
    public function scopeInternal($query)
56
    {
57
        return $query;
58
    }
59
60
    public function scopeRealcourses($query)
61
    {
62
        return $query->doesntHave('children');
63
    }
64
65
    /*
66
    |--------------------------------------------------------------------------
67
    | RELATIONS
68
    |--------------------------------------------------------------------------
69
    */
70
71
    /** the scheduled day/times for the course, that repeat throughout the course date span */
72
    public function times()
73
    {
74
        return $this->hasMany(CourseTime::class, 'course_id');
75
    }
76
77
    /** course sessions (classes) with a specific start and end date/time */
78
    public function events()
79
    {
80
        return $this->hasMany(Event::class)->orderBy('start');
81
    }
82
83
    /** may be null if the teacher is not yet assigned */
84
    public function teacher()
85
    {
86
        return $this->belongsTo(Teacher::class)->withTrashed();
87
    }
88
89
    public function campus()
90
    {
91
        return $this->belongsTo(Campus::class);
92
    }
93
94
    /** may be null if the room is not yet assigned */
95
    public function room()
96
    {
97
        return $this->belongsTo(Room::class)->withTrashed();
98
    }
99
100
    /** the "category" of course */
101
    public function rhythm()
102
    {
103
        return $this->belongsTo(Rhythm::class)->withTrashed();
104
    }
105
106
    /** a course can only have one level. Parent courses would generally have no level defined */
107
    public function level()
108
    {
109
        return $this->belongsTo(Level::class)->withTrashed();
110
    }
111
112
    /** a course needs to belong to a period */
113
    public function period()
114
    {
115
        return $this->belongsTo(Period::class);
116
    }
117
118
    /** children courses = sub-courses, or course modules */
119
    public function children()
120
    {
121
        return $this->hasMany(self::class, 'parent_course_id');
122
    }
123
124
    public function parent()
125
    {
126
        return $this->belongsTo(self::class, 'parent_course_id');
127
    }
128
129
    /** evaluation methods associated to the course - grades, skill-based evaluation... */
130
    public function evaluation_types()
131
    {
132
        return $this->belongsToMany(EvaluationType::class);
133
    }
134
135
    /** a Grade model = an individual grade, belongs to a student */
136
    public function grades()
137
    {
138
        return $this->hasManyThrough(Grade::class, Enrollment::class);
139
    }
140
141
    /** the different grade types associated to the course, ie. criteria that will receive the grades */
142
    public function grade_types()
143
    {
144
        return $this->belongsToMany(GradeType::class);
145
    }
146
147
    /** in the case of skills-based evaluation, Skill models are attached to the course
148
     * This represents the "criteria" that will need to be evaluated to each student (enrollment) in the course
149
     */
150
    public function skills()
151
    {
152
        return $this->belongsToMany(Skill::class)->orderBy('order');
153
    }
154
155
    public function books()
156
    {
157
        return $this->belongsToMany(Book::class);
158
    }
159
160
    /**
161
     * return attendance records associated to the course
162
     * Since the attendance records are linked to the event, we use a hasManyThrough relation.
163
     */
164
    public function attendance()
165
    {
166
        return $this->hasManyThrough(Attendance::class, Event::class);
167
    }
168
169
    /**
170
     * Return events for which the attendance records do not match the course student count.
171
     *
172
     * todo - optimize this method (reduce the number of queries and avoid the foreach loop)
173
     * but filtering the collection increases the number of DB queries... (why ?)
174
     */
175
    public function getPendingAttendanceAttribute()
176
    {
177
        $events = Event::where(function ($query) {
178
            $query->where('course_id', $this->id);
179
            $query->where('exempt_attendance', '!=', true);
180
            $query->where('exempt_attendance', '!=', 1);
181
            $query->orWhereNull('exempt_attendance');
182
        })
183
        ->where('course_id', '!=', null)
184
        ->with('attendance')
185
        ->with('teacher')
186
        ->with('course.enrollments')
187
        ->where('start', '<', Carbon::now(config('settings.courses_timezone'))->toDateTimeString())
188
        ->get();
189
190
        $pending_events = [];
191
192
        foreach ($events as $event) {
193
            // if the attendance record count do not match the enrollment count, push the event to array
194
            $pending_attendance = $event->course->enrollments->count() - $event->attendance->count();
195
196
            if ($pending_attendance != 0) {
197
                $pending_events[$event->id]['event'] = $event->name ?? '';
198
                $pending_events[$event->id]['event_id'] = $event->id;
199
                $pending_events[$event->id]['course_id'] = $event->course_id;
200
                $pending_events[$event->id]['event_date'] = Carbon::parse($event->start)->toDateString();
201
                $pending_events[$event->id]['teacher'] = $event->teacher->name ?? '';
202
                $pending_events[$event->id]['pending'] = $pending_attendance ?? '';
203
            }
204
        }
205
206
        return $pending_events;
207
    }
208
209
    public function enrollments()
210
    {
211
        return $this
212
            ->hasMany(Enrollment::class, 'course_id', 'id')
213
            ->whereIn('status_id', [1, 2]) // pending or paid enrollments only
214
            ->with('student');
215
    }
216
217
    /** returns only pending or paid enrollments, without the child enrollments */
218
    public function real_enrollments()
219
    {
220
        return $this->hasMany(Enrollment::class, 'course_id', 'id')
221
        ->whereIn('status_id', ['1', '2']) // pending or paid
222
        ->where('parent_id', null);
223
    }
224
225
    /*
226
    |--------------------------------------------------------------------------
227
    | ACCESORS
228
    |--------------------------------------------------------------------------
229
    */
230
231
    /** returns the course repeating schedule */
232
    public function getCourseTimesAttribute()
233
    {
234
        $parsedCourseTimes = [];
235
        $daysInitials = ['D', 'L', 'M', 'X', 'J', 'V', 'S'];
236
237
        $courseTimes = null;
238
        if ($this->times->count() > 0) {
239
            $courseTimes = $this->times;
240
        } elseif (($this->children->count() > 0) && ($this->children->first()->times->count() > 0)) {
241
            $courseTimes = $this->children->first()->times;
242
        }
243
244
        if ($courseTimes) {
245
            foreach ($courseTimes as $courseTime) {
246
                $initial = $daysInitials[$courseTime->day];
247
248
                if (! isset($parsedCourseTimes[$initial])) {
249
                    $parsedCourseTimes[$initial] = [];
250
                }
251
252
                $parsedCourseTimes[$initial][] = sprintf(
253
                    '%s - %s',
254
                    Carbon::parse($courseTime->start)->format('g:i'),
255
                    Carbon::parse($courseTime->end)->format('g:i')
256
                );
257
            }
258
        }
259
260
        $result = '';
261
        foreach ($parsedCourseTimes as $day => $times) {
262
            $result .= $day.' -> '.implode(' / ', $times).' | ';
263
        }
264
265
        return trim($result, ' | ');
266
    }
267
268
    public function getCourseRoomNameAttribute()
269
    {
270
        return strtoupper($this->room->name);
271
    }
272
273
    public function getCourseLevelNameAttribute()
274
    {
275
        return $this->level->name;
276
    }
277
278
    public function getCourseRhythmNameAttribute()
279
    {
280
        return strtoupper($this->rhythm->name);
281
    }
282
283
    public function getCoursePeriodNameAttribute()
284
    {
285
        return $this->period->name ?? '';
286
    }
287
288
    public function getCourseTeacherNameAttribute()
289
    {
290
        if ($this->teacher_id) {
291
            return $this->teacher->firstname.' '.$this->teacher->lastname;
292
        } else {
293
            return '-';
294
        }
295
    }
296
297
    public function getChildrenCountAttribute()
298
    {
299
        return self::where('parent_course_id', $this->id)->count();
300
    }
301
302
    public function getChildrenAttribute()
303
    {
304
        return self::where('parent_course_id', $this->id)->get();
305
    }
306
307
    public function getCourseEnrollmentsCountAttribute()
308
    {
309
        return $this->enrollments()->count();
310
    }
311
312
    public function getParentAttribute()
313
    {
314
        if ($this->parent_course_id !== null) {
315
            return $this->parent_course_id;
316
        } else {
317
            return $this->id;
318
        }
319
    }
320
321
    public function eventsWithExpectedAttendance()
322
    {
323
        return $this->events()->where(function ($query) {
324
            $query->where('exempt_attendance', '!=', true);
325
            $query->where('exempt_attendance', '!=', 1);
326
            $query->orWhereNull('exempt_attendance');
327
        })->where('start', '<', Carbon::now(config('settings.courses_timezone'))->toDateTimeString());
328
    }
329
330
    public function getSortableIdAttribute()
331
    {
332
        if ($this->parent_course_id !== null) {
333
            return $this->parent_course_id;
334
        } else {
335
            return $this->id;
336
        }
337
    }
338
339
    /*
340
    |--------------------------------------------------------------------------
341
    | MUTATORS
342
    |--------------------------------------------------------------------------
343
    */
344
}
345