Completed
Push — master ( 75befc...3b0033 )
by Abdelrahman
06:46 queued 05:33
created

Booking::calculatePrice()   D

Complexity

Conditions 17
Paths 132

Size

Total Lines 81
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 81
rs 4.66
c 0
b 0
f 0
cc 17
eloc 57
nc 132
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Bookings\Models;
6
7
use Carbon\Carbon;
8
use Illuminate\Database\Eloquent\Model;
9
use Rinvex\Cacheable\CacheableEloquent;
10
use Illuminate\Database\Eloquent\Builder;
11
use Rinvex\Support\Traits\ValidatingTrait;
12
use Illuminate\Database\Eloquent\Relations\MorphTo;
13
14
/**
15
 * Rinvex\Bookings\Models\Booking.
16
 *
17
 * @property int                                                $id
18
 * @property int                                                $bookable_id
19
 * @property string                                             $bookable_type
20
 * @property string                                             $currency
21
 * @property int                                                $user_id
22
 * @property string                                             $user_type
23
 * @property \Carbon\Carbon                                     $starts_at
24
 * @property \Carbon\Carbon                                     $ends_at
25
 * @property float                                              $price
26
 * @property array                                              $price_equation
27
 * @property \Carbon\Carbon                                     $cancelled_at
28
 * @property string                                             $notes
29
 * @property \Carbon\Carbon|null                                $created_at
30
 * @property \Carbon\Carbon|null                                $updated_at
31
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $bookable
32
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $user
33
 *
34
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelled()
35
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledAfter($date)
36
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledBefore($date)
37
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledBetween($startsAt, $endsAt)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
38
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking current()
39
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsAfter($date)
40
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsBefore($date)
41
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsBetween($startsAt, $endsAt)
42
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking future()
43
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking ofBookable(\Illuminate\Database\Eloquent\Model $bookable)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
44
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking ofUser(\Illuminate\Database\Eloquent\Model $user)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
45
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking past()
46
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking range()
47
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsAfter($date)
48
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsBefore($date)
49
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsBetween($startsAt, $endsAt)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
50
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCancelledAt($value)
51
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCreatedAt($value)
52
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCurrency($value)
53
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereUserId($value)
54
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereUserType($value)
55
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereEndsAt($value)
56
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereId($value)
57
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereNotes($value)
58
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePrice($value)
59
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePriceEquation($value)
60
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereBookableId($value)
61
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereBookableType($value)
62
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereStartsAt($value)
63
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereUpdatedAt($value)
64
 * @mixin \Eloquent
65
 */
66
class Booking extends Model
67
{
68
    use ValidatingTrait;
69
    use CacheableEloquent;
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    protected $fillable = [
75
        'bookable_id',
76
        'bookable_type',
77
        'user_id',
78
        'user_type',
79
        'starts_at',
80
        'ends_at',
81
        'price',
82
        'currency',
83
        'price_equation',
84
        'cancelled_at',
85
        'notes',
86
    ];
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    protected $casts = [
92
        'bookable_id' => 'integer',
93
        'bookable_type' => 'string',
94
        'user_id' => 'integer',
95
        'user_type' => 'string',
96
        'starts_at' => 'datetime',
97
        'ends_at' => 'datetime',
98
        'price' => 'float',
99
        'currency' => 'string',
100
        'price_equation' => 'json',
101
        'cancelled_at' => 'datetime',
102
        'notes' => 'string',
103
    ];
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    protected $observables = [
109
        'validating',
110
        'validated',
111
    ];
112
113
    /**
114
     * The default rules that the model will validate against.
115
     *
116
     * @var array
117
     */
118
    protected $rules = [
119
        'bookable_id' => 'required|integer',
120
        'bookable_type' => 'required|string',
121
        'user_id' => 'required|integer',
122
        'user_type' => 'required|string',
123
        'starts_at' => 'required|date',
124
        'ends_at' => 'required|date',
125
        'price' => 'required|numeric',
126
        'currency' => 'required|alpha|size:3',
127
        'price_equation' => 'nullable|array',
128
        'cancelled_at' => 'nullable|date',
129
        'notes' => 'nullable|string|max:10000',
130
    ];
131
132
    /**
133
     * Whether the model should throw a
134
     * ValidationException if it fails validation.
135
     *
136
     * @var bool
137
     */
138
    protected $throwValidationExceptions = true;
139
140
    /**
141
     * Create a new Eloquent model instance.
142
     *
143
     * @param array $attributes
144
     */
145
    public function __construct(array $attributes = [])
146
    {
147
        parent::__construct($attributes);
148
149
        $this->setTable(config('rinvex.bookings.tables.bookings'));
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    protected static function boot()
156
    {
157
        parent::boot();
158
159
        static::validating(function (self $booking) {
160
            list($price, $priceEquation, $currency) = is_null($booking->price)
161
                ? $booking->calculatePrice($booking->bookable, $booking->starts_at, $booking->ends_at) : [$booking->price, $booking->price_equation, $booking->currency];
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 169 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
162
163
            $booking->price_equation = $priceEquation;
164
            $booking->currency = $currency;
165
            $booking->price = $price;
166
        });
167
    }
168
169
    /**
170
     * Calculate the booking price.
171
     *
172
     * @param \Illuminate\Database\Eloquent\Model $bookable
173
     * @param string                              $startsAt
0 ignored issues
show
Documentation introduced by
Should the type for parameter $startsAt not be Carbon?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
174
     * @param string                              $endsAt
0 ignored issues
show
Documentation introduced by
Should the type for parameter $endsAt not be null|Carbon?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
175
     *
176
     * @return array
177
     */
178
    public static function calculatePrice(Model $bookable, Carbon $startsAt, Carbon $endsAt = null): array
179
    {
180
        switch ($bookable->unit) {
181
            case 'd':
182
                $method = 'addDay';
183
                break;
184
            case 'm':
185
                $method = 'addMinute';
186
                break;
187
            case 'h':
188
            default:
189
                $method = 'addHour';
190
                break;
191
        }
192
193
        $prices = $bookable->prices->map(function (Price $price) {
194
            return [
195
                'weekday' => $price->weekday,
196
                'starts_at' => $price->starts_at,
197
                'ends_at' => $price->ends_at,
198
                'percentage' => $price->percentage,
199
            ];
200
        });
201
202
        $totalUnits = 0;
203
        $totalPrice = 0;
204
        for ($date = clone $startsAt; $date->lt($endsAt ?? $date->addDay()); $date->$method()) {
0 ignored issues
show
Documentation introduced by
$endsAt ?? $date->addDay() is of type object<Carbon\Carbon>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
205
            // Count units
206
            $totalUnits++;
207
208
            // Get applicable custom prices. Use first custom price matched, and ignore
209
            // others. We should not have multiple custom prices for same time range anyway!
210
            $customPrice = $prices->search(function ($price) use ($date, $bookable) {
211
                $dayMatched = $price['weekday'] === mb_strtolower($date->format('D'));
212
213
                return $bookable->unit === 'd' ? $dayMatched : $dayMatched && (new Carbon($date->format('H:i:s')))->between(new Carbon($price['starts_at']), new Carbon($price['ends_at']));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 188 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
Documentation introduced by
new \Carbon\Carbon($price['starts_at']) is of type object<Carbon\Carbon>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
new \Carbon\Carbon($price['ends_at']) is of type object<Carbon\Carbon>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
            });
215
216
            // Use custom price if exists (custom price is a +/- percentage of original resource price)
217
            $totalPrice += $customPrice !== false ? $bookable->price + (($bookable->price * $prices[$customPrice]['percentage']) / 100) : $bookable->price;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 155 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
218
        }
219
220
        $rates = $bookable->rates->map(function (Rate $rate) {
221
            return [
222
                'percentage' => $rate->percentage,
223
                'operator' => $rate->operator,
224
                'amount' => $rate->amount,
225
            ];
226
        })->toArray();
227
228
        foreach ($rates as $rate) {
229
            switch ($rate['operator']) {
230
                case '^':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
231
                    $units = $totalUnits <= $rate['amount'] ? $totalUnits : $rate['amount'];
232
                    $totalPrice += (($rate['percentage'] * $bookable->price) / 100) * $units;
233
                    break;
234
                case '>':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
235
                    $totalPrice += $totalUnits > $rate['amount'] ? ((($rate['percentage'] * $bookable->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
236
                    break;
237
                case '<':
238
                    $totalPrice += $totalUnits < $rate['amount'] ? ((($rate['percentage'] * $bookable->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
239
                    break;
240
                case '=':
241
                default:
242
                    $totalPrice += $totalUnits === $rate['amount'] ? ((($rate['percentage'] * $bookable->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 138 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
243
                    break;
244
            }
245
        }
246
247
        $priceEquation = [
248
            'price' => $bookable->price,
249
            'unit' => $bookable->unit,
250
            'currency' => $bookable->currency,
251
            'total_units' => $totalUnits,
252
            'total_price' => $totalPrice,
253
            'prices' => $prices,
254
            'rates' => $rates,
255
        ];
256
257
        return [$totalPrice, $priceEquation, $bookable->currency];
258
    }
259
260
    /**
261
     * Get the owning resource model.
262
     *
263
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
264
     */
265
    public function bookable(): MorphTo
266
    {
267
        return $this->morphTo();
268
    }
269
270
    /**
271
     * Get the owning user.
272
     *
273
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
274
     */
275
    public function user(): MorphTo
276
    {
277
        return $this->morphTo();
278
    }
279
280
    /**
281
     * Get bookings of the given resource.
282
     *
283
     * @param \Illuminate\Database\Eloquent\Builder $builder
284
     * @param \Illuminate\Database\Eloquent\Model   $bookable
285
     *
286
     * @return \Illuminate\Database\Eloquent\Builder
287
     */
288
    public function scopeOfBookable(Builder $builder, Model $bookable): Builder
289
    {
290
        return $builder->where('bookable_type', $bookable->getMorphClass())->where('bookable_id', $bookable->getKey());
291
    }
292
293
    /**
294
     * Get bookings of the given user.
295
     *
296
     * @param \Illuminate\Database\Eloquent\Builder $builder
297
     * @param \Illuminate\Database\Eloquent\Model   $user
298
     *
299
     * @return \Illuminate\Database\Eloquent\Builder
300
     */
301
    public function scopeOfUser(Builder $builder, Model $user): Builder
302
    {
303
        return $builder->where('user_type', $user->getMorphClass())->where('user_id', $user->getKey());
304
    }
305
306
    /**
307
     * Get the past bookings.
308
     *
309
     * @param \Illuminate\Database\Eloquent\Builder $builder
310
     *
311
     * @return \Illuminate\Database\Eloquent\Builder
312
     */
313
    public function scopePast(Builder $builder): Builder
314
    {
315
        return $builder->whereNull('cancelled_at')
316
                       ->whereNotNull('ends_at')
317
                       ->where('ends_at', '<', now());
318
    }
319
320
    /**
321
     * Get the future bookings.
322
     *
323
     * @param \Illuminate\Database\Eloquent\Builder $builder
324
     *
325
     * @return \Illuminate\Database\Eloquent\Builder
326
     */
327
    public function scopeFuture(Builder $builder): Builder
328
    {
329
        return $builder->whereNull('cancelled_at')
330
                       ->whereNotNull('starts_at')
331
                       ->where('starts_at', '>', now());
332
    }
333
334
    /**
335
     * Get the current bookings.
336
     *
337
     * @param \Illuminate\Database\Eloquent\Builder $builder
338
     *
339
     * @return \Illuminate\Database\Eloquent\Builder
340
     */
341
    public function scopeCurrent(Builder $builder): Builder
342
    {
343
        return $builder->whereNull('cancelled_at')
344
                       ->whereNotNull('starts_at')
345
                       ->whereNotNull('ends_at')
346
                       ->where('starts_at', '<', now())
347
                       ->where('ends_at', '>', now());
348
    }
349
350
    /**
351
     * Get the cancelled bookings.
352
     *
353
     * @param \Illuminate\Database\Eloquent\Builder $builder
354
     *
355
     * @return \Illuminate\Database\Eloquent\Builder
356
     */
357
    public function scopeCancelled(Builder $builder): Builder
358
    {
359
        return $builder->whereNotNull('cancelled_at');
360
    }
361
362
    /**
363
     * Get bookings starts before the given date.
364
     *
365
     * @param \Illuminate\Database\Eloquent\Builder $builder
366
     * @param string                                $date
367
     *
368
     * @return \Illuminate\Database\Eloquent\Builder
369
     */
370
    public function scopeStartsBefore(Builder $builder, string $date): Builder
371
    {
372
        return $builder->whereNull('cancelled_at')
373
                       ->whereNotNull('starts_at')
374
                       ->where('starts_at', '<', new Carbon($date));
375
    }
376
377
    /**
378
     * Get bookings starts after the given date.
379
     *
380
     * @param \Illuminate\Database\Eloquent\Builder $builder
381
     * @param string                                $date
382
     *
383
     * @return \Illuminate\Database\Eloquent\Builder
384
     */
385
    public function scopeStartsAfter(Builder $builder, string $date): Builder
386
    {
387
        return $builder->whereNull('cancelled_at')
388
                       ->whereNotNull('starts_at')
389
                       ->where('starts_at', '>', new Carbon($date));
390
    }
391
392
    /**
393
     * Get bookings starts between the given dates.
394
     *
395
     * @param \Illuminate\Database\Eloquent\Builder $builder
396
     * @param string                                $startsAt
397
     * @param string                                $endsAt
398
     *
399
     * @return \Illuminate\Database\Eloquent\Builder
400
     */
401
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
402
    {
403
        return $builder->whereNull('cancelled_at')
404
                       ->whereNotNull('starts_at')
405
                       ->where('starts_at', '>=', new Carbon($startsAt))
406
                       ->where('starts_at', '<=', new Carbon($endsAt));
407
    }
408
409
    /**
410
     * Get bookings ends before the given date.
411
     *
412
     * @param \Illuminate\Database\Eloquent\Builder $builder
413
     * @param string                                $date
414
     *
415
     * @return \Illuminate\Database\Eloquent\Builder
416
     */
417
    public function scopeEndsBefore(Builder $builder, string $date): Builder
418
    {
419
        return $builder->whereNull('cancelled_at')
420
                       ->whereNotNull('ends_at')
421
                       ->where('ends_at', '<', new Carbon($date));
422
    }
423
424
    /**
425
     * Get bookings ends after the given date.
426
     *
427
     * @param \Illuminate\Database\Eloquent\Builder $builder
428
     * @param string                                $date
429
     *
430
     * @return \Illuminate\Database\Eloquent\Builder
431
     */
432
    public function scopeEndsAfter(Builder $builder, string $date): Builder
433
    {
434
        return $builder->whereNull('cancelled_at')
435
                       ->whereNotNull('ends_at')
436
                       ->where('ends_at', '>', new Carbon($date));
437
    }
438
439
    /**
440
     * Get bookings ends between the given dates.
441
     *
442
     * @param \Illuminate\Database\Eloquent\Builder $builder
443
     * @param string                                $startsAt
444
     * @param string                                $endsAt
445
     *
446
     * @return \Illuminate\Database\Eloquent\Builder
447
     */
448
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
449
    {
450
        return $builder->whereNull('cancelled_at')
451
                       ->whereNotNull('ends_at')
452
                       ->where('ends_at', '>=', new Carbon($startsAt))
453
                       ->where('ends_at', '<=', new Carbon($endsAt));
454
    }
455
456
    /**
457
     * Get bookings cancelled before the given date.
458
     *
459
     * @param \Illuminate\Database\Eloquent\Builder $builder
460
     * @param string                                $date
461
     *
462
     * @return \Illuminate\Database\Eloquent\Builder
463
     */
464
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
465
    {
466
        return $builder->whereNotNull('cancelled_at')
467
                       ->where('cancelled_at', '<', new Carbon($date));
468
    }
469
470
    /**
471
     * Get bookings cancelled after the given date.
472
     *
473
     * @param \Illuminate\Database\Eloquent\Builder $builder
474
     * @param string                                $date
475
     *
476
     * @return \Illuminate\Database\Eloquent\Builder
477
     */
478
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
479
    {
480
        return $builder->whereNotNull('cancelled_at')
481
                       ->where('cancelled_at', '>', new Carbon($date));
482
    }
483
484
    /**
485
     * Get bookings cancelled between the given dates.
486
     *
487
     * @param \Illuminate\Database\Eloquent\Builder $builder
488
     * @param string                                $startsAt
489
     * @param string                                $endsAt
490
     *
491
     * @return \Illuminate\Database\Eloquent\Builder
492
     */
493
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
494
    {
495
        return $builder->whereNotNull('cancelled_at')
496
                       ->where('cancelled_at', '>=', new Carbon($startsAt))
497
                       ->where('cancelled_at', '<=', new Carbon($endsAt));
498
    }
499
500
    /**
501
     * Get bookings between the given dates.
502
     *
503
     * @param \Illuminate\Database\Eloquent\Builder $builder
504
     * @param string                                $startsAt
505
     * @param string                                $endsAt
506
     *
507
     * @return \Illuminate\Database\Eloquent\Builder
508
     */
509
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
510
    {
511
        return $builder->whereNull('cancelled_at')
512
                       ->whereNotNull('starts_at')
513
                       ->where('starts_at', '>=', new Carbon($startsAt))
514
                       ->where(function (Builder $builder) use ($endsAt) {
515
                           $builder->whereNull('ends_at')
516
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
517
                                     $builder->whereNotNull('ends_at')
518
                                           ->where('ends_at', '<=', new Carbon($endsAt));
519
                                 });
520
                       });
521
    }
522
523
    /**
524
     * Check if the booking is cancelled.
525
     *
526
     * @return bool
527
     */
528
    public function isCancelled(): bool
529
    {
530
        return (bool) $this->cancelled_at;
531
    }
532
533
    /**
534
     * Check if the booking is past.
535
     *
536
     * @return bool
537
     */
538
    public function isPast(): bool
539
    {
540
        return ! $this->isCancelled() && $this->ends_at->isPast();
541
    }
542
543
    /**
544
     * Check if the booking is future.
545
     *
546
     * @return bool
547
     */
548
    public function isFuture(): bool
549
    {
550
        return ! $this->isCancelled() && $this->starts_at->isFuture();
551
    }
552
553
    /**
554
     * Check if the booking is current.
555
     *
556
     * @return bool
557
     */
558
    public function isCurrent(): bool
559
    {
560
        return ! $this->isCancelled() && now()->between($this->starts_at, $this->ends_at);
561
    }
562
}
563