Completed
Pull Request — master (#20)
by Vojta
11:17 queued 02:33
created

ReservationsFacade::transformDateTime()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 40
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.0283

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 40
ccs 11
cts 12
cp 0.9167
rs 6.7272
cc 7
eloc 18
nc 6
nop 1
crap 7.0283
1
<?php namespace VojtaSvoboda\Reservations\Facades;
2
3
use Auth;
4
use Carbon\Carbon;
5
use Config;
6
use Event;
7
use Lang;
8
use Illuminate\Database\Eloquent\Collection;
9
use Illuminate\Support\Facades\DB;
10
use October\Rain\Exception\ApplicationException;
11
use October\Rain\Exception\ValidationException;
12
use VojtaSvoboda\Reservations\Classes\DatesResolver;
13
use VojtaSvoboda\Reservations\Classes\Variables;
14
use VojtaSvoboda\Reservations\Mailers\ReservationAdminMailer;
15
use VojtaSvoboda\Reservations\Mailers\ReservationMailer;
16
use VojtaSvoboda\Reservations\Models\Reservation;
17
use VojtaSvoboda\Reservations\Models\Settings;
18
use VojtaSvoboda\Reservations\Models\Status;
19
20
/**
21
 * Public reservations facade.
22
 *
23
 * Usage: App::make(ReservationsFacade::class);
24
 *
25
 * @package VojtaSvoboda\Reservations\Facades
26
 */
27
class ReservationsFacade
28
{
29
    /** @var Reservation $reservations */
30
    private $reservations;
31
32
    /** @var Status $statuses */
33
    private $statuses;
34
35
    /** @var DatesResolver $datesResolver */
36
    private $datesResolver;
37
38
    /** @var array $returningUsersCache */
39
    private $returningUsersCache;
40
41
    /** @var ReservationMailer $mailer */
42
    private $mailer;
43
44
    /** @var ReservationAdminMailer $adminMailer */
45
    private $adminMailer;
46
47
    /**
48
     * ReservationsFacade constructor.
49
     *
50
     * @param Reservation $reservations
51
     * @param Status $statuses
52
     * @param DatesResolver $resolver
53
     * @param ReservationMailer $mailer
54
     * @param ReservationAdminMailer $adminMailer
55 14
     */
56
    public function __construct(
57
        Reservation $reservations, Status $statuses, DatesResolver $resolver,
58
        ReservationMailer $mailer, ReservationAdminMailer $adminMailer
59 14
    ) {
60 14
        $this->reservations = $reservations;
61 14
        $this->statuses = $statuses;
62 14
        $this->datesResolver = $resolver;
63 14
        $this->mailer = $mailer;
64 14
        $this->adminMailer = $adminMailer;
65
    }
66
67
    /**
68
     * Create and store reservation.
69
     *
70
     * @param array $data
71
     *
72
     * @return Reservation $reservation
73
     *
74
     * @throws ApplicationException
75
     * @throws ValidationException
76 7
     */
77
    public function storeReservation($data)
78
    {
79 7
        // check number of sends
80
        $this->checkLimits();
81
82 7
        // transform date and time to Carbon
83
        $data['date'] = $this->transformDateTime($data);
84
85 5
        // place for extending
86
        Event::fire('vojtasvoboda.reservations.processReservation', [&$data]);
87
88 5
        // create reservation
89
        $reservation = $this->reservations->create($data);
90
91 5
        // send mails to client and admin
92
        $this->sendMails($reservation);
93 5
94
        return $reservation;
95
    }
96
97
    /**
98
     * Send mail to client and admin.
99
     *
100
     * @param Reservation $reservation
101 5
     */
102
    public function sendMails($reservation)
103
    {
104 5
        // calculate reservations by same email
105
        $sameCount = $this->getReservationsCountByMail($reservation->email);
106
107 5
        // send reservation confirmation to customer
108
        $this->mailer->send($reservation, $sameCount);
109
110 5
        // send reservation confirmation to admin
111 5
        $this->adminMailer->send($reservation, $sameCount);
112
    }
113
114
    /**
115
     * Get all reservations.
116
     *
117
     * @return Collection
118
     */
119
    public function getReservations()
120
    {
121
        return $this->reservations->all();
122
    }
123
124
    /**
125
     * Get all active (not cancelled) reservations.
126
     *
127
     * @return Collection
128
     */
129
    public function getActiveReservations()
130
    {
131
        return $this->reservations->notCancelled()->get();
132
    }
133
134
    /**
135
     * Get all reserved time slots.
136
     *
137
     * @return array
138
     */
139
    public function getReservedDates()
140
    {
141
        $reservations = $this->getActiveReservations();
142
143
        return $this->datesResolver->getDatesFromReservations($reservations);
144
    }
145
146
    /**
147
     * Get all reservations by given date interval.
148
     *
149
     * @param Carbon $since Date from.
150
     * @param Carbon $till Date to.
151
     *
152
     * @return mixed
153
     */
154
    public function getReservationsByInterval(Carbon $since, Carbon $till)
155
    {
156
        return $this->reservations->whereBetween('date', [$since, $till])->get();
157
    }
158
159
    /**
160
     * Get reservations count by one email.
161
     *
162
     * @param $email
163
     *
164
     * @return int
165 5
     */
166
    public function getReservationsCountByMail($email)
167 5
    {
168
        return $this->reservations->where('email', $email)->notCancelled()->count();
169
    }
170
171
    /**
172
     * Is user returning or not? You have to set this parameter at Backend Reservations setting.
173
     *
174
     * @param $email
175
     *
176
     * @return bool
177 1
     */
178
    public function isUserReturning($email)
179
    {
180 1
        // when disabled, user is never returning
181 1
        $threshold = Settings::get('returning_mark', 0);
182
        if ($threshold < 1) {
183
            return false;
184
        }
185
186 1
        // load emails count
187
        if ($this->returningUsersCache === null) {
188 1
            $items = $this
189 1
                ->reservations
190 1
                ->select(DB::raw('email, count(*) as count'))
191 1
                ->groupBy('email')
192
                ->get();
193 1
            // refactor to mapWithKeys after upgrade to Laravel 5.3.
194 1
            foreach($items as $item) {
195
                $this->returningUsersCache[$item['email']] = $item['count'];
196
            }
197
        }
198 1
199 1
        $returning = $this->returningUsersCache;
200
        $actual = isset($returning[$email]) ? $returning[$email] : 0;
201 1
202
        return $threshold > 0 && $actual >= $threshold;
203
    }
204
205
    /**
206
     * Bulk reservation state change.
207
     *
208
     * @param array $ids
209
     * @param string $ident
210
     */
211
    public function bulkStateChange($ids, $ident)
212
    {
213
        // get state
214
        $status = $this->statuses->where('ident', $ident)->first();
215
        if (!$status) {
216
            return;
217
        }
218
219
        // go through reservations
220
        foreach ($ids as $id)
221
        {
222
            $reservation = $this->reservations->find($id);
223
            if (!$reservation) {
224
                continue;
225
            }
226
227
            $reservation->status = $status;
228
            $reservation->save();
229
        }
230
    }
231
232
    /**
233
     * Bulk reservations delete.
234
     *
235
     * @param array $ids
236
     */
237
    public function bulkDelete($ids)
238
    {
239
        // go through reservations
240
        foreach ($ids as $id)
241
        {
242
            $reservation = $this->reservations->find($id);
243
            if (!$reservation) {
244
                continue;
245
            }
246
247
            $reservation->delete();
248
        }
249
    }
250
251
    /**
252
     * Transform date and time to DateTime string.
253
     *
254
     * @param $data
255
     *
256
     * @return Carbon
257
     *
258
     * @throws ApplicationException
259 8
     */
260
    public function transformDateTime($data)
261
    {
262 8
        // validate date
263 1
        if (empty($data['date'])) {
264
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.empty_date'));
265
        }
266
267 7
        // validate time
268 1
        if (empty($data['time'])) {
269
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.empty_hour'));
270
        }
271 6
272
        // convert input to datetime format
273 6
        $format = Variables::getDateTimeFormat();
274
        $dateTime = Carbon::createFromFormat($format, trim($data['date'] . ' ' . $data['time']));
275
276
        // validate date + time > current
277
        if ($dateTime->timestamp < Carbon::now()->timestamp) {
278
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.past_date'));
279
        }
280
281
        // validate days off
282
        if (!in_array($dateTime->dayOfWeek, $this->getWorkingDays())) {
283
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.days_off'));
284 11
        }
285
286
        // validate out of hours
287 11
        $workTime = $this->getWorkingTime();
288
289
        // convert hour and minutes to minutes
290 11
        $timeToMinute = $dateTime->hour * 60 + $dateTime->minute;
291
        $workTimeFrom = $workTime['from']['hour'] * 60 + $workTime['from']['minute'];
292
        $workTimeTo   = $workTime['to']['hour'] * 60 + $workTime['to']['minute'];
293 11
294
        if ($timeToMinute < $workTimeFrom || $timeToMinute > $workTimeTo) {
295
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.out_of_hours'));
296
        }
297 11
298
        return $dateTime;
299
    }
300
301
    /**
302
     * Get working days.
303
     *
304
     * @return array
305 7
     */
306
    public function getWorkingDays()
307 7
    {
308 1
        $daysWorkInput = Variables::getWorkingDays();
309
        $daysWork = [];
310 7
        $allDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
311
312
        foreach ($allDays as $index => $day) {
313
            if (in_array($day, $daysWorkInput)) {
314
                $daysWork[] = $index + 1;
315
            }
316
        }
317 7
318
        return $daysWork;
319
    }
320 7
321 7
    /**
322
     * Get working time.
323
     *
324 7
     * @return array
325
     */
326 7
    public function getWorkingTime()
327
    {
328
        $workTime = [];
329
330
        $work_time_from = explode(':', Variables::getWorkTimeFrom());
331
        $workTime['from']['hour'] = (int) $work_time_from[0];
332
        $workTime['from']['minute'] = isset($work_time_from[1]) ? (int) $work_time_from[1] : 0;
333
334
        $work_time_to = explode(':', Variables::getWorkTimeTo());
335
        $workTime['to']['hour'] = (int) $work_time_to[0];
336
        $workTime['to']['minute'] = isset($work_time_to[1]) ? (int) $work_time_to[1] : 0;
337
338
        return $workTime;
339
    }
340
341
    /**
342
     * Returns if given date is available.
343
     *
344
     * @param Carbon $date
345
     * @param int $exceptId Except reservation ID.
346
     *
347
     * @return bool
348
     */
349
    public function isDateAvailable($date, $exceptId = null)
350
    {
351
        // get boundary dates for given reservation date
352
        $boundaries = $this->datesResolver->getBoundaryDates($date);
353
354
        // get all reservations in this date
355
        $query = $this->reservations->notCancelled()->whereBetween('date', $boundaries);
356
357
        // if updating reservation, we should skip existing reservation
358
        if ($exceptId !== null) {
359
            $query->where('id', '!=', $exceptId);
360
        }
361
362
        return $query->count() === 0;
363
    }
364
365
    /**
366
     * Check reservations amount limit per time.
367
     *
368
     * @throws ApplicationException
369
     */
370
    private function checkLimits()
371
    {
372
        if ($this->isCreatedWhileAgo()) {
373
            throw new ApplicationException(Lang::get('vojtasvoboda.reservations::lang.errors.please_wait'));
374
        }
375
    }
376
377
    /**
378
     * Try to find some reservation in less then given limit (default 30 seconds).
379
     *
380
     * @return boolean
381
     */
382
    public function isCreatedWhileAgo()
383
    {
384
        // protection time
385
        $time = Config::get('vojtasvoboda.reservations::config.protection_time', '-30 seconds');
386
        $timeLimit = Carbon::parse($time)->toDateTimeString();
387
388
        // try to find some message
389
        $item = $this->reservations->machine()->where('created_at', '>', $timeLimit)->first();
390
391
        return $item !== null;
392
    }
393
}
394