$requestId, 'identifier' => $identifier, 'device' => $device, 'direction' => $direction, 'originalDirection' => $direction, 'verifyOnly' => $verifyOnly ]); try { $stopWatch = new StopWatch(); // for testing purposes if (Helper::isRestAllowVerifyOnly() === false) { Yii::info("$requestId: verifyonly not allowed"); $verifyOnly = false; } Yii::info("$requestId: move with next parameers:" . ";identifier" . $identifier . ";device" . $device . ";direction" . $direction . ";verifyOnly" . $verifyOnly . ";createdAt" . print_r($createdAt, true) . ";date" . print_r($date, true)); Yii::info("$requestId: move get request: " . print_r($_GET, true)); Yii::info("$requestId: move post request: " . print_r($_GET, true)); if (isset($createdAt)) { $createdAt = DateUtil::parseDateTime($createdAt); } else { $createdAt = DateUtil::utcDateTime(); } $context->createdAt = $createdAt; if (isset($date)) { $date = DateUtil::parseDateTime($date); } else { $date = DateUtil::utcDate(); } $context->date = $date; if ($device === 'E') { $this->moveEmergency($context/*;$requestId,$identifier, $device, $direction, $verifyOnly, $createdAt, $date*/); return; } $cardNumber = $identifier; switch ($direction) { case 'IN': $direction = DoorLog::$DIRECTION_IN; break; case 'OUT': $direction = DoorLog::$DIRECTION_OUT; break; default: throw new FitnessException( "Direction $direction not supported", FitnessException::TYPE_BAD_REQUEST, "INVALID_DIRECTION", $context ); } $context->direction = $direction; // if device is qr code if ($device == 'Q') { // load virtual key by virtual_key.number $virtualKey = VirtualKey::findOne(['number' => $identifier]); if (!isset($virtualKey)) { throw new FitnessException( "Virtual Key Not Found", FitnessException::TYPE_BAD_REQUEST, "VIRTUAL_KEY_NOT_FOUND", $context ); } // load card by virutal key $card = Card::findOne($virtualKey->id_card); if (!isset($card)) { throw new FitnessException( "Virtual Key Not Found", FitnessException::TYPE_BAD_REQUEST, "CARD_FOR_VIRTUAL_KEY_NOT_FOUND", $context ); } $cardNumber = $card->number; Yii::info("$requestId: virtual key and card loaded in sec " . $stopWatch->split()); } else { // load card by rfid $card = Card::readCard(Helper::fixAsciiChars($identifier)); Yii::info("$requestId: Card loaded in sec " . $stopWatch->split()); if (!isset($card)) { throw new FitnessException( "Virtual Key Not Found", FitnessException::TYPE_BAD_REQUEST, "CARD_NOT_FOUND", $context ); } Yii::info("$requestId: card loaded in sec " . $stopWatch->split()); // load virtual key by card $virtualKey = VirtualKey::findOne(['id_card' => $card->id_card]); Yii::info("$requestId: virtual key for card loaded in sec " . $stopWatch->split()); } $context->virtualKey = $virtualKey; $context->card = $card; $context->cardNumber = $cardNumber; Yii::info("$requestId: Card number " . $card->number); // check card status if ($card->status !== Card::STATUS_ACTIVE) { throw new FitnessException( "Card Status is inactive", FitnessException::TYPE_BAD_REQUEST, "CARD_STATUS_INACTIVE", $context ); } // load assigned key $keyAssignment = CardKeyAssignment::findOne(['id_card' => $card->id_card]); if (isset($keyAssignment)) { $context->key = Key::findOne($keyAssignment->id_key); } if ($card->type == Card::TYPE_EMPLOYEE) { $this->moveEmployee($context); return; } if (!isset($virtualKey)) { throw new FitnessException( "Virtual Key Not Found", FitnessException::TYPE_BAD_REQUEST, "VIRTUAL_KEY_NOT_FOUND", $context ); } $this->moveCustomer($context); } catch (FitnessException $e) { $context->error = true; $context->exception = $e->originalException; $context->errorCode = $e->errorCode; if ($e->type == FitnessException::TYPE_BAD_REQUEST) { throw new BadRequestHttpException($e->getMessage()); } else { throw new ServerErrorHttpException($e->getMessage()); } // throw $e->originalException; // throw new BadRequestHttpException(); } catch (\Exception $e) { $context->error = true; $context->exception = $e; $context->errorCode = "UNKNOWN"; throw $e; } finally { // do logging $this->logContext($context); } } /** * @param $ctx DoorMoveContext * @return void */ function logContext($ctx) { try { $result = [ //datetime $updated_at -- //datetime $created_at -- //string $ticket_usage //string $request_id 'request_id' => $ctx->requestId, //string $identifier 'identifier' => $ctx->identifier, //bool $verify_only 'verify_only' => $ctx->verifyOnly, //string $device 'device' => $ctx->device, //string $direction 'direction' => $ctx->direction, //string $original_direction 'original_direction' => $ctx->originalDirection, //integer $card_id_card 'card_id_card' => isset($ctx->card) ? $ctx->card->id_card : null, //string $card_number 'card_number' => isset($ctx->card) ? $ctx->card->number : null, //string $ticket_id_ticket 'ticket_id_ticket' => isset($ctx->ticket) ? $ctx->ticket->id_ticket : null, //string $ticket_type_name 'ticket_type_name' => isset($ctx->ticket) ? $ctx->ticket->ticketType->name : null, //string $ticket_usage_count 'ticket_usage_count' => isset($ctx->ticket) ? $ctx->ticket->usage_count : null, //string $customer_id_customer 'customer_id_customer' => isset($ctx->customer) ? $ctx->customer->id_customer : null, //string $customer_name 'customer_name' => isset($ctx->customer) ? $ctx->customer->name : null, //string $customer_email 'customer_email' => isset($ctx->customer) ? $ctx->customer->email : null, //integer $key_id_key 'key_id_key' => isset($ctx->key) ? $ctx->key->id_key : null, //string $key_number 'key_number' => isset($ctx->key) ? $ctx->key->number : null, //integer $virtual_key_id 'virtual_key_id' => isset($ctx->virtualKey) ? $ctx->virtualKey->id : null, //string $validation_kind 'validation_kind' => $ctx->kind, //bool $error 'error' => $ctx->error, //string $error_code 'error_code' => $ctx->errorCode, 'ticket_usage' => $ctx->increasedTicketUsageCount, // 'actions' => implode(",",$ctx->actions), ]; if ($ctx->error === true) { if (isset($ctx->exception)) { // string $error_message $result['error_message'] = substr($ctx->exception->getMessage(), 0, 100); } } Yii::info("door log: " . implode(";", $result)); Yii::$app->db->beginTransaction(); $log = new DoorManagerLog($result); $log->save(false); Yii::$app->db->transaction->commit(); } catch (\Exception $e) { Yii::error("Failed to log door context:" . $e->getMessage()); } } function moveEmergency($ctx) { $ctx->kind = "EMERGENCY"; Yii::info("$ctx->requestId: emergency move"); try { $createdAtStr = DateUtil::formatDateTimeUtc($ctx->createdAt); Yii::$app->db->beginTransaction(); $doorLog = new DoorLog(); $doorLog->version = 2; $doorLog->direction = DoorLog::$DIRECTION_ALL_EMERGENCY; $doorLog->source_app = $ctx->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 ] ); $ctx->actions[] = "EMERGENCY_MOVE"; Yii::$app->db->transaction->commit(); } catch (\Exception $e) { Yii::$app->db->transaction->rollBack(); throw new FitnessException( "unknowon error", FitnessException::UNKNOWN_ERROR, "EMERGENCY_OPEN_FAILED", $ctx, $e ); } } function moveEmployee($ctx /*$identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey*/) { $ctx->kind = "EMPLOYEE"; /** * if the card type is employee, neither customer nor ticket is needed. * Free to enter/leave */ Yii::info("employee move"); } /** * @param $ctx * * @return void * @throws FitnessException */ function moveCustomer($ctx/* $requestId, $identifier, $device, $direction, $verifyOnly, $createdAt, $date, $card, $cardNumber, $virtualKey*/) { $ctx->kind = "CUSTOMER"; Yii::info("$ctx->requestId: move customer"); $stopWatch = new StopWatch(); try { Yii::$app->db->beginTransaction(); // load active ticket $activeTickets = Ticket::readActive($ctx->card, clone $ctx->date); Yii::info("$ctx->requestId: active ticket count:" . count($activeTickets)); /** @var Ticket $ticket */ $ticket = null; if (isset($activeTickets) && count($activeTickets) > 0) { for ( $i = 0; ($i < count($activeTickets)) && !isset($ticket); $i++){ /**@var $currentTicket Ticket **/ $currentTicket = $activeTickets[$i]; if ( $currentTicket->usage_count < $currentTicket->max_usage_count){ $ticket = $currentTicket; } } } if (!isset($ticket)) { throw new FitnessException( "$ctx->requestId: No active ticket found for:" . $ctx->card->number, FitnessException::TYPE_BAD_REQUEST, "NOT_FOUND_ACTIVE_TICKET", $ctx ); } $ctx->ticket = $ticket; Yii::info("$ctx->requestId: ticket {$ticket->id_ticket} loaded in sec " . $stopWatch->split()); // load customer $customer = $ctx->card->customer; if (!isset($customer)) { throw new FitnessException( "$ctx->requestId: Customer not found for:" . $ctx->card->number, FitnessException::TYPE_BAD_REQUEST, "NOT_FOUND_CUSTOMER", $ctx ); } $ctx->customer = $customer; Yii::info("$ctx->requestId: customer {$customer->id_customer} loaded in sec " . $stopWatch->split()); // if direction is in, check if usage count must be increased if ($ctx->direction == DoorLog::$DIRECTION_IN) { // Key required if (!isset($ctx->key)) { throw new FitnessException( "$ctx->requestId: Key required: " . $ctx->card->id_card, FitnessException::TYPE_BAD_REQUEST, "REQUIRED_KEY", $ctx ); } // virtual_key without entry required if (isset($ctx->virtualKey->direction_in_at)) { throw new FitnessException( "$ctx->requestId: Virtual key - already moved in: " . $ctx->identifier . '/' . $ctx->virtualKey->id_card, FitnessException::TYPE_BAD_REQUEST, "VIRTUAL_KEY_ALREADY_IN", $ctx ); } $ctx->virtualKey->direction_in_at = Helper::getDateTimeString(); Yii::info("$ctx->requestId: Setting virtual key direction_in_at"); $ctx->actions[] = "VIRTUAL_KEY_MOVE_IN"; if (!$ctx->verifyOnly) { Yii::info("$ctx->requestId: Updating virtual key"); $ctx->virtualKey->save(false); } // if not verifyonly, check, if ticket usage count must be increased $allDoorLogToday = DoorManagerLog::findAllEntryForTicketFromTime($ctx->ticket->id_ticket); $countDoorLogsForTicketSince = count($allDoorLogToday); $ctx->increasedTicketUsageCount = false; // if the current event is the first door log today if ($countDoorLogsForTicketSince == 0) { $ctx->increasedTicketUsageCount = true; // $usageCount = $ticket->usage_count; // $ticket->usage_count += 1; // $ticket->save(false); // \Yii::info("$ctx->requestId: First ticket usage today, increasing usage count for card: " . $ctx->card->id_card); // \Yii::info("$ctx->requestId: Ticket usage count increased after first doorlog of day in sec " . $stopWatch->split()); $ctx->actions[] = "TICKET_FIRST_USAGE_TODAY"; } else { Yii::info("$ctx->requestId: more then one door log today for card: " . $ctx->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 $firstInToday = $allDoorLogToday[0]; Yii::info("$ctx->requestId: first in today for card: " . $ctx->card->id_card . " was at " . $firstInToday->created_at); $firstEntryDateTimeToday = DateUtil::parseDateTime($firstInToday->created_at); $interval = \DateInterval::createFromDateString('3 hours'); $startOfTomorrow = DateUtil::tomorrowStart(clone $ctx->date); $daterange = new \DatePeriod($firstEntryDateTimeToday, $interval, $startOfTomorrow); $intervals = []; $intervalStart = null; foreach ($daterange as $intervalEnd) { if (isset($intervalStart)) { $intervals[] = $this->createTicketUsageInterval($intervalStart, $intervalEnd, $allDoorLogToday); } $intervalStart = clone $intervalEnd; } if ($intervalStart < $startOfTomorrow) { $intervals[] = $this->createTicketUsageInterval($intervalStart, $startOfTomorrow, $allDoorLogToday); } $activeInterval = $this->getActiveInterval($intervals, $ctx->createdAt); if (!isset($activeInterval)) { throw new ServerErrorHttpException("$ctx->requestId: Active Interval not found"); } $logCountInActiveInterval = count($activeInterval['logs']); if ($logCountInActiveInterval == 0) { $ctx->increasedTicketUsageCount = true; $ctx->actions[] = "TICKET_INCREASE_USAGE"; } } if ($ctx->increasedTicketUsageCount === true) { $ticket->usage_count = $ticket->usage_count + 1; if ($ticket->usage_count > $ticket->max_usage_count) { throw new FitnessException( "$ctx->requestId: Ticket usage count exceeded", FitnessException::TYPE_BAD_REQUEST, "TICKET_MAX_USAGE_COUNT_EXCEEDED", $ctx ); } if (!$ctx->verifyOnly) { $ticket->save(false); } Yii::info("$ctx->requestId: Ticket usage count increased after first IN after first door_log in interval in sec " . $stopWatch->split()); } $ctx->actions[] = "MOVE_IN"; } if ($ctx->direction == DoorLog::$DIRECTION_OUT || $ctx->direction == DoorLog::$DIRECTION_OUT_WITHOUT_MOVE) { // virtual key with entry required if (!isset($ctx->virtualKey->direction_in_at)) { throw new FitnessException( "$ctx->requestId: Can't exit without move ind", FitnessException::TYPE_BAD_REQUEST, "VIRTUAL_KEY_MOVE_OUT_WITHOUT_MOVE_IN", $ctx ); } // virtual_key: only one move out is allowed if (isset($ctx->virtualKey->direction_out_at)) { throw new FitnessException( "$ctx->requestId: virtual key already out. Only one move out allowed.", FitnessException::TYPE_BAD_REQUEST, "VIRTUAL_KEY_ALREADY_OUT", $ctx ); } // key must not be unassigned if (isset($ctx->key)) { throw new FitnessException( "$ctx->requestId: Can't exit with card has a key assigned", FitnessException::TYPE_BAD_REQUEST, "CARD_LOCKER_KEY_ASSIGNED_FLAG", $ctx ); } // set move_out datetime $ctx->virtualKey->direction_out_at = Helper::getDateTimeString(); // save virtual key if (!$ctx->verifyOnly) { $ctx->virtualKey->save(false); } // set ticket move out counter $ticket->count_move_out = $ticket->usage_count; if (!$ctx->verifyOnly) { $ticket->save(false); } $ctx->actions[] = "MOVE_OUT"; Yii::info("$ctx->requestId: direction_out: ticket count_move_out set after direction_out in sec " . $stopWatch->split()); } $stopWatch->stop(); Yii::info("$ctx->requestId: finished in sec " . $stopWatch->getTotal()); Yii::$app->db->transaction->commit(); Yii::info("$ctx->requestId: Commited"); } catch (FitnessException $fe) { $this->doRollback(); Yii::info("$ctx->requestId: rollbacked"); throw $fe; } catch (\Exception $e) { $this->doRollback(); Yii::info("$ctx->requestId: rollbacked"); Yii::error($e->getMessage()); throw new FitnessException( "UNKNOWN_ERROR", FitnessException::UNKNOWN_ERROR, "CUSTOMER_UNKNOWN_ERROR", $ctx, $e ); } } function doRollback() { if (isset(Yii::$app->db->transaction) && Yii::$app->db->transaction->isActive) { Yii::$app->db->transaction->rollBack(); } } 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) { $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; } 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 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(); } }