Completed
Push — develop ( a4806d...898c2d )
by Abdelrahman
02:21
created

BookableBooking::scopeFuture()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Bookings\Models;
6
7
use Carbon\Carbon;
8
use Illuminate\Database\Eloquent\Model;
9
use Rinvex\Cacheable\CacheableEloquent;
10
use Illuminate\Database\Eloquent\Builder;
11
use Rinvex\Support\Traits\ValidatingTrait;
12
use Illuminate\Database\Eloquent\Relations\MorphTo;
13
14
abstract class BookableBooking extends Model
15
{
16
    use ValidatingTrait;
17
    use CacheableEloquent;
18
19
    /**
20
     * {@inheritdoc}
21
     */
22
    protected $fillable = [
23
        'bookable_id',
24
        'bookable_type',
25
        'customer_id',
26
        'customer_type',
27
        'starts_at',
28
        'ends_at',
29
        'price',
30
        'currency',
31
        'actual_paid',
32
        'price_equation',
33
        'is_approved',
34
        'is_confirmed',
35
        'is_attended',
36
        'canceled_at',
37
        'notes',
38
    ];
39
40
    /**
41
     * {@inheritdoc}
42
     */
43
    protected $casts = [
44
        'bookable_id' => 'integer',
45
        'bookable_type' => 'string',
46
        'customer_id' => 'integer',
47
        'customer_type' => 'string',
48
        'starts_at' => 'datetime',
49
        'ends_at' => 'datetime',
50
        'price' => 'float',
51
        'currency' => 'string',
52
        'actual_paid' => 'float',
53
        'price_equation' => 'json',
54
        'is_approved' => 'boolean',
55
        'is_confirmed' => 'boolean',
56
        'is_attended' => 'boolean',
57
        'canceled_at' => 'datetime',
58
        'notes' => 'string',
59
    ];
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    protected $observables = [
65
        'validating',
66
        'validated',
67
    ];
68
69
    /**
70
     * The default rules that the model will validate against.
71
     *
72
     * @var array
73
     */
74
    protected $rules = [
75
        'bookable_id' => 'required|integer',
76
        'bookable_type' => 'required|string',
77
        'customer_id' => 'required|integer',
78
        'customer_type' => 'required|string',
79
        'starts_at' => 'required|date',
80
        'ends_at' => 'required|date',
81
        'price' => 'required|numeric',
82
        'currency' => 'required|alpha|size:3',
83
        'actual_paid' => 'required|numeric',
84
        'price_equation' => 'nullable|array',
85
        'is_approved' => 'sometimes|boolean',
86
        'is_confirmed' => 'sometimes|boolean',
87
        'is_attended' => 'sometimes|boolean',
88
        'canceled_at' => 'nullable|date',
89
        'notes' => 'nullable|string|max:10000',
90
    ];
91
92
    /**
93
     * Whether the model should throw a
94
     * ValidationException if it fails validation.
95
     *
96
     * @var bool
97
     */
98
    protected $throwValidationExceptions = true;
99
100
    /**
101
     * Create a new Eloquent model instance.
102
     *
103
     * @param array $attributes
104
     */
105
    public function __construct(array $attributes = [])
106
    {
107
        parent::__construct($attributes);
108
109
        $this->setTable(config('rinvex.bookings.tables.bookings'));
110
    }
111
112
    /**
113
     * @TODO: refactor
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
114
     *
115
     * {@inheritdoc}
116
     */
117
    protected static function boot()
118
    {
119
        parent::boot();
120
121
        static::validating(function (self $bookableAvailability) {
122
            list($price, $priceEquation, $currency) = is_null($bookableAvailability->price)
123
                ? $bookableAvailability->calculatePrice($bookableAvailability->bookable, $bookableAvailability->starts_at, $bookableAvailability->ends_at) : [$bookableAvailability->price, $bookableAvailability->price_equation, $bookableAvailability->currency];
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 260 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...
124
125
            $bookableAvailability->price_equation = $priceEquation;
126
            $bookableAvailability->currency = $currency;
127
            $bookableAvailability->price = $price;
128
        });
129
    }
130
131
    /**
132
     * @TODO: implement rates, availabilites, minimum & maximum units
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
133
     *
134
     * Calculate the booking price.
135
     *
136
     * @param \Illuminate\Database\Eloquent\Model $bookable
137
     * @param \Carbon\Carbon                      $startsAt
138
     * @param \Carbon\Carbon                      $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...
139
     *
140
     * @return array
141
     */
142
    public function calculatePrice(Model $bookable, Carbon $startsAt, Carbon $endsAt = null): array
143
    {
144
        $totalUnits = 0;
145
        $method = 'add'.ucfirst($bookable->unit);
146
147
        for ($date = clone $startsAt; $date->lt($endsAt ?? $date->addDay()); $date->$method()) {
148
            // Count units
149
            $totalUnits++;
150
        }
151
152
        $totalPrice = $bookable->base_cost + ($bookable->unit_cost * $totalUnits);
153
154
        return [
155
            'base_cost' => $bookable->base_cost,
156
            'unit_cost' => $bookable->unit_cost,
157
            'unit' => $bookable->unit,
158
            'currency' => $bookable->currency,
159
            'total_units' => $totalUnits,
160
            'total_price' => $totalPrice,
161
        ];
162
    }
163
164
    /**
165
     * Get the owning resource model.
166
     *
167
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
168
     */
169
    public function bookable(): MorphTo
170
    {
171
        return $this->morphTo('bookable', 'bookable_type', 'bookable_id');
172
    }
173
174
    /**
175
     * Get the booking customer.
176
     *
177
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
178
     */
179
    public function customer(): MorphTo
180
    {
181
        return $this->morphTo('customer', 'customer_type', 'customer_id');
182
    }
183
184
    /**
185
     * Get bookings of the given resource.
186
     *
187
     * @param \Illuminate\Database\Eloquent\Builder $builder
188
     * @param \Illuminate\Database\Eloquent\Model   $bookable
189
     *
190
     * @return \Illuminate\Database\Eloquent\Builder
191
     */
192
    public function scopeOfBookable(Builder $builder, Model $bookable): Builder
193
    {
194
        return $builder->where('bookable_type', $bookable->getMorphClass())->where('bookable_id', $bookable->getKey());
195
    }
196
197
    /**
198
     * Get bookings of the given customer.
199
     *
200
     * @param \Illuminate\Database\Eloquent\Builder $builder
201
     * @param \Illuminate\Database\Eloquent\Model   $customer
202
     *
203
     * @return \Illuminate\Database\Eloquent\Builder
204
     */
205
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
206
    {
207
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->getKey());
208
    }
209
210
    /**
211
     * Get the past bookings.
212
     *
213
     * @param \Illuminate\Database\Eloquent\Builder $builder
214
     *
215
     * @return \Illuminate\Database\Eloquent\Builder
216
     */
217
    public function scopePast(Builder $builder): Builder
218
    {
219
        return $builder->whereNull('canceled_at')
220
                       ->whereNotNull('ends_at')
221
                       ->where('ends_at', '<', now());
222
    }
223
224
    /**
225
     * Get the future bookings.
226
     *
227
     * @param \Illuminate\Database\Eloquent\Builder $builder
228
     *
229
     * @return \Illuminate\Database\Eloquent\Builder
230
     */
231
    public function scopeFuture(Builder $builder): Builder
232
    {
233
        return $builder->whereNull('canceled_at')
234
                       ->whereNotNull('starts_at')
235
                       ->where('starts_at', '>', now());
236
    }
237
238
    /**
239
     * Get the current bookings.
240
     *
241
     * @param \Illuminate\Database\Eloquent\Builder $builder
242
     *
243
     * @return \Illuminate\Database\Eloquent\Builder
244
     */
245
    public function scopeCurrent(Builder $builder): Builder
246
    {
247
        return $builder->whereNull('canceled_at')
248
                       ->whereNotNull('starts_at')
249
                       ->whereNotNull('ends_at')
250
                       ->where('starts_at', '<', now())
251
                       ->where('ends_at', '>', now());
252
    }
253
254
    /**
255
     * Get the cancelled bookings.
256
     *
257
     * @param \Illuminate\Database\Eloquent\Builder $builder
258
     *
259
     * @return \Illuminate\Database\Eloquent\Builder
260
     */
261
    public function scopeCancelled(Builder $builder): Builder
262
    {
263
        return $builder->whereNotNull('canceled_at');
264
    }
265
266
    /**
267
     * Get bookings starts before the given date.
268
     *
269
     * @param \Illuminate\Database\Eloquent\Builder $builder
270
     * @param string                                $date
271
     *
272
     * @return \Illuminate\Database\Eloquent\Builder
273
     */
274
    public function scopeStartsBefore(Builder $builder, string $date): Builder
275
    {
276
        return $builder->whereNull('canceled_at')
277
                       ->whereNotNull('starts_at')
278
                       ->where('starts_at', '<', new Carbon($date));
279
    }
280
281
    /**
282
     * Get bookings starts after the given date.
283
     *
284
     * @param \Illuminate\Database\Eloquent\Builder $builder
285
     * @param string                                $date
286
     *
287
     * @return \Illuminate\Database\Eloquent\Builder
288
     */
289
    public function scopeStartsAfter(Builder $builder, string $date): Builder
290
    {
291
        return $builder->whereNull('canceled_at')
292
                       ->whereNotNull('starts_at')
293
                       ->where('starts_at', '>', new Carbon($date));
294
    }
295
296
    /**
297
     * Get bookings starts between the given dates.
298
     *
299
     * @param \Illuminate\Database\Eloquent\Builder $builder
300
     * @param string                                $startsAt
301
     * @param string                                $endsAt
302
     *
303
     * @return \Illuminate\Database\Eloquent\Builder
304
     */
305
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
306
    {
307
        return $builder->whereNull('canceled_at')
308
                       ->whereNotNull('starts_at')
309
                       ->where('starts_at', '>=', new Carbon($startsAt))
310
                       ->where('starts_at', '<=', new Carbon($endsAt));
311
    }
312
313
    /**
314
     * Get bookings ends before the given date.
315
     *
316
     * @param \Illuminate\Database\Eloquent\Builder $builder
317
     * @param string                                $date
318
     *
319
     * @return \Illuminate\Database\Eloquent\Builder
320
     */
321
    public function scopeEndsBefore(Builder $builder, string $date): Builder
322
    {
323
        return $builder->whereNull('canceled_at')
324
                       ->whereNotNull('ends_at')
325
                       ->where('ends_at', '<', new Carbon($date));
326
    }
327
328
    /**
329
     * Get bookings ends after the given date.
330
     *
331
     * @param \Illuminate\Database\Eloquent\Builder $builder
332
     * @param string                                $date
333
     *
334
     * @return \Illuminate\Database\Eloquent\Builder
335
     */
336
    public function scopeEndsAfter(Builder $builder, string $date): Builder
337
    {
338
        return $builder->whereNull('canceled_at')
339
                       ->whereNotNull('ends_at')
340
                       ->where('ends_at', '>', new Carbon($date));
341
    }
342
343
    /**
344
     * Get bookings ends between the given dates.
345
     *
346
     * @param \Illuminate\Database\Eloquent\Builder $builder
347
     * @param string                                $startsAt
348
     * @param string                                $endsAt
349
     *
350
     * @return \Illuminate\Database\Eloquent\Builder
351
     */
352
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
353
    {
354
        return $builder->whereNull('canceled_at')
355
                       ->whereNotNull('ends_at')
356
                       ->where('ends_at', '>=', new Carbon($startsAt))
357
                       ->where('ends_at', '<=', new Carbon($endsAt));
358
    }
359
360
    /**
361
     * Get bookings cancelled before the given date.
362
     *
363
     * @param \Illuminate\Database\Eloquent\Builder $builder
364
     * @param string                                $date
365
     *
366
     * @return \Illuminate\Database\Eloquent\Builder
367
     */
368
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
369
    {
370
        return $builder->whereNotNull('canceled_at')
371
                       ->where('canceled_at', '<', new Carbon($date));
372
    }
373
374
    /**
375
     * Get bookings cancelled after the given date.
376
     *
377
     * @param \Illuminate\Database\Eloquent\Builder $builder
378
     * @param string                                $date
379
     *
380
     * @return \Illuminate\Database\Eloquent\Builder
381
     */
382
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
383
    {
384
        return $builder->whereNotNull('canceled_at')
385
                       ->where('canceled_at', '>', new Carbon($date));
386
    }
387
388
    /**
389
     * Get bookings cancelled between the given dates.
390
     *
391
     * @param \Illuminate\Database\Eloquent\Builder $builder
392
     * @param string                                $startsAt
393
     * @param string                                $endsAt
394
     *
395
     * @return \Illuminate\Database\Eloquent\Builder
396
     */
397
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
398
    {
399
        return $builder->whereNotNull('canceled_at')
400
                       ->where('canceled_at', '>=', new Carbon($startsAt))
401
                       ->where('canceled_at', '<=', new Carbon($endsAt));
402
    }
403
404
    /**
405
     * Get bookings between the given dates.
406
     *
407
     * @param \Illuminate\Database\Eloquent\Builder $builder
408
     * @param string                                $startsAt
409
     * @param string                                $endsAt
410
     *
411
     * @return \Illuminate\Database\Eloquent\Builder
412
     */
413
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
414
    {
415
        return $builder->whereNull('canceled_at')
416
                       ->whereNotNull('starts_at')
417
                       ->where('starts_at', '>=', new Carbon($startsAt))
418
                       ->where(function (Builder $builder) use ($endsAt) {
419
                           $builder->whereNull('ends_at')
420
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
421
                                     $builder->whereNotNull('ends_at')
422
                                           ->where('ends_at', '<=', new Carbon($endsAt));
423
                                 });
424
                       });
425
    }
426
427
    /**
428
     * Check if the booking is cancelled.
429
     *
430
     * @return bool
431
     */
432
    public function isCancelled(): bool
433
    {
434
        return (bool) $this->canceled_at;
0 ignored issues
show
Documentation introduced by
The property canceled_at does not exist on object<Rinvex\Bookings\Models\BookableBooking>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
435
    }
436
437
    /**
438
     * Check if the booking is past.
439
     *
440
     * @return bool
441
     */
442
    public function isPast(): bool
443
    {
444
        return ! $this->isCancelled() && $this->ends_at->isPast();
0 ignored issues
show
Documentation introduced by
The property ends_at does not exist on object<Rinvex\Bookings\Models\BookableBooking>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
445
    }
446
447
    /**
448
     * Check if the booking is future.
449
     *
450
     * @return bool
451
     */
452
    public function isFuture(): bool
453
    {
454
        return ! $this->isCancelled() && $this->starts_at->isFuture();
0 ignored issues
show
Documentation introduced by
The property starts_at does not exist on object<Rinvex\Bookings\Models\BookableBooking>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
455
    }
456
457
    /**
458
     * Check if the booking is current.
459
     *
460
     * @return bool
461
     */
462
    public function isCurrent(): bool
463
    {
464
        return ! $this->isCancelled() && now()->between($this->starts_at, $this->ends_at);
0 ignored issues
show
Documentation introduced by
The property starts_at does not exist on object<Rinvex\Bookings\Models\BookableBooking>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property ends_at does not exist on object<Rinvex\Bookings\Models\BookableBooking>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
465
    }
466
}
467