Failed Conditions
Push — master ( 013c9c...9a2f15 )
by Adrien
05:31
created

Bookable::bookableTagAdded()   A

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\DBAL\Types\BookableStateType;
8
use Application\DBAL\Types\BookingStatusType;
9
use Application\DBAL\Types\BookingTypeType;
10
use Application\Repository\BookableTagRepository;
11
use Application\Traits\HasCode;
12
use Application\Traits\HasRemarks;
13
use Cake\Chronos\Date;
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\Common\Collections\Collection;
16
use Doctrine\ORM\Mapping as ORM;
17
use Ecodev\Felix\Model\Traits\HasDescription;
18
use Ecodev\Felix\Model\Traits\HasName;
19
use GraphQL\Doctrine\Annotation as API;
20
use Money\Money;
21
22
/**
23
 * An item that can be booked by a user.
24
 *
25
 * @ORM\Entity(repositoryClass="Application\Repository\BookableRepository")
26
 * @API\Filters({
27
 *     @API\Filter(field="custom", operator="Application\Api\Input\Operator\BookableUsageOperatorType", type="id"),
28
 *     @API\Filter(field="bookableBookingCount", operator="Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountEqualOperatorType", type="int"),
29
 *     @API\Filter(field="bookableBookingCount", operator="Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountGreaterOperatorType", type="int"),
30
 *     @API\Filter(field="bookableBookingCount", operator="Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountGreaterOrEqualOperatorType", type="int"),
31
 *     @API\Filter(field="bookableBookingCount", operator="Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountLessOperatorType", type="int"),
32
 *     @API\Filter(field="bookableBookingCount", operator="Application\Api\Input\Operator\BookableBookingCount\BookableBookingCountLessOrEqualOperatorType", type="int"),
33
 * })
34
 */
35
class Bookable extends AbstractModel
36
{
37
    use HasCode;
38
    use HasDescription;
39
    use HasName;
40
    use HasRemarks;
41
42
    /**
43
     * @ORM\Column(type="Money", options={"default" = 0})
44
     */
45
    private Money $initialPrice;
46
47
    /**
48
     * @ORM\Column(type="Money", options={"default" = 0})
49
     */
50
    private Money $periodicPrice;
51
52
    /**
53
     * @ORM\Column(type="Money", nullable=true, options={"unsigned" = true})
54
     */
55
    private ?Money $purchasePrice = null;
56
57
    /**
58
     * @ORM\Column(type="smallint", options={"default" = "-1"})
59
     */
60
    private int $simultaneousBookingMaximum = 1;
61
62
    /**
63
     * @ORM\Column(type="BookingType", length=10, options={"default" = BookingTypeType::SELF_APPROVED})
64
     */
65
    private string $bookingType = BookingTypeType::SELF_APPROVED;
66
67
    /**
68
     * @ORM\Column(type="boolean", options={"default" = 1})
69
     */
70
    private bool $isActive = true;
71
72
    /**
73
     * @ORM\Column(type="BookableState", length=10, options={"default" = BookableStateType::GOOD})
74
     */
75
    private string $state = BookableStateType::GOOD;
76
77
    /**
78
     * @ORM\Column(type="date", nullable=true)
79
     */
80
    private ?Date $verificationDate = null;
81
82
    /**
83
     * @var Collection<BookableTag>
84
     *
85
     * @ORM\ManyToMany(targetEntity="BookableTag", mappedBy="bookables")
86
     */
87
    private Collection $bookableTags;
88
89
    /**
90
     * @var Collection<Booking>
91
     * @ORM\OneToMany(targetEntity="Booking", mappedBy="bookable")
92
     */
93
    private Collection $bookings;
94
95
    /**
96
     * @var Collection<License>
97
     * @ORM\ManyToMany(targetEntity="License", mappedBy="bookables")
98
     */
99
    private Collection $licenses;
100
101
    /**
102
     * @ORM\OneToOne(targetEntity="Image", orphanRemoval=true)
103
     * @ORM\JoinColumn(name="image_id", referencedColumnName="id")
104
     */
105
    private ?Image $image = null;
106
107
    /**
108
     * @ORM\ManyToOne(targetEntity="Account")
109
     * @ORM\JoinColumns({
110
     *     @ORM\JoinColumn(nullable=true, onDelete="CASCADE")
111
     * })
112
     */
113
    private ?Account $creditAccount = null;
114
115
    /**
116
     * Constructor.
117
     */
118 15
    public function __construct()
119
    {
120 15
        $this->initialPrice = Money::CHF(0);
121 15
        $this->periodicPrice = Money::CHF(0);
122
123 15
        $this->bookings = new ArrayCollection();
124 15
        $this->licenses = new ArrayCollection();
125 15
        $this->bookableTags = new ArrayCollection();
126
    }
127
128 3
    public function getBookings(): Collection
129
    {
130 3
        return $this->bookings;
131
    }
132
133
    /**
134
     * Notify the bookable that it has a new booking.
135
     * This should only be called by Booking::addBookable().
136
     */
137 11
    public function bookingAdded(Booking $booking): void
138
    {
139 11
        $this->bookings->add($booking);
140
    }
141
142
    /**
143
     * Notify the bookable that it a booking was removed.
144
     * This should only be called by Booking::removeBookable().
145
     */
146 1
    public function bookingRemoved(Booking $booking): void
147
    {
148 1
        $this->bookings->removeElement($booking);
149
    }
150
151 3
    public function getLicenses(): Collection
152
    {
153 3
        return $this->licenses;
154
    }
155
156
    /**
157
     * Notify the bookable that it has a new license.
158
     * This should only be called by License::addBookable().
159
     */
160 1
    public function licenseAdded(License $license): void
161
    {
162 1
        $this->licenses->add($license);
163
    }
164
165
    /**
166
     * Notify the bookable that it a license was removed.
167
     * This should only be called by License::removeBookable().
168
     */
169 1
    public function licenseRemoved(License $license): void
170
    {
171 1
        $this->licenses->removeElement($license);
172
    }
173
174 6
    public function getInitialPrice(): Money
175
    {
176 6
        return $this->initialPrice;
177
    }
178
179 6
    public function setInitialPrice(Money $initialPrice): void
180
    {
181 6
        $this->initialPrice = $initialPrice;
182
    }
183
184 10
    public function getPeriodicPrice(): Money
185
    {
186 10
        return $this->periodicPrice;
187
    }
188
189 8
    public function setPeriodicPrice(Money $periodicPrice): void
190
    {
191 8
        $this->periodicPrice = $periodicPrice;
192
    }
193
194
    public function getPurchasePrice(): ?Money
195
    {
196
        return $this->purchasePrice;
197
    }
198
199
    public function setPurchasePrice(?Money $purchasePrice): void
200
    {
201
        $this->purchasePrice = $purchasePrice;
202
    }
203
204 1
    public function getSimultaneousBookingMaximum(): int
205
    {
206 1
        return $this->simultaneousBookingMaximum;
207
    }
208
209
    public function setSimultaneousBookingMaximum(int $simultaneousBookingMaximum): void
210
    {
211
        $this->simultaneousBookingMaximum = $simultaneousBookingMaximum;
212
    }
213
214
    /**
215
     * @API\Field(type="BookingType")
216
     */
217 13
    public function getBookingType(): string
218
    {
219 13
        return $this->bookingType;
220
    }
221
222
    /**
223
     * Whether this bookable can be booked.
224
     */
225 2
    public function isActive(): bool
226
    {
227 2
        return $this->isActive;
228
    }
229
230
    /**
231
     * Whether this bookable can be booked.
232
     */
233
    public function setIsActive(bool $isActive): void
234
    {
235
        $this->isActive = $isActive;
236
    }
237
238
    /**
239
     * @API\Input(type="BookingType")
240
     */
241 1
    public function setBookingType(string $state): void
242
    {
243 1
        $this->bookingType = $state;
244
    }
245
246
    /**
247
     * State of the bookable.
248
     *
249
     * @API\Field(type="BookableState")
250
     */
251
    public function getState(): string
252
    {
253
        return $this->state;
254
    }
255
256
    /**
257
     * State of the bookable.
258
     *
259
     * @API\Input(type="BookableState")
260
     */
261
    public function setState(string $state): void
262
    {
263
        $this->state = $state;
264
    }
265
266
    /**
267
     * The date then the bookable was last checked.
268
     */
269
    public function getVerificationDate(): ?Date
270
    {
271
        return $this->verificationDate;
272
    }
273
274
    /**
275
     * The date then the bookable was last checked.
276
     */
277
    public function setVerificationDate(?Date $verificationDate): void
278
    {
279
        $this->verificationDate = $verificationDate;
280
    }
281
282 6
    public function getBookableTags(): Collection
283
    {
284 6
        return $this->bookableTags;
285
    }
286
287
    /**
288
     * Notify the user that it has a new bookableTag.
289
     * This should only be called by BookableTag::addUser().
290
     */
291
    public function bookableTagAdded(BookableTag $bookableTag): void
292
    {
293
        $this->bookableTags->add($bookableTag);
294
    }
295
296
    /**
297
     * Notify the user that it a bookableTag was removed.
298
     * This should only be called by BookableTag::removeUser().
299
     */
300
    public function bookableTagRemoved(BookableTag $bookableTag): void
301
    {
302
        $this->bookableTags->removeElement($bookableTag);
303
    }
304
305 1
    public function getImage(): ?Image
306
    {
307 1
        return $this->image;
308
    }
309
310 1
    public function setImage(?Image $image): void
311
    {
312
        // We must trigger lazy loading, otherwise Doctrine will seriously
313
        // mess up lifecycle callbacks and delete unrelated image on disk
314 1
        if ($this->image) {
315 1
            $this->image->getFilename();
316
        }
317
318 1
        $this->image = $image;
319
    }
320
321
    /**
322
     * The account to credit when booking this bookable.
323
     */
324 8
    public function getCreditAccount(): ?Account
325
    {
326 8
        return $this->creditAccount;
327
    }
328
329
    /**
330
     * The account to credit when booking this bookable.
331
     */
332 5
    public function setCreditAccount(?Account $creditAccount): void
333
    {
334 5
        $this->creditAccount = $creditAccount;
335
    }
336
337
    /**
338
     * Returns list of active bookings.
339
     *
340
     * Limits to admin-assigned, application and admin-approved
341
     *
342
     * @return Booking[]
343
     */
344 1
    public function getSharedBookings(): array
345
    {
346 1
        $bookableType = $this->getBookingType();
347 1
        $bookableTypesAllowed = [BookingTypeType::ADMIN_ASSIGNED, BookingTypeType::APPLICATION, BookingTypeType::ADMIN_APPROVED];
348
349 1
        if (!in_array($bookableType, $bookableTypesAllowed, true)) {
350 1
            return [];
351
        }
352
353
        // Only consider approved and unterminated bookings
354 1
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate() && $booking->getStatus() !== BookingStatusType::APPLICATION)->toArray();
355
356 1
        return $bookings;
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() === BookingTypeType::ADMIN_ASSIGNED;
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