Completed
Push — develop ( 898c2d...06e19b )
by Abdelrahman
12:02
created

BookableBooking::getOptionsAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
use Spatie\SchemalessAttributes\SchemalessAttributes;
14
15
abstract class BookableBooking extends Model
16
{
17
    use ValidatingTrait;
18
    use CacheableEloquent;
19
20
    /**
21
     * {@inheritdoc}
22
     */
23
    protected $fillable = [
24
        'bookable_id',
25
        'bookable_type',
26
        'customer_id',
27
        'customer_type',
28
        'starts_at',
29
        'ends_at',
30
        'price',
31
        'currency',
32
        'formula',
33
        'actual_paid',
34
        'canceled_at',
35
        'options',
36
        'notes',
37
    ];
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    protected $casts = [
43
        'bookable_id' => 'integer',
44
        'bookable_type' => 'string',
45
        'customer_id' => 'integer',
46
        'customer_type' => 'string',
47
        'starts_at' => 'datetime',
48
        'ends_at' => 'datetime',
49
        'price' => 'float',
50
        'currency' => 'string',
51
        'formula' => 'json',
52
        'actual_paid' => 'float',
53
        'canceled_at' => 'datetime',
54
        'options' => 'array',
55
        'notes' => 'string',
56
    ];
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    protected $observables = [
62
        'validating',
63
        'validated',
64
    ];
65
66
    /**
67
     * The default rules that the model will validate against.
68
     *
69
     * @var array
70
     */
71
    protected $rules = [
72
        'bookable_id' => 'required|integer',
73
        'bookable_type' => 'required|string',
74
        'customer_id' => 'required|integer',
75
        'customer_type' => 'required|string',
76
        'starts_at' => 'required|date',
77
        'ends_at' => 'required|date',
78
        'price' => 'required|numeric',
79
        'currency' => 'required|alpha|size:3',
80
        'formula' => 'nullable|array',
81
        'actual_paid' => 'required|numeric',
82
        'canceled_at' => 'nullable|date',
83
        'options' => 'nullable|array',
84
        'notes' => 'nullable|string|max:10000',
85
    ];
86
87
    /**
88
     * Whether the model should throw a
89
     * ValidationException if it fails validation.
90
     *
91
     * @var bool
92
     */
93
    protected $throwValidationExceptions = true;
94
95
    /**
96
     * Create a new Eloquent model instance.
97
     *
98
     * @param array $attributes
99
     */
100
    public function __construct(array $attributes = [])
101
    {
102
        parent::__construct($attributes);
103
104
        $this->setTable(config('rinvex.bookings.tables.bookings'));
105
    }
106
107
    /**
108
     * @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...
109
     *
110
     * {@inheritdoc}
111
     */
112
    protected static function boot()
113
    {
114
        parent::boot();
115
116
        static::validating(function (self $bookableAvailability) {
117
            list($price, $formula, $currency) = is_null($bookableAvailability->price)
118
                ? $bookableAvailability->calculatePrice($bookableAvailability->bookable, $bookableAvailability->starts_at, $bookableAvailability->ends_at) : [$bookableAvailability->price, $bookableAvailability->formula, $bookableAvailability->currency];
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 253 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...
119
120
            $bookableAvailability->currency = $currency;
121
            $bookableAvailability->formula = $formula;
122
            $bookableAvailability->price = $price;
123
        });
124
    }
125
126
    /**
127
     * Get options attributes.
128
     *
129
     * @return \Spatie\SchemalessAttributes\SchemalessAttributes
130
     */
131
    public function getOptionsAttribute(): SchemalessAttributes
132
    {
133
        return SchemalessAttributes::createForModel($this, 'options');
134
    }
135
136
    /**
137
     * Scope with options attributes.
138
     *
139
     * @return \Illuminate\Database\Eloquent\Builder
140
     */
141
    public function scopeWithOptions(): Builder
142
    {
143
        return SchemalessAttributes::scopeWithSchemalessAttributes('options');
144
    }
145
146
    /**
147
     * @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...
148
     *
149
     * Calculate the booking price.
150
     *
151
     * @param \Illuminate\Database\Eloquent\Model $bookable
152
     * @param \Carbon\Carbon                      $startsAt
153
     * @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...
154
     *
155
     * @return array
156
     */
157
    public function calculatePrice(Model $bookable, Carbon $startsAt, Carbon $endsAt = null): array
158
    {
159
        $totalUnits = 0;
160
        $method = 'add'.ucfirst($bookable->unit);
161
162
        for ($date = clone $startsAt; $date->lt($endsAt ?? $date->addDay()); $date->$method()) {
163
            // Count units
164
            $totalUnits++;
165
        }
166
167
        $totalPrice = $bookable->base_cost + ($bookable->unit_cost * $totalUnits);
168
169
        return [
170
            'base_cost' => $bookable->base_cost,
171
            'unit_cost' => $bookable->unit_cost,
172
            'unit' => $bookable->unit,
173
            'currency' => $bookable->currency,
174
            'total_units' => $totalUnits,
175
            'total_price' => $totalPrice,
176
        ];
177
    }
178
179
    /**
180
     * Get the owning resource model.
181
     *
182
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
183
     */
184
    public function bookable(): MorphTo
185
    {
186
        return $this->morphTo('bookable', 'bookable_type', 'bookable_id');
187
    }
188
189
    /**
190
     * Get the booking customer.
191
     *
192
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
193
     */
194
    public function customer(): MorphTo
195
    {
196
        return $this->morphTo('customer', 'customer_type', 'customer_id');
197
    }
198
199
    /**
200
     * Get bookings of the given resource.
201
     *
202
     * @param \Illuminate\Database\Eloquent\Builder $builder
203
     * @param \Illuminate\Database\Eloquent\Model   $bookable
204
     *
205
     * @return \Illuminate\Database\Eloquent\Builder
206
     */
207
    public function scopeOfBookable(Builder $builder, Model $bookable): Builder
208
    {
209
        return $builder->where('bookable_type', $bookable->getMorphClass())->where('bookable_id', $bookable->getKey());
210
    }
211
212
    /**
213
     * Get bookings of the given customer.
214
     *
215
     * @param \Illuminate\Database\Eloquent\Builder $builder
216
     * @param \Illuminate\Database\Eloquent\Model   $customer
217
     *
218
     * @return \Illuminate\Database\Eloquent\Builder
219
     */
220
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
221
    {
222
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->getKey());
223
    }
224
225
    /**
226
     * Get the past bookings.
227
     *
228
     * @param \Illuminate\Database\Eloquent\Builder $builder
229
     *
230
     * @return \Illuminate\Database\Eloquent\Builder
231
     */
232
    public function scopePast(Builder $builder): Builder
233
    {
234
        return $builder->whereNull('canceled_at')
235
                       ->whereNotNull('ends_at')
236
                       ->where('ends_at', '<', now());
237
    }
238
239
    /**
240
     * Get the future bookings.
241
     *
242
     * @param \Illuminate\Database\Eloquent\Builder $builder
243
     *
244
     * @return \Illuminate\Database\Eloquent\Builder
245
     */
246
    public function scopeFuture(Builder $builder): Builder
247
    {
248
        return $builder->whereNull('canceled_at')
249
                       ->whereNotNull('starts_at')
250
                       ->where('starts_at', '>', now());
251
    }
252
253
    /**
254
     * Get the current bookings.
255
     *
256
     * @param \Illuminate\Database\Eloquent\Builder $builder
257
     *
258
     * @return \Illuminate\Database\Eloquent\Builder
259
     */
260
    public function scopeCurrent(Builder $builder): Builder
261
    {
262
        return $builder->whereNull('canceled_at')
263
                       ->whereNotNull('starts_at')
264
                       ->whereNotNull('ends_at')
265
                       ->where('starts_at', '<', now())
266
                       ->where('ends_at', '>', now());
267
    }
268
269
    /**
270
     * Get the cancelled bookings.
271
     *
272
     * @param \Illuminate\Database\Eloquent\Builder $builder
273
     *
274
     * @return \Illuminate\Database\Eloquent\Builder
275
     */
276
    public function scopeCancelled(Builder $builder): Builder
277
    {
278
        return $builder->whereNotNull('canceled_at');
279
    }
280
281
    /**
282
     * Get bookings starts before 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 scopeStartsBefore(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 after the given date.
298
     *
299
     * @param \Illuminate\Database\Eloquent\Builder $builder
300
     * @param string                                $date
301
     *
302
     * @return \Illuminate\Database\Eloquent\Builder
303
     */
304
    public function scopeStartsAfter(Builder $builder, string $date): Builder
305
    {
306
        return $builder->whereNull('canceled_at')
307
                       ->whereNotNull('starts_at')
308
                       ->where('starts_at', '>', new Carbon($date));
309
    }
310
311
    /**
312
     * Get bookings starts between the given dates.
313
     *
314
     * @param \Illuminate\Database\Eloquent\Builder $builder
315
     * @param string                                $startsAt
316
     * @param string                                $endsAt
317
     *
318
     * @return \Illuminate\Database\Eloquent\Builder
319
     */
320
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
321
    {
322
        return $builder->whereNull('canceled_at')
323
                       ->whereNotNull('starts_at')
324
                       ->where('starts_at', '>=', new Carbon($startsAt))
325
                       ->where('starts_at', '<=', new Carbon($endsAt));
326
    }
327
328
    /**
329
     * Get bookings ends before 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 scopeEndsBefore(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 after the given date.
345
     *
346
     * @param \Illuminate\Database\Eloquent\Builder $builder
347
     * @param string                                $date
348
     *
349
     * @return \Illuminate\Database\Eloquent\Builder
350
     */
351
    public function scopeEndsAfter(Builder $builder, string $date): Builder
352
    {
353
        return $builder->whereNull('canceled_at')
354
                       ->whereNotNull('ends_at')
355
                       ->where('ends_at', '>', new Carbon($date));
356
    }
357
358
    /**
359
     * Get bookings ends between the given dates.
360
     *
361
     * @param \Illuminate\Database\Eloquent\Builder $builder
362
     * @param string                                $startsAt
363
     * @param string                                $endsAt
364
     *
365
     * @return \Illuminate\Database\Eloquent\Builder
366
     */
367
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
368
    {
369
        return $builder->whereNull('canceled_at')
370
                       ->whereNotNull('ends_at')
371
                       ->where('ends_at', '>=', new Carbon($startsAt))
372
                       ->where('ends_at', '<=', new Carbon($endsAt));
373
    }
374
375
    /**
376
     * Get bookings cancelled before the given date.
377
     *
378
     * @param \Illuminate\Database\Eloquent\Builder $builder
379
     * @param string                                $date
380
     *
381
     * @return \Illuminate\Database\Eloquent\Builder
382
     */
383
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
384
    {
385
        return $builder->whereNotNull('canceled_at')
386
                       ->where('canceled_at', '<', new Carbon($date));
387
    }
388
389
    /**
390
     * Get bookings cancelled after the given date.
391
     *
392
     * @param \Illuminate\Database\Eloquent\Builder $builder
393
     * @param string                                $date
394
     *
395
     * @return \Illuminate\Database\Eloquent\Builder
396
     */
397
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
398
    {
399
        return $builder->whereNotNull('canceled_at')
400
                       ->where('canceled_at', '>', new Carbon($date));
401
    }
402
403
    /**
404
     * Get bookings cancelled between the given dates.
405
     *
406
     * @param \Illuminate\Database\Eloquent\Builder $builder
407
     * @param string                                $startsAt
408
     * @param string                                $endsAt
409
     *
410
     * @return \Illuminate\Database\Eloquent\Builder
411
     */
412
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
413
    {
414
        return $builder->whereNotNull('canceled_at')
415
                       ->where('canceled_at', '>=', new Carbon($startsAt))
416
                       ->where('canceled_at', '<=', new Carbon($endsAt));
417
    }
418
419
    /**
420
     * Get bookings between the given dates.
421
     *
422
     * @param \Illuminate\Database\Eloquent\Builder $builder
423
     * @param string                                $startsAt
424
     * @param string                                $endsAt
425
     *
426
     * @return \Illuminate\Database\Eloquent\Builder
427
     */
428
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
429
    {
430
        return $builder->whereNull('canceled_at')
431
                       ->whereNotNull('starts_at')
432
                       ->where('starts_at', '>=', new Carbon($startsAt))
433
                       ->where(function (Builder $builder) use ($endsAt) {
434
                           $builder->whereNull('ends_at')
435
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
436
                                     $builder->whereNotNull('ends_at')
437
                                           ->where('ends_at', '<=', new Carbon($endsAt));
438
                                 });
439
                       });
440
    }
441
442
    /**
443
     * Check if the booking is cancelled.
444
     *
445
     * @return bool
446
     */
447
    public function isCancelled(): bool
448
    {
449
        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...
450
    }
451
452
    /**
453
     * Check if the booking is past.
454
     *
455
     * @return bool
456
     */
457
    public function isPast(): bool
458
    {
459
        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...
460
    }
461
462
    /**
463
     * Check if the booking is future.
464
     *
465
     * @return bool
466
     */
467
    public function isFuture(): bool
468
    {
469
        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...
470
    }
471
472
    /**
473
     * Check if the booking is current.
474
     *
475
     * @return bool
476
     */
477
    public function isCurrent(): bool
478
    {
479
        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...
480
    }
481
}
482