Completed
Push — develop ( 52e8a2...fa8a7a )
by Abdelrahman
02:09
created

Booking::calculatePrice()   C

Complexity

Conditions 14
Paths 33

Size

Total Lines 70
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 70
rs 5.6188
c 0
b 0
f 0
cc 14
eloc 47
nc 33
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                                                $customer_id
22
 * @property string                                             $customer_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 $customer
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 ofCustomer(\Illuminate\Database\Eloquent\Model $customer)
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...
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 whereCustomerId($value)
54
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCustomerType($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
        'customer_id',
78
        'customer_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
        'customer_id' => 'integer',
95
        'customer_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
        'customer_id' => 'required|integer',
122
        'customer_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
        $prices = $bookable->prices->map(function (Price $price) {
181
            return [
182
                'weekday' => $price->weekday,
183
                'starts_at' => $price->starts_at,
184
                'ends_at' => $price->ends_at,
185
                'percentage' => $price->percentage,
186
            ];
187
        });
188
189
        $totalUnits = 0;
190
        $totalPrice = 0;
191
        $method = 'add'.ucfirst($bookable->unit).'s';
192
193
        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...
194
            // Count units
195
            $totalUnits++;
196
197
            // Get applicable custom prices. Use first custom price matched, and ignore
198
            // others. We should not have multiple custom prices for same time range anyway!
199
            $customPrice = $prices->search(function ($price) use ($date, $bookable) {
200
                $dayMatched = $price['weekday'] === mb_strtolower($date->format('D'));
201
202
                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
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...
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...
203
            });
204
205
            // Use custom price if exists (custom price is a +/- percentage of original resource price)
206
            $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...
207
        }
208
209
        $rates = $bookable->rates->map(function (Rate $rate) {
210
            return [
211
                'percentage' => $rate->percentage,
212
                'operator' => $rate->operator,
213
                'amount' => $rate->amount,
214
            ];
215
        })->toArray();
216
217
        foreach ($rates as $rate) {
218
            switch ($rate['operator']) {
219
                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...
220
                    $units = $totalUnits <= $rate['amount'] ? $totalUnits : $rate['amount'];
221
                    $totalPrice += (($rate['percentage'] * $bookable->price) / 100) * $units;
222
                    break;
223
                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...
224
                    $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...
225
                    break;
226
                case '<':
227
                    $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...
228
                    break;
229
                case '=':
230
                default:
231
                    $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...
232
                    break;
233
            }
234
        }
235
236
        $priceEquation = [
237
            'price' => $bookable->price,
238
            'unit' => $bookable->unit,
239
            'currency' => $bookable->currency,
240
            'total_units' => $totalUnits,
241
            'total_price' => $totalPrice,
242
            'prices' => $prices,
243
            'rates' => $rates,
244
        ];
245
246
        return [$totalPrice, $priceEquation, $bookable->currency];
247
    }
248
249
    /**
250
     * Get the owning resource model.
251
     *
252
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
253
     */
254
    public function bookable(): MorphTo
255
    {
256
        return $this->morphTo('bookable', 'bookable_type', 'bookable_id');
257
    }
258
259
    /**
260
     * Get the booking customer.
261
     *
262
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
263
     */
264
    public function customer(): MorphTo
265
    {
266
        return $this->morphTo('customer', 'customer_type', 'customer_id');
267
    }
268
269
    /**
270
     * Get bookings of the given resource.
271
     *
272
     * @param \Illuminate\Database\Eloquent\Builder $builder
273
     * @param \Illuminate\Database\Eloquent\Model   $bookable
274
     *
275
     * @return \Illuminate\Database\Eloquent\Builder
276
     */
277
    public function scopeOfBookable(Builder $builder, Model $bookable): Builder
278
    {
279
        return $builder->where('bookable_type', $bookable->getMorphClass())->where('bookable_id', $bookable->getKey());
280
    }
281
282
    /**
283
     * Get bookings of the given customer.
284
     *
285
     * @param \Illuminate\Database\Eloquent\Builder $builder
286
     * @param \Illuminate\Database\Eloquent\Model   $customer
287
     *
288
     * @return \Illuminate\Database\Eloquent\Builder
289
     */
290
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
291
    {
292
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->getKey());
293
    }
294
295
    /**
296
     * Get the past bookings.
297
     *
298
     * @param \Illuminate\Database\Eloquent\Builder $builder
299
     *
300
     * @return \Illuminate\Database\Eloquent\Builder
301
     */
302
    public function scopePast(Builder $builder): Builder
303
    {
304
        return $builder->whereNull('cancelled_at')
305
                       ->whereNotNull('ends_at')
306
                       ->where('ends_at', '<', now());
307
    }
308
309
    /**
310
     * Get the future bookings.
311
     *
312
     * @param \Illuminate\Database\Eloquent\Builder $builder
313
     *
314
     * @return \Illuminate\Database\Eloquent\Builder
315
     */
316
    public function scopeFuture(Builder $builder): Builder
317
    {
318
        return $builder->whereNull('cancelled_at')
319
                       ->whereNotNull('starts_at')
320
                       ->where('starts_at', '>', now());
321
    }
322
323
    /**
324
     * Get the current bookings.
325
     *
326
     * @param \Illuminate\Database\Eloquent\Builder $builder
327
     *
328
     * @return \Illuminate\Database\Eloquent\Builder
329
     */
330
    public function scopeCurrent(Builder $builder): Builder
331
    {
332
        return $builder->whereNull('cancelled_at')
333
                       ->whereNotNull('starts_at')
334
                       ->whereNotNull('ends_at')
335
                       ->where('starts_at', '<', now())
336
                       ->where('ends_at', '>', now());
337
    }
338
339
    /**
340
     * Get the cancelled bookings.
341
     *
342
     * @param \Illuminate\Database\Eloquent\Builder $builder
343
     *
344
     * @return \Illuminate\Database\Eloquent\Builder
345
     */
346
    public function scopeCancelled(Builder $builder): Builder
347
    {
348
        return $builder->whereNotNull('cancelled_at');
349
    }
350
351
    /**
352
     * Get bookings starts before the given date.
353
     *
354
     * @param \Illuminate\Database\Eloquent\Builder $builder
355
     * @param string                                $date
356
     *
357
     * @return \Illuminate\Database\Eloquent\Builder
358
     */
359
    public function scopeStartsBefore(Builder $builder, string $date): Builder
360
    {
361
        return $builder->whereNull('cancelled_at')
362
                       ->whereNotNull('starts_at')
363
                       ->where('starts_at', '<', new Carbon($date));
364
    }
365
366
    /**
367
     * Get bookings starts after the given date.
368
     *
369
     * @param \Illuminate\Database\Eloquent\Builder $builder
370
     * @param string                                $date
371
     *
372
     * @return \Illuminate\Database\Eloquent\Builder
373
     */
374
    public function scopeStartsAfter(Builder $builder, string $date): Builder
375
    {
376
        return $builder->whereNull('cancelled_at')
377
                       ->whereNotNull('starts_at')
378
                       ->where('starts_at', '>', new Carbon($date));
379
    }
380
381
    /**
382
     * Get bookings starts between the given dates.
383
     *
384
     * @param \Illuminate\Database\Eloquent\Builder $builder
385
     * @param string                                $startsAt
386
     * @param string                                $endsAt
387
     *
388
     * @return \Illuminate\Database\Eloquent\Builder
389
     */
390
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
391
    {
392
        return $builder->whereNull('cancelled_at')
393
                       ->whereNotNull('starts_at')
394
                       ->where('starts_at', '>=', new Carbon($startsAt))
395
                       ->where('starts_at', '<=', new Carbon($endsAt));
396
    }
397
398
    /**
399
     * Get bookings ends before the given date.
400
     *
401
     * @param \Illuminate\Database\Eloquent\Builder $builder
402
     * @param string                                $date
403
     *
404
     * @return \Illuminate\Database\Eloquent\Builder
405
     */
406
    public function scopeEndsBefore(Builder $builder, string $date): Builder
407
    {
408
        return $builder->whereNull('cancelled_at')
409
                       ->whereNotNull('ends_at')
410
                       ->where('ends_at', '<', new Carbon($date));
411
    }
412
413
    /**
414
     * Get bookings ends after the given date.
415
     *
416
     * @param \Illuminate\Database\Eloquent\Builder $builder
417
     * @param string                                $date
418
     *
419
     * @return \Illuminate\Database\Eloquent\Builder
420
     */
421
    public function scopeEndsAfter(Builder $builder, string $date): Builder
422
    {
423
        return $builder->whereNull('cancelled_at')
424
                       ->whereNotNull('ends_at')
425
                       ->where('ends_at', '>', new Carbon($date));
426
    }
427
428
    /**
429
     * Get bookings ends between the given dates.
430
     *
431
     * @param \Illuminate\Database\Eloquent\Builder $builder
432
     * @param string                                $startsAt
433
     * @param string                                $endsAt
434
     *
435
     * @return \Illuminate\Database\Eloquent\Builder
436
     */
437
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
438
    {
439
        return $builder->whereNull('cancelled_at')
440
                       ->whereNotNull('ends_at')
441
                       ->where('ends_at', '>=', new Carbon($startsAt))
442
                       ->where('ends_at', '<=', new Carbon($endsAt));
443
    }
444
445
    /**
446
     * Get bookings cancelled before the given date.
447
     *
448
     * @param \Illuminate\Database\Eloquent\Builder $builder
449
     * @param string                                $date
450
     *
451
     * @return \Illuminate\Database\Eloquent\Builder
452
     */
453
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
454
    {
455
        return $builder->whereNotNull('cancelled_at')
456
                       ->where('cancelled_at', '<', new Carbon($date));
457
    }
458
459
    /**
460
     * Get bookings cancelled after the given date.
461
     *
462
     * @param \Illuminate\Database\Eloquent\Builder $builder
463
     * @param string                                $date
464
     *
465
     * @return \Illuminate\Database\Eloquent\Builder
466
     */
467
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
468
    {
469
        return $builder->whereNotNull('cancelled_at')
470
                       ->where('cancelled_at', '>', new Carbon($date));
471
    }
472
473
    /**
474
     * Get bookings cancelled between the given dates.
475
     *
476
     * @param \Illuminate\Database\Eloquent\Builder $builder
477
     * @param string                                $startsAt
478
     * @param string                                $endsAt
479
     *
480
     * @return \Illuminate\Database\Eloquent\Builder
481
     */
482
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
483
    {
484
        return $builder->whereNotNull('cancelled_at')
485
                       ->where('cancelled_at', '>=', new Carbon($startsAt))
486
                       ->where('cancelled_at', '<=', new Carbon($endsAt));
487
    }
488
489
    /**
490
     * Get bookings between the given dates.
491
     *
492
     * @param \Illuminate\Database\Eloquent\Builder $builder
493
     * @param string                                $startsAt
494
     * @param string                                $endsAt
495
     *
496
     * @return \Illuminate\Database\Eloquent\Builder
497
     */
498
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
499
    {
500
        return $builder->whereNull('cancelled_at')
501
                       ->whereNotNull('starts_at')
502
                       ->where('starts_at', '>=', new Carbon($startsAt))
503
                       ->where(function (Builder $builder) use ($endsAt) {
504
                           $builder->whereNull('ends_at')
505
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
506
                                     $builder->whereNotNull('ends_at')
507
                                           ->where('ends_at', '<=', new Carbon($endsAt));
508
                                 });
509
                       });
510
    }
511
512
    /**
513
     * Check if the booking is cancelled.
514
     *
515
     * @return bool
516
     */
517
    public function isCancelled(): bool
518
    {
519
        return (bool) $this->cancelled_at;
520
    }
521
522
    /**
523
     * Check if the booking is past.
524
     *
525
     * @return bool
526
     */
527
    public function isPast(): bool
528
    {
529
        return ! $this->isCancelled() && $this->ends_at->isPast();
530
    }
531
532
    /**
533
     * Check if the booking is future.
534
     *
535
     * @return bool
536
     */
537
    public function isFuture(): bool
538
    {
539
        return ! $this->isCancelled() && $this->starts_at->isFuture();
540
    }
541
542
    /**
543
     * Check if the booking is current.
544
     *
545
     * @return bool
546
     */
547
    public function isCurrent(): bool
548
    {
549
        return ! $this->isCancelled() && now()->between($this->starts_at, $this->ends_at);
550
    }
551
}
552