Passed
Push — master ( 26caeb...0e28f7 )
by Thomas
11:18
created

Period::newStudents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 10
rs 10
1
<?php
2
3
namespace App\Models;
4
5
use Backpack\CRUD\app\Models\Traits\CrudTrait;
6
use Carbon\Carbon;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Support\Facades\DB;
10
use Spatie\Activitylog\Traits\LogsActivity;
11
12
/**
13
 * @mixin IdeHelperPeriod
14
 */
15
class Period extends Model
16
{
17
    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\Period: $fakeColumns, $identifiableAttribute, $Type
Loading history...
18
    use LogsActivity;
19
20
    public $timestamps = false;
21
22
    protected $fillable = ['name', 'year_id', 'start', 'end'];
23
24
    protected static bool $logUnguarded = true;
25
26
    protected static function boot()
27
    {
28
        parent::boot();
29
30
        static::addGlobalScope('order', function (Builder $builder) {
31
            $builder->orderBy('year_id')->orderBy('order')->orderBy('id');
32
        });
33
    }
34
35
    /**
36
     * Return the current period to be used as a default system-wide.
37
     * First look in Config DB table; otherwise select current or closest next period.
38
     */
39
    public static function get_default_period()
40
    {
41
        $configPeriod = Config::where('name', 'current_period');
42
43
        if ($configPeriod->exists()) {
44
            $currentPeriodId = $configPeriod->first()->value;
45
46
            if (self::where('id', $currentPeriodId)->count() > 0) {
47
                return self::find($currentPeriodId);
48
            } else {
49
                return self::where('end', '>=', date('Y-m-d'))->first();
50
            }
51
        }
52
53
        return self::first();
54
    }
55
56
    /**
57
     * Return the period to preselect for all enrollment-related methods.
58
     */
59
    public static function get_enrollments_period()
60
    {
61
        $selected_period = Config::where('name', 'default_enrollment_period')->first()->value;
62
63
        if (self::where('id', $selected_period)->count() > 0) {
64
            return self::find($selected_period);
65
        } else {
66
            // if the current period ends within 15 days, switch to the next one
67
            $default_period = self::get_default_period();
68
69
            // the number of days between the end and today is 2x less than the number of days between start and end
70
            if (Carbon::parse($default_period->end)->diffInDays() < 0.5 * Carbon::parse($default_period->start)->diffInDays($default_period->end)) {
71
                return self::where('id', '>', $default_period->id)->orderBy('id')->first();
72
            } else {
73
                return $default_period;
74
            }
75
        }
76
    }
77
78
    public function enrollments()
79
    {
80
        return $this->hasManyThrough(Enrollment::class, Course::class)
81
            ->with('course');
82
    }
83
84
    public function courses()
85
    {
86
        return $this->hasMany(Course::class);
87
    }
88
89
    public function internal_courses()
90
    {
91
        return $this->hasMany(Course::class)->internal();
92
    }
93
94
    public function external_courses()
95
    {
96
        return $this->hasMany(Course::class)->external();
97
    }
98
99
    public function year()
100
    {
101
        return $this->belongsTo(Year::class);
102
    }
103
104
    /** returns only pending or paid enrollments, without the child enrollments */
105
    public function real_enrollments()
106
    {
107
        return $this->hasManyThrough(Enrollment::class, Course::class)
108
        ->whereIn('status_id', ['1', '2']) // pending or paid
109
        ->where('parent_id', null);
110
    }
111
112
    /**
113
     * getPendingEnrollmentsCountAttribute
114
     * Do not count children enrollments.
115
     */
116
    public function getPendingEnrollmentsCountAttribute()
117
    {
118
        return $this
119
            ->enrollments
120
            ->where('status_id', 1) // pending
121
            ->where('parent_id', null)
122
            ->count();
123
    }
124
125
    /**
126
     * getPaidEnrollmentsCountAttribute
127
     * Do not count enrollments in children courses.
128
     */
129
    public function getPaidEnrollmentsCountAttribute()
130
    {
131
        return $this
132
            ->enrollments
133
            ->where('status_id', 2) // paid
134
            ->where('parent_id', null)
135
            ->count();
136
    }
137
138
    public function getStudentsCountAttribute()
139
    {
140
        return DB::table('enrollments')
141
            ->join('courses', 'enrollments.course_id', 'courses.id')
142
            ->where('courses.period_id', $this->id)
143
            ->where('enrollments.deleted_at', null)
144
            ->where('enrollments.parent_id', null)
145
            ->whereIn('enrollments.status_id', ['1', '2']) // filter out cancelled enrollments, todo make this configurable.
146
            ->distinct('student_id')
147
            ->count('enrollments.student_id');
148
    }
149
150
    public function getInternalEnrollmentsCountAttribute()
151
    {
152
        return $this->paid_enrollments_count + $this->pending_enrollments_count;
153
    }
154
155
    public function getExternalEnrollmentsCountAttribute()
156
    {
157
        return $this->external_courses->sum('head_count');
0 ignored issues
show
Bug introduced by
The method sum() does not exist on Illuminate\Database\Eloquent\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

157
        return $this->external_courses->/** @scrutinizer ignore-call */ sum('head_count');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
158
    }
159
160
    public function getExternalStudentsCountAttribute()
161
    {
162
        return $this->external_courses->sum('new_students');
163
    }
164
165
    public function getExternalCoursesCountAttribute()
166
    {
167
        return $this->external_courses->count();
0 ignored issues
show
Bug introduced by
The method count() does not exist on Illuminate\Database\Eloquent\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

167
        return $this->external_courses->/** @scrutinizer ignore-call */ count();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
    }
169
170
    public function getPartnershipsCountAttribute()
171
    {
172
        return $this->courses()->pluck('partner_id')->unique()->count();
173
    }
174
175
    public function getPreviousPeriodAttribute()
176
    {
177
        $period = self::where('id', '<', $this->id)->orderBy('id', 'desc')->first();
178
179
        if (! $period == null) {
180
            return $period;
181
        } else {
182
            return self::first();
183
        }
184
    }
185
186
    public function getNextPeriodAttribute()
187
    {
188
        return self::where('id', '>', $this->id)->orderBy('id')->first();
189
    }
190
191
    /** Compute the acquisition rate = the part of students from period P-1 who have been kept in period P */
192
    public function getAcquisitionRateAttribute()
193
    {
194
        // get students enrolled in period P-1
195
        $previous_period_student_ids = $this->previous_period->real_enrollments->pluck('student_id');
196
197
        // and students enrolled in period P
198
        $current_students_ids = $this->real_enrollments->pluck('student_id');
199
200
        // students both in period p-1 and period p
201
        $acquired_students = $previous_period_student_ids->intersect($current_students_ids);
0 ignored issues
show
Bug introduced by
The method intersect() does not exist on Illuminate\Support\Collection. It seems like you code against a sub-type of Illuminate\Support\Collection such as Illuminate\Database\Eloquent\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

201
        /** @scrutinizer ignore-call */ 
202
        $acquired_students = $previous_period_student_ids->intersect($current_students_ids);
Loading history...
202
203
        return number_format((100 * $acquired_students->count()) / max($previous_period_student_ids->count(), 1), 1).'%';
0 ignored issues
show
Bug introduced by
The method count() does not exist on Illuminate\Support\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

203
        return number_format((100 * $acquired_students->count()) / max($previous_period_student_ids->/** @scrutinizer ignore-call */ count(), 1), 1).'%';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
204
    }
205
206
    public function newStudents()
207
    {
208
        // get students IDs enrolled in all previous periods
209
        $previous_period_student_ids = DB::table('enrollments')->join('courses', 'enrollments.course_id', 'courses.id')->where('period_id', '<', $this->id)->pluck('enrollments.student_id');
210
211
        // and students enrolled in period P
212
        $current_students_ids = $this->real_enrollments->unique('student_id');
213
214
        // students in period P who have never been enrolled in previous periods
215
        return $current_students_ids->whereNotIn('student_id', $previous_period_student_ids);
216
    }
217
218
    public function getPeriodTaughtHoursCountAttribute()
219
    {
220
        // return the sum of all courses' volume for period
221
        return $this->internal_courses->where('parent_course_id', null)->sum('total_volume');
0 ignored issues
show
Bug introduced by
The method where() does not exist on Illuminate\Database\Eloquent\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
        return $this->internal_courses->/** @scrutinizer ignore-call */ where('parent_course_id', null)->sum('total_volume');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
    }
223
224
    public function getPeriodSoldHoursCountAttribute()
225
    {
226
        $total = 0;
227
        foreach ($this->courses()->internal()->withCount('real_enrollments')->get() as $course) {
228
            $total += $course->total_volume * $course->real_enrollments_count;
229
        }
230
231
        return $total;
232
    }
233
234
    public function getTakingsAttribute()
235
    {
236
        return $this->real_enrollments->sum('total_paid_price');
237
    }
238
239
    public function getExternalTaughtHoursCountAttribute()
240
    {
241
        // return the sum of all courses' volume for period
242
        return $this->external_courses->where('parent_course_id', null)->sum('total_volume');
243
    }
244
245
    public function getExternalSoldHoursCountAttribute()
246
    {
247
        $total = 0;
248
        foreach ($this->external_courses as $course) {
249
            $total += $course->total_volume * $course->head_count;
250
        }
251
252
        return $total;
253
    }
254
255
    /** TODO this method can be furthered optimized and refactored */
256
    public function getCoursesWithPendingAttendanceAttribute()
257
    {
258
        // get all courses for period and preload relations
259
        $courses = $this->courses()->where(function ($query) {
260
            $query->where('exempt_attendance', '!=', true);
261
            $query->where('exempt_attendance', '!=', 1);
262
            $query->orWhereNull('exempt_attendance');
263
        })->whereHas('events')->with('attendance')->whereNotNull('exempt_attendance')->get();
264
        $coursesWithMissingAttendanceCount = 0;
265
266
        // loop through all courses and get the number of events with incomplete attendance
267
        foreach ($courses as $course) {
268
            foreach ($course->eventsWithExpectedAttendance as $event) {
269
                foreach ($course->enrollments as $enrollment) {
270
                    // if a student has no attendance record for the class (event)
271
                    $hasNotAttended = $course->attendance->where('student_id', $enrollment->student_id)
272
                     ->where('event_id', $event->id)
273
                     ->isEmpty();
274
275
                    // count one and break loop
276
                    if ($hasNotAttended) {
277
                        $coursesWithMissingAttendanceCount++;
278
                        break 2;
279
                    }
280
                }
281
            }
282
        }
283
284
        // sort by number of events with missing attendance
285
        return $coursesWithMissingAttendanceCount;
286
    }
287
}
288