Completed
Push — develop ( aee9c6...0706d0 )
by Abdelrahman
04:46
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($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...
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($startsAt, $endsAt)
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 range()
50
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsAfter($date)
51
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking startsBefore($date)
52
 * @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...
53
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCancelledAt($value)
54
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCreatedAt($value)
55
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCurrency($value)
56
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCustomerId($value)
57
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereCustomerType($value)
58
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereEndsAt($value)
59
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereId($value)
60
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereNotes($value)
61
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePrice($value)
62
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking wherePriceEquation($value)
63
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereResourceId($value)
64
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereResourceType($value)
65
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereStartsAt($value)
66
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereUpdatedAt($value)
67
 * @mixin \Eloquent
68
 */
69
class Booking extends Model implements BookingContract
70
{
71
    use ValidatingTrait;
72
    use CacheableEloquent;
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    protected $fillable = [
78
        'resource_id',
79
        'resource_type',
80
        'customer_id',
81
        'customer_type',
82
        'starts_at',
83
        'ends_at',
84
        'price',
85
        'currency',
86
        'price_equation',
87
        'cancelled_at',
88
        'notes',
89
    ];
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    protected $casts = [
95
        'resource_id' => 'integer',
96
        'resource_type' => 'string',
97
        'customer_id' => 'integer',
98
        'customer_type' => 'string',
99
        'starts_at' => 'datetime',
100
        'ends_at' => 'datetime',
101
        'price' => 'float',
102
        'currency' => 'string',
103
        'price_equation' => 'json',
104
        'cancelled_at' => 'datetime',
105
        'notes' => 'string',
106
    ];
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    protected $observables = [
112
        'validating',
113
        'validated',
114
    ];
115
116
    /**
117
     * The default rules that the model will validate against.
118
     *
119
     * @var array
120
     */
121
    protected $rules = [];
122
123
    /**
124
     * Whether the model should throw a
125
     * ValidationException if it fails validation.
126
     *
127
     * @var bool
128
     */
129
    protected $throwValidationExceptions = true;
130
131
    /**
132
     * Create a new Eloquent model instance.
133
     *
134
     * @param array $attributes
135
     */
136
    public function __construct(array $attributes = [])
137
    {
138
        parent::__construct($attributes);
139
140
        $this->setTable(config('rinvex.bookings.tables.bookings'));
141
        $this->setRules([
142
            'resource_id' => 'required|integer',
143
            'resource_type' => 'required|string',
144
            'customer_id' => 'required|integer',
145
            'customer_type' => 'required|string',
146
            'starts_at' => 'nullable|date',
147
            'ends_at' => 'nullable|date',
148
            'price' => 'required|numeric',
149
            'currency' => 'required|alpha|size:3',
150
            'price_equation' => 'nullable|array',
151
            'cancelled_at' => 'nullable|date',
152
            'notes' => 'nullable|string|max:10000',
153
        ]);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    protected static function boot()
160
    {
161
        parent::boot();
162
163
        static::validating(function (self $booking) {
164
            list($price, $priceEquation, $currency) = is_null($booking->price)
165
                ? $booking->calculatePrice() : [$booking->price, $booking->price_equation, $booking->currency];
166
167
            $booking->price_equation = $priceEquation;
168
            $booking->currency = $currency;
169
            $booking->price = $price;
170
        });
171
    }
172
173
    /**
174
     * Calculate the booking price.
175
     *
176
     * @return array
177
     */
178
    protected function calculatePrice(): array
179
    {
180
        switch ($this->resource->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 = $this->resource->prices->map(function (PriceContract $price) {
194
            return [
195
                '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...
196
                '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...
197
                '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...
198
                '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...
199
            ];
200
        });
201
202
        $totalUnits = 0;
203
        $totalPrice = 0;
204
205
        for ($date = $this->starts_at; $date->lte($this->ends_at); $date->$method()) {
206
            // Count units
207
            $totalUnits++;
208
209
            // Get applicable custom prices. Use first custom price matched, and ignore
210
            // others. We should not have multiple custom prices for same time range anyway!
211
            $customPrice = $prices->search(function ($price) use ($date) {
212
                $dayMatched = $price['weekday'] === mb_strtolower($date->format('D'));
213
214
                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...
215
            });
216
217
            // Use custom price if exists (custom price is a +/- percentage of original resource price)
218
            $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...
219
        }
220
221
        $rates = $this->resource->rates->map(function (RateContract $rate) {
222
            return [
223
                '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...
224
                '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...
225
                '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...
226
            ];
227
        })->toArray();
228
229
        foreach ($rates as $rate) {
230
            switch ($rate['operator']) {
231
                case '^':
232
                    $units = $totalUnits <= $rate['amount'] ? $totalUnits : $rate['amount'];
233
                    $totalPrice += (($rate['percentage'] * $this->resource->price) / 100) * $units;
234
                    break;
235
                case '>':
236
                    $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...
237
                    break;
238
                case '<':
239
                    $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...
240
                    break;
241
                case '=':
242
                default:
243
                    $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...
244
                    break;
245
            }
246
        }
247
248
        $priceEquation = [
249
            'price' => $this->resource->price,
250
            'unit' => $this->resource->unit,
251
            'currency' => $this->resource->currency,
252
            'total_units' => $totalUnits,
253
            'total_price' => $totalPrice,
254
            'prices' => $prices,
255
            'rates' => $rates,
256
        ];
257
258
        return [$totalPrice, $priceEquation, $this->resource->currency];
259
    }
260
261
    /**
262
     * Get the owning resource model.
263
     *
264
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
265
     */
266
    public function resource(): MorphTo
267
    {
268
        return $this->morphTo();
269
    }
270
271
    /**
272
     * Get the owning customer.
273
     *
274
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
275
     */
276
    public function customer(): MorphTo
277
    {
278
        return $this->morphTo();
279
    }
280
281
    /**
282
     * Get bookings of the given resource.
283
     *
284
     * @param \Illuminate\Database\Eloquent\Builder $builder
285
     * @param \Illuminate\Database\Eloquent\Model   $resource
286
     *
287
     * @return \Illuminate\Database\Eloquent\Builder
288
     */
289
    public function scopeOfResource(Builder $builder, Model $resource): Builder
290
    {
291
        return $builder->where('resource_type', $resource->getMorphClass())->where('resource_id', $resource->getKey());
292
    }
293
294
    /**
295
     * Get bookings of the given customer.
296
     *
297
     * @param \Illuminate\Database\Eloquent\Builder $builder
298
     * @param \Illuminate\Database\Eloquent\Model   $customer
299
     *
300
     * @return \Illuminate\Database\Eloquent\Builder
301
     */
302
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
303
    {
304
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->getKey());
305
    }
306
307
    /**
308
     * Get the past bookings.
309
     *
310
     * @param \Illuminate\Database\Eloquent\Builder $builder
311
     *
312
     * @return \Illuminate\Database\Eloquent\Builder
313
     */
314
    public function scopePast(Builder $builder): Builder
315
    {
316
        return $builder->whereNull('cancelled_at')
317
                       ->whereNotNull('ends_at')
318
                       ->where('ends_at', '<', Carbon::now());
319
    }
320
321
    /**
322
     * Get the future bookings.
323
     *
324
     * @param \Illuminate\Database\Eloquent\Builder $builder
325
     *
326
     * @return \Illuminate\Database\Eloquent\Builder
327
     */
328
    public function scopeFuture(Builder $builder): Builder
329
    {
330
        return $builder->whereNull('cancelled_at')
331
                       ->whereNotNull('starts_at')
332
                       ->where('starts_at', '>', Carbon::now());
333
    }
334
335
    /**
336
     * Get the current bookings.
337
     *
338
     * @param \Illuminate\Database\Eloquent\Builder $builder
339
     *
340
     * @return \Illuminate\Database\Eloquent\Builder
341
     */
342
    public function scopeCurrent(Builder $builder): Builder
343
    {
344
        return $builder->whereNull('cancelled_at')
345
                       ->whereNotNull('starts_at')
346
                       ->whereNotNull('ends_at')
347
                       ->where('starts_at', '<', Carbon::now())
348
                       ->where('ends_at', '>', Carbon::now());
349
    }
350
351
    /**
352
     * Get the cancelled bookings.
353
     *
354
     * @param \Illuminate\Database\Eloquent\Builder $builder
355
     *
356
     * @return \Illuminate\Database\Eloquent\Builder
357
     */
358
    public function scopeCancelled(Builder $builder): Builder
359
    {
360
        return $builder->whereNotNull('cancelled_at');
361
    }
362
363
    /**
364
     * Get bookings starts before the given date.
365
     *
366
     * @param \Illuminate\Database\Eloquent\Builder $builder
367
     * @param string                                $date
368
     *
369
     * @return \Illuminate\Database\Eloquent\Builder
370
     */
371
    public function scopeStartsBefore(Builder $builder, string $date): Builder
372
    {
373
        return $builder->whereNull('cancelled_at')
374
                       ->whereNotNull('starts_at')
375
                       ->where('starts_at', '<', new Carbon($date));
376
    }
377
378
    /**
379
     * Get bookings starts after the given date.
380
     *
381
     * @param \Illuminate\Database\Eloquent\Builder $builder
382
     * @param string                                $date
383
     *
384
     * @return \Illuminate\Database\Eloquent\Builder
385
     */
386
    public function scopeStartsAfter(Builder $builder, string $date): Builder
387
    {
388
        return $builder->whereNull('cancelled_at')
389
                       ->whereNotNull('starts_at')
390
                       ->where('starts_at', '>', new Carbon($date));
391
    }
392
393
    /**
394
     * Get bookings starts between the given dates.
395
     *
396
     * @param \Illuminate\Database\Eloquent\Builder $builder
397
     * @param string                                $startsAt
398
     * @param string                                $endsAt
399
     *
400
     * @return \Illuminate\Database\Eloquent\Builder
401
     */
402
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
403
    {
404
        return $builder->whereNull('cancelled_at')
405
                       ->whereNotNull('starts_at')
406
                       ->where('starts_at', '>=', new Carbon($startsAt))
407
                       ->where('starts_at', '<=', new Carbon($endsAt));
408
    }
409
410
    /**
411
     * Get bookings ends before the given date.
412
     *
413
     * @param \Illuminate\Database\Eloquent\Builder $builder
414
     * @param string                                $date
415
     *
416
     * @return \Illuminate\Database\Eloquent\Builder
417
     */
418
    public function scopeEndsBefore(Builder $builder, string $date): Builder
419
    {
420
        return $builder->whereNull('cancelled_at')
421
                       ->whereNotNull('ends_at')
422
                       ->where('ends_at', '<', new Carbon($date));
423
    }
424
425
    /**
426
     * Get bookings ends after the given date.
427
     *
428
     * @param \Illuminate\Database\Eloquent\Builder $builder
429
     * @param string                                $date
430
     *
431
     * @return \Illuminate\Database\Eloquent\Builder
432
     */
433
    public function scopeEndsAfter(Builder $builder, string $date): Builder
434
    {
435
        return $builder->whereNull('cancelled_at')
436
                       ->whereNotNull('ends_at')
437
                       ->where('ends_at', '>', new Carbon($date));
438
    }
439
440
    /**
441
     * Get bookings ends between the given dates.
442
     *
443
     * @param \Illuminate\Database\Eloquent\Builder $builder
444
     * @param string                                $startsAt
445
     * @param string                                $endsAt
446
     *
447
     * @return \Illuminate\Database\Eloquent\Builder
448
     */
449
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
450
    {
451
        return $builder->whereNull('cancelled_at')
452
                       ->whereNotNull('ends_at')
453
                       ->where('ends_at', '>=', new Carbon($startsAt))
454
                       ->where('ends_at', '<=', new Carbon($endsAt));
455
    }
456
457
    /**
458
     * Get bookings cancelled before the given date.
459
     *
460
     * @param \Illuminate\Database\Eloquent\Builder $builder
461
     * @param string                                $date
462
     *
463
     * @return \Illuminate\Database\Eloquent\Builder
464
     */
465
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
466
    {
467
        return $builder->whereNotNull('cancelled_at')
468
                       ->where('cancelled_at', '<', new Carbon($date));
469
    }
470
471
    /**
472
     * Get bookings cancelled after the given date.
473
     *
474
     * @param \Illuminate\Database\Eloquent\Builder $builder
475
     * @param string                                $date
476
     *
477
     * @return \Illuminate\Database\Eloquent\Builder
478
     */
479
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
480
    {
481
        return $builder->whereNotNull('cancelled_at')
482
                       ->where('cancelled_at', '>', new Carbon($date));
483
    }
484
485
    /**
486
     * Get bookings cancelled between the given dates.
487
     *
488
     * @param \Illuminate\Database\Eloquent\Builder $builder
489
     * @param string                                $startsAt
490
     * @param string                                $endsAt
491
     *
492
     * @return \Illuminate\Database\Eloquent\Builder
493
     */
494
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
495
    {
496
        return $builder->whereNotNull('cancelled_at')
497
                       ->where('cancelled_at', '>=', new Carbon($startsAt))
498
                       ->where('cancelled_at', '<=', new Carbon($endsAt));
499
    }
500
501
    /**
502
     * Get bookings between the given dates.
503
     *
504
     * @param \Illuminate\Database\Eloquent\Builder $builder
505
     * @param string                                $startsAt
506
     * @param string                                $endsAt
507
     *
508
     * @return \Illuminate\Database\Eloquent\Builder
509
     */
510
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
511
    {
512
        return $builder->whereNull('cancelled_at')
513
                       ->whereNotNull('starts_at')
514
                       ->where('starts_at', '>=', new Carbon($startsAt))
515
                       ->where(function (Builder $builder) use ($endsAt) {
516
                           $builder->whereNull('ends_at')
517
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
518
                                     $builder->whereNotNull('ends_at')
519
                                           ->where('ends_at', '<=', new Carbon($endsAt));
520
                                 });
521
                       });
522
    }
523
524
    /**
525
     * Check if the booking is cancelled.
526
     *
527
     * @return bool
528
     */
529
    public function isCancelled(): bool
530
    {
531
        return (bool) $this->cancelled_at;
532
    }
533
534
    /**
535
     * Check if the booking is past.
536
     *
537
     * @return bool
538
     */
539
    public function isPast(): bool
540
    {
541
        return ! $this->isCancelled() && $this->ends_at->isPast();
542
    }
543
544
    /**
545
     * Check if the booking is future.
546
     *
547
     * @return bool
548
     */
549
    public function isFuture(): bool
550
    {
551
        return ! $this->isCancelled() && $this->starts_at->isFuture();
552
    }
553
554
    /**
555
     * Check if the booking is current.
556
     *
557
     * @return bool
558
     */
559
    public function isCurrent(): bool
560
    {
561
        return ! $this->isCancelled() && Carbon::now()->between($this->starts_at, $this->ends_at);
562
    }
563
}
564