Enrollment::setTotalPriceAttribute()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace App\Models;
4
5
use App\Events\EnrollmentCreated;
6
use App\Events\EnrollmentDeleting;
7
use App\Events\EnrollmentUpdated;
8
use App\Events\EnrollmentUpdating;
9
use App\Models\Skills\SkillEvaluation;
10
use Backpack\CRUD\app\Models\Traits\CrudTrait;
11
use Carbon\Carbon;
12
use Illuminate\Database\Eloquent\Builder;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Support\Facades\App;
15
use Spatie\Activitylog\Traits\LogsActivity;
16
17
/**
18
 * @mixin IdeHelperEnrollment
19
 */
20
class Enrollment extends Model
21
{
22
    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\Enrollment: $fakeColumns, $identifiableAttribute, $Type
Loading history...
23
    use LogsActivity;
24
25
    protected $guarded = ['id'];
26
27
    protected $appends = ['type', 'name', 'result_name', 'product_code', 'price', 'price_with_currency'];
28
29
    protected $with = ['student', 'course', 'childrenEnrollments'];
30
31
    protected static bool $logUnguarded = true;
32
33
    protected $dispatchesEvents = [
34
        'deleting' => EnrollmentDeleting::class,
35
        'created' => EnrollmentCreated::class,
36
        'updating' => EnrollmentUpdating::class,
37
        'updated' => EnrollmentUpdated::class,
38
    ];
39
40
    /**
41
     * return all pending enrollments, without the child enrollments.
42
     */
43
    public function scopeParent($query)
44
    {
45
        return $query
46
        ->where('parent_id', null)
47
        ->get();
48
    }
49
50
    public function scopeReal($query)
51
    {
52
        return $query
53
            ->whereDoesntHave('childrenEnrollments')
54
            ->whereIn('status_id', ['1', '2'])
55
            ->get();
56
    }
57
58
    public function scopeWithoutChildren($query)
59
    {
60
        return $query
61
            ->where(function ($query) {
62
                $query->whereDoesntHave('childrenEnrollments')
63
                ->where('parent_id', null);
64
            })
65
            ->orWhere(function ($query) {
66
                $query->where('parent_id', null);
67
            })
68
            ->get();
69
    }
70
71
    /** only pending enrollments */
72
    public function scopePending($query)
73
    {
74
        return $query
75
            ->where('status_id', 1)
76
            ->where('parent_id', null)
77
            ->get();
78
    }
79
80
    public function scopeNoresult($query)
81
    {
82
        return $query->doesntHave('result');
83
    }
84
85
    public function scopePeriod(Builder $query, int $periodId)
86
    {
87
        return $query->whereHas('course', function ($q) use ($periodId) {
88
            $q->where('period_id', $periodId);
89
        });
90
    }
91
92
    public function scopeCourse(Builder $query, int $courseId)
93
    {
94
        return $query->where('course_id', $courseId);
95
    }
96
97
    /** FUNCTIONS */
98
    public function changeCourse(Course $newCourse)
99
    {
100
        $this->course_id = $newCourse->id;
101
        $this->save();
102
    }
103
104
    public function markAsPaid()
105
    {
106
        $this->status_id = 2;
107
        $this->save();
108
109
        // also mark children as paid
110
        foreach ($this->childrenEnrollments as $child) {
111
            $child->status_id = 2;
112
            $child->save();
113
        }
114
    }
115
116
    public function isPaid()
117
    {
118
        return $this->status_id === 2;
119
    }
120
121
    public function markAsUnpaid()
122
    {
123
        $this->status_id = 1;
124
        $this->save();
125
126
        $this->invoiceDetails()->delete();
127
128
        // also mark children as unpaid
129
        foreach ($this->childrenEnrollments as $child) {
130
            $child->status_id = 1;
131
            $child->invoiceDetails()->delete();
132
            $child->save();
133
        }
134
    }
135
136
    public function addScholarship(Scholarship $scholarship)
137
    {
138
        $this->scholarships()->sync($scholarship);
139
        if (config('invoicing.adding_scholarship_marks_as_paid')) {
140
            $this->markAsPaid();
141
        }
142
    }
143
144
    public function removeScholarship($scholarship)
145
    {
146
        $this->scholarships()->detach($scholarship);
147
        if (config('invoicing.adding_scholarship_marks_as_paid')) {
148
            $this->markAsUnpaid();
149
        }
150
    }
151
152
    /** RELATIONS */
153
    public function student()
154
    {
155
        return $this->belongsTo(Student::class, 'student_id');
156
    }
157
158
    public function user()
159
    {
160
        return $this->belongsTo(User::class, 'student_id');
161
    }
162
163
    public function course()
164
    {
165
        return $this->belongsTo(Course::class, 'course_id');
166
    }
167
168
    public function invoiceDetails()
169
    {
170
        return $this->morphMany(InvoiceDetail::class, 'product');
171
    }
172
173
    public function invoices()
174
    {
175
        return $this->invoiceDetails->map(fn (InvoiceDetail $invoiceDetail) => $invoiceDetail->invoice)->filter();
176
    }
177
178
    // also includes invoices for this enrollment's scheduled payments.
179
    public function relatedInvoices()
180
    {
181
        $scheduledPaymentsInvoices = $this->scheduledPayments->map(fn (ScheduledPayment $scheduledPayment) => $scheduledPayment->invoices());
182
183
        return $this->invoices()->concat($scheduledPaymentsInvoices)->flatten(1);
184
    }
185
186
    public function comments()
187
    {
188
        return $this->morphMany(Comment::class, 'commentable');
189
    }
190
191
    public function scholarships()
192
    {
193
        return $this->belongsToMany(Scholarship::class);
194
    }
195
196
    public function result()
197
    {
198
        return $this->hasOne(Result::class)
199
            ->with('result_name')
200
            ->with('comments');
201
    }
202
203
    public function childrenEnrollments()
204
    {
205
        return $this->hasMany(self::class, 'parent_id');
206
    }
207
208
    public function enrollmentStatus()
209
    {
210
        return $this->belongsTo(EnrollmentStatusType::class, 'status_id');
211
    }
212
213
    public function grades()
214
    {
215
        return $this->hasMany(Grade::class);
216
    }
217
218
    public function scheduledPayments()
219
    {
220
        return $this->hasMany(ScheduledPayment::class);
221
    }
222
223
    public function saveScheduledPayments($payments)
224
    {
225
        $paymentsToDelete = $this->scheduledPayments()->pluck('id')->diff($payments->pluck('id'));
226
        ScheduledPayment::whereIn('id', $paymentsToDelete)->delete();
227
228
        foreach ($payments as $payment) {
229
            $this->scheduledPayments()->updateOrCreate([
230
                'id' => $payment->id,
231
            ], [
232
                'date' => $payment->date,
233
                'value' => $payment->value,
234
                'status' => $payment->status,
235
            ]);
236
        }
237
    }
238
239
    /* Accessors */
240
241
    public function getResultNameAttribute()
242
    {
243
        return $this->result->result_name->name ?? '-';
244
    }
245
246
    public function skill_evaluations()
247
    {
248
        return $this->hasMany(SkillEvaluation::class);
249
    }
250
251
    public function getStudentNameAttribute()
252
    {
253
        return $this->student->name ?? '';
254
    }
255
256
    public function getNameAttribute()
257
    {
258
        return __('Enrollment for').' '.$this->student_name;
0 ignored issues
show
Bug introduced by
Are you sure __('Enrollment for') of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

258
        return /** @scrutinizer ignore-type */ __('Enrollment for').' '.$this->student_name;
Loading history...
259
    }
260
261
    public function getTypeAttribute()
262
    {
263
        return 'enrollment';
264
    }
265
266
    /*     public function getStudentIdAttribute()
267
        {
268
            return $this->student['id'];
269
        } */
270
271
    public function getStudentAgeAttribute()
272
    {
273
        return $this->student->student_age;
274
    }
275
276
    public function getStudentBirthdateAttribute()
277
    {
278
        return $this->student->birthdate;
279
    }
280
281
    public function getStudentEmailAttribute()
282
    {
283
        return $this->student['email'];
284
    }
285
286
    public function getDateAttribute()
287
    {
288
        return Carbon::parse($this->created_at, 'UTC')->locale(App::getLocale())->isoFormat('LL');
289
    }
290
291
    public function getChildrenCountAttribute()
292
    {
293
        return self::where('parent_id', $this->id)->count();
294
    }
295
296
    public function getChildrenAttribute()
297
    {
298
        return self::where('parent_id', $this->id)->with('course')->get();
299
    }
300
301
    public function getStatusAttribute()
302
    {
303
        return $this->enrollmentStatus->name;
304
    }
305
306
    public function getProductCodeAttribute()
307
    {
308
        return $this->course->rhythm->product_code ?? ' ';
309
    }
310
311
    public function getAttendanceRatioAttribute()
312
    {
313
        $courseEventIds = $this->course->events->pluck('id');
314
        $attendances = $this->student->attendance()->with('event')->get()->whereIn('event_id', $courseEventIds);
315
        if ($attendances->count() > 0) {
316
            return round(100 * (($attendances->where('attendance_type_id', 1)->count() + $attendances->where('attendance_type_id', 2)->count() * 0.75) / $attendances->count()));
317
        }
318
    }
319
320
    public function getAbsenceCountAttribute()
321
    {
322
        $courseEventIds = $this->course->events->pluck('id');
323
        $attendances = $this->student->attendance()->with('event')->get()->whereIn('event_id', $courseEventIds);
324
325
        return $attendances->where('attendance_type_id', 3)->count() + $attendances->where('attendance_type_id', 4)->count();
326
    }
327
328
    public function getPriceAttribute()
329
    {
330
        if ($this->total_price !== null) {
331
            return $this->total_price / 100;
332
        }
333
334
        // if enabled, retrieve the default price category for the student
335
        if (config('invoicing.price_categories_enabled') && $this->student?->price_category) {
336
            $price_category = $this->student->price_category;
337
338
            return $this->course->$price_category ?? 0;
339
        }
340
341
        // finally, we default to the course price or 0 (because some screens need a value here, it cannot be null)
342
        return $this->course->price ?? 0;
343
    }
344
345
    public function getPriceWithCurrencyAttribute()
346
    {
347
        if (config('app.currency_position') === 'before') {
348
            return config('app.currency_symbol').' '.$this->price;
349
        }
350
351
        return $this->price.' '.config('app.currency_symbol');
352
    }
353
354
    public function cancel()
355
    {
356
        // if the enrollment had children, delete them entirely
357
        if ($this->childrenEnrollments->count() > 0) {
358
            foreach ($this->childrenEnrollments as $child) {
359
                $child->delete();
360
            }
361
        }
362
363
        // delete attendance records related to the enrollment
364
        $attendances = $this->course->attendance->where('student_id', $this->student->id);
365
        Attendance::destroy($attendances->map(fn ($item, $key) => $item->id));
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

365
        Attendance::destroy($attendances->map(fn ($item, /** @scrutinizer ignore-unused */ $key) => $item->id));

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
366
367
        foreach ($this->course->children as $child) {
368
            $attendances = $child->attendance->where('student_id', $this->student->id);
369
            Attendance::destroy($attendances->map(fn ($item, $key) => $item->id));
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

369
            Attendance::destroy($attendances->map(fn ($item, /** @scrutinizer ignore-unused */ $key) => $item->id));

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
370
        }
371
372
        $this->delete();
373
    }
374
375
    public function getTotalPaidPriceAttribute()
376
    {
377
        $total = 0;
378
        foreach ($this->invoices() as $invoice) {
379
            $total += $invoice->paidTotal();
380
        }
381
382
        return $total;
383
    }
384
385
    public function setTotalPriceAttribute($value)
386
    {
387
        $this->attributes['total_price'] = $value * 100;
388
    }
389
390
    public function getHasBookForCourseAttribute()
391
    {
392
        if ($this->course->books->count() > 0) {
393
            foreach ($this->course->books as $book) {
394
                // if the student doesn't have one of the course books
395
                if ($this->student->books->where('id', $book->id)->count() == 0) {
396
                    return false;
397
                }
398
399
                // if one book is expired
400
                if ($this->student && $this->student->books->where('id', $book->id)->filter(fn ($book) => $book->pivot->expiry_date == null || $book->pivot->expiry_date > Carbon::now())->count() == 0) {
401
                    return 'EXP';
402
                }
403
            }
404
405
            return 'OK';
406
        }
407
    }
408
409
    public function getBalanceAttribute()
410
    {
411
        if (! config('invoicing.invoices_contain_enrollments_only')) {
412
            abort(422, 'Configuration options forbid to access this value');
413
        }
414
415
        $balance = $this->price;
416
        foreach ($this->invoices() as $invoice) {
417
            $balance -= $invoice->paidTotal();
418
        }
419
420
        return number_format($balance, 2);
421
    }
422
}
423