Completed
Push — develop ( 2cd7f1...aee9c6 )
by Abdelrahman
01:11
created

Booking::scopeBetween()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 3
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 Rinvex\Bookings\Contracts\RateContract;
13
use Rinvex\Bookings\Contracts\PriceContract;
14
use Rinvex\Bookings\Contracts\BookingContract;
15
use Illuminate\Database\Eloquent\Relations\MorphTo;
16
17
/**
18
 * Rinvex\Bookings\Models\Booking.
19
 *
20
 * @property int                                                $id
21
 * @property int                                                $resource_id
22
 * @property string                                             $resource_type
23
 * @property string                                             $currency
24
 * @property int                                                $customer_id
25
 * @property string                                             $customer_type
26
 * @property \Carbon\Carbon                                     $starts_at
27
 * @property \Carbon\Carbon                                     $ends_at
28
 * @property float                                              $price
29
 * @property array                                              $price_equation
30
 * @property \Carbon\Carbon                                     $cancelled_at
31
 * @property string                                             $notes
32
 * @property \Carbon\Carbon|null                                $created_at
33
 * @property \Carbon\Carbon|null                                $updated_at
34
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $resource
35
 * @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $customer
36
 *
37
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelled()
38
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledAfter($date)
39
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledBefore($date)
40
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking cancelledBetween($starts, $ends)
41
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking current()
42
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsAfter($date)
43
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsBefore($date)
44
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking endsBetween($starts, $ends)
45
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking future()
46
 * @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...
47
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking ofResource(\Illuminate\Database\Eloquent\Model $resource)
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...
48
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking past()
49
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsAfter($date)
50
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsBefore($date)
51
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsBetween($starts, $ends)
52
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCancelledAt($value)
53
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCreatedAt($value)
54
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCurrency($value)
55
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCustomerId($value)
56
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCustomerType($value)
57
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereEndsAt($value)
58
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereId($value)
59
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereNotes($value)
60
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePrice($value)
61
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePriceEquation($value)
62
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereResourceId($value)
63
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereResourceType($value)
64
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereStartsAt($value)
65
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereUpdatedAt($value)
66
 * @mixin \Eloquent
67
 */
68
class Booking extends Model implements BookingContract
69
{
70
    use ValidatingTrait;
71
    use CacheableEloquent;
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    protected $fillable = [
77
        'resource_id',
78
        'resource_type',
79
        'customer_id',
80
        'customer_type',
81
        'starts_at',
82
        'ends_at',
83
        'price',
84
        'currency',
85
        'price_equation',
86
        'cancelled_at',
87
        'notes',
88
    ];
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    protected $casts = [
94
        'resource_id' => 'integer',
95
        'resource_type' => 'string',
96
        'customer_id' => 'integer',
97
        'customer_type' => 'string',
98
        'starts_at' => 'datetime',
99
        'ends_at' => 'datetime',
100
        'price' => 'float',
101
        'currency' => 'string',
102
        'price_equation' => 'json',
103
        'cancelled_at' => 'datetime',
104
        'notes' => 'string',
105
    ];
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    protected $observables = [
111
        'validating',
112
        'validated',
113
    ];
114
115
    /**
116
     * The default rules that the model will validate against.
117
     *
118
     * @var array
119
     */
120
    protected $rules = [];
121
122
    /**
123
     * Whether the model should throw a
124
     * ValidationException if it fails validation.
125
     *
126
     * @var bool
127
     */
128
    protected $throwValidationExceptions = true;
129
130
    /**
131
     * Create a new Eloquent model instance.
132
     *
133
     * @param array $attributes
134
     */
135
    public function __construct(array $attributes = [])
136
    {
137
        parent::__construct($attributes);
138
139
        $this->setTable(config('rinvex.bookings.tables.bookings'));
140
        $this->setRules([
141
            'resource_id' => 'required|integer',
142
            'resource_type' => 'required|string',
143
            'customer_id' => 'required|integer',
144
            'customer_type' => 'required|string',
145
            'starts_at' => 'nullable|date',
146
            'ends_at' => 'nullable|date',
147
            'price' => 'required|numeric',
148
            'currency' => 'required|alpha|size:3',
149
            'price_equation' => 'nullable|array',
150
            'cancelled_at' => 'nullable|date',
151
            'notes' => 'nullable|string|max:10000',
152
        ]);
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158
    protected static function boot()
159
    {
160
        parent::boot();
161
162
        static::validating(function (self $booking) {
163
            list($price, $priceEquation, $currency) = is_null($booking->price)
164
                ? $booking->calculatePrice() : [$booking->price, $booking->price_equation, $booking->currency];
165
166
            $booking->price_equation = $priceEquation;
167
            $booking->currency = $currency;
168
            $booking->price = $price;
169
        });
170
    }
171
172
    /**
173
     * Calculate the booking price.
174
     *
175
     * @return array
176
     */
177
    protected function calculatePrice(): array
178
    {
179
        switch ($this->resource->unit) {
180
            case 'd':
181
                $method = 'addDay';
182
                break;
183
            case 'm':
184
                $method = 'addMinute';
185
                break;
186
            case 'h':
187
            default:
188
                $method = 'addHour';
189
                break;
190
        }
191
192
        $prices = $this->resource->prices->map(function (PriceContract $price) {
193
            return [
194
                'weekday' => $price->weekday,
0 ignored issues
show
Bug introduced by
Accessing weekday on the interface Rinvex\Bookings\Contracts\PriceContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
195
                'starts_at' => $price->starts_at,
0 ignored issues
show
Bug introduced by
Accessing starts_at on the interface Rinvex\Bookings\Contracts\PriceContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
196
                'ends_at' => $price->ends_at,
0 ignored issues
show
Bug introduced by
Accessing ends_at on the interface Rinvex\Bookings\Contracts\PriceContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
197
                'percentage' => $price->percentage,
0 ignored issues
show
Bug introduced by
Accessing percentage on the interface Rinvex\Bookings\Contracts\PriceContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
198
            ];
199
        });
200
201
        $totalUnits = 0;
202
        $totalPrice = 0;
203
204
        for ($date = $this->starts_at; $date->lte($this->ends_at); $date->$method()) {
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) {
211
                $dayMatched = $price['weekday'] === mb_strtolower($date->format('D'));
212
213
                return $this->resource->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 194 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...
214
            });
215
216
            // Use custom price if exists (custom price is a +/- percentage of original resource price)
217
            $totalPrice += $customPrice !== false ? $this->resource->price + (($this->resource->price * $prices[$customPrice]['percentage']) / 100) : $this->resource->price;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 173 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 = $this->resource->rates->map(function (RateContract $rate) {
221
            return [
222
                'percentage' => $rate->percentage,
0 ignored issues
show
Bug introduced by
Accessing percentage on the interface Rinvex\Bookings\Contracts\RateContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
223
                'operator' => $rate->operator,
0 ignored issues
show
Bug introduced by
Accessing operator on the interface Rinvex\Bookings\Contracts\RateContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
224
                'amount' => $rate->amount,
0 ignored issues
show
Bug introduced by
Accessing amount on the interface Rinvex\Bookings\Contracts\RateContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
225
            ];
226
        })->toArray();
227
228
        foreach ($rates as $rate) {
229
            switch ($rate['operator']) {
230
                case '^':
231
                    $units = $totalUnits <= $rate['amount'] ? $totalUnits : $rate['amount'];
232
                    $totalPrice += (($rate['percentage'] * $this->resource->price) / 100) * $units;
233
                    break;
234
                case '>':
235
                    $totalPrice += $totalUnits > $rate['amount'] ? ((($rate['percentage'] * $this->resource->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 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'] * $this->resource->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 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'] * $this->resource->price) / 100) * $totalUnits) : 0;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 144 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' => $this->resource->price,
249
            'unit' => $this->resource->unit,
250
            'currency' => $this->resource->currency,
251
            'total_units' => $totalUnits,
252
            'total_price' => $totalPrice,
253
            'prices' => $prices,
254
            'rates' => $rates,
255
        ];
256
257
        return [$totalPrice, $priceEquation, $this->resource->currency];
258
    }
259
260
    /**
261
     * Get the owning resource model.
262
     *
263
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
264
     */
265
    public function resource(): MorphTo
266
    {
267
        return $this->morphTo();
268
    }
269
270
    /**
271
     * Get the owning customer.
272
     *
273
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
274
     */
275
    public function customer(): 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   $resource
285
     *
286
     * @return \Illuminate\Database\Eloquent\Builder
287
     */
288
    public function scopeOfResource(Builder $builder, Model $resource): Builder
289
    {
290
        return $builder->where('resource_type', $resource->getMorphClass())->where('resource_id', $resource->getKey());
291
    }
292
293
    /**
294
     * Get bookings of the given customer.
295
     *
296
     * @param \Illuminate\Database\Eloquent\Builder $builder
297
     * @param \Illuminate\Database\Eloquent\Model   $customer
298
     *
299
     * @return \Illuminate\Database\Eloquent\Builder
300
     */
301
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
302
    {
303
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->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', '<', Carbon::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', '>', Carbon::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', '<', Carbon::now())
347
                       ->where('ends_at', '>', Carbon::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                                $starts
397
     * @param string                                $ends
398
     *
399
     * @return \Illuminate\Database\Eloquent\Builder
400
     */
401
    public function scopeStartsBetween(Builder $builder, string $starts, string $ends): Builder
402
    {
403
        return $builder->whereNull('cancelled_at')
404
                       ->whereNotNull('starts_at')
405
                       ->where('starts_at', '>=', new Carbon($starts))
406
                       ->where('starts_at', '<=', new Carbon($ends));
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                                $starts
444
     * @param string                                $ends
445
     *
446
     * @return \Illuminate\Database\Eloquent\Builder
447
     */
448
    public function scopeEndsBetween(Builder $builder, string $starts, string $ends): Builder
449
    {
450
        return $builder->whereNull('cancelled_at')
451
                       ->whereNotNull('ends_at')
452
                       ->where('ends_at', '>=', new Carbon($starts))
453
                       ->where('ends_at', '<=', new Carbon($ends));
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                                $starts
489
     * @param string                                $ends
490
     *
491
     * @return \Illuminate\Database\Eloquent\Builder
492
     */
493
    public function scopeCancelledBetween(Builder $builder, string $starts, string $ends): Builder
494
    {
495
        return $builder->whereNotNull('cancelled_at')
496
                       ->where('cancelled_at', '>=', new Carbon($starts))
497
                       ->where('cancelled_at', '<=', new Carbon($ends));
498
    }
499
500
    /**
501
     * Get bookings between the given dates.
502
     *
503
     * @param \Illuminate\Database\Eloquent\Builder $builder
504
     * @param string                                $starts
505
     * @param string                                $ends
506
     *
507
     * @return \Illuminate\Database\Eloquent\Builder
508
     */
509
    public function scopeBetween(Builder $builder, string $starts, string $ends): Builder
510
    {
511
        return $builder->whereNull('cancelled_at')
512
                       ->whereNotNull('starts_at')
513
                       ->whereNotNull('ends_at')
514
                       ->where('starts_at', '>=', new Carbon($starts))
515
                       ->where('ends_at', '<=', new Carbon($ends));
516
    }
517
518
    /**
519
     * Check if the booking is cancelled.
520
     *
521
     * @return bool
522
     */
523
    public function isCancelled(): bool
524
    {
525
        return (bool) $this->cancelled_at;
526
    }
527
528
    /**
529
     * Check if the booking is past.
530
     *
531
     * @return bool
532
     */
533
    public function isPast(): bool
534
    {
535
        return ! $this->isCancelled() && $this->ends_at->isPast();
536
    }
537
538
    /**
539
     * Check if the booking is future.
540
     *
541
     * @return bool
542
     */
543
    public function isFuture(): bool
544
    {
545
        return ! $this->isCancelled() && $this->starts_at->isFuture();
546
    }
547
548
    /**
549
     * Check if the booking is current.
550
     *
551
     * @return bool
552
     */
553
    public function isCurrent(): bool
554
    {
555
        return ! $this->isCancelled() && Carbon::now()->between($this->starts_at, $this->ends_at);
556
    }
557
}
558