BookableBooking::customer()   A
last analyzed

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
        'quantity',
32
        'total_paid',
33
        'currency',
34
        'formula',
35
        'canceled_at',
36
        'options',
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
        'quantity' => 'integer',
52
        'total_paid' => 'float',
53
        'currency' => 'string',
54
        'formula' => 'json',
55
        'canceled_at' => 'datetime',
56
        'options' => 'array',
57
        'notes' => 'string',
58
    ];
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    protected $observables = [
64
        'validating',
65
        'validated',
66
    ];
67
68
    /**
69
     * The default rules that the model will validate against.
70
     *
71
     * @var array
72
     */
73
    protected $rules = [
74
        'bookable_id' => 'required|integer',
75
        'bookable_type' => 'required|string',
76
        'customer_id' => 'required|integer',
77
        'customer_type' => 'required|string',
78
        'starts_at' => 'required|date',
79
        'ends_at' => 'required|date',
80
        'price' => 'required|numeric',
81
        'quantity' => 'required|integer',
82
        'total_paid' => 'required|numeric',
83
        'currency' => 'required|alpha|size:3',
84
        'formula' => 'nullable|array',
85
        'canceled_at' => 'nullable|date',
86
        'options' => 'nullable|array',
87
        'notes' => 'nullable|string|max:10000',
88
    ];
89
90
    /**
91
     * Whether the model should throw a
92
     * ValidationException if it fails validation.
93
     *
94
     * @var bool
95
     */
96
    protected $throwValidationExceptions = true;
97
98
    /**
99
     * Create a new Eloquent model instance.
100
     *
101
     * @param array $attributes
102
     */
103
    public function __construct(array $attributes = [])
104
    {
105
        parent::__construct($attributes);
106
107
        $this->setTable(config('rinvex.bookings.tables.bookable_bookings'));
108
    }
109
110
    /**
111
     * @TODO: refactor
112
     *
113
     * {@inheritdoc}
114
     */
115
    protected static function boot()
116
    {
117
        parent::boot();
118
119
        static::validating(function (self $bookableAvailability) {
120
            [$price, $formula, $currency] = is_null($bookableAvailability->price)
0 ignored issues
show
Bug introduced by
The variable $price does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $formula does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $currency does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
121
                ? $bookableAvailability->calculatePrice($bookableAvailability->bookable, $bookableAvailability->starts_at, $bookableAvailability->ends_at) : [$bookableAvailability->price, $bookableAvailability->formula, $bookableAvailability->currency];
122
123
            $bookableAvailability->currency = $currency;
124
            $bookableAvailability->formula = $formula;
125
            $bookableAvailability->price = $price;
126
        });
127
    }
128
129
    /**
130
     * Get options attributes.
131
     *
132
     * @return \Spatie\SchemalessAttributes\SchemalessAttributes
133
     */
134
    public function getOptionsAttribute(): SchemalessAttributes
135
    {
136
        return SchemalessAttributes::createForModel($this, 'options');
137
    }
138
139
    /**
140
     * Scope with options attributes.
141
     *
142
     * @return \Illuminate\Database\Eloquent\Builder
143
     */
144
    public function scopeWithOptions(): Builder
145
    {
146
        return SchemalessAttributes::scopeWithSchemalessAttributes('options');
147
    }
148
149
    /**
150
     * @TODO: implement rates, availabilites, minimum & maximum units
151
     *
152
     * Calculate the booking price.
153
     *
154
     * @param \Illuminate\Database\Eloquent\Model $bookable
155
     * @param \Carbon\Carbon                      $startsAt
156
     * @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...
157
     * @param int                                 $quantity
158
     *
159
     * @return array
160
     */
161
    public function calculatePrice(Model $bookable, Carbon $startsAt, Carbon $endsAt = null, int $quantity = 1): array
162
    {
163
        $totalUnits = 0;
164
165
        switch ($bookable->unit) {
166
            case 'use':
167
                $totalUnits = 1;
168
                $totalPrice = $bookable->base_cost + ($bookable->unit_cost * $totalUnits * $quantity);
169
                break;
170
            default:
171
                $method = 'add'.ucfirst($bookable->unit);
172
173
                for ($date = clone $startsAt; $date->lt($endsAt ?? $date->addDay()); $date->{$method}()) {
174
                    $totalUnits++;
175
                }
176
177
                $totalPrice = $bookable->base_cost + ($bookable->unit_cost * $totalUnits * $quantity);
178
                break;
179
        }
180
181
        return [
182
            'base_cost' => $bookable->base_cost,
183
            'unit_cost' => $bookable->unit_cost,
184
            'unit' => $bookable->unit,
185
            'currency' => $bookable->currency,
186
            'total_units' => $totalUnits,
187
            'total_price' => $totalPrice,
188
        ];
189
    }
190
191
    /**
192
     * Get the owning resource model.
193
     *
194
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
195
     */
196
    public function bookable(): MorphTo
197
    {
198
        return $this->morphTo('bookable', 'bookable_type', 'bookable_id');
199
    }
200
201
    /**
202
     * Get the booking customer.
203
     *
204
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
205
     */
206
    public function customer(): MorphTo
207
    {
208
        return $this->morphTo('customer', 'customer_type', 'customer_id');
209
    }
210
211
    /**
212
     * Get bookings of the given resource.
213
     *
214
     * @param \Illuminate\Database\Eloquent\Builder $builder
215
     * @param \Illuminate\Database\Eloquent\Model   $bookable
216
     *
217
     * @return \Illuminate\Database\Eloquent\Builder
218
     */
219
    public function scopeOfBookable(Builder $builder, Model $bookable): Builder
220
    {
221
        return $builder->where('bookable_type', $bookable->getMorphClass())->where('bookable_id', $bookable->getKey());
222
    }
223
224
    /**
225
     * Get bookings of the given customer.
226
     *
227
     * @param \Illuminate\Database\Eloquent\Builder $builder
228
     * @param \Illuminate\Database\Eloquent\Model   $customer
229
     *
230
     * @return \Illuminate\Database\Eloquent\Builder
231
     */
232
    public function scopeOfCustomer(Builder $builder, Model $customer): Builder
233
    {
234
        return $builder->where('customer_type', $customer->getMorphClass())->where('customer_id', $customer->getKey());
235
    }
236
237
    /**
238
     * Get the past bookings.
239
     *
240
     * @param \Illuminate\Database\Eloquent\Builder $builder
241
     *
242
     * @return \Illuminate\Database\Eloquent\Builder
243
     */
244
    public function scopePast(Builder $builder): Builder
245
    {
246
        return $builder->whereNull('canceled_at')
247
                       ->whereNotNull('ends_at')
248
                       ->where('ends_at', '<', now());
249
    }
250
251
    /**
252
     * Get the future bookings.
253
     *
254
     * @param \Illuminate\Database\Eloquent\Builder $builder
255
     *
256
     * @return \Illuminate\Database\Eloquent\Builder
257
     */
258
    public function scopeFuture(Builder $builder): Builder
259
    {
260
        return $builder->whereNull('canceled_at')
261
                       ->whereNotNull('starts_at')
262
                       ->where('starts_at', '>', now());
263
    }
264
265
    /**
266
     * Get the current bookings.
267
     *
268
     * @param \Illuminate\Database\Eloquent\Builder $builder
269
     *
270
     * @return \Illuminate\Database\Eloquent\Builder
271
     */
272
    public function scopeCurrent(Builder $builder): Builder
273
    {
274
        return $builder->whereNull('canceled_at')
275
                       ->whereNotNull('starts_at')
276
                       ->whereNotNull('ends_at')
277
                       ->where('starts_at', '<', now())
278
                       ->where('ends_at', '>', now());
279
    }
280
281
    /**
282
     * Get the cancelled bookings.
283
     *
284
     * @param \Illuminate\Database\Eloquent\Builder $builder
285
     *
286
     * @return \Illuminate\Database\Eloquent\Builder
287
     */
288
    public function scopeCancelled(Builder $builder): Builder
289
    {
290
        return $builder->whereNotNull('canceled_at');
291
    }
292
293
    /**
294
     * Get bookings starts before the given date.
295
     *
296
     * @param \Illuminate\Database\Eloquent\Builder $builder
297
     * @param string                                $date
298
     *
299
     * @return \Illuminate\Database\Eloquent\Builder
300
     */
301
    public function scopeStartsBefore(Builder $builder, string $date): Builder
302
    {
303
        return $builder->whereNull('canceled_at')
304
                       ->whereNotNull('starts_at')
305
                       ->where('starts_at', '<', new Carbon($date));
306
    }
307
308
    /**
309
     * Get bookings starts after the given date.
310
     *
311
     * @param \Illuminate\Database\Eloquent\Builder $builder
312
     * @param string                                $date
313
     *
314
     * @return \Illuminate\Database\Eloquent\Builder
315
     */
316
    public function scopeStartsAfter(Builder $builder, string $date): Builder
317
    {
318
        return $builder->whereNull('canceled_at')
319
                       ->whereNotNull('starts_at')
320
                       ->where('starts_at', '>', new Carbon($date));
321
    }
322
323
    /**
324
     * Get bookings starts between the given dates.
325
     *
326
     * @param \Illuminate\Database\Eloquent\Builder $builder
327
     * @param string                                $startsAt
328
     * @param string                                $endsAt
329
     *
330
     * @return \Illuminate\Database\Eloquent\Builder
331
     */
332
    public function scopeStartsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
333
    {
334
        return $builder->whereNull('canceled_at')
335
                       ->whereNotNull('starts_at')
336
                       ->where('starts_at', '>=', new Carbon($startsAt))
337
                       ->where('starts_at', '<=', new Carbon($endsAt));
338
    }
339
340
    /**
341
     * Get bookings ends before the given date.
342
     *
343
     * @param \Illuminate\Database\Eloquent\Builder $builder
344
     * @param string                                $date
345
     *
346
     * @return \Illuminate\Database\Eloquent\Builder
347
     */
348
    public function scopeEndsBefore(Builder $builder, string $date): Builder
349
    {
350
        return $builder->whereNull('canceled_at')
351
                       ->whereNotNull('ends_at')
352
                       ->where('ends_at', '<', new Carbon($date));
353
    }
354
355
    /**
356
     * Get bookings ends after the given date.
357
     *
358
     * @param \Illuminate\Database\Eloquent\Builder $builder
359
     * @param string                                $date
360
     *
361
     * @return \Illuminate\Database\Eloquent\Builder
362
     */
363
    public function scopeEndsAfter(Builder $builder, string $date): Builder
364
    {
365
        return $builder->whereNull('canceled_at')
366
                       ->whereNotNull('ends_at')
367
                       ->where('ends_at', '>', new Carbon($date));
368
    }
369
370
    /**
371
     * Get bookings ends between the given dates.
372
     *
373
     * @param \Illuminate\Database\Eloquent\Builder $builder
374
     * @param string                                $startsAt
375
     * @param string                                $endsAt
376
     *
377
     * @return \Illuminate\Database\Eloquent\Builder
378
     */
379
    public function scopeEndsBetween(Builder $builder, string $startsAt, string $endsAt): Builder
380
    {
381
        return $builder->whereNull('canceled_at')
382
                       ->whereNotNull('ends_at')
383
                       ->where('ends_at', '>=', new Carbon($startsAt))
384
                       ->where('ends_at', '<=', new Carbon($endsAt));
385
    }
386
387
    /**
388
     * Get bookings cancelled before the given date.
389
     *
390
     * @param \Illuminate\Database\Eloquent\Builder $builder
391
     * @param string                                $date
392
     *
393
     * @return \Illuminate\Database\Eloquent\Builder
394
     */
395
    public function scopeCancelledBefore(Builder $builder, string $date): Builder
396
    {
397
        return $builder->whereNotNull('canceled_at')
398
                       ->where('canceled_at', '<', new Carbon($date));
399
    }
400
401
    /**
402
     * Get bookings cancelled after the given date.
403
     *
404
     * @param \Illuminate\Database\Eloquent\Builder $builder
405
     * @param string                                $date
406
     *
407
     * @return \Illuminate\Database\Eloquent\Builder
408
     */
409
    public function scopeCancelledAfter(Builder $builder, string $date): Builder
410
    {
411
        return $builder->whereNotNull('canceled_at')
412
                       ->where('canceled_at', '>', new Carbon($date));
413
    }
414
415
    /**
416
     * Get bookings cancelled between the given dates.
417
     *
418
     * @param \Illuminate\Database\Eloquent\Builder $builder
419
     * @param string                                $startsAt
420
     * @param string                                $endsAt
421
     *
422
     * @return \Illuminate\Database\Eloquent\Builder
423
     */
424
    public function scopeCancelledBetween(Builder $builder, string $startsAt, string $endsAt): Builder
425
    {
426
        return $builder->whereNotNull('canceled_at')
427
                       ->where('canceled_at', '>=', new Carbon($startsAt))
428
                       ->where('canceled_at', '<=', new Carbon($endsAt));
429
    }
430
431
    /**
432
     * Get bookings between the given dates.
433
     *
434
     * @param \Illuminate\Database\Eloquent\Builder $builder
435
     * @param string                                $startsAt
436
     * @param string                                $endsAt
437
     *
438
     * @return \Illuminate\Database\Eloquent\Builder
439
     */
440
    public function scopeRange(Builder $builder, string $startsAt, string $endsAt): Builder
441
    {
442
        return $builder->whereNull('canceled_at')
443
                       ->whereNotNull('starts_at')
444
                       ->where('starts_at', '>=', new Carbon($startsAt))
445
                       ->where(function (Builder $builder) use ($endsAt) {
446
                           $builder->whereNull('ends_at')
447
                                 ->orWhere(function (Builder $builder) use ($endsAt) {
448
                                     $builder->whereNotNull('ends_at')
449
                                           ->where('ends_at', '<=', new Carbon($endsAt));
450
                                 });
451
                       });
452
    }
453
454
    /**
455
     * Check if the booking is cancelled.
456
     *
457
     * @return bool
458
     */
459
    public function isCancelled(): bool
460
    {
461
        return (bool) $this->canceled_at;
462
    }
463
464
    /**
465
     * Check if the booking is past.
466
     *
467
     * @return bool
468
     */
469
    public function isPast(): bool
470
    {
471
        return ! $this->isCancelled() && $this->ends_at->isPast();
472
    }
473
474
    /**
475
     * Check if the booking is future.
476
     *
477
     * @return bool
478
     */
479
    public function isFuture(): bool
480
    {
481
        return ! $this->isCancelled() && $this->starts_at->isFuture();
482
    }
483
484
    /**
485
     * Check if the booking is current.
486
     *
487
     * @return bool
488
     */
489
    public function isCurrent(): bool
490
    {
491
        return ! $this->isCancelled() && Carbon::now()->between($this->starts_at, $this->ends_at);
492
    }
493
}
494