Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 138
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
EmployeesController
0.00% covered (danger)
0.00%
0 / 138
0.00% covered (danger)
0.00%
0 / 12
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 pageEmployees
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 getEmployees
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getClockedInEmployees
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getClockedInEmployeesFromWiwAPI
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
132
 getClockedInEmployeesFromCache
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 invalidateClockedInCache
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getClockedInEmployeesForQueue
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 getEmployeesFromWiwAPI
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getEmployeesFromCache
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 syncEmployeesTable
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 getEmployeesFromDB
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BuyerKiosk\WhenIWork\Controllers;
4
5use BuyerKiosk\WhenIWork\Employee;
6
7class EmployeesController extends \BuyerKiosk\Core\Controllers\BaseController
8{
9    private $store;
10    private $predis;
11    private $key;
12    private $clockedInKey;
13    private $isFromCache;
14    private $log;
15
16    /** @var int Cache TTL in seconds (5 minutes) */
17    private const CACHE_TTL = 300;
18
19    public function __construct($app, \Store $store)
20    {
21        parent::__construct($app);
22        $this->store = $store;
23        $this->predis = new \Predis\Client($_ENV['REDIS_URL']);
24        $this->key = $store->getTypeNum()."_employees";
25        $this->clockedInKey = $store->getTypeNum()."_clocked_in_employees";
26        $this->isFromCache = 0;
27        $this->log = new \KLogger($_ENV['LOG_DIR']."wiw.log",\KLogger::DEBUG);
28        //$this->log->LogDebug("Page Employees");
29    }
30
31    public function pageEmployees() {
32
33        if (!$this->_app->user->checkAccess('wiw_labor_report')) {
34            $this->_app->notFound();
35        }
36        $fc = new FinancialsController($this->_app, $this->store);
37        $this->_app->render('employees/wiw-employees.html', [
38            'page' => [
39                'author' => $this->_app->site->author,
40                'title' => "BuyerKiosk Employee Management",
41                'alerts' => $this->_app->alerts->getAndClearMessages(),
42                'currentStore' => $this->store->getTypeNum()
43            ],
44            'groups' => $this->_app->user->getGroups(),
45            "store" => getStoreInfo($this->store),
46            "employees" => $this->getEmployees(),
47            "laborTotals" => $fc->getLaborTotals(),
48            "isFromCache" => $fc->isFromCache,
49            "employeeData" => $fc->employeeDataArray,
50            "clockedInEmployees" => $this->getClockedInEmployees()
51        ]);
52
53    }
54    public function getEmployees() {
55        // Check if employees list is in Redis Cache, if not load it from the WhenIWork API
56        if ($this->predis->exists($this->key)) {
57            $this->isFromCache = 1;
58            return $this->getEmployeesFromCache();
59        }
60        return $this->getEmployeesFromWiwAPI();
61    }
62    public function getClockedInEmployees() {
63        // Check if clocked-in employees list is in Redis Cache
64        if ($this->predis->exists($this->clockedInKey)) {
65            return $this->getClockedInEmployeesFromCache();
66        }
67
68        return $this->getClockedInEmployeesFromWiwAPI();
69    }
70
71    /**
72     * Fetch clocked-in employees from WhenIWork API and cache the result
73     *
74     * @return array<int, Employee> Array of clocked-in employees keyed by WIW ID
75     */
76    private function getClockedInEmployeesFromWiwAPI(): array {
77        $clockedIn = [];
78        $notClockedIn = [];
79        $employeeArray = [];
80        $fc = new FinancialsController($this->_app, $this->store);
81        $fc->getLaborTotals();
82        $employees = $this->getEmployees();
83        $start = new \DateTime('now', new \DateTimeZone($this->store->getTimeZone()));
84        $start->setTime(0,0,0);
85        $end = new \DateTime('now', new \DateTimeZone($this->store->getTimeZone()));
86        $end->setTime(23,59,59);
87        $wiw = new \Wheniwork($this->store->getWiwToken());
88        $result = $wiw->get("shifts", array(
89            "location_id" => $this->store->getWiwLocationID(),
90            "start" => $start->format("Y-m-d H:i:s"),
91            "end" => $end->format("Y-m-d H:i:s")
92        ));
93        $dataArray = $fc->employeeDataArray;
94
95        /** @var Employee $employee */
96        foreach($employees as $employee) {
97            $employeeArray[$employee->wiwID] = $employee;
98        }
99
100        if(isset($dataArray) && count($dataArray) > 0) {
101            foreach($dataArray as $key => $employee) {
102                if(isset($employeeArray[$key])) {
103                    if((float)$employee["shiftHours"] > 0) {
104                        $clockedIn[$key] = $employeeArray[$key];
105                    } else {
106                        $notClockedIn[$key] = $employeeArray[$key];
107                    }
108                }
109            }
110        } else {
111            foreach($employees as $employee) {
112                $clockedIn[$employee->wiwID] = $employee;
113            }
114        }
115
116        $result = count($clockedIn) !== 0 ? $clockedIn : $notClockedIn;
117
118        // Cache the clocked-in employees as JSON
119        $cacheData = [];
120        /** @var Employee $emp */
121        foreach ($result as $key => $emp) {
122            $cacheData[$key] = json_encode([
123                'wiwID' => $emp->wiwID,
124                'firstName' => $emp->firstName,
125                'lastName' => $emp->lastName
126            ]);
127        }
128
129        if (count($cacheData) > 0) {
130            // Store as hash for easy retrieval
131            $this->predis->hmset($this->clockedInKey, $cacheData);
132            $this->predis->expire($this->clockedInKey, self::CACHE_TTL);
133        }
134
135        return $result;
136    }
137
138    /**
139     * Get clocked-in employees from Redis cache
140     *
141     * @return array<int, Employee> Array of clocked-in employees keyed by WIW ID
142     */
143    private function getClockedInEmployeesFromCache(): array {
144        $cached = $this->predis->hgetall($this->clockedInKey);
145        $employees = [];
146
147        foreach ($cached as $key => $json) {
148            $data = json_decode($json);
149            if ($data) {
150                // Create a minimal stdClass to pass to Employee constructor
151                $wiwObject = (object)[
152                    'id' => $data->wiwID,
153                    'first_name' => $data->firstName,
154                    'last_name' => $data->lastName,
155                    'hourly_rate' => 0
156                ];
157                $employees[(int)$key] = new Employee($wiwObject);
158            }
159        }
160
161        return $employees;
162    }
163
164    /**
165     * Invalidate the clocked-in employees cache
166     * Call this when an employee clocks in or out
167     *
168     * @param string $typeNum Store type number
169     * @return void
170     */
171    public static function invalidateClockedInCache(string $typeNum): void {
172        $predis = new \Predis\Client($_ENV['REDIS_URL']);
173        $key = $typeNum . "_clocked_in_employees";
174        $predis->del($key);
175    }
176    public function getClockedInEmployeesForQueue($clockedIn) {
177        $result = [];
178        /** @var Employee $employee */
179        foreach($clockedIn as $employee) {
180            if($employee->firstName == "" || $employee->firstName == "") {
181
182            } else {
183                $tempEmployee['employeeID'] = $employee->wiwID;
184                $tempEmployee['employeeFirstName'] = $employee->firstName;
185                $tempEmployee['employeeLastName'] = $employee->lastName;
186                $result[] = $tempEmployee;
187            }
188        }
189        return $result;
190    }
191
192
193    public function getEmployeesFromWiwAPI() {
194        $wiw = new \Wheniwork($this->store->getWiwToken());
195
196        $employeesArray = $wiw->get('users', array("location_id" => $this->store->getWiwLocationID()));
197
198        $employees = [];
199        $employeesAsJSON = [];
200
201        if (isset($employeesArray->users) && !empty($employeesArray->users)) {
202            foreach($employeesArray->users as $employeeObject) {
203                $tempEmployee = new Employee($employeeObject);
204                $employees[] = $tempEmployee;
205                $employeesAsJSON[] = json_encode($employeeObject);
206            }
207
208            $this->predis->sadd($this->key, $employeesAsJSON);
209            $this->predis->expire($this->key, 60*5);
210
211            $this->syncEmployeesTable($employees);
212        } else {
213            //error_log("No employees found in the WhenIWork API response");
214            return $this->getEmployeesFromDB();
215
216        }
217
218        return $employees;
219    }
220
221    public function getEmployeesFromCache() {
222        $employees = [];
223        foreach($this->predis->smembers($this->key) as $employee) {
224            $employees[] = new Employee(json_decode($employee));
225        }
226        return $employees;
227    }
228
229    public function syncEmployeesTable($wiwEmployees) {
230        $storeDB = dbConnectByName($this->store->getDbName());
231        $stmt = $storeDB->query("SELECT employeeID, employeeFirstName, employeeLastName FROM employees");
232        $employees = $stmt->fetchAll(\PDO::FETCH_ASSOC);
233        $dbEmployees = [];
234        foreach($employees as $employee) {
235            $dbEmployees[(int)$employee['employeeID']] = array('firstName' => $employee['employeeFirstName'], 'lastName' => $employee['employeeLastName']);
236        }
237        /** @var Employee $wiwEmployee */
238        foreach($wiwEmployees as $wiwEmployee) {
239            if(array_key_exists($wiwEmployee->wiwID, $dbEmployees)) {
240
241            } else {
242                $insertEmployeeQuery = $storeDB->prepare("INSERT INTO employees (employeeID, login, password, employeeFirstName, employeeLastName, role, averagePerContainer, averagePerBuy, active) VALUES (:id,'', '',:firstName, :lastName,4,0,0,1)");
243                $insertEmployeeQuery->bindValue(":id", $wiwEmployee->wiwID);
244                $insertEmployeeQuery->bindValue(":firstName", $wiwEmployee->firstName);
245                $insertEmployeeQuery->bindValue(":lastName", $wiwEmployee->lastName);
246                if($insertEmployeeQuery->execute()) {
247                } else {
248                    error_log("Error inserting employee into database");
249                }
250            }
251        }
252    }
253    private function getEmployeesFromDB()
254    {
255        $storeDB = dbConnectByName($this->store->getDbName());
256        $stmt = $storeDB->query("SELECT employeeID, employeeFirstName, employeeLastName FROM employees WHERE active = 1");
257        $employees = $stmt->fetchAll(\PDO::FETCH_ASSOC);
258        $dbEmployees = [];
259        foreach ($employees as $employee) {
260            $tempEmployee = new Employee();
261            $tempEmployee->wiwID = $employee['employeeID'];
262            $tempEmployee->firstName = $employee['employeeFirstName'];
263            $tempEmployee->lastName = $employee['employeeLastName'];
264            $dbEmployees[] = $tempEmployee;
265        }
266        return $dbEmployees;
267    }
268
269}