Bookable::setVerificationDate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Model;
6
7
use Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountEqualOperatorType;
8
use Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountGreaterOperatorType;
9
use Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountGreaterOrEqualOperatorType;
10
use Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountLessOperatorType;
11
use Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountLessOrEqualOperatorType;
12
use Application\Api\Input\Operator\BookableUsageOperatorType;
13
use Application\Enum\BookableState;
14
use Application\Enum\BookingStatus;
15
use Application\Enum\BookingType;
16
use Application\Repository\BookableRepository;
17
use Application\Repository\BookableTagRepository;
18
use Application\Traits\HasCode;
19
use Application\Traits\HasRemarks;
20
use Cake\Chronos\ChronosDate;
21
use Doctrine\Common\Collections\ArrayCollection;
22
use Doctrine\Common\Collections\Collection;
23
use Doctrine\ORM\Mapping as ORM;
24
use Ecodev\Felix\Model\Traits\HasDescription;
25
use Ecodev\Felix\Model\Traits\HasName;
26
use GraphQL\Doctrine\Attribute as API;
27
use Money\Money;
28
29
/**
30
 * An item that can be booked by a user.
31
 */
32
#[API\Filter(field: 'custom', operator: BookableUsageOperatorType::class, type: 'id')]
33
#[API\Filter(field: 'bookableBookingCount', operator: BookableBookingCountEqualOperatorType::class, type: 'int')]
34
#[API\Filter(field: 'bookableBookingCount', operator: BookableBookingCountGreaterOperatorType::class, type: 'int')]
35
#[API\Filter(field: 'bookableBookingCount', operator: BookableBookingCountGreaterOrEqualOperatorType::class, type: 'int')]
36
#[API\Filter(field: 'bookableBookingCount', operator: BookableBookingCountLessOperatorType::class, type: 'int')]
37
#[API\Filter(field: 'bookableBookingCount', operator: BookableBookingCountLessOrEqualOperatorType::class, type: 'int')]
38
#[ORM\Entity(BookableRepository::class)]
39
class Bookable extends AbstractModel
40
{
41
    use HasCode;
42
    use HasDescription;
43
    use HasName;
44
    use HasRemarks;
45
46
    #[ORM\Column(type: 'Money', options: ['default' => 0])]
47
    private Money $initialPrice;
48
49
    #[ORM\Column(type: 'Money', options: ['default' => 0])]
50
    private Money $periodicPrice;
51
52
    #[ORM\Column(type: 'Money', nullable: true, options: ['unsigned' => true])]
53
    private ?Money $purchasePrice = null;
54
55
    #[ORM\Column(type: 'smallint', options: ['default' => -1])]
56
    private int $simultaneousBookingMaximum = 1;
57
58
    #[ORM\Column(type: 'smallint', options: ['default' => 0, 'unsigned' => true])]
59
    private int $waitingListLength = 0;
60
61
    #[ORM\Column(type: 'enum', length: 10, options: ['default' => BookingType::AdminApproved])]
62
    private BookingType $bookingType = BookingType::AdminApproved;
63
64
    #[ORM\Column(type: 'boolean', options: ['default' => true])]
65
    private bool $isActive = true;
66
67
    #[ORM\Column(type: 'enum', length: 10, options: ['default' => BookableState::Good])]
68
    private BookableState $state = BookableState::Good;
69
70
    #[ORM\Column(type: 'date', nullable: true)]
71
    private ?ChronosDate $verificationDate = null;
72
73
    /**
74
     * @var Collection<int, BookableTag>
75
     */
76
    #[ORM\ManyToMany(targetEntity: BookableTag::class, mappedBy: 'bookables')]
77
    private Collection $bookableTags;
78
79
    /**
80
     * @var Collection<int, Booking>
81
     */
82
    #[ORM\OneToMany(targetEntity: Booking::class, mappedBy: 'bookable')]
83
    private Collection $bookings;
84
85
    /**
86
     * @var Collection<int, License>
87
     */
88
    #[ORM\ManyToMany(targetEntity: License::class, mappedBy: 'bookables')]
89
    private Collection $licenses;
90
91
    #[ORM\OneToOne(targetEntity: Image::class, orphanRemoval: true)]
92
    #[ORM\JoinColumn(name: 'image_id', referencedColumnName: 'id')]
93
    private ?Image $image = null;
94
95
    #[ORM\JoinColumn(onDelete: 'CASCADE')]
96
    #[ORM\ManyToOne(targetEntity: Account::class)]
97
    private ?Account $creditAccount = null;
98
99 30
    public function __construct()
100
    {
101 30
        $this->initialPrice = Money::CHF(0);
102 30
        $this->periodicPrice = Money::CHF(0);
103
104 30
        $this->bookings = new ArrayCollection();
105 30
        $this->licenses = new ArrayCollection();
106 30
        $this->bookableTags = new ArrayCollection();
107
    }
108
109 17
    public function getBookings(): Collection
110
    {
111 17
        return $this->bookings;
112
    }
113
114
    /**
115
     * Notify the bookable that it has a new booking.
116
     * This should only be called by Booking::addBookable().
117
     */
118 27
    public function bookingAdded(Booking $booking): void
119
    {
120 27
        $this->bookings->add($booking);
121
    }
122
123
    /**
124
     * Notify the bookable that it a booking was removed.
125
     * This should only be called by Booking::removeBookable().
126
     */
127 1
    public function bookingRemoved(Booking $booking): void
128
    {
129 1
        $this->bookings->removeElement($booking);
130
    }
131
132 17
    public function getLicenses(): Collection
133
    {
134 17
        return $this->licenses;
135
    }
136
137
    /**
138
     * Notify the bookable that it has a new license.
139
     * This should only be called by License::addBookable().
140
     */
141 4
    public function licenseAdded(License $license): void
142
    {
143 4
        $this->licenses->add($license);
144
    }
145
146
    /**
147
     * Notify the bookable that it a license was removed.
148
     * This should only be called by License::removeBookable().
149
     */
150 1
    public function licenseRemoved(License $license): void
151
    {
152 1
        $this->licenses->removeElement($license);
153
    }
154
155 6
    public function getInitialPrice(): Money
156
    {
157 6
        return $this->initialPrice;
158
    }
159
160 6
    public function setInitialPrice(Money $initialPrice): void
161
    {
162 6
        $this->initialPrice = $initialPrice;
163
    }
164
165 10
    public function getPeriodicPrice(): Money
166
    {
167 10
        return $this->periodicPrice;
168
    }
169
170 8
    public function setPeriodicPrice(Money $periodicPrice): void
171
    {
172 8
        $this->periodicPrice = $periodicPrice;
173
    }
174
175
    public function getPurchasePrice(): ?Money
176
    {
177
        return $this->purchasePrice;
178
    }
179
180
    public function setPurchasePrice(?Money $purchasePrice): void
181
    {
182
        $this->purchasePrice = $purchasePrice;
183
    }
184
185 14
    public function getSimultaneousBookingMaximum(): int
186
    {
187 14
        return $this->simultaneousBookingMaximum;
188
    }
189
190 14
    public function setSimultaneousBookingMaximum(int $simultaneousBookingMaximum): void
191
    {
192 14
        $this->simultaneousBookingMaximum = $simultaneousBookingMaximum;
193
    }
194
195 28
    public function getBookingType(): BookingType
196
    {
197 28
        return $this->bookingType;
198
    }
199
200
    /**
201
     * Whether this bookable can be booked.
202
     */
203 17
    public function isActive(): bool
204
    {
205 17
        return $this->isActive;
206
    }
207
208
    /**
209
     * Whether this bookable can be booked.
210
     */
211 14
    public function setIsActive(bool $isActive): void
212
    {
213 14
        $this->isActive = $isActive;
214
    }
215
216 2
    public function setBookingType(BookingType $state): void
217
    {
218 2
        $this->bookingType = $state;
219
    }
220
221
    /**
222
     * State of the bookable.
223
     */
224
    public function getState(): BookableState
225
    {
226
        return $this->state;
227
    }
228
229
    /**
230
     * State of the bookable.
231
     */
232
    public function setState(BookableState $state): void
233
    {
234
        $this->state = $state;
235
    }
236
237
    /**
238
     * The date then the bookable was last checked.
239
     */
240
    public function getVerificationDate(): ?ChronosDate
241
    {
242
        return $this->verificationDate;
243
    }
244
245
    /**
246
     * The date then the bookable was last checked.
247
     */
248
    public function setVerificationDate(?ChronosDate $verificationDate): void
249
    {
250
        $this->verificationDate = $verificationDate;
251
    }
252
253 6
    public function getBookableTags(): Collection
254
    {
255 6
        return $this->bookableTags;
256
    }
257
258
    /**
259
     * Notify the user that it has a new bookableTag.
260
     * This should only be called by BookableTag::addUser().
261
     */
262
    public function bookableTagAdded(BookableTag $bookableTag): void
263
    {
264
        $this->bookableTags->add($bookableTag);
265
    }
266
267
    /**
268
     * Notify the user that it a bookableTag was removed.
269
     * This should only be called by BookableTag::removeUser().
270
     */
271
    public function bookableTagRemoved(BookableTag $bookableTag): void
272
    {
273
        $this->bookableTags->removeElement($bookableTag);
274
    }
275
276 1
    public function getImage(): ?Image
277
    {
278 1
        return $this->image;
279
    }
280
281 1
    public function setImage(?Image $image): void
282
    {
283
        // We must trigger lazy loading, otherwise Doctrine will seriously
284
        // mess up lifecycle callbacks and delete unrelated image on disk
285 1
        if ($this->image) {
286 1
            $this->image->getFilename();
287
        }
288
289 1
        $this->image = $image;
290
    }
291
292
    /**
293
     * The account to credit when booking this bookable.
294
     */
295 8
    public function getCreditAccount(): ?Account
296
    {
297 8
        return $this->creditAccount;
298
    }
299
300
    /**
301
     * The account to credit when booking this bookable.
302
     */
303 5
    public function setCreditAccount(?Account $creditAccount): void
304
    {
305 5
        $this->creditAccount = $creditAccount;
306
    }
307
308
    /**
309
     * Returns list of active, non-application, bookings. But only if the bookable has a limit of simultaneous booking. Otherwise, returns empty list.
310
     *
311
     * @return Booking[]
312
     */
313 16
    public function getSimultaneousBookings(): array
314
    {
315
        // Pretend to have no simultaneous bookings to avoid too many SQL queries when we don't really care about it
316 16
        if ($this->avoidTooManySqlQueries()) {
317 3
            return [];
318
        }
319
320
        // Only consider approved and unterminated bookings
321 15
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate() && $booking->getStatus() !== BookingStatus::Application)->toArray();
322
323 15
        return $bookings;
324
    }
325
326
    /**
327
     * Returns list of active, non-application, bookings. But only if the bookable has a limit of simultaneous booking. Otherwise, returns empty list.
328
     *
329
     * @return Booking[]
330
     */
331 16
    public function getSimultaneousApplications(): array
332
    {
333
        // Pretend to have no simultaneous bookings to avoid too many SQL queries when we don't really care about it
334 16
        if ($this->avoidTooManySqlQueries()) {
335 3
            return [];
336
        }
337
338 15
        $bookableType = $this->getBookingType();
339 15
        $bookableTypesAllowed = [BookingType::AdminAssigned, BookingType::Application, BookingType::AdminApproved];
340
341 15
        if (!in_array($bookableType, $bookableTypesAllowed, true)) {
342
            return [];
343
        }
344
345
        // Only consider approved and unterminated bookings
346 15
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate() && $booking->getStatus() === BookingStatus::Application)->toArray();
347
348 15
        return $bookings;
349
    }
350
351 16
    private function avoidTooManySqlQueries(): bool
352
    {
353 16
        $bookableType = $this->getBookingType();
354 16
        $bookableTypesAllowed = [BookingType::AdminAssigned, BookingType::Application, BookingType::AdminApproved];
355
356 16
        return !in_array($bookableType, $bookableTypesAllowed, true);
357
    }
358
359
    /**
360
     * Return a list of effective active bookings including sharing conditions.
361
     *
362
     * Only "admin-assigned" + storage tags are sharable bookables. In this case, a list of bookings is returned.
363
     *
364
     * For other bookable types, returns null
365
     *
366
     * @return Booking[]
367
     */
368 6
    public function getPeriodicPriceDividerBookings(): array
369
    {
370 6
        $isAdminAssigned = $this->getBookingType() === BookingType::AdminAssigned;
371
372 6
        $isTagAllowed = false;
373 6
        $allowedTagIds = [BookableTagRepository::STORAGE_ID, BookableTagRepository::FORMATION_ID, BookableTagRepository::WELCOME_ID];
374 6
        foreach ($this->getBookableTags() as $tag) {
375 2
            if (in_array($tag->getId(), $allowedTagIds, true)) {
376 1
                $isTagAllowed = true;
377
378 1
                break;
379
            }
380
        }
381
382 6
        if (!$isAdminAssigned || !$isTagAllowed) {
383 6
            return [];
384
        }
385
386 1
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate())->toArray();
387
388 1
        return $bookings;
389
    }
390
391
    /**
392
     * If non-zero, it allows creating more application bookings, even if the bookable reached its
393
     * simultaneousBookingMaximum. So in effect, it will be a sort of "waiting list" of people who would like to
394
     * participate in case of someone else canceling their booking.
395
     */
396 12
    public function getWaitingListLength(): int
397
    {
398 12
        return $this->waitingListLength;
399
    }
400
401 14
    public function setWaitingListLength(int $waitingListLength): void
402
    {
403 14
        $this->waitingListLength = $waitingListLength;
404
    }
405
}
406