Scheduler   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 297
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 37
eloc 75
dl 0
loc 297
rs 9.44
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
B validateSchedule() 0 23 7
A hasConflict() 0 9 2
A init() 0 3 1
A isShouldntAdd() 0 3 2
A availableToday() 0 3 1
A __clone() 0 6 2
A parseToSchedule() 0 6 2
B availableOn() 0 40 8
A whereBetween() 0 6 1
A setModelType() 0 5 1
A parseToCarbon() 0 12 5
A getModelType() 0 3 1
A newInstance() 0 3 1
A byModel() 0 5 1
A avoid() 0 5 1
A __construct() 0 3 1
1
<?php namespace H4ad\Scheduler;
2
3
/**
4
 * Esse arquivo faz parte do Scheduler,
5
 * uma biblioteca para auxiliar com agendamentos.
6
 *
7
 * @license MIT
8
 * @package H4ad\Scheduler
9
 */
10
11
use Closure;
12
use Carbon\Carbon;
13
use H4ad\Scheduler\Models\Schedule;
14
use Illuminate\Support\Facades\Config;
15
use Illuminate\Support\Traits\Macroable;
16
use H4ad\Scheduler\Models\ScheduleStatus;
17
use H4ad\Scheduler\Exceptions\CantAddWithoutEnd;
18
use H4ad\Scheduler\Exceptions\IntInvalidArgument;
19
use H4ad\Scheduler\Exceptions\EndCantBeforeStart;
20
use H4ad\Scheduler\Exceptions\CantAddWithSameStartAt;
21
22
class Scheduler
23
{
24
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by H4ad\Scheduler\Scheduler.
Loading history...
25
26
    /**
27
     * Query a ser executada.
28
     *
29
     * @var \Illuminate\Database\Eloquent\Builder
30
     */
31
    protected $query;
32
33
    /**
34
     * Tipo de model usada para validar.
35
     *
36
     * @var string
37
     */
38
    protected $model_type;
39
40
    /**
41
     * Closure de boot.
42
     *
43
     * @var Closure
44
     */
45
    protected static $boot;
46
47
    /**
48
     * Construtor da classe.
49
     */
50
    public function __construct()
51
    {
52
        $this->query = Schedule::query();
53
    }
54
55
    /**
56
     * Define método executado toda vez que é gerada uma nova instância do Scheduler.
57
     * Há apenas um unico parâmetro passado para a função que é o query.
58
     *
59
     * @param  \Closure $boot_method
60
     * @return void
61
     */
62
    public function init(Closure $boot)
63
    {
64
        static::$boot = $boot;
65
    }
66
67
    /**
68
     * Ignora uma lista de ids da model.
69
     *
70
     * @param  array  $model_ids
71
     * @return $this
72
     */
73
    public function avoid(array $model_ids)
74
    {
75
        $this->query->whereNotIn('model_id', $model_ids);
76
77
        return $this;
78
    }
79
80
    /**
81
     * Verifica se há conflito de horários registrados em um intervalo de tempo.
82
     *
83
     * @param  \Carbon\Carbon|string $start_at
84
     * @param  \Carbon\Carbon|string $end_at
85
     * @return boolean
86
     */
87
    public function hasConflict($start_at, $end_at)
88
    {
89
        if(!Config::get('scheduler.enable_schedule_conflict'))
90
            return false;
91
92
        $this->byModel();
93
        $this->whereBetween($start_at, $end_at);
94
95
        return $this->query->exists();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->exists() also could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the documented return type boolean.
Loading history...
96
    }
97
98
    /**
99
     * Busca horários que estejam dentro um intervalo de tempo.
100
     *
101
     * @param  \Carbon\Carbon|string $start_at
102
     * @param  \Carbon\Carbon|string $end_at
103
     * @return $this
104
     */
105
    public function whereBetween($start_at, $end_at)
106
    {
107
        $this->query->where('start_at', '>=', $start_at)
108
              ->where('end_at', '<=', $end_at);
109
110
        return $this;
111
    }
112
113
    /**
114
     * Define o tipo de model buscada.
115
     *
116
     * @param  string|null $model_type
117
     * @return $this
118
     */
119
    public function byModel()
120
    {
121
        $this->query->where('model_type', $this->getModelType());
122
123
        return $this;
124
    }
125
126
    /**
127
     * Seta a model padrão.
128
     *
129
     * @param string $model_type
130
     * @return $this
131
     */
132
    public function setModelType(string $model_type)
133
    {
134
        $this->model_type = $model_type;
135
136
        return $this;
137
    }
138
139
    /**
140
     * Retorna a model type.
141
     *
142
     * @return string
143
     */
144
    public function getModelType()
145
    {
146
        return $this->model_type ?? Config::get('scheduler.default_model');
147
    }
148
    /**
149
     * Retorna os horários disponiveis hoje para uma determinada model.
150
     * .
151
     * @param  int $duration Serve para facilitar na hora de buscar horários livres que precisem ter uma certa duração.
152
     * @param \Carbon\Carbon|null $openingTime Serve como referencia para buscar horários livres. Se for nulo, ele busca a referencia da config.
153
     * @return array
154
     */
155
    public function availableToday(int $duration, Carbon $openingTime = null)
156
    {
157
        return $this->availableOn(Carbon::today(), $duration, $openingTime);
158
    }
159
160
    /**
161
     * Retorna os horários disponiveis em um determinado dia para uma certa model.
162
     *
163
     * @param  \Carbon\Carbon $today Data para o qual ele irá fazer a busca.
164
     * @param  int $durationMinutes Serve para facilitar na hora de buscar horários livres que precisem ter uma certa duração.
165
     * @param  \Carbon\Carbon|null $openingTime Serve como referencia para buscar horários livres. Se for nulo, ele busca a referencia da config.
166
     * @return array
167
     */
168
    public function availableOn(Carbon $today, int $durationMinutes, Carbon $openingTime = null)
169
    {
170
        $openingTime = $openingTime ?? Carbon::parse(Config::get('scheduler.opening_time'))->setDateFrom($today);
171
        $closingTime = Carbon::parse(Config::get('scheduler.closing_time'))->setDateFrom($today);
172
173
        $livres = [];
174
        $today = Carbon::parse($today->toDateString());
175
        while($openingTime <= $closingTime)
176
        {
177
            $add = true;
178
179
            $opening = Carbon::parse($openingTime->toDateTimeString());
180
            $closing = Carbon::parse($openingTime->toDateTimeString())->addMinutes($durationMinutes);
181
182
            foreach ($this->query->orderBy('start_at', 'DESC')->cursor() as $schedule) {
183
                $start = Carbon::parse($schedule->start_at);
184
                $begin = Carbon::parse($start->toDateString());
185
186
                if($begin->greaterThan($today))
187
                    break;
188
189
                if($begin->notEqualTo($today))
190
                    continue;
191
192
                $end = Carbon::parse($schedule->end_at);
193
194
                if($this->isShouldntAdd($opening, $closing, $start, $end))
195
                    $add = false;
196
            }
197
198
            if($add && $closing->lessThanOrEqualTo($closingTime))
199
                $livres[] = [
200
                    'start_at' => $opening,
201
                    'end_at' => $closing
202
                ];
203
204
            $openingTime->addMinutes($durationMinutes);
205
        }
206
207
        return $livres;
208
    }
209
210
    /**
211
     * Verifica se ele não deve ser adicionado ao array de horários livres.
212
     *
213
     * @param  \Carbon\Carbon  $opening
214
     * @param  \Carbon\Carbon  $closing
215
     * @param  \Carbon\Carbon  $start
216
     * @param  \Carbon\Carbon  $end
217
     * @return boolean
218
     */
219
    private function isShouldntAdd(Carbon $opening, Carbon $closing, Carbon $start, Carbon $end)
220
    {
221
        return $start <= $opening && $end >= $closing;
222
    }
223
224
    /**
225
     * Valida e retorna os dados formatados de forma correta em um [array].
226
     *
227
     * @param  \Carbon\Carbon|string $start_at  Data em que será agendado, pode ser em string ou em numa classe Carbon.
228
     * @param  \Carbon\Carbon|string|int|null $end_at   Data em que acabada esse agendamento, pode ser em string, ou numa classe Carbon ou em int(sendo considerado os minutos de duração).
229
     * @param  int|null $status Status desse horário ao ser agendado.
230
     * @return array
231
     *
232
     * @throws \H4ad\Scheduler\Exceptions\CantAddWithoutEnd
233
     * @throws \H4ad\Scheduler\Exceptions\EndCantBeforeStart
234
     * @throws \H4ad\Scheduler\Exceptions\CantAddWithSameStartAt
235
     */
236
    public function validateSchedule($start_at, $end_at = null, int $status = null)
237
    {
238
        if(!Config::get('scheduler.enable_schedule_without_end') && is_null($end_at))
239
            throw new CantAddWithoutEnd;
240
241
        $start_at  = $this->parseToCarbon($start_at);
242
243
        if(isset($end_at)) {
244
            $end_at = $this->parseToCarbon($end_at, $start_at);
245
246
            if($start_at->greaterThan($end_at))
247
                throw new EndCantBeforeStart;
248
        }
249
250
        if(isset($status))
251
            ScheduleStatus::findOrFail($status);
252
253
        if($this->hasConflict($start_at, $end_at ?? $start_at))
254
            throw new CantAddWithSameStartAt;
255
256
        $model_type = $this->getModelType();
257
258
        return compact('model_type', 'start_at', 'end_at', 'status');
259
    }
260
261
    /**
262
     * Faz um parse na data e retorna uma instância em Carbon.
263
     *
264
     * @param  \Carbon\Carbon|string|int $date Data final que será transformada numa instancia Carbon.
265
     * @param  \Carbon\Carbon $reference Data de referencia quando o [date] é inteiro.
266
     * @return \Carbon\Carbon
267
     *
268
     * @throws \H4ad\Scheduler\Exceptions\IntInvalidArgument
269
     */
270
    public function parseToCarbon($date, $reference = null)
271
    {
272
        if($date instanceof Carbon)
273
            return $date;
274
275
        if(is_string($date))
276
            return Carbon::parse($date);
277
278
        if(is_int($date) && !is_null($reference))
279
            return Carbon::parse($reference->toDateTimeString())->addMinutes($date);
280
281
        throw new IntInvalidArgument;
282
    }
283
284
    /**
285
     * Faz um parse e retorna um Schedule.
286
     *
287
     * @param  \Carbon\Carbon|string|int $value Valor que representará a data ou o id a ser buscado.
288
     * @return \H4ad\Scheduler\Models\Schedule|null
289
     */
290
    public function parseToSchedule($value)
291
    {
292
        if(is_int($value))
293
            return Schedule::find($value);
294
295
        return Schedule::byStartAt($value)->first();
296
   }
297
298
    /**
299
     * Retorna uma instância do Scheduler.
300
     *
301
     * @return $this
302
     */
303
    public function newInstance()
304
    {
305
        return clone $this;
306
    }
307
308
   /**
309
    * Callback apos chamar um clone.
310
    *
311
    * @return void
312
    */
313
    public function __clone()
314
    {
315
        $this->query = Schedule::query();
316
317
        if(isset(static::$boot))
318
            (static::$boot)($this);
319
    }
320
}