Completed
Push — develop ( 55f672...f50907 )
by Abdelrahman
01:18
created

Booking::scopeOfResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
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                                                $bookable_id
22
 * @property string                                             $bookable_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 $bookable
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 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...
47
 * @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...
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 whereBookableId($value)
64
 * @method static \Illuminate\Database\Eloquent\Builder|\Rinvex\Bookings\Models\Booking whereBookableType($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
        'bookable_id',
79
        'bookable_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
        'bookable_id' => 'integer',
96
        'bookable_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
            'bookable_id' => 'required|integer',
143
            'bookable_type' => 'required|string',
144
            'customer_id' => 'required|integer',
145
            'customer_type' => 'required|string',
146
            'starts_at' => 'required|date',
147
            'ends_at' => 'required|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->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...
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
     * @param \Illuminate\Database\Eloquent\Model $bookable
177
     * @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...
178
     * @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...
179
     *
180
     * @return array
181
     */
182
    public static function calculatePrice(Model $bookable, Carbon $startsAt, Carbon $endsAt = null): array
183
    {
184
        switch ($bookable->unit) {
185
            case 'd':
186
                $method = 'addDay';
187
                break;
188
            case 'm':
189
                $method = 'addMinute';
190
                break;
191
            case 'h':
192
            default:
193
                $method = 'addHour';
194
                break;
195
        }
196
197
        $prices = $bookable->prices->map(function (PriceContract $price) {
198
            return [
199
                '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...
200
                '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...
201
                '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...
202
                '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...
203
            ];
204
        });
205
206
        $totalUnits = 0;
207
        $totalPrice = 0;
208
        for ($date = clone $startsAt; $date->lt($endsAt ?? $date->addDay()); $date->$method()) {
209
            // Count units
210
            $totalUnits++;
211
212
            // Get applicable custom prices. Use first custom price matched, and ignore
213
            // others. We should not have multiple custom prices for same time range anyway!
214
            $customPrice = $prices->search(function ($price) use ($date, $bookable) {
215
                $dayMatched = $price['weekday'] === mb_strtolower($date->format('D'));
216
217
                return $bookable->unit === 'd' ? $dayMatched : $dayMatched && (new Carbon($date->format('H:i:s')))->between(new Carbon($price['starts_at']), new Carbon($price['ends_at']));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 188 characters

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

Loading history...
218
            });
219
220
            // Use custom price if exists (custom price is a +/- percentage of original resource price)
221
            $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...
222
        }
223
224
        $rates = $bookable->rates->map(function (RateContract $rate) {
225
            return [
226
                '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...
227
                '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...
228
                '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...
229
            ];
230
        })->toArray();
231
232
        foreach ($rates as $rate) {
233
            switch ($rate['operator']) {
234
                case '^':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

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