protected function run1($stage) { $this->console->out('Initializing quarterly_budget data...'); $quarters = new Quarters(SettingEntity::getQuarters()); $currentYearPeriod = $quarters->getPeriodForDate(); $currentFiscalYear = $currentYearPeriod->year; $prevFiscalYear = $currentFiscalYear - 1; $this->db->Execute("\n UPDATE quarterly_budget b\n SET b.`cumulativespend` = 0.000000\n WHERE b.`year` = ? OR b.`year` = ?\n ", [$currentFiscalYear, $prevFiscalYear]); foreach ([$prevFiscalYear, $currentFiscalYear] as $year) { for ($quarter = 1; $quarter <= 4; $quarter++) { $quarterPeriod = $quarters->getPeriodForQuarter($quarter, $year); $this->db->Execute("\n INSERT INTO quarterly_budget (`year`, `quarter`, `subject_type`, `subject_id`, `cumulativespend`)\n SELECT ?, u.`quarter`, u.`subject_type`, u.`subject_id`, u.`cumulativespend`\n FROM (\n SELECT ? AS `quarter`, ? AS `subject_type`, `cc_id` AS `subject_id`, SUM(`cost`) AS `cumulativespend`\n FROM usage_d\n WHERE `date` BETWEEN ? AND ?\n GROUP BY `cc_id`\n UNION ALL\n SELECT ? AS `quarter`, ? AS `subject_type`, `project_id` AS `subject_id`, SUM(`cost`) AS `cumulativespend`\n FROM usage_d\n WHERE `date` BETWEEN ? AND ?\n GROUP BY `project_id`\n ) AS u\n ON DUPLICATE KEY UPDATE\n `cumulativespend` = u.`cumulativespend`\n ", [$year, $quarter, QuarterlyBudgetEntity::SUBJECT_TYPE_CC, $quarterPeriod->start->format('Y-m-d'), $quarterPeriod->end->format('Y-m-d'), $quarter, QuarterlyBudgetEntity::SUBJECT_TYPE_PROJECT, $quarterPeriod->start->format('Y-m-d'), $quarterPeriod->end->format('Y-m-d')]); $this->db->Execute("\n UPDATE quarterly_budget b\n JOIN (\n (SELECT SUM(`cost`) AS `cost`, `date`, `cc_id` AS `subject_id`\n FROM usage_d\n WHERE `date` BETWEEN ? AND ?\n GROUP BY `cc_id`, `date`)\n UNION ALL\n (SELECT SUM(`cost`) AS `cost`, `date`, `project_id` AS `subject_id`\n FROM usage_d\n WHERE `date` BETWEEN ? AND ?\n GROUP BY `project_id`, `date`)\n ORDER BY `date`\n ) AS ud ON b.subject_id = ud.`subject_id` AND b.`year` = ?\n SET b.`spentondate` = IF (\n b.`budget` > 0 AND b.`spentondate` IS NULL AND ud.`cost` >= b.`budget`,\n ud.`date`,\n b.`spentondate`\n )\n WHERE b.`quarter` = ?\n ", [$quarterPeriod->start->format('Y-m-d'), $quarterPeriod->end->format('Y-m-d'), $quarterPeriod->start->format('Y-m-d'), $quarterPeriod->end->format('Y-m-d'), $year, $quarter]); } } }
/** * @test * @dataProvider providerConstructor */ public function testConstructor($days, $fixtures) { $quarters = new Quarters($days); foreach ($fixtures as $i => $v) { $this->assertEquals($v['quarter'], $quarters->getQuarterForDate($v['date']), sprintf('The number of the quarter for the date "%s" is expected to be %d.', $v['date'], $v['quarter'])); $period = $quarters->getPeriodForQuarter($v['quarter'], $v['year']); $this->assertInternalType('object', $period); $this->assertEquals($v['start'], $period->start->format('Y-m-d'), sprintf('Start date is expected to be "%s".', $v['start'])); $this->assertEquals($v['end'], $period->end->format('Y-m-d'), sprintf('End date is expected to be "%s".', $v['end'])); $this->assertEquals($v['year'], $period->year); $this->assertEquals($v['quarter'], $period->quarter); $periodForDate = $quarters->getPeriodForDate(new DateTime($v['date'], new DateTimeZone('UTC'))); $this->assertEquals($v['start'], $periodForDate->start->format('Y-m-d'), sprintf('getPeriodForDate for fixture#%d failed. Start date is expected to be "%s".', $i, $v['start'])); $this->assertEquals($v['end'], $periodForDate->end->format('Y-m-d'), sprintf('getPeriodForDate for fixture#%d failed. End date is expected to be "%s".', $i, $v['end'])); $this->assertEquals($v['year'], $periodForDate->year, sprintf('getPeriodForDate for fixture#%d failed. Year is expected to be "%s".', $i, $v['year'])); $this->assertEquals($v['quarter'], $periodForDate->quarter, sprintf('getPeriodForDate for fixture#%d failed. Quarter is expected to be "%s".', $i, $v['quarter'])); } }
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'); }
/** * {@inheritdoc} * @see \Scalr\System\Zmq\Cron\TaskInterface::enqueue() */ public function enqueue() { if (!\Scalr::getContainer()->analytics->enabled) { $this->getLogger()->info("Terminating the process as Cost analytics is disabled in the config.\n"); return new ArrayObject(); } $this->getLogger()->info("%s (UTC) Start Analytics Notifications process", gmdate('Y-m-d')); $notifications = NotificationEntity::find(); $this->getLogger()->info('Calculating data for projects and cost centers notifications'); foreach ($notifications as $notification) { /* @var $notification NotificationEntity */ if ($notification->status === NotificationEntity::STATUS_DISABLED) { continue; } if ($notification->subjectType === NotificationEntity::SUBJECT_TYPE_CC) { $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\CostCentre'; } else { if ($notification->subjectType === NotificationEntity::SUBJECT_TYPE_PROJECT) { $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\Project'; } } if (!empty($notification->subjectId)) { $subject = call_user_func($subjectEntityName . 'Entity::findPk', $notification->subjectId); $this->saveNotificationData($subject, $notification); } else { $subjects = call_user_func($subjectEntityName . 'Entity::find'); foreach ($subjects as $subject) { if ($subject->archived) { continue; } $this->saveNotificationData($subject, $notification); } } } $this->getLogger()->info('Calculating data for reports'); $reports = ReportEntity::find(); foreach ($reports as $report) { /* @var $report ReportEntity */ if ($report->status === ReportEntity::STATUS_DISABLED) { continue; } switch ($report->period) { case ReportEntity::PERIOD_DAILY: $period = 'custom'; $start = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = $start; $startForecast = (new \DateTime('first day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); if ($startForecast == (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d')) { $startForecast = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); } $periodForecast = 'month'; $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j'); $formatedForecastDate = (new \DateTime($start, new \DateTimeZone('UTC')))->format('F'); break; case ReportEntity::PERIOD_MONTHLY: $period = 'month'; $start = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M Y'); break; case ReportEntity::PERIOD_QUARTELY: $period = 'quarter'; $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('yesterday', new \DateTimeZone('UTC'))); $currentYear = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y'); if ($currentQuarter === 1) { $quarter = 4; $year = $currentYear - 1; } else { $quarter = $currentQuarter - 1; $year = $currentYear; } $date = $quarters->getPeriodForQuarter($quarter, $year); $start = $date->start->format('Y-m-d'); $end = $date->end->format('Y-m-d'); $formatedTitle = 'Q' . $quarter . ' ' . $year; $formatedForecastDate = 'End of ' . $currentYear; $startForecast = $currentYear . '-01-01'; $endForecast = $currentYear . '-12-31'; $periodForecast = 'year'; break; case ReportEntity::PERIOD_WEEKLY: $period = 'week'; $end = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->modify('last saturday')->format('Y-m-d'); $start = (new \DateTime($end, new \DateTimeZone('UTC')))->modify('last sunday')->format('Y-m-d'); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j') . ' - ' . (new \DateTime($end, new \DateTimeZone('UTC')))->format('M j'); break; } if ($report->period !== ReportEntity::PERIOD_DAILY && $report->period !== ReportEntity::PERIOD_QUARTELY) { $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime($start, new \DateTimeZone('UTC'))); $currentYear = (new \DateTime($start, new \DateTimeZone('UTC')))->format('Y'); $date = $quarters->getPeriodForQuarter($currentQuarter, $currentYear); $formatedForecastDate = 'End of Q' . $currentQuarter; $startForecast = $date->start->format('Y-m-d'); $endForecast = $date->end->format('Y-m-d'); $periodForecast = 'quarter'; } if ($report->subjectType === ReportEntity::SUBJECT_TYPE_CC) { $getPeriodicSubjectData = 'getCostCenterPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\CostCentre'; $subjectId = 'ccId'; } else { if ($report->subjectType === ReportEntity::SUBJECT_TYPE_PROJECT) { $getPeriodicSubjectData = 'getProjectPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\Project'; $subjectId = 'projectId'; } else { $baseUrl = rtrim(\Scalr::getContainer()->config('scalr.endpoint.scheme') . "://" . \Scalr::getContainer()->config('scalr.endpoint.host'), '/'); $periodData = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($period, $start, $end); $periodDataForecast = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($periodForecast, $startForecast, $endForecast); $periodData['period'] = $period; $periodData['forecastPeriod'] = $formatedForecastDate; $periodData['totals']['forecastCost'] = $periodDataForecast['totals']['forecastCost']; $periodData['name'] = 'Cloud Cost Report'; $periodData['jsonVersion'] = '1.0.0'; $periodData['detailsUrl'] = $baseUrl . '#/analytics/dashboard'; $periodData['totals']['clouds'] = $this->changeCloudNames($periodData['totals']['clouds']); $periodData['date'] = $formatedTitle; $periodData['totals']['budget']['budget'] = null; if ($period !== 'custom') { $periodData['totals']['prevPeriodDate'] = (new \DateTime($periodData['previousStartDate'], new \DateTimeZone('UTC')))->format('M d') . " - " . (new \DateTime($periodData['previousEndDate'], new \DateTimeZone('UTC')))->format('M d'); } else { $periodData['totals']['prevPeriodDate'] = (new \DateTime($periodData['previousEndDate'], new \DateTimeZone('UTC')))->format('M d'); } if ($period == 'quarter') { $periodData['totals']['budget'] = ['quarter' => $quarter, 'year' => $year, 'quarterStartDate' => $start, 'quarterEndDate' => $end]; } else { if ($period == 'month') { $periodData['totals']['budget'] = ['quarter' => $currentQuarter]; } } unset($periodData['projects'], $periodData['budget']['projects']); if (count($periodData['costcenters'] > 1)) { uasort($periodData['costcenters'], array($this, 'sortItems')); if (count($periodData['costcenters'] > 6)) { array_splice($periodData['costcenters'], 6, count($periodData['costcenters'])); } } if (count($periodData['totals']['clouds'] > 1)) { usort($periodData['totals']['clouds'], array($this, 'sortItems')); } $entity = ReportPayloadEntity::init([$report->subjectType, $report->subjectId, $period], $periodData, $start); if (!ReportPayloadEntity::findPk($entity->uuid)) { $payload = json_decode($entity->payload, true); \Scalr::getContainer()->mailer->setSubject('Summary report.')->setContentType('text/html')->sendTemplate(SCALR_TEMPLATES_PATH . '/emails/report_summary.html.php', $payload, $report->emails); $this->getLogger()->info('Summary report email has been sent'); $payload['date'] = $entity->created->format('Y-m-d'); $entity->payload = json_encode($payload); $entity->save(); } } } unset($currentQuarter, $currentYear); if (!empty($report->subjectType) && !empty($report->subjectId)) { $subject = call_user_func($subjectEntityName . 'Entity::findPk', $report->subjectId); if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $report->subjectId, $report->subjectType, $report->emails, $formatedTitle, $formatedForecastDate); } else { if (!empty($report->subjectType)) { $subjects = call_user_func($subjectEntityName . 'Entity::find'); foreach ($subjects as $subject) { if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $subject->{$subjectId}, $report->subjectType, $report->emails, $formatedTitle, $formatedForecastDate); } } } } $this->getLogger()->info('Done'); return new ArrayObject(); }
/** * 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; }
/** * Returns iterator for current quarter * * @return Iterator\ChartQuarterlyIterator */ public function getCurrentQuarterIterator() { $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('now', new \DateTimeZone('UTC'))); $currentYear = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y'); if ($currentQuarter === 1) { $quarter = 4; $year = $currentYear - 1; } else { $quarter = $currentQuarter - 1; $year = $currentYear; } $date = $quarters->getPeriodForQuarter($quarter, $year); $iterator = ChartPeriodIterator::create('quarter', $date->start, $date->end, 'UTC'); return $iterator; }
/** * {@inheritdoc} * @see \Scalr\System\Pcntl\ProcessInterface::OnStartForking() */ public function OnStartForking() { if (!\Scalr::getContainer()->analytics->enabled) { die("Terminating the process as Cost analytics is disabled in the config.\n"); } $this->console->out("%s (UTC) Start Analytics Notifications process", gmdate('Y-m-d')); if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_CCS_ENABLED) || SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_PROJECTS_ENABLED)) { $this->console->out('Calculating data for projects notifications'); $quarters = new Quarters(SettingEntity::getQuarters()); $date = $quarters->getPeriodForDate('yesterday'); $formatedTitle = 'Q' . $quarters->getQuarterForDate('now') . ' budget (' . (new \DateTime('now', new \DateTimeZone('UTC')))->format('M j, Y') . ')'; $projects = ProjectEntity::find(); foreach ($projects as $project) { $periodProjectData = \Scalr::getContainer()->analytics->usage->getProjectPeriodData($project->projectId, 'quarter', $date->start->format('Y-m-d'), $date->end->format('Y-m-d')); $projectAnalytics[$project->projectId] = ['budget' => $periodProjectData['totals']['budget'], 'name' => $project->name, 'trends' => $periodProjectData['totals']['trends'], 'forecastCost' => $periodProjectData['totals']['forecastCost'], 'interval' => $periodProjectData['interval'], 'jsonVersion' => '1.0.0', 'farms' => []]; if (!empty($periodProjectData['totals']['farms'])) { foreach ($periodProjectData['totals']['farms'] as $farm) { $projectAnalytics[$project->projectId]['farms'][] = ['id' => $farm['id'], 'name' => $farm['name'], 'median' => $farm['median'] / 7, 'cost' => $farm['cost'], 'costPct' => $farm['costPct']]; } if (count($projectAnalytics[$project->projectId]['farms'] > 1)) { usort($projectAnalytics[$project->projectId]['farms'], array($this, 'sortItems')); if (count($projectAnalytics[$project->projectId]['farms'] > 6)) { array_splice($projectAnalytics[$project->projectId]['farms'], 6, count($projectAnalytics[$project->projectId]['farms'])); } } } $projectAnalytics[$project->projectId]['date'] = $formatedTitle; if ($project->archived) { continue; } if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_PROJECTS_ENABLED)) { $projectNotifications = NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_PROJECT); foreach ($projectNotifications as $notification) { $this->saveNotificationData('project', $notification, $project->projectId, $projectAnalytics); } } } } if (SettingEntity::getValue(SettingEntity::ID_NOTIFICATIONS_CCS_ENABLED)) { $this->console->out('Calculating data for cost center notifications'); $ccs = CostCentreEntity::find(); foreach ($ccs as $cc) { if ($cc->archived) { continue; } $periodCostCenterData = \Scalr::getContainer()->analytics->usage->getCostCenterPeriodData($cc->ccId, 'quarter', $date->start->format('Y-m-d'), $date->end->format('Y-m-d')); $ccAnalytics[$cc->ccId] = ['budget' => $periodCostCenterData['totals']['budget'], 'name' => $cc->name, 'trends' => $periodCostCenterData['totals']['trends'], 'forecastCost' => $periodCostCenterData['totals']['forecastCost'], 'interval' => $periodCostCenterData['interval'], 'jsonVersion' => '1.0.0', 'projects' => []]; if (!empty($periodCostCenterData['totals']['projects'])) { foreach ($periodCostCenterData['totals']['projects'] as $key => $project) { if (!empty($project['id'])) { $projectBudget = $projectAnalytics[$project['id']]['budget']; $projectBudget['name'] = $project['name']; $projectBudget['id'] = $project['id']; $projectBudget['median'] = $project['median'] / 7; $ccAnalytics[$cc->ccId]['projects'][] = $projectBudget; } else { $otherProjectsKey = $key; } } if (isset($otherProjectsKey)) { $ccAnalytics[$cc->ccId]['projects'][] = ['id' => '', 'budgetSpent' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['cost'], 'median' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['median'] / 7, 'name' => $periodCostCenterData['totals']['projects'][$otherProjectsKey]['name'], 'estimateOverspend' => null]; unset($otherProjectsKey); } if (count($ccAnalytics[$cc->ccId]['projects'] > 1)) { usort($ccAnalytics[$cc->ccId]['projects'], array($this, 'sortItems')); if (count($ccAnalytics[$cc->ccId]['projects'] > 6)) { array_splice($ccAnalytics[$cc->ccId]['projects'], 6, count($ccAnalytics[$cc->ccId]['projects'])); } } } $ccAnalytics[$cc->ccId]['date'] = $formatedTitle; $ccsNotifications = NotificationEntity::findBySubjectType(NotificationEntity::SUBJECT_TYPE_CC); foreach ($ccsNotifications as $notification) { $this->saveNotificationData('cc', $notification, $cc->ccId, $ccAnalytics); } } } if (SettingEntity::getValue(SettingEntity::ID_REPORTS_ENABLED)) { $this->console->out('Calculating data for reports'); $reports = ReportEntity::find(); foreach ($reports as $report) { switch ($report->period) { case ReportEntity::PERIOD_DAILY: $period = 'custom'; $start = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = $start; $startForecast = (new \DateTime('first day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); if ($startForecast == (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d')) { $startForecast = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); } $periodForecast = 'month'; $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j'); break; case ReportEntity::PERIOD_MONTHLY: $period = 'month'; $start = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $end = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime($start, new \DateTimeZone('UTC'))); $currentYear = (new \DateTime($start, new \DateTimeZone('UTC')))->format('Y'); $date = $quarters->getPeriodForQuarter($currentQuarter, $currentYear); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M Y'); $startForecast = $date->start->format('Y-m-d'); $endForecast = $date->end->format('Y-m-d'); $periodForecast = 'quarter'; break; case ReportEntity::PERIOD_QUARTELY: $period = 'quarter'; $quarters = new Quarters(SettingEntity::getQuarters()); $currentQuarter = $quarters->getQuarterForDate(new \DateTime('yesterday', new \DateTimeZone('UTC'))); $currentYear = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->format('Y'); if ($currentQuarter === 1) { $quarter = 4; $year = $currentYear - 1; } else { $quarter = $currentQuarter - 1; $year = $currentYear; } $date = $quarters->getPeriodForQuarter($quarter, $year); $start = $date->start->format('Y-m-d'); $end = $date->end->format('Y-m-d'); $formatedTitle = 'Q' . $quarter . ' ' . $year; $startForecast = $currentYear . '-01-01'; $endForecast = $currentYear . '-12-31'; $periodForecast = 'year'; break; case ReportEntity::PERIOD_WEEKLY: $period = 'week'; $end = (new \DateTime('yesterday', new \DateTimeZone('UTC')))->modify('last saturday')->format('Y-m-d'); $start = (new \DateTime($end, new \DateTimeZone('UTC')))->modify('last sunday')->format('Y-m-d'); $formatedTitle = (new \DateTime($start, new \DateTimeZone('UTC')))->format('M j') . ' - ' . (new \DateTime($end, new \DateTimeZone('UTC')))->format('M j'); $startForecast = (new \DateTime('first day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of this month', new \DateTimeZone('UTC')))->format('Y-m-d'); if ($startForecast == (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d')) { $startForecast = (new \DateTime('first day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); $endForecast = (new \DateTime('last day of last month', new \DateTimeZone('UTC')))->format('Y-m-d'); } $periodForecast = 'month'; break; } if ($report->subjectType === ReportEntity::SUBJECT_TYPE_CC) { $getPeriodicSubjectData = 'getCostCenterPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\CostCentre'; $subjectId = 'ccId'; } else { if ($report->subjectType === ReportEntity::SUBJECT_TYPE_PROJECT) { $getPeriodicSubjectData = 'getProjectPeriodData'; $subjectEntityName = 'Scalr\\Stats\\CostAnalytics\\Entity\\Project'; $subjectId = 'projectId'; } else { $periodData = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($period, $start, $end); $periodDataForecast = \Scalr::getContainer()->analytics->usage->getDashboardPeriodData($periodForecast, $startForecast, $endForecast); $periodData['period'] = $period; $periodData['forecastPeriod'] = $periodForecast; $periodData['totals']['forecastCost'] = $periodDataForecast['totals']['forecastCost']; $periodData['totals']['trends'] = $periodDataForecast['totals']['trends']; $periodData['name'] = 'Cloud Cost Report'; $periodData['jsonVersion'] = '1.0.0'; $periodData['totals']['clouds'] = $this->changeCloudNames($periodData['totals']['clouds']); $periodData['date'] = $formatedTitle; $periodData['totals']['budget']['budget'] = null; if ($period == 'quarter') { $periodData['totals']['budget'] = ['quarter' => $quarter, 'year' => $year, 'quarterStartDate' => $start, 'quarterEndDate' => $end]; } else { if ($period == 'month') { $periodData['totals']['budget'] = ['quarter' => $currentQuarter]; } } unset($periodData['projects'], $periodData['budget']['projects']); if (count($periodData['costcenters'] > 1)) { uasort($periodData['costcenters'], array($this, 'sortItems')); if (count($periodData['costcenters'] > 6)) { array_splice($periodData['costcenters'], 6, count($periodData['costcenters'])); } } if (count($periodData['totals']['clouds'] > 1)) { usort($periodData['totals']['clouds'], array($this, 'sortItems')); } $entity = ReportPayloadEntity::init([$report->subjectType, $report->subjectId, $period], $periodData, $start); if (!ReportPayloadEntity::findPk($entity->uuid)) { $payload = json_decode($entity->payload, true); \Scalr::getContainer()->mailer->setSubject('Summary report.')->setContentType('text/html')->sendTemplate(SCALR_TEMPLATES_PATH . '/emails/report_summary.html.php', $payload, $report->emails); $this->console->out('Summary report email has been sent'); $payload['date'] = $entity->created->format('Y-m-d'); $entity->payload = json_encode($payload); $entity->save(); } } } unset($currentQuarter, $currentYear); if (!empty($report->subjectType) && !empty($report->subjectId)) { $subject = call_user_func($subjectEntityName . 'Entity::findPk', $report->subjectId); if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $report->subjectId, $report->subjectType, $report->emails, $formatedTitle); } else { if (!empty($report->subjectType)) { $subjects = call_user_func($subjectEntityName . 'Entity::find'); foreach ($subjects as $subject) { if ($subject->archived) { continue; } $this->saveReportData($getPeriodicSubjectData, $subjectEntityName, ['period' => $period, 'start' => $start, 'end' => $end], ['period' => $periodForecast, 'start' => $startForecast, 'end' => $endForecast], $subject->{$subjectId}, $report->subjectType, $report->emails, $formatedTitle); } } } } } $this->console->out('Done'); exit; }