Passed
Push — master ( 7f5f0e...4369d4 )
by Adrien
05:49
created

Bookable::getSharedBookings()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 11
rs 10
c 0
b 0
f 0
ccs 5
cts 5
cp 1
cc 3
nc 2
nop 0
crap 3
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\DBAL\Types\BookableStateType;
14
use Application\DBAL\Types\BookingStatusType;
15
use Application\DBAL\Types\BookingTypeType;
16
use Application\Repository\BookableRepository;
17
use Application\Repository\BookableTagRepository;
18
use Application\Traits\HasCode;
19
use Application\Traits\HasRemarks;
20
use Cake\Chronos\Date;
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: 'BookingType', length: 10, options: ['default' => BookingTypeType::ADMIN_APPROVED])]
59
    private string $bookingType = BookingTypeType::ADMIN_APPROVED;
60
61
    #[ORM\Column(type: 'boolean', options: ['default' => 1])]
62
    private bool $isActive = true;
63
64
    #[ORM\Column(type: 'BookableState', length: 10, options: ['default' => BookableStateType::GOOD])]
65
    private string $state = BookableStateType::GOOD;
66
67
    #[ORM\Column(type: 'date', nullable: true)]
68
    private ?Date $verificationDate = null;
69
70
    /**
71
     * @var Collection<BookableTag>
72
     */
73
    #[ORM\ManyToMany(targetEntity: BookableTag::class, mappedBy: 'bookables')]
74
    private Collection $bookableTags;
75
76
    /**
77
     * @var Collection<Booking>
78
     */
79
    #[ORM\OneToMany(targetEntity: Booking::class, mappedBy: 'bookable')]
80
    private Collection $bookings;
81
82
    /**
83
     * @var Collection<License>
84
     */
85
    #[ORM\ManyToMany(targetEntity: License::class, mappedBy: 'bookables')]
86
    private Collection $licenses;
87
88
    #[ORM\OneToOne(targetEntity: Image::class, orphanRemoval: true)]
89
    #[ORM\JoinColumn(name: 'image_id', referencedColumnName: 'id')]
90
    private ?Image $image = null;
91
92
    #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')]
93
    #[ORM\ManyToOne(targetEntity: Account::class)]
94
    private ?Account $creditAccount = null;
95
96
    /**
97
     * Constructor.
98
     */
99 15
    public function __construct()
100
    {
101 15
        $this->initialPrice = Money::CHF(0);
102 15
        $this->periodicPrice = Money::CHF(0);
103
104 15
        $this->bookings = new ArrayCollection();
105 15
        $this->licenses = new ArrayCollection();
106 15
        $this->bookableTags = new ArrayCollection();
107
    }
108
109 5
    public function getBookings(): Collection
110
    {
111 5
        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 12
    public function bookingAdded(Booking $booking): void
119
    {
120 12
        $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 4
    public function getLicenses(): Collection
133
    {
134 4
        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 1
    public function licenseAdded(License $license): void
142
    {
143 1
        $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 3
    public function getSimultaneousBookingMaximum(): int
186
    {
187 3
        return $this->simultaneousBookingMaximum;
188
    }
189
190 1
    public function setSimultaneousBookingMaximum(int $simultaneousBookingMaximum): void
191
    {
192 1
        $this->simultaneousBookingMaximum = $simultaneousBookingMaximum;
193
    }
194
195 12
    #[API\Field(type: 'BookingType')]
196
    public function getBookingType(): string
197
    {
198 12
        return $this->bookingType;
199
    }
200
201
    /**
202
     * Whether this bookable can be booked.
203
     */
204 3
    public function isActive(): bool
205
    {
206 3
        return $this->isActive;
207
    }
208
209
    /**
210
     * Whether this bookable can be booked.
211
     */
212
    public function setIsActive(bool $isActive): void
213
    {
214
        $this->isActive = $isActive;
215
    }
216
217
    #[API\Input(type: 'BookingType')]
218
    public function setBookingType(string $state): void
219
    {
220
        $this->bookingType = $state;
221
    }
222
223
    /**
224
     * State of the bookable.
225
     */
226
    #[API\Field(type: 'BookableState')]
227
    public function getState(): string
228
    {
229
        return $this->state;
230
    }
231
232
    /**
233
     * State of the bookable.
234
     */
235
    #[API\Input(type: 'BookableState')]
236
    public function setState(string $state): void
237
    {
238
        $this->state = $state;
239
    }
240
241
    /**
242
     * The date then the bookable was last checked.
243
     */
244
    public function getVerificationDate(): ?Date
245
    {
246
        return $this->verificationDate;
247
    }
248
249
    /**
250
     * The date then the bookable was last checked.
251
     */
252
    public function setVerificationDate(?Date $verificationDate): void
253
    {
254
        $this->verificationDate = $verificationDate;
255
    }
256
257 6
    public function getBookableTags(): Collection
258
    {
259 6
        return $this->bookableTags;
260
    }
261
262
    /**
263
     * Notify the user that it has a new bookableTag.
264
     * This should only be called by BookableTag::addUser().
265
     */
266
    public function bookableTagAdded(BookableTag $bookableTag): void
267
    {
268
        $this->bookableTags->add($bookableTag);
269
    }
270
271
    /**
272
     * Notify the user that it a bookableTag was removed.
273
     * This should only be called by BookableTag::removeUser().
274
     */
275
    public function bookableTagRemoved(BookableTag $bookableTag): void
276
    {
277
        $this->bookableTags->removeElement($bookableTag);
278
    }
279
280 1
    public function getImage(): ?Image
281
    {
282 1
        return $this->image;
283
    }
284
285 1
    public function setImage(?Image $image): void
286
    {
287
        // We must trigger lazy loading, otherwise Doctrine will seriously
288
        // mess up lifecycle callbacks and delete unrelated image on disk
289 1
        if ($this->image) {
290 1
            $this->image->getFilename();
291
        }
292
293 1
        $this->image = $image;
294
    }
295
296
    /**
297
     * The account to credit when booking this bookable.
298
     */
299 8
    public function getCreditAccount(): ?Account
300
    {
301 8
        return $this->creditAccount;
302
    }
303
304
    /**
305
     * The account to credit when booking this bookable.
306
     */
307 5
    public function setCreditAccount(?Account $creditAccount): void
308
    {
309 5
        $this->creditAccount = $creditAccount;
310
    }
311
312
    /**
313
     * Returns list of active bookings, but only if the bookable has a limit of simultaneous booking. Otherwise, returns empty list.
314
     *
315
     * @return Booking[]
316
     */
317 3
    public function getSimultaneousBookings(): array
318
    {
319
        // Pretend to have no simultaneous bookings to avoid too many SQL queries when we don't really care about it
320 3
        if ($this->getSimultaneousBookingMaximum() < 0) {
321 1
            return [];
322
        }
323
324
        // Only consider approved and unterminated bookings
325 3
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate() && $booking->getStatus() !== BookingStatusType::APPLICATION)->toArray();
326
327 3
        return $bookings;
328
    }
329
330
    /**
331
     * Return a list of effective active bookings including sharing conditions.
332
     *
333
     * Only "admin-assigned" + storage tags are sharable bookables. In this case, a list of bookings is returned.
334
     *
335
     * For other bookable types, returns null
336
     *
337
     * @return Booking[]
338
     */
339 6
    public function getPeriodicPriceDividerBookings(): array
340
    {
341 6
        $isAdminAssigned = $this->getBookingType() === BookingTypeType::ADMIN_ASSIGNED;
342
343 6
        $isTagAllowed = false;
344 6
        $allowedTagIds = [BookableTagRepository::STORAGE_ID, BookableTagRepository::FORMATION_ID, BookableTagRepository::WELCOME_ID];
345 6
        foreach ($this->getBookableTags() as $tag) {
346 2
            if (in_array($tag->getId(), $allowedTagIds, true)) {
347 1
                $isTagAllowed = true;
348
349 1
                break;
350
            }
351
        }
352
353 6
        if (!$isAdminAssigned || !$isTagAllowed) {
354 6
            return [];
355
        }
356
357 1
        $bookings = $this->getBookings()->filter(fn (Booking $booking): bool => !$booking->getEndDate())->toArray();
358
359 1
        return $bookings;
360
    }
361
}
362