moveEmergency($identifier, $device, $direction, $verifyOnly, $createdAt, $date); return; } $cardNumber = $identifier; $virtualKey = null; switch ($direction) { case 'IN': $direction = DoorLog::$DIRECTION_IN; break; case 'OUT': $direction = DoorLog::$DIRECTION_OUT; break; default: throw new BadRequestHttpException("Direction not supported: " . $direction); } // if device is qr code if ($device == 'Q') { // allow only virtual key $virtualKey = VirtualKey::findOne(['number' => $identifier]); if (!isset($virtualKey)) { throw new BadRequestHttpException("Virtual key not found: " . $identifier); } $card = Card::findOne($virtualKey->id_card); if ($card != null) { $card = Card::readCard($card->number); } if ($card == null) { throw new BadRequestHttpException("Card not found by virtual key: " . $identifier . '/' . $virtualKey->id_card); } $cardNumber = $card->number; \Yii::info("virtual key and card loaded in sec " . $stopWatch->split()); } else { // load by rfid or card number $card = Card::readCard(Helper::fixAsciiChars($identifier)); \Yii::info("Card loaded in sec " . $stopWatch->split()); if (!isset($card)) { throw new BadRequestHttpException('Card not found with number: ' . $identifier); } \Yii::info("card loaded in sec " . $stopWatch->split()); $virtualKey = VirtualKey::findOne(['id_card' => $card->id_card]); \Yii::info("virtual key for card loaded in sec " . $stopWatch->split()); } if ($card->type == Card::TYPE_EMPLOYEE) { $this->moveEmployee($identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey); return; } $this->moveCustomer($identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey); } function moveEmergency($identifier, $device, $direction, $verifyOnly, $createdAt, $date) { \Yii::info("emergency move"); try { $createdAtStr = DateUtil::formatDateTimeUtc($createdAt); \Yii::$app->db->beginTransaction(); $doorLog = new DoorLog(); $doorLog->version = 2; $doorLog->direction = DoorLog::$DIRECTION_ALL_EMERGENCY; $doorLog->source_app = $device; $doorLog->created_at = $createdAtStr; $doorLog->save(false); Log::log( [ 'type' => Log::$TYPE_INFO, 'message' => 'Ajtó nyitás: vészhelyzet', 'id_door_log' => $doorLog->id_door_log ] ); \Yii::$app->response->statusCode = 204; \Yii::$app->db->transaction->commit(); } catch (\Exception $e) { \Yii::$app->db->transaction->rollBack(); throw $e; } } function moveEmployee($identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey) { /** * if the card type is employee, neither customer nor ticket is needed. * Free to enter/leave */ \Yii::info("employee move"); try { $createdAtStr = DateUtil::formatDateTimeUtc($createdAt); \Yii::$app->db->beginTransaction(); $doorLog = new DoorLog(); $doorLog->version = 2; $doorLog->direction = $direction; $doorLog->source_app = $device; $doorLog->created_at = $createdAtStr; $doorLog->id_card = $card->id_card; $doorLog->card_flag = $card->flag; if (!$verifyOnly) { $doorLog->save(false); Log::log( [ 'type' => Log::$TYPE_INFO, 'message' => 'Ajtó nyitás: munkatárs', 'id_door_log' => $doorLog->id_door_log ] ); } \Yii::$app->response->statusCode = 204; \Yii::$app->db->transaction->commit(); } catch (\Exception $e) { \Yii::$app->db->transaction->rollBack(); throw $e; } } /** * @param $identifier string virtual key id, card rfid or card number * @param $device string device * @param $direction number direction * @param $verifyOnly boolean only check or real move * @param $createdAt * @param $date * @param $card * @param $cardNumber * @param $virtualKey * @return void * @throws BadRequestHttpException * @throws ServerErrorHttpException * @throws \Throwable * @throws \yii\base\InvalidConfigException * @throws \yii\db\Exception * @throws \yii\db\StaleObjectException */ function moveCustomer($identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey) { \Yii::info("move customer"); $stopWatch = new StopWatch(); try { $createdAtStr = DateUtil::formatDateTimeUtc($createdAt); \Yii::info("crated at str: ". $createdAtStr); \Yii::$app->db->beginTransaction(); $doorLog = new DoorLog(); $doorLog->version = 2; $doorLog->direction = $direction; $doorLog->source_app = $device; $doorLog->created_at = $createdAtStr; $doorLog->id_card = $card->id_card; $doorLog->card_flag = $card->flag; $activeTickets = Ticket::readActive($card, clone $date); \Yii::info('active ticket count:' . count($activeTickets)); /** @var Ticket $ticket */ $ticket = null; if (isset($activeTickets) && count($activeTickets) > 0) { $ticket = $activeTickets[0]; } if (!isset($ticket)) { throw new BadRequestHttpException("No active ticket found for:" . $card->number); } \Yii::info("ticket {$ticket->id_ticket} loaded in sec " . $stopWatch->split()); $doorLog->id_ticket_current = $ticket->id_ticket; // customer is also required $customer = $card->customer; if (!isset($customer)) { throw new BadRequestHttpException("Customer not found for:" . $card->number); } $doorLog->id_customer = $customer->id_customer; \Yii::info("customer {$customer->id_customer} loaded in sec " . $stopWatch->split()); if (!$verifyOnly) { // save the door log $doorLog->save(false); } \Yii::info("door log {$doorLog->id_door_log} saved in sec " . $stopWatch->split()); // if direction is in if ($direction == DoorLog::$DIRECTION_IN) { if ($card->isFlagDoor()) { throw new BadRequestHttpException("Card already 'IN': " . $card->id_card); } if ($card->isFlagKey()) { throw new BadRequestHttpException("Key required: " . $card->id_card); } if ($card->isFlagStatus()) { throw new BadRequestHttpException("Card has no active status: " . $card->id_card); } if (isset($virtualKey)) { if (isset($virtualKey->direction_in_at)) { throw new BadRequestHttpException("Virtual key - already moved in: " . $identifier . '/' . $virtualKey->id_card); } $virtualKey->direction_in_at = Helper::getDateTimeString(); \Yii::info("Setting virtual key direction_in_at"); if (!$verifyOnly) { \Yii::info("Updating virtual key"); $virtualKey->save(false); } } // detect if need to increase usage count for ticket if (!$verifyOnly) { // if not verifyonly, check, if ticket usage count must be increased $countDoorLogsForTicketSince = $this->getCountDoorLogsForTicketSince($ticket->id_ticket, DateUtil::utcDate(clone $date)); \Yii::info("getCountDoorLogsForTicketSince: " . $countDoorLogsForTicketSince); if (!isset($countDoorLogsForTicketSince)) { $countDoorLogsForTicketSince = 0; } \Yii::info("count of door logs '{$countDoorLogsForTicketSince}' loaded in sec " . $stopWatch->split()); // if the current event is the first door log today if ($countDoorLogsForTicketSince == 1) { // increase the ticket usage count with 1 $usageCount = $ticket->usage_count; $ticket->usage_count += 1; $ticket->save(false); \Yii::info("First ticket usage today, increasing usage count for card: " . $card->id_card); Log::log( [ 'type' => Log::$TYPE_TICKET_USAGE_FIRST, 'message' => 'Bérlet használat(előtte: ' . $usageCount . ' -> utána: ' . $ticket->usage_count, 'id_ticket' => $ticket->id_ticket, 'id_door_log' => $doorLog->id_door_log ] ); \Yii::info("Ticket usage count increased after first doorlog of day in sec " . $stopWatch->split()); } else { \Yii::info("more then one door log today for card: " . $card->id_card); // we have already a door log for today, other than this // Now we split the day into 3hour intervalls, starting with the createdAt value of the first event. // If the actual event happens in an interval, in which still now doorlog event happend, we increase // the usage count with 1 // 3 óránként 1-et levonunk $startOfDay = DateUtil::utcDate(clone $date); $startOfTomorrow = DateUtil::tomorrowStart(clone $date); $allDoorLogToday = DoorLog::find() ->andWhere(['>=', 'door_log.created_at', DateUtil::formatDateUtc($startOfDay)]) ->andWhere(['<', 'door_log.created_at', DateUtil::formatDateUtc($startOfTomorrow)]) ->andWhere(['id_ticket_current' => $ticket->id_ticket]) ->andWhere(['in', 'direction', [DoorLog::$DIRECTION_IN_WITHOUT_MOVE, DoorLog::$DIRECTION_IN]]) ->orderBy(['door_log.created_at' => SORT_ASC]) ->all(); \Yii::info("All door logs for today loaded in sec " . $stopWatch->split()); \Yii::info("allDoorLogToday", print_r($allDoorLogToday, true)); if (isset($allDoorLogToday) && count($allDoorLogToday) > 0) { $firstInToday = $allDoorLogToday[0]; } if (isset($firstInToday)) { \Yii::info("first in today for card: " . $card->id_card . " was at " . $firstInToday->created_at); $firstEntryDateTimeToday = DateUtil::parseDateTime($firstInToday->created_at); $interval = \DateInterval::createFromDateString('3 hours'); $daterange = new \DatePeriod($firstEntryDateTimeToday, $interval, $startOfTomorrow); $intervals = []; $intervalStart = null; foreach ($daterange as $intervalEnd) { if (isset($intervalStart)) { $intervals[] = $this->createTicketUsageInterval($intervalStart, $intervalEnd, $allDoorLogToday, $doorLog); } $intervalStart = clone $intervalEnd; } if ($intervalStart < $startOfTomorrow) { $intervals[] = $this->createTicketUsageInterval($intervalStart, $startOfTomorrow, $allDoorLogToday, $doorLog); } $activeInterval = $this->getActiveInterval($intervals, $createdAt); if (!isset($activeInterval)) { throw new ServerErrorHttpException("Active Interval not found"); } $logCountInActiveInterval = count($activeInterval['logs']); if ($logCountInActiveInterval == 1) { $ticket->usage_count = $ticket->usage_count + 1; $ticket->save(false); \Yii::info("Ticket usage count increased after first IN after first door_log in interval in sec " . $stopWatch->split()); } } } } } if ($direction == DoorLog::$DIRECTION_OUT || $direction == DoorLog::$DIRECTION_OUT_WITHOUT_MOVE) { if ($card->isFlagOutKey()) { throw new BadRequestHttpException("Can't exit with card has a key assigned"); } if ($card->isFlagStatus()) { throw new BadRequestHttpException("Can't exit with card has inactive status"); } $keyAssigned = CardKeyAssignment::findOne(['id_card' => $card->id_card]); if (isset($keyAssigned)) { throw new BadRequestHttpException("Can't exit with card has a key assigned"); } if (isset($virtualKey)) { if (!isset($virtualKey->direction_in_at)) { throw new BadRequestHttpException("Virtual key: move out without move in"); } if (isset($virtualKey->direction_out_at)) { throw new BadRequestHttpException("Virtual key: already move out"); } $virtualKey->direction_out_at = Helper::getDateTimeString(); if (!$verifyOnly) { $virtualKey->save(false); } } $ticket->count_move_out = $ticket->usage_count; if (!$verifyOnly) { $ticket->save(false); } \Yii::info("direction_out: ticket count_move_out set after direction_out in sec " . $stopWatch->split()); } if (!$verifyOnly) { Card::updateCardFlagTicket($ticket->id_ticket); \Yii::info("updateCardFlagTicket: card flag updated in sec " . $stopWatch->split()); // reload card after flag is set $card = Card::readCard($cardNumber); if ($direction == DoorLog::$DIRECTION_OUT_WITHOUT_MOVE || $direction == DoorLog::$DIRECTION_OUT) { $card->flag_out = Helper::setBit($card->flag_out, Card::$FLAG_DOOR, true); $card->flag = Helper::setBit($card->flag, Card::$FLAG_DOOR, false); $card->save(false); \Yii::info("direction_out: Door flag updated in sec " . $stopWatch->split()); } else if ($direction == DoorLog::$DIRECTION_IN || $direction == DoorLog::$DIRECTION_IN_WITHOUT_MOVE) { $card->flag_out = Helper::setBit($card->flag_out, Card::$FLAG_DOOR, false); $card->flag = Helper::setBit($card->flag, Card::$FLAG_DOOR, true); $card->save(false); \Yii::info("direction_in: Card flag updated in sec " . $stopWatch->split()); } } $stopWatch->stop(); \Yii::info("finished in sec " . $stopWatch->getTotal()); \Yii::$app->db->transaction->commit(); \Yii::info("Commited"); } catch (\Exception $e) { \Yii::$app->db->transaction->rollBack(); \Yii::info("rollbacked"); throw $e; } } function getActiveInterval($intervals, $date) { foreach ($intervals as $interval) { $start = $interval['start']; $end = $interval['end']; if ($start <= $date && $date < $end) { return $interval; } } return null; } function createTicketUsageInterval($start, $end, $allLogs, $actualDoorLog) { $result = ['start' => $start, 'end' => $end, 'logs' => []]; foreach ($allLogs as $log) { $createdAt = DateUtil::parseDateTime($log->created_at); if ($createdAt >= $start && $createdAt < $end) { $result['logs'][] = $log; } } return $result; } function getCountDoorLogsForTicketSince($idTicket, $since) { \Yii::info("getting door log count for today"); return DoorLog::find() ->innerJoinWith('card') ->andWhere(['card.id_ticket_current' => $idTicket]) ->andWhere(['in', 'door_log.direction', [DoorLog::$DIRECTION_IN, DoorLog::$DIRECTION_IN_WITHOUT_MOVE]]) ->andWhere(['>=', 'door_log.created_at', DateUtil::formatDateUtc($since)]) ->count(); } public function readActive($cardNumber) { $card = Card::readCard($cardNumber); return Ticket::readActive($card); } public function resetLogs($cardNumber) { $card = Card::readCard($cardNumber); $card->flag = 0; $card->flag_out = 0; $card->save(false); Card::updateCardFlagTicket($card->id_card); DoorLog::deleteAll( ['id_card' => $card->id_card] ); // todo: revoke all assigned key $this->revokeKey($cardNumber, "f100"); } public function getLogs($cardNumber) { return DoorLog::findAll( ['id_card' => $cardNumber] ); } public function getInfo($cardNumber) { $card = Card::readCard($cardNumber); return [ 'card' => $card, 'customer' => $card->customer, 'tickets' => Ticket::readActive($card), 'doorLogs' => DoorLog::findAll( ['id_card' => $card->id_card] ), 'lastDoorLog' => DoorLog::find()->orderBy(['id_door_log' => SORT_DESC])->limit(1)->one(), 'doorLogCount' => DoorLog::find()->count() ]; } public function createLog() { \Yii::info("Post create log:" . \Yii::$app->request->method); if (\Yii::$app->request->isPost) { $log = new DoorLogForTest(); if ($log->load(\Yii::$app->request->post(), "")) { if ($log->validate()) { \Yii::info("Door log saving:" . $log->created_at); $log->save(false); return $log; } else { throw new BadRequestHttpException(print_r($log->getErrors(), true)); } } else { \Yii::info("validated" . print_r($log->errors, true)); throw new BadRequestHttpException(); } } throw new BadRequestHttpException('Not a Post'); } function checkoutKey($cardNumber, $keyNumber) { $model = new KeyToggleForm(); $model->card = Card::readCard($cardNumber); $model->customer = $model->card->customer; $model->keyCard = $model->card; $model->keyModel = $model->readKey($keyNumber); $model->assign(); } function revokeKey($cardNumber, $keyNumber) { $model = new KeyToggleForm(); $model->card = Card::readCard($cardNumber); $model->customer = $model->card->customer; $model->keyCard = $model->card; $model->keyModel = $model->readKey($keyNumber); $model->revoke(); } }