Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 143 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
| BuyerStats | |
0.00% |
0 / 143 |
|
0.00% |
0 / 4 |
992 | |
0.00% |
0 / 1 |
| gatherDailyBuyerStatsArray | |
0.00% |
0 / 75 |
|
0.00% |
0 / 1 |
306 | |||
| gatherDailySorterStatsArray | |
0.00% |
0 / 66 |
|
0.00% |
0 / 1 |
156 | |||
| setBuyerStatsArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getBuyerStatsArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace BuyerKiosk\Core; |
| 4 | |
| 5 | /** |
| 6 | * Buyer Statistics Calculator |
| 7 | * |
| 8 | * Calculates buyer and sorter performance statistics based on completed buys. |
| 9 | * Updated to use the unified users table (kiosk_users.users) with store assignments |
| 10 | * (kiosk_users.userStoreAssignments) instead of per-store employee tables. |
| 11 | * |
| 12 | * @package BuyerKiosk\Core |
| 13 | */ |
| 14 | class BuyerStats extends CompletedBuys |
| 15 | { |
| 16 | |
| 17 | public $BuyerStatsArray = array(); |
| 18 | public $SorterStatsArray = array(); |
| 19 | |
| 20 | /** |
| 21 | * Gather daily buyer statistics |
| 22 | * |
| 23 | * Queries buyQueue data joined with unified users table to calculate |
| 24 | * buyer performance metrics. |
| 25 | * |
| 26 | * @param Store $store Store object |
| 27 | * @param string|null $date Optional date (Y-m-d format) |
| 28 | */ |
| 29 | public function gatherDailyBuyerStatsArray(Store $store, $date = null) |
| 30 | { |
| 31 | $storeDb = dbConnectByName($store->getDbName()); |
| 32 | $centralDb = \dbConnectByName('kiosk_users'); |
| 33 | $typeNum = $store->getTypeNum(); |
| 34 | |
| 35 | $this->gatherCompletedBuys($store, $date); |
| 36 | $buyArray = $this->getCompletedBuys(); |
| 37 | $buyerArray = array(); |
| 38 | |
| 39 | foreach ($buyArray as $buy) { |
| 40 | if (!in_array($buy['buyerID'], $buyerArray)) { |
| 41 | $buyerArray[] = $buy['buyerID']; |
| 42 | } |
| 43 | $timeStarted = new \DateTime($buy['timestampStarted']); |
| 44 | $timeCompleted = new \DateTime($buy['timestampCompleted']); |
| 45 | |
| 46 | $diffStartedCompleted = $timeCompleted->diff($timeStarted); |
| 47 | } |
| 48 | |
| 49 | if($date !== null) { |
| 50 | $dateRange = getDateRange($date); |
| 51 | } else { |
| 52 | $dateRange = getCurrentDateRange(); |
| 53 | } |
| 54 | |
| 55 | foreach ($buyerArray as $buyer) { |
| 56 | // Query buyQueue from store database |
| 57 | $query = "SELECT * FROM buyQueue WHERE buyQueue.buyerID = :buyerId |
| 58 | AND timeCompleted BETWEEN :dateStart AND :dateEnd |
| 59 | AND buyQueue.isProcessed = 1"; |
| 60 | |
| 61 | $stmt = $storeDb->prepare($query); |
| 62 | $stmt->execute([ |
| 63 | ':buyerId' => $buyer, |
| 64 | ':dateStart' => $dateRange['dateStart']->format("Y-m-d H:i:s"), |
| 65 | ':dateEnd' => $dateRange['dateEnd']->format("Y-m-d H:i:s") |
| 66 | ]); |
| 67 | $buys = $stmt; |
| 68 | |
| 69 | if ($buys) { |
| 70 | $totalProcessTime = 0; |
| 71 | $totalWorkedBuys = 0; |
| 72 | $numBuys = 0; |
| 73 | $numContainers = 0; |
| 74 | $workedContainers = 0; |
| 75 | $buyerFullName = ""; |
| 76 | |
| 77 | // Fetch user info from central database once per buyer |
| 78 | $userStmt = $centralDb->prepare(" |
| 79 | SELECT u.firstName, u.lastName |
| 80 | FROM users u |
| 81 | INNER JOIN userStoreAssignments usa ON u.id = usa.userId |
| 82 | WHERE u.id = :userId AND usa.typeNum = :typeNum |
| 83 | LIMIT 1 |
| 84 | "); |
| 85 | $userStmt->execute([':userId' => $buyer, ':typeNum' => $typeNum]); |
| 86 | $userInfo = $userStmt->fetch(\PDO::FETCH_ASSOC); |
| 87 | |
| 88 | if ($userInfo) { |
| 89 | $buyerFullName = $userInfo['firstName'] . ". " . substr($userInfo['lastName'], 0, 1); |
| 90 | } |
| 91 | |
| 92 | while ($data = $buys->fetch(\PDO::FETCH_ASSOC)) { |
| 93 | $timeDroppedOff = new \DateTime($data['timeEntered']); |
| 94 | $timeStarted = new \DateTime($data['timeStarted']); |
| 95 | $timeCompleted = new \DateTime($data['timeCompleted']); |
| 96 | |
| 97 | $diffStartedCompleted = $timeCompleted->diff($timeStarted); |
| 98 | |
| 99 | if ((int)$diffStartedCompleted->format("%h") > 0) { |
| 100 | $processTime = $diffStartedCompleted->format('%hh %im %Ss'); |
| 101 | } else if ((int)$diffStartedCompleted->format("%i") > 0) { |
| 102 | $processTime = $diffStartedCompleted->format('%im %Ss'); |
| 103 | } else { |
| 104 | $processTime = $diffStartedCompleted->format('%Ss'); |
| 105 | } |
| 106 | |
| 107 | $secondsForProcessTime = ((int)$diffStartedCompleted->format('%h') * 60 * 60) + ((int)$diffStartedCompleted->format('%i') * 60) + ((int)$diffStartedCompleted->format('%s')); |
| 108 | if ($secondsForProcessTime > 30) { |
| 109 | $totalProcessTime += $secondsForProcessTime; |
| 110 | if($data['sortStarted'] !== "0000-00-00 00:00:00" && $data['remainingContainers'] > 0) { |
| 111 | $numContainers += $data['remainingContainers']; |
| 112 | } else { |
| 113 | $numContainers += $data['numContainers']; |
| 114 | } |
| 115 | $numBuys++; |
| 116 | } |
| 117 | if($data['sortStarted'] !== "0000-00-00 00:00:00" && $data['remainingContainers'] > 0) { |
| 118 | $workedContainers += $data['remainingContainers']; |
| 119 | } else { |
| 120 | $workedContainers += $data['numContainers']; |
| 121 | } |
| 122 | $totalWorkedBuys++; |
| 123 | } |
| 124 | |
| 125 | if ($numBuys > 0) { |
| 126 | $averageProcessTime = secondsToReadable(floor($totalProcessTime / $numBuys)); |
| 127 | if ($totalProcessTime < 15) { |
| 128 | $averageProcessTime = "Not Enough Data"; |
| 129 | $processTimePerContainer = "Not Enough Data"; |
| 130 | } else { |
| 131 | $processTimePerContainer = secondsToReadable(floor($totalProcessTime / $numContainers)); |
| 132 | } |
| 133 | } else { |
| 134 | $averageProcessTime = "Not Enough Data"; |
| 135 | $processTimePerContainer = "Not Enough Data"; |
| 136 | } |
| 137 | $tempArray = array( |
| 138 | 'buyerFullName' => $buyerFullName, |
| 139 | 'averageProcessTime' => $averageProcessTime, |
| 140 | 'processTimePerContainer' => $processTimePerContainer, |
| 141 | 'totalContainers' => $workedContainers, |
| 142 | 'numBuys' => $totalWorkedBuys |
| 143 | ); |
| 144 | $this->BuyerStatsArray[] = $tempArray; |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Gather daily sorter statistics |
| 151 | * |
| 152 | * Queries buyQueue data joined with unified users table to calculate |
| 153 | * sorter performance metrics. |
| 154 | * |
| 155 | * @param Store $store Store object |
| 156 | * @param string|null $date Optional date (Y-m-d format) |
| 157 | */ |
| 158 | public function gatherDailySorterStatsArray(Store $store, $date = null) |
| 159 | { |
| 160 | $storeDb = dbConnectByName($store->getDbName()); |
| 161 | $centralDb = \dbConnectByName('kiosk_users'); |
| 162 | $typeNum = $store->getTypeNum(); |
| 163 | |
| 164 | $this->gatherCompletedBuys($store, $date); |
| 165 | $buyArray = $this->getCompletedBuys(); |
| 166 | $sorterArray = array(); |
| 167 | |
| 168 | foreach ($buyArray as $buy) { |
| 169 | if($buy['sortStarted'] !== "-") { |
| 170 | if (!in_array($buy['sorterID'], $sorterArray)) { |
| 171 | $sorterArray[] = $buy['sorterID']; |
| 172 | } |
| 173 | $timeStarted = new \DateTime($buy['sortStarted']); |
| 174 | $timeCompleted = new \DateTime($buy['sortCompleted']); |
| 175 | |
| 176 | $diffStartedCompleted = $timeCompleted->diff($timeStarted); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | if($date !== null) { |
| 181 | $dateRange = getDateRange($date); |
| 182 | } else { |
| 183 | $dateRange = getCurrentDateRange(); |
| 184 | } |
| 185 | |
| 186 | foreach ($sorterArray as $sorter) { |
| 187 | // Query buyQueue from store database |
| 188 | $query = "SELECT * FROM buyQueue WHERE buyQueue.sorterID = :sorterId |
| 189 | AND timeCompleted BETWEEN :dateStart AND :dateEnd |
| 190 | AND buyQueue.isProcessed = 1"; |
| 191 | |
| 192 | $stmt = $storeDb->prepare($query); |
| 193 | $stmt->execute([ |
| 194 | ':sorterId' => $sorter, |
| 195 | ':dateStart' => $dateRange['dateStart']->format("Y-m-d H:i:s"), |
| 196 | ':dateEnd' => $dateRange['dateEnd']->format("Y-m-d H:i:s") |
| 197 | ]); |
| 198 | $buys = $stmt; |
| 199 | |
| 200 | if ($buys) { |
| 201 | $totalProcessTime = 0; |
| 202 | $totalWorkedBuys = 0; |
| 203 | $numBuys = 0; |
| 204 | $numContainers = 0; |
| 205 | $workedContainers = 0; |
| 206 | $sorterFullName = ""; |
| 207 | |
| 208 | // Fetch user info from central database once per sorter |
| 209 | $userStmt = $centralDb->prepare(" |
| 210 | SELECT u.firstName, u.lastName |
| 211 | FROM users u |
| 212 | INNER JOIN userStoreAssignments usa ON u.id = usa.userId |
| 213 | WHERE u.id = :userId AND usa.typeNum = :typeNum |
| 214 | LIMIT 1 |
| 215 | "); |
| 216 | $userStmt->execute([':userId' => $sorter, ':typeNum' => $typeNum]); |
| 217 | $userInfo = $userStmt->fetch(\PDO::FETCH_ASSOC); |
| 218 | |
| 219 | if ($userInfo) { |
| 220 | $sorterFullName = $userInfo['firstName'] . " " . substr($userInfo['lastName'], 0, 1); |
| 221 | } |
| 222 | |
| 223 | while ($data = $buys->fetch(\PDO::FETCH_ASSOC)) { |
| 224 | $timeStarted = new \DateTime($data['sortStarted']); |
| 225 | $timeCompleted = new \DateTime($data['sortCompleted']); |
| 226 | |
| 227 | $diffStartedCompleted = $timeCompleted->diff($timeStarted); |
| 228 | |
| 229 | $secondsForProcessTime = ((int)$diffStartedCompleted->format('%h') * 60 * 60) + ((int)$diffStartedCompleted->format('%i') * 60) + ((int)$diffStartedCompleted->format('%s')); |
| 230 | if ($secondsForProcessTime > 30) { |
| 231 | $totalProcessTime += $secondsForProcessTime; |
| 232 | $numBuys++; |
| 233 | $numContainers += $data['numContainers']; |
| 234 | } |
| 235 | $workedContainers += $data['numContainers']; |
| 236 | $totalWorkedBuys++; |
| 237 | } |
| 238 | |
| 239 | if ($numBuys > 0) { |
| 240 | $averageSortTime = secondsToReadable(floor($totalProcessTime / $numBuys)); |
| 241 | if ($totalProcessTime < 15) { |
| 242 | $averageSortTime = "Not Enough Data"; |
| 243 | $sortTimePerContainer = "Not Enough Data"; |
| 244 | } else { |
| 245 | $sortTimePerContainer = secondsToReadable(floor($totalProcessTime / $numContainers)); |
| 246 | } |
| 247 | } else { |
| 248 | $averageSortTime = "Not Enough Data"; |
| 249 | $sortTimePerContainer = "Not Enough Data"; |
| 250 | } |
| 251 | |
| 252 | $tempArray = array( |
| 253 | 'sorterFullName' => $sorterFullName, |
| 254 | 'averageSortTime' => $averageSortTime, |
| 255 | 'sortTimePerContainer' => $sortTimePerContainer, |
| 256 | 'totalContainers' => $workedContainers, |
| 257 | 'numBuys' => $totalWorkedBuys |
| 258 | ); |
| 259 | $this->SorterStatsArray[] = $tempArray; |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | public function setBuyerStatsArray($BuyerStatsArray) |
| 265 | { |
| 266 | $this->BuyerStatsArray = $BuyerStatsArray; |
| 267 | } |
| 268 | |
| 269 | public function getBuyerStatsArray() |
| 270 | { |
| 271 | return $this->BuyerStatsArray; |
| 272 | } |
| 273 | |
| 274 | |
| 275 | } |