Completed
Pull Request — master (#28)
by claudio
07:42 queued 01:16
created

Optimise::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 7
Bugs 3 Features 0
Metric Value
c 7
b 3
f 0
dl 0
loc 8
ccs 6
cts 6
cp 1
rs 9.4286
cc 1
eloc 5
nc 1
nop 3
crap 1
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: claudio
5
 * Date: 12/12/15
6
 * Time: 15.41
7
 */
8
9
namespace plunner\Console\Commands\Optimise;
10
11
use Illuminate\Console\Scheduling\Schedule;
12
use plunner\company;
13
use plunner\Events\Optimise\ErrorEvent;
14
15
/**
16
 * Class Optimise
17
 * @author Claudio Cardinale <[email protected]>
18
 * @copyright 2015 Claudio Cardinale
19
 * @version 1.0.0
20
 * @package plunner\Console\Commands\Optimise
21
 */
22
class Optimise
23
{
24
    //TODO insert MAX timeslots limit during meeting creation
25
    //TODo max timeslots can be an environment var
26
    const TIME_SLOT_DURATION = 900; //seconds -> 15 minutes
27
28
    private $max_time_slots = 20; //max duration of a meeting in term of timeslots //20
29
    private $time_slots = 672; //total amount of timeslots that must be optimised -> one week 4*24*7 = 672
30
31
    //TODO timezone
32
    //TODO fix here
33
    /**
34
     * @var \DateTime
35
     */
36
    private $startTime;
37
    /**
38
     * @var \DateTime
39
     */
40
    private $endTime;
41
42
    /**
43
     * @var Company
44
     */
45
    private $company;
46
47
    /**
48
    * @var Schedule laravel schedule object needed to perform command in background
49
    */
50
    private $schedule;
51
52
    /**
53
     * @var \Illuminate\Contracts\Foundation\Application;
54
     */
55
    private $laravel;
56
57
    /**
58
     * @var Solver
59
     */
60
    private $solver = null;
61
62
    //TODO clone
63
    //TODO to_string
64
65
    /**
66
     * Optimise constructor.
67
     * @param company $company
68
* @param Schedule $schedule
69
     * @param \Illuminate\Contracts\Foundation\Application $laravel
70
     */
71 2
    public function __construct(company $company, Schedule $schedule, \Illuminate\Contracts\Foundation\Application $laravel)
72
    {
73 2
        $this->company = $company;
0 ignored issues
show
Documentation Bug introduced by
It seems like $company of type object<plunner\Company> is incompatible with the declared type object<plunner\Console\Commands\Optimise\Company> of property $company.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
74 2
        $this->schedule = $schedule;
75 2
        $this->laravel = $laravel;
76
77 2
        $this->setStartTime((new \DateTime())->modify('next monday'));
78 2
    }
79
80
81
    /**
82
     * @param \DateTime $startTime
83
     */
84 2
    public function setStartTime(\DateTime $startTime)
85
    {
86 2
        $this->startTime = clone $startTime;
87 2
        $this->endTime = clone $this->startTime;
88 2
        $this->endTime->add(new \DateInterval('PT'.(($this->max_time_slots+$this->time_slots)*self::TIME_SLOT_DURATION).'S'));
89 2
    }
90
91
    /**
92
     * @return int
93
     */
94
    public function getMaxTimeSlots()
95
    {
96
        return $this->max_time_slots;
97
    }
98
99
    /**
100
     * @param int $max_time_slots
101
     */
102 2
    public function setMaxTimeSlots($max_time_slots)
103
    {
104 2
        $this->max_time_slots = $max_time_slots;
105 2
    }
106
107
    /**
108
     * @return int
109
     */
110 2
    public function getTimeSlots()
111
    {
112 2
        return $this->time_slots;
113
    }
114
115
    /**
116
     * @param int $time_slots
117
     */
118 2
    public function setTimeSlots($time_slots)
119
    {
120 2
        $this->time_slots = $time_slots;
121 2
    }
122
123
124
    /**
125
     * @return Company
126
     */
127
    public function getCompany()
128
    {
129
        return $this->company;
130
    }
131
132
    /**
133
     * @param Company $company
134
     */
135
    public function setCompany($company)
136
    {
137
        $this->company = $company;
138
    }
139
140
    /**
141
     * @return Solver
142
     */
143 2
    public function getSolver()
144
    {
145 2
        return $this->solver;
146
    }
147
148
149
    //TODo fix php doc
150
    /**
151
     * @return Optimise
152
     */
153 2
    public function optimise()
154 2
    {
155
        //TODO ...
156 2
        $solver = new Solver($this->schedule, $this->laravel);
0 ignored issues
show
Compatibility introduced by
$this->laravel of type object<Illuminate\Contra...Foundation\Application> is not a sub-type of object<Illuminate\Foundation\Application>. It seems like you assume a concrete implementation of the interface Illuminate\Contracts\Foundation\Application to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
157 2
        $solver = $this->setData($solver);
158 2
        $solver = $solver->solve();
159 2
        $this->solver = $solver;
160 2
        return $this;
161
        //print_r($solver->getOutput());
162
        //print_r($solver->getXResults());
163
        //print_r($solver->getYResults());
164
        //TODO try...catch
165
    }
166
167
    /**
168
     * @return Optimise
169
     */
170 2
    public function save()
171
    {
172 2
        if(!($this->solver instanceof Solver)) {
173
            \Event::fire(new ErrorEvent($this->company, 'solver is not an instace of Solver'));
174
            return;
175
        }
176
        //TODO try catch solver
177
        //TODO check results before save them
178 2
        $this->saveMeetings($this->solver);
179 2
        $this->saveEmployeesMeetings($this->solver);
180 2
        return $this;
181
    }
182
183
    /**
184
     * @param Solver $solver
185
     */
186 2
    private function saveMeetings(Solver $solver)
187
    {
188 2
        $meetings = $solver->getYResults();
189 2
        foreach($meetings as $id=>$meeting){
190 2
            $meetingO = \plunner\Meeting::findOrFail($id);//TODO catch error
191 2
            $meetingO->start_time = $this->toDateTime(array_search('1', $meeting));
192 2
            $meetingO->save();
193 2
        }
194 2
    }
195
196
    /**
197
     * @param Solver $solver
198
     */
199 2
    private function saveEmployeesMeetings(Solver $solver)
200
    {
201 2
        $employeesMeetings = $solver->getXResults();
202 2
        foreach($employeesMeetings as $eId =>$employeeMeetings)
203
        {
204 2
            $employee = \plunner\Employee::findOrFail($eId);
205 2
            $employeeMeetings = collect($employeeMeetings);
206
            $employeeMeetings = $employeeMeetings->filter(function ($item) {
207 2
                return $item == 1;
208 2
            });
209 2
            $employee->meetings()->attach($employeeMeetings->keys()->toArray());
210 2
        }
211 2
    }
212
213
214
    /**
215
     * @param Solver $solver
216
     * @return Solver
217
     * @throws OptimiseException
218
     */
219 2
    private function setData(Solver $solver)
220
    {
221
        //TODO...
222
        //TODO get avalability only of this week
223
224 2
        $solver = $this->setTimeSlotsSolver($solver);
225 2
        $solver = $this->setUsers($solver);
226 2
        $solver = $this->setAllMeetingsInfo($solver);
227 2
        $solver = $this->setUserAvailability($solver);
228 2
        $solver = $this->setUsersMeetings($solver);
229 2
        return $solver;
230
    }
231
232
    /**
233
     * @param Solver $solver
234
     * @return Solver
235
     * @throws OptimiseException
236
     */
237 2
    private function setTimeSlotsSolver(Solver $solver)
238
    {
239 2
        return $solver->setTimeSlots($this->time_slots)->setMaxTimeSlots($this->max_time_slots);
240
    }
241
242
    /**
243
     * @param Solver $solver
244
     * @return Solver
245
     */
246 2
    private function setUsers(Solver $solver)
247
    {
248
        //since we consider busy timeslots, we need to get all users
249 2
        $users = $this->company->employees->pluck('id')->toArray();
250 2
        return $solver->setUsers($users);
251
    }
252
253
    /**
254
     * @param Solver $solver
255
     * @return Solver
256
     */
257 2
    private function setAllMeetingsInfo(Solver $solver)
258
    {
259
        /**
260
         * @var $meetings \Illuminate\Support\Collection
261
         */
262 2
        $meetings = collect($this->company->getMeetingsTimeSlots($this->startTime, $this->endTime));
263
        $timeslots = $meetings->groupBy('id')->map(function($item) { //convert timeslots
264 2
                return $this->timeSlotsConverter($item);
265 2
            });
266 2
        return $solver->setMeetings($timeslots->keys()->toArray())
267 2
            ->setMeetingsDuration($meetings->pluck('duration','id')->toArray())
268 2
            ->setMeetingsAvailability(self::getAvailabilityArray($timeslots, $this->time_slots));
269
    }
270
271
    /**
272
     * @param Solver $solver
273
     * @return Solver
274
     * @throws OptimiseException
275
     */
276 2
    private function setUserAvailability(Solver $solver)
277
    {
278
        /**
279
         * @var $users \Illuminate\Support\Collection
280
         */
281 2
        $users = collect($this->company->getEmployeesTimeSlots($this->startTime, $this->endTime));
282
        $timeslots = $users->groupBy('id')->map(function($item) { //convert timeslots
283 2
                return $this->timeSlotsConverter($item);
284 2
            });
285 2
        return $solver->setUsersAvailability(self::getAvailabilityArray($timeslots, $this->time_slots, false));
286
    }
287
288
    /**
289
     * @param Solver $solver
290
     * @return Solver
291
     * @throws OptimiseException
292
     */
293 2
    private function setUsersMeetings(Solver $solver)
294
    {
295 2
        $users = $solver->getUsers();
296 2
        $meetings = $solver->getMeetings();
297
        /**
298
         * @var $usersMeetings \Illuminate\Support\Collection
299
         */
300 2
        $usersMeetings = collect($this->company->getUsersMeetings($users, $meetings))->groupBy('employee_id');
301
302 2
        return $solver->setUsersMeetings(self::getUsersMeetingsArray($users, $meetings, $usersMeetings));
303
    }
304
305
    /**
306
     * @param array $users
307
     * @param array $meetings
308
     * @param \Illuminate\Support\Collection $usersMeetings
309
     * @return array
310
     */
311 2
    static private function getUsersMeetingsArray($users, $meetings, \Illuminate\Support\Collection $usersMeetings)
312
    {
313 2
        $ret = [];
314 2
        foreach($users as $user)
315
        {
316 2
            $usersMeetingsTmp = $usersMeetings->get($user);
317 2
            foreach($meetings as $meeting){
318 2
                if($usersMeetingsTmp->contains('meeting_id', $meeting)){
319 2
                    $ret[$user][$meeting] = 1;
320 2
                }else{
321 2
                    $ret[$user][$meeting] = 0;
322
                }
323 2
            }
324 2
        }
325
326 2
        return $ret;
327
    }
328
329
    private function timeSlotsConverter($item)
330
    {
331 2
        return $item->each(function($item2){
332 2
            $item2->time_start = $this->toTimeSlot($item2->time_start);
333 2
            $item2->time_end = $this->toTimeSlot($item2->time_end);
334 2
            return $item2;
335
            //TODO try catch
336 2
        });
337
    }
338
339
    /**
340
     * @param \Illuminate\Support\Collection $timeSlots
341
     * @param bool|true $free if true the array is filled with 1 for timeslots values else with 0 for timeslots values
342
     * @return array
343
     */
344 2
    static private function getAvailabilityArray(\Illuminate\Support\Collection $timeSlots, $timeslotsN, $free=true)
345
    {
346 2
        $ret = [];
347 2
        foreach($timeSlots as $id=>$timeSlots2)
348
        {
349 2
            $ret = self::fillTimeSlots($ret, $id, $timeSlots2, $free?'1':'0');
350 2
            $ret = self::fillRow($ret, $id, $timeslotsN, $free?'0':'1');
351 2
        }
352
353 2
        return $ret;
354
    }
355
356
    /**
357
     * @param array $array
358
     * @param int $id
359
     * @param \Illuminate\Support\Collection $timeSlots
360
     * @param string $fill
361
     * @return array
362
     */
363 2
    static private function fillTimeSlots(array $array, $id, \Illuminate\Support\Collection $timeSlots, $fill = '0')
364
    {
365 2
        foreach($timeSlots as $timeSlot) {
366 2
            if(!isset($array[$id]))
367 2
                $array[$id] = [];
368 2
            $array[$id] = self::arrayPadInterval($array[$id], $timeSlot->time_start, $timeSlot->time_end, $fill);
369 2
        }
370 2
        return $array;
371
    }
372
373
    /**
374
     * @param array $array
375
     * @param int $id
376
     * @param string $fill
377
     * @return array
378
     */
379 2
    static private function fillRow(array $array, $id, $until, $fill = '0')
380
    {
381 2
        for($i = 1; $i <= $until; $i++){
382 2
            if(!isset($array[$id][$i]))
383 2
                $array[$id][$i] = $fill;
384 2
        }
385
386 2
        return $array;
387
    }
388
389
    /**
390
     * @param array $array
391
     * @param int $from
392
     * @param int $to
393
     * @param string $pad
394
     * @return array
395
     */
396 2
    static private function arrayPadInterval(array $array, $from, $to, $pad = '0')
397
    {
398 2
        for($i = $from; $i<$to; $i++)
399 2
            $array[$i] = $pad;
400 2
        return $array;
401
    }
402
403
404
    /**
405
     * @param mixed $time
406
     * @return int
407
     * @throws OptimiseException
408
     */
409 2
    private function toTimeSlot($time)
410
    {
411 2
        $dateTime = new \DateTime($time);
412 2
        $diff = $dateTime->diff($this->startTime);
413 2
        $diff = explode(':',$diff->format('%R:%d:%h:%i:%s'));
414 2
        $diff = $diff[1]*86400 + $diff[2]*3600 + $diff[3]*60 + $diff[4];
415
        //if($diff[0] != '-' && $diff != 0)
416
          //  throw new OptimiseException('timeslot time <= startTime');
417
        //TODO fix check
418
        //TODO check if diff makes sense
419
        //TODO check upper limit
420 2
        return (int)(round($diff/self::TIME_SLOT_DURATION)+1); //TODO can round cause overlaps?
421
    }
422
423
    /**
424
     * @param int $timeslot
425
     * @return \DateTime
426
     */
427 2
    private function toDateTime($timeslot)
428
    {
429 2
        $ret = clone $this->startTime;
430 2
        return $ret->add(new \DateInterval('PT'.(($timeslot-1)*self::TIME_SLOT_DURATION).'S'));
431
    }
432
}