public function getContent($params = array()) { $this->request->restrictAccess(Acl::RESOURCE_OWN_FARMS, Acl::PERM_FARMS_CREATE); $projects = []; if ($this->getContainer()->analytics->enabled && $this->getEnvironment()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID)) { $costCenter = $this->getContainer()->analytics->ccs->get($this->getEnvironment()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID)); $currentYear = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y'); $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('now', new \DateTimeZone('UTC'))); if ($costCenter instanceof CostCentreEntity) { $projectsIterator = new SharedProjectsFilterIterator($costCenter->getProjects(), $costCenter->ccId, $this->user, $this->getEnvironment()); foreach ($projectsIterator as $item) { $quarterBudget = QuarterlyBudgetEntity::findOne([['year' => $currentYear], ['subjectType' => QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT], ['subjectId' => $item->projectId], ['quarter' => $currentQuarter]]); $projects[] = array('projectId' => $item->projectId, 'name' => $item->name, 'budgetRemain' => !is_null($quarterBudget) && $quarterBudget->budget > 0 ? max(0, round($quarterBudget->budget - $quarterBudget->cumulativespend)) : null); } //$costCentreName = $costCenter->name; $isLocked = $costCenter->getProperty(CostCentrePropertyEntity::NAME_LOCKED); $accountCcs = AccountCostCenterEntity::findOne([['accountId' => $this->environment->clientId], ['ccId' => $costCenter->ccId]]); if ($isLocked || !$accountCcs instanceof AccountCostCenterEntity) { $costCentreLocked = 1; } else { $costCentreLocked = 0; } } else { $costCentreName = ''; $costCentreLocked = 0; } } return ['costCenterLocked' => $costCentreLocked, 'projects' => $projects]; }
public function xSaveAction() { $this->request->defineParams(array('projectId' => ['type' => 'string'], 'year' => ['type' => 'int'], 'quarters' => ['type' => 'json'], 'selectedQuarter' => ['type' => 'string'])); $year = $this->getParam('year'); $selectedQuarter = $this->getParam('selectedQuarter'); if ($selectedQuarter !== 'year' && ($selectedQuarter < 1 || $selectedQuarter > 4)) { throw new OutOfBoundsException(sprintf("Invalid selectedQuarter number.")); } $quarterReq = []; foreach ($this->getParam('quarters') as $q) { if (!isset($q['quarter'])) { throw new InvalidArgumentException(sprintf("Missing quarter property for quarters data set in the request.")); } if ($q['quarter'] < 1 || $q['quarter'] > 4) { throw new OutOfRangeException(sprintf("Quarter value should be between 1 and 4.")); } if (!isset($q['budget'])) { throw new InvalidArgumentException(sprintf("Missing budget property for quarters data set in the request.")); } $quarterReq[$q['quarter']] = $q; } if ($this->getParam('projectId')) { $subjectType = QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT; $subjectId = $this->getParam('projectId'); $subjectEntity = ProjectEntity::findPk($subjectId); /* @var $subjectEntity ProjectEntity */ if ($subjectEntity->accountId != $this->user->getAccountId() || $subjectEntity->shared != ProjectEntity::SHARED_WITHIN_ACCOUNT) { throw new Scalr_Exception_InsufficientPermissions(); } } else { throw new InvalidArgumentException(sprintf('ProjectId must be provided with the request.')); } if (!preg_match("/^[[:xdigit:]-]{36}\$/", $subjectId)) { throw new InvalidArgumentException(sprintf("Invalid UUID has been passed.")); } if (!preg_match('/^\\d{4}$/', $year)) { throw new InvalidArgumentException(sprintf("Invalid year has been passed.")); } //Fetches the previous state of the entities from database $collection = QuarterlyBudgetEntity::getProjectBudget($year, $subjectId); $quarters = new Quarters(SettingEntity::getQuarters(true)); //Updates|creates entities for ($quarter = 1; $quarter <= 4; ++$quarter) { if (!isset($quarterReq[$quarter])) { continue; } $period = $quarters->getPeriodForQuarter($quarter, $year); //Checks if period has already been closed and forbids update if ($period->end->format('Y-m-d') < gmdate('Y-m-d')) { continue; } $entity = current($collection->filterByQuarter($quarter)); if ($entity instanceof QuarterlyBudgetEntity) { //We should update an entity $entity->budget = abs((double) $quarterReq[$quarter]['budget']); } else { //We should create a new one. $entity = new QuarterlyBudgetEntity($year, $quarter); $entity->subjectType = $subjectType; $entity->subjectId = $subjectId; $entity->budget = abs((double) $quarterReq[$quarter]['budget']); } $entity->save(); } if ($selectedQuarter == 'year') { $selectedPeriod = $quarters->getPeriodForYear($year); } else { $selectedPeriod = $quarters->getPeriodForQuarter($selectedQuarter, $year); } $data = $this->getProjectData(ProjectEntity::findPk($subjectId), $selectedPeriod, true); $budgetInfo = $this->getBudgetInfo($year, $data['ccId'], $data['projectId']); $this->response->data(['data' => $data, 'budgetInfo' => $budgetInfo]); $this->response->success('Budget changes have been saved'); }
/** * Gets quarterly budget of specified year for the CC * * @param int $year The year * @throws \RuntimeException * @return \Scalr\Model\Collections\ArrayCollection Returns collection of the QuarterlyBudgetEntity objects */ public function getQuarterlyBudget($year) { if ($this->projectId === null) { throw new \RuntimeException(sprintf("Identifier of the cost center has not been initialized yet for %s", get_class($this))); } return QuarterlyBudgetEntity::getCcBudget($year, $this->ccId); }
/** * Gets budget used percentage * * @param array $request Request array should look like * ['projectId' => id, 'ccId' => id, 'period' => period, 'getRelationDependentBudget' => true] * @return array * @throws \InvalidArgumentException */ public function getBudgetUsedPercentage($request) { $ret = []; if (!empty($request['projectId'])) { $subjectType = QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT; $subjectId = $request['projectId']; } else { if (!empty($request['ccId'])) { $subjectType = QuarterlyBudgetEntity::SUBJECT_TYPE_CC; $subjectId = $request['ccId']; } } $ret['budget'] = 0; $ret['budgetSpentPct'] = null; $ret['budgetSpent'] = 0; $ret['budgetRemain'] = null; $ret['budgetRemainPct'] = null; $ret['budgetOverspend'] = 0; $ret['budgetOverspendPct'] = 0; $ret['budgetSpentOnDate'] = null; $ret['budgetSpentThisPeriodPct'] = 0; $ret['quarter'] = null; $ret['year'] = null; $ret['quarterStartDate'] = null; $ret['quarterEndDate'] = null; //This is a rudiment as we use cumulativespend column everywhere $ret['budgetFinalSpent'] = 0; if (isset($subjectId)) { $period = isset($request['period']) ? $request['period'] : $this->_getCurrentPeriod(); if (!$period instanceof QuarterPeriod) { throw new InvalidArgumentException(sprintf("Period must be instance of the Scalr\\Stats\\CostAnalytics\\QuarterPeriod class. %s given", gettype($period))); } $quarter = $period->quarter; $year = $period->year; $ret['quarter'] = $quarter; $ret['year'] = $year; $ret['quarterStartDate'] = $period->start->format('Y-m-d'); $ret['quarterEndDate'] = $period->end->format('Y-m-d'); $ret['closed'] = $ret['quarterEndDate'] < gmdate('Y-m-d'); //Retrieves budget from database if ($quarter == 'year') { $quarters = new Quarters(SettingEntity::getQuarters()); //Calculates total budgeted cost for the specified year $quarterlyBudget = new QuarterlyBudgetEntity(); $quarterlyBudget->year = $year; $quarterlyBudget->subjectId = $subjectId; $quarterlyBudget->subjectType = $subjectType; $collection = QuarterlyBudgetEntity::find([['year' => $year], ['subjectType' => $subjectType], ['subjectId' => $subjectId]]); //List of the quarters which budget has not been set for $arrNotSet = [1, 2, 3, 4]; foreach ($collection as $entity) { $period = $quarters->getPeriodForQuarter($entity->quarter, $year); //It shoud take into account only ongoing or future quarters without budget set if ($entity->budget > 0 || $period->end->format('Y-m-d') < gmdate('Y-m-d')) { if (isset($arrNotSet[$entity->quarter - 1])) { unset($arrNotSet[$entity->quarter - 1]); } } $quarterlyBudget->budget += $entity->budget; $quarterlyBudget->cumulativespend += $entity->cumulativespend; } if (!empty($arrNotSet)) { $ret['budgetAlert'] = "Budget has not been allocated for Q" . join(", Q", $arrNotSet); } } else { $quarterlyBudget = QuarterlyBudgetEntity::findPk($year, $subjectType, $subjectId, $quarter); } if ($quarterlyBudget instanceof QuarterlyBudgetEntity) { $ret['budget'] = round($quarterlyBudget->budget); $ret['budgetSpent'] = round($quarterlyBudget->cumulativespend); if ($ret['budget']) { $ret['budgetOverspend'] = max(round($quarterlyBudget->cumulativespend - $quarterlyBudget->budget), 0); $ret['budgetOverspendPct'] = round($ret['budgetOverspend'] / $ret['budget'] * 100); } $ret['budgetSpentPct'] = $ret['budget'] == 0 ? null : min(100, round($ret['budgetSpent'] / $ret['budget'] * 100)); if (isset($request['usage'])) { $ret['budgetSpentThisPeriodPct'] = $ret['budget'] == 0 ? null : min(100, round($request['usage'] / $ret['budget'] * 100)); } $ret['budgetRemain'] = max(0, round($ret['budget'] - $ret['budgetSpent'])); $ret['budgetRemainPct'] = $ret['budgetSpentPct'] !== null ? 100 - $ret['budgetSpentPct'] : null; if ($ret['closed']) { $ret['costVariance'] = $ret['budgetSpent'] - $ret['budget']; $ret['costVariancePct'] = $ret['budget'] == 0 ? null : round(abs($ret['costVariance']) / $ret['budget'] * 100); } $ret['budgetFinalSpent'] = round($quarterlyBudget->final); $ret['budgetSpentOnDate'] = $quarterlyBudget->spentondate instanceof DateTime ? $quarterlyBudget->spentondate->format('Y-m-d') : null; if (!empty($request['getRelationDependentBudget'])) { if ($quarterlyBudget->subjectType != QuarterlyBudgetEntity::SUBJECT_TYPE_CC && isset($request['ccId'])) { $ccQuarterlyBudget = clone $quarterlyBudget; $ccQuarterlyBudget->subjectType = QuarterlyBudgetEntity::SUBJECT_TYPE_CC; $ccQuarterlyBudget->subjectId = $request['ccId']; $ret['relationDependentBudget'] = round($ccQuarterlyBudget->getRelationDependentBudget()); unset($ccQuarterlyBudget); } else { $ret['relationDependentBudget'] = round($quarterlyBudget->getRelationDependentBudget()); } } } } return $ret; }
/** * Gets project moving average to date * * @param string|null $projectId The identifier of the project * @param string $mode The mode * @param string $date The UTC date within period ('Y-m-d H:00') * @param string $startDate The start date of the period in UTC ('Y-m-d') * @param string $endDate The end date of the period in UTC ('Y-m-d') * @param string $ccId optional The identifier of the cost center (It is used only when project is null) * @return array Returns project moving average to date * @throws InvalidArgumentException * @throws AnalyticsException */ public function getProjectMovingAverageToDate($projectId, $mode, $date, $startDate, $endDate, $ccId = null) { $projectId = empty($projectId) ? null : $projectId; if (!preg_match('/^[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:00$/', $date)) { throw new InvalidArgumentException(sprintf("Invalid date:%s. 'YYYY-MM-DD HH:00' is expected.", strip_tags($date))); } if (!preg_match('/^[[:xdigit:]-]{36}$/', $projectId)) { throw new InvalidArgumentException(sprintf("Invalid identifier of the Project:%s. UUID is expected.", strip_tags($projectId))); } $iterator = ChartPeriodIterator::create($mode, $startDate, $endDate ?: null, 'UTC'); //Interval which is used in the database query for grouping $queryInterval = preg_replace('/^1 /', '', $iterator->getInterval()); if ($projectId !== null) { $project = ProjectEntity::findPk($projectId); if ($project === null) { if (empty($ccId)) { throw new AnalyticsException(sprintf("Project %s does not exist. Please provide ccId.", $projectId)); } } else { $ccId = $project->ccId; } } $data = $this->getRollingAvg(['projectId' => $projectId], $queryInterval, $date); $data['budgetUseToDate'] = null; $data['budgetUseToDatePct'] = null; //Does not calculate budget use to date for those cases if ($mode != 'custom' && $mode != 'year') { $pointPosition = $iterator->searchPoint($date); if ($pointPosition !== false) { $chartPoint = $iterator->current(); //Gets the end date of the selected interval $end = clone $chartPoint->dt; if ($chartPoint->interval != '1 day') { $end->modify("+" . $chartPoint->interval . " -1 day"); } //Gets quarters config $quarters = new Quarters(SettingEntity::getQuarters()); //Gets start and end of the quarter for the end date of the current interval $period = $quarters->getPeriodForDate($end); $data['year'] = $period->year; $data['quarter'] = $period->quarter; $data['quarterStartDate'] = $period->start->format('Y-m-d'); $data['quarterEndDate'] = $period->end->format('Y-m-d'); //Gets budgeted cost $budget = current(QuarterlyBudgetEntity::getProjectBudget($period->year, $projectId)->filterByQuarter($period->quarter)); //If budget has not been set we should not calculate anything if ($budget instanceof QuarterlyBudgetEntity && round($budget->budget) > 0) { $data['budget'] = round($budget->budget); //Calculates usage from the start date of the quarter to date $usage = $this->get(['projectId' => $projectId], $period->start, $end); $data['budgetUseToDate'] = $usage['cost']; $data['budgetUseToDatePct'] = $data['budget'] == 0 ? null : min(100, round($usage['cost'] / $data['budget'] * 100)); } } } return ['data' => $data]; }
private function getFarmCostData($farmId) { $result = []; $costCenter = $this->getContainer()->analytics->ccs->get($this->getEnvironment()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID)); $currentYear = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y'); $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('now', new \DateTimeZone('UTC'))); $projects = []; if ($farmId) { $farm = DBFarm::LoadByID($farmId); $currentProjectId = $farm->GetSetting(Entity\FarmSetting::PROJECT_ID); $currentProject = ProjectEntity::findPk($currentProjectId); /* @var $currentProject ProjectEntity */ if (!empty($currentProject)) { $quarterBudget = QuarterlyBudgetEntity::findOne([['year' => $currentYear], ['subjectType' => QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT], ['subjectId' => $currentProject->projectId], ['quarter' => $currentQuarter]]); $projects[] = ['projectId' => $currentProject->projectId, 'name' => "{$currentProject->name} / {$currentProject->getCostCenter()->name}", 'budgetRemain' => !is_null($quarterBudget) && $quarterBudget->budget > 0 ? max(0, round($quarterBudget->budget - $quarterBudget->cumulativespend)) : null]; } $result['projectId'] = $farm->GetSetting(Entity\FarmSetting::PROJECT_ID); $result['farmCostMetering'] = $result['projectId'] ? $this->getContainer()->analytics->usage->getFarmCostMetering($this->user->getAccountId(), $farmId) : null; } if ($costCenter instanceof CostCentreEntity) { $projectsIterator = new SharedProjectsFilterIterator($costCenter->getProjects(), $costCenter->ccId, $this->user, $this->getEnvironment()); foreach ($projectsIterator as $item) { /* @var $item Scalr\Stats\CostAnalytics\Entity\ProjectEntity */ if (!empty($currentProjectId) && $item->projectId == $currentProjectId) { continue; } $quarterBudget = QuarterlyBudgetEntity::findOne([['year' => $currentYear], ['subjectType' => QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT], ['subjectId' => $item->projectId], ['quarter' => $currentQuarter]]); $projects[] = array('projectId' => $item->projectId, 'name' => "{$item->name} / {$costCenter->name}", 'budgetRemain' => !is_null($quarterBudget) && $quarterBudget->budget > 0 ? max(0, round($quarterBudget->budget - $quarterBudget->cumulativespend)) : null); } $costCentreName = $costCenter->name; $isLocked = $costCenter->getProperty(CostCentrePropertyEntity::NAME_LOCKED); $accountCcs = AccountCostCenterEntity::findOne([['accountId' => $this->environment->clientId], ['ccId' => $costCenter->ccId]]); if ($isLocked || !$accountCcs instanceof AccountCostCenterEntity) { $costCentreLocked = 1; } else { $costCentreLocked = 0; } } else { $costCentreName = ''; $costCentreLocked = 0; } $supportedClouds = $this->getContainer()->analytics->prices->getSupportedClouds(); $result['analytics'] = array('costCenterName' => $costCentreName, 'costCenterLocked' => $costCentreLocked, 'projects' => $projects, 'unsupportedClouds' => array_values(array_diff($this->environment->getEnabledPlatforms(), $supportedClouds))); return $result; }