public function testCompletionCorrect() { $timePeriod = new TimePeriod(); $timePeriod->setStartTimestamp(100); $timePeriod->setEndTimestamp(200); $timePeriod->setCurrentTimestamp(150); $this->assertEquals(0.5, $timePeriod->getCompletion()); }
public function __construct() { //Override module_name to distinguish bean for BeanFactory $this->module_name = 'QuarterTimePeriods'; parent::__construct(); //The time period type $this->type = TimePeriod::QUARTER_TYPE; //The leaf period type $this->leaf_period_type = TimePeriod::MONTH_TYPE; //The number of leaf periods $this->leaf_periods = 3; //The number of periods in a year $this->periods_in_year = 4; //Fiscal is 52-week based, chronological is year based $this->is_fiscal = false; $this->is_fiscal_year = false; //The next period modifier $this->next_date_modifier = '3 month'; //The previous period modifier $this->previous_date_modifier = '-3 month'; //The name template global $app_strings; $this->name_template = $app_strings['LBL_QUARTER_TIMEPERIOD_FORMAT']; //The leaf name template $this->leaf_name_template = $app_strings['LBL_MONTH_TIMEPERIOD_FORMAT']; //The chart label $this->chart_label = "F Y"; //The date formatting key for chart labels $this->chart_data_key = "m-Y"; //The chart data interval modifier $this->chart_data_modifier = '+1 month'; }
public function export(ServiceBase $api, $args = array()) { ob_start(); // Load up a seed bean $seed = BeanFactory::getBean('ForecastWorksheets'); if (!$seed->ACLAccess('list')) { throw new SugarApiExceptionNotAuthorized('No access to view records for module: ' . $seed->object_name); } $args['timeperiod_id'] = isset($args['timeperiod_id']) ? $args['timeperiod_id'] : TimePeriod::getCurrentId(); $args['user_id'] = isset($args['user_id']) ? $args['user_id'] : $api->user->id; if (!isset($args['filters'])) { $args['filters'] = array(); } elseif (!is_array($args['filters'])) { $args['filters'] = array($args['filters']); } // don't allow encoding to html for data used in export $args['encode_to_html'] = false; // base file and class name $file = 'include/SugarForecasting/Export/Individual.php'; $klass = 'SugarForecasting_Export_Individual'; // check for a custom file exists SugarAutoLoader::requireWithCustom($file); $klass = SugarAutoLoader::customClass($klass); // create the class /* @var $obj SugarForecasting_Export_AbstractExport */ $obj = new $klass($args); $content = $obj->process($api); ob_end_clean(); return $this->doExport($api, $obj->getFilename(), $content); }
/** * constructor override * * @param null $start_date date string to set the start date of the quarter time period */ public function __construct($start_date = null) { parent::__construct(); //set defaults $this->type = 'Quarter445'; $this->is_fiscal = true; $this->date_modifier = '13 week'; }
public function getTimePeriodByDate(ServiceBase $api, $args) { if (!isset($args["date"]) || $args["date"] == 'undefined') { // return a 404 throw new SugarApiExceptionNotFound(); } $tp = TimePeriod::retrieveFromDate($args["date"]); return $tp ? $tp->toArray() : $tp; }
public function queryFilternot_one_of($layout_def) { $arr = array(); foreach ($layout_def['input_name0'] as $value) { if ($value == 'current') { $name = array_keys(TimePeriod::getCurrentName()); $name = !empty($name) ? $name[0] : ''; $arr[] = $this->reporter->db->quoted($name); } else { $arr[] = $this->reporter->db->quoted($value); } } $str = implode(",", $arr); return $this->_get_column_select($layout_def) . " NOT IN (" . $str . ")\n"; }
/** * populateSeedData * * This is a static function to create TimePeriods. * * @static * @return array Array of TimePeriods created */ public static function populateSeedData() { //Simulate settings to create 2 forward and 2 backward timeperiods $settings = array(); $settings['timeperiod_start_date'] = date("Y") . "-01-01"; $settings['timeperiod_interval'] = TimePeriod::ANNUAL_TYPE; $settings['timeperiod_leaf_interval'] = TimePeriod::QUARTER_TYPE; $settings['timeperiod_shown_backward'] = 2; $settings['timeperiod_shown_forward'] = 2; $timePeriod = TimePeriod::getByType(TimePeriod::ANNUAL_TYPE); $timePeriod->rebuildForecastingTimePeriods(array(), $settings); $ids = TimePeriod::get_not_fiscal_timeperiods_dom(); $timeperiods = array(); foreach ($ids as $id => $name) { $timeperiods[$id] = TimePeriod::getBean($id); } return $timeperiods; }
/** * Collect up the timeperiod data * * @return array * @throws SugarQueryException */ private function getCurrentTimePeriod() { $admin = BeanFactory::getBean('Administration'); $settings = $admin->getConfigForModule('Forecasts', 'base'); $forward = $settings['timeperiod_shown_forward']; $backward = $settings['timeperiod_shown_backward']; $type = $settings['timeperiod_interval']; $leafType = $settings['timeperiod_leaf_interval']; $timeDate = TimeDate::getInstance(); $timePeriods = array(); $current = TimePeriod::getCurrentTimePeriod($type); //If the current TimePeriod cannot be found for the type, just create one using the current date as a reference point if (empty($current)) { $current = TimePeriod::getByType($type); $current->setStartDate($timeDate->getNow()->asDbDate()); } $startDate = $timeDate->fromDbDate($current->start_date); //Move back for the number of backward TimePeriod(s) while ($backward-- > 0) { $startDate->modify($current->previous_date_modifier); } $endDate = $timeDate->fromDbDate($current->end_date); //Increment for the number of forward TimePeriod(s) while ($forward-- > 0) { $endDate->modify($current->next_date_modifier); } $db = DBManagerFactory::getInstance(); $sq = new SugarQuery(); $sq->from(BeanFactory::getBean('TimePeriods')); $sq->select(array('id', 'name')); $sq->where()->notNull('parent_id')->gte('start_date', $startDate->asDbDate())->lte('start_date', $endDate->asDbDate())->addRaw("coalesce({$db->convert('type', 'length')},0) > 0"); $sq->orderBy('start_date', 'ASC'); $beans = $sq->execute(); //I am gather all of these as I might have to update more than one time period in the future foreach ($beans as $row) { $timePeriods['list'][$row['id']] = $row; } //the one is the current time period $current = TimePeriod::getCurrentTimePeriod(); $timePeriods['current'] = $current->id; return $timePeriods; }
/** * Returns the initialization data for the module including currently logged-in user data, * timeperiods, and admin config settings * * @param $api * @param $args * @return array * @throws SugarApiExceptionNotAuthorized */ public function forecastsInitialization($api, $args) { global $current_user; if (!SugarACL::checkAccess('Forecasts', 'access')) { throw new SugarApiExceptionNotAuthorized(); } $returnInitData = array(); $defaultSelections = array(); // Add Forecasts-specific items to returned data $returnInitData["initData"]["userData"]['showOpps'] = false; $returnInitData["initData"]["userData"]['first_name'] = $current_user->first_name; $returnInitData["initData"]["userData"]['last_name'] = $current_user->last_name; // INVESTIGATE: these need to be more dynamic and deal with potential customizations based on how filters are built in admin and/or studio /* @var $admin Administration */ $admin = BeanFactory::getBean("Administration"); $forecastsSettings = $admin->getConfigForModule("Forecasts", "base"); // we need to make sure all the default setting are there, if they are not // it should set them to the default value + clear the metadata and kick out a 412 error to force // the metadata to reload $this->compareSettingsToDefaults($admin, $forecastsSettings, $api); // TODO: These should probably get moved in with the config/admin settings, or by themselves since this file will probably going away. $tp = TimePeriod::getCurrentTimePeriod($forecastsSettings['timeperiod_leaf_interval']); if (!empty($tp->id)) { $defaultSelections["timeperiod_id"] = array('id' => $tp->id, 'label' => $tp->name, 'start' => $tp->start_date, 'end' => $tp->end_date); } else { $defaultSelections["timeperiod_id"]["id"] = ''; $defaultSelections["timeperiod_id"]["label"] = ''; $defaultSelections["timeperiod_id"]["start"] = ''; $defaultSelections["timeperiod_id"]["end"] = ''; } $returnInitData["initData"]['forecasts_setup'] = isset($forecastsSettings['is_setup']) ? $forecastsSettings['is_setup'] : 0; $defaultSelections["ranges"] = $forecastsSettings['commit_stages_included']; $defaultSelections["group_by"] = 'forecast'; $defaultSelections["dataset"] = 'likely'; // push in defaultSelections $returnInitData["defaultSelections"] = $defaultSelections; return $returnInitData; }
public function __construct() { $this->module_name = 'AnnualTimePeriods'; parent::__construct(); //The time period type $this->type = TimePeriod::ANNUAL_TYPE; //The leaf period type $this->leaf_period_type = TimePeriod::QUARTER_TYPE; //The number of leaf periods $this->leaf_periods = 4; $this->periods_in_year = 1; //Fiscal is 52-week based, chronological is year based $this->is_fiscal = false; $this->is_fiscal_year = true; //The next period modifier $this->next_date_modifier = $this->is_fiscal ? '52 week' : '1 year'; //The previous period modifier $this->previous_date_modifier = $this->is_fiscal ? '-52 week' : '-1 year'; global $app_strings; //The name template $this->name_template = $app_strings['LBL_ANNUAL_TIMEPERIOD_FORMAT']; //The leaf name template $this->leaf_name_template = $app_strings['LBL_QUARTER_TIMEPERIOD_FORMAT']; }
public function __construct() { //Override module_name to distinguish bean for BeanFactory $this->module_name = 'MonthTimePeriods'; parent::__construct(); //The time period type $this->type = TimePeriod::MONTH_TYPE; //Fiscal is 52-week based, chronological is year based $this->is_fiscal = false; $this->is_fiscal_year = false; //The number of periods in a year $this->periods_in_year = 12; //The next period modifier $this->next_date_modifier = '1 month'; //The previous period modifier $this->previous_date_modifier = '-1 month'; //The name template global $app_strings; $this->name_template = $app_strings['LBL_MONTH_TIMEPERIOD_FORMAT']; //The chart label $this->chart_label = "n/j"; //The chart data interval modifier $this->chart_data_modifier = '+1 week'; }
private function addRemainingEffortData(Tracker_Chart_Data_Burndown $burndown_data, TimePeriod $time_period, Tracker_Artifact $artifact, PFUser $user, $start_date) { $field = $this->getBurndownRemainingEffortField($artifact, $user); if (!$field) { return; } $tonight = mktime(23, 59, 59, date('n'), date('j'), date('Y')); foreach ($time_period->getDayOffsets() as $day_offset) { $timestamp = strtotime("+{$day_offset} day 23 hours 59 minutes 59 seconds", $start_date); if ($timestamp <= $tonight) { $remaining_effort = $this->getCachedValueOrComputeValue($field, $user, $artifact, $timestamp); $burndown_data->addEffortAt($day_offset, $remaining_effort); } } }
/** * Utility Method to create the filter for the filer API to use * * @param ServiceBase $api Service Api Class * @param mixed $user_id Passed in User ID, if false, it will use the current use from $api->user * @param mixed $timeperiod_id TimePeriod Id, if false, the current time period will be found an used * @param string $parent_type Type of worksheet to return, defaults to 'opportunities', but can be 'products' * @return array The Filer array to be passed back into the filerList Api * @throws SugarApiExceptionNotAuthorized * @throws SugarApiExceptionInvalidParameter */ protected function createFilter(ServiceBase $api, $user_id, $timeperiod_id, $parent_type = 'Opportunities') { $filter = array(); // default draft to be 1 $draft = 1; // if we did not find a user in the filters array, set it to the current user's id if ($user_id == false) { // use the current user, since on one was passed in $user_id = $api->user->id; } else { // make sure that the passed in user is a valid user /* @var $user User */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['user_id'] is invalid $user = BeanFactory::retrieveBean('Users', $user_id); if (is_null($user)) { throw new SugarApiExceptionInvalidParameter('Provided User is not valid'); } // we found a user, so check to make sure that if it's not the current user, they only see committed data $draft = $user_id == $api->user->id ? 1 : 0; } // so we have a valid user, and it's not the $api->user, we need to check if the $api->user is a manager // if they are not a manager, throw back a 403 (Not Authorized) error if ($draft == 0 && !User::isManager($api->user->id)) { throw new SugarApiExceptionNotAuthorized(); } // todo-sfa: Make sure that the passed in user can be viewed by the $api->user, need to check reportee tree // set the assigned_user_id array_push($filter, array('assigned_user_id' => $user_id)); // set the draft flag depending on the assigned_user_id that is set from above array_push($filter, array('draft' => $draft)); // if we didn't find a time period, set the time period to be the current time period if (!is_guid($timeperiod_id) && is_numeric($timeperiod_id) && $timeperiod_id != 0) { // we have a timestamp, find timeperiod it belongs in $timeperiod_id = TimePeriod::getIdFromTimestamp($timeperiod_id); } if (!is_guid($timeperiod_id)) { $timeperiod_id = TimePeriod::getCurrentId(); } // fix up the timeperiod filter /* @var $tp TimePeriod */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['timeperiod_id'] is invalid $tp = BeanFactory::retrieveBean('TimePeriods', $timeperiod_id); if (is_null($tp)) { throw new SugarApiExceptionInvalidParameter('Provided TimePeriod is not valid'); } array_push($filter, array('$and' => array(array('date_closed_timestamp' => array('$gte' => $tp->start_date_timestamp)), array('date_closed_timestamp' => array('$lte' => $tp->end_date_timestamp))))); if (empty($parent_type)) { // get the forecast_by setting /* @var $admin Administration */ $admin = BeanFactory::getBean('Administration'); $settings = $admin->getConfigForModule('Forecasts', $api->platform); $parent_type = $settings['forecast_by']; } // we only want to view parent_types of 'Opportunities' here array_push($filter, array('parent_type' => $parent_type)); return $filter; }
/** * Utility Method to create the filter for the filer API to use * * @param ServiceBase $api Service Api Class * @param mixed $user_id Passed in User ID, if false, it will use the current use from $api->user * @param mixed $timeperiod_id TimePeriod Id, if false, the current time period will be found an used * @return array The Filer array to be passed back into the filerList Api * @throws SugarApiExceptionNotAuthorized * @throws SugarApiExceptionInvalidParameter */ protected function createFilter(ServiceBase $api, $user_id, $timeperiod_id) { // we need to check if the $api->user is a manager // if they are not a manager, throw back a 403 (Not Authorized) error if (!User::isManager($api->user->id)) { throw new SugarApiExceptionNotAuthorized(); } $filter = array(); // default draft to be 1 $draft = 1; // if we did not find a user in the filters array, set it to the current user's id if ($user_id == false) { // use the current user, since on one was passed in $user_id = $api->user->id; } else { // make sure that the passed in user is a valid user /* @var $user User */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['user_id'] is invalid $user = BeanFactory::retrieveBean('Users', $user_id); if (is_null($user)) { throw new SugarApiExceptionInvalidParameter('Provided User is not valid'); } // we found a user, so check to make sure that if it's not the current user, they only see committed data $draft = $user_id == $api->user->id ? 1 : 0; } // todo-sfa: Make sure that the passed in user can be viewed by the $api->user, need to check reportee tree // set the assigned_user_id array_push($filter, array('assigned_user_id' => $user_id)); // set the draft flag depending on the assigned_user_id that is set from above array_push($filter, array('draft' => $draft)); // if we didn't find a time period, set the time period to be the current time period if (!is_guid($timeperiod_id) && is_numeric($timeperiod_id) && $timeperiod_id != 0) { // we have a timestamp, find timeperiod it belongs in $timeperiod_id = TimePeriod::getIdFromTimestamp($timeperiod_id); } if (!is_guid($timeperiod_id)) { $timeperiod_id = TimePeriod::getCurrentId(); } // fix up the timeperiod filter /* @var $tp TimePeriod */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['timeperiod_id'] is invalid $tp = BeanFactory::retrieveBean('TimePeriods', $timeperiod_id); if (is_null($tp)) { throw new SugarApiExceptionInvalidParameter('Provided TimePeriod is not valid'); } array_push($filter, array('timeperiod_id' => $tp->id)); return $filter; }
function getCalendarView($period = null) { global $service, $database, $db; if ((empty($period) === true) || !TimePeriod::checkPeriod($period)) $period = Timestamp::getYearMonth(); $calendar = array('days' => array()); $calendar['period'] = $period; $calendar['year'] = substr($period, 0, 4); $calendar['month'] = substr($period, 4, 2); if ($db->query("SELECT DISTINCT DAYOFMONTH(FROM_UNIXTIME(written)) FROM {$database['prefix']}FeedItems WHERE YEAR(FROM_UNIXTIME(written)) = {$calendar['year']} AND MONTH(FROM_UNIXTIME(written)) = {$calendar['month']}")) { while (list($day) = $db->fetchArray()) array_push($calendar['days'], $day); } $calendar['days'] = array_flip($calendar['days']); $current = $calendar['year'] . $calendar['month']; $previous = TimePeriod::addPeriod($current, - 1); $next = TimePeriod::addPeriod($current, 1); $firstWeekday = date('w', mktime(0, 0, 0, $calendar['month'], 1, $calendar['year'])); $lastDay = date('t', mktime(0, 0, 0, $calendar['month'], 1, $calendar['year'])); $today = ($current == Timestamp::get('Ym') ? Timestamp::get('j') : null); $currentMonthStr = Timestamp::format('%Y.%m', TimePeriod::getTimeFromPeriod($current)); define('CRLF', "\r\n"); ob_start(); ?> <table class="calendar" cellpadding="0" cellspacing="1" style="width: 100%; table-layout: fixed"> <caption class="cal_month"> <?php echo $currentMonthStr;?> </caption> <thead> <tr> <th class="cal_week2"><?php echo _t('일요일');?></th> <th class="cal_week1"><?php echo _t('월요일');?></th> <th class="cal_week1"><?php echo _t('화요일');?></th> <th class="cal_week1"><?php echo _t('수요일');?></th> <th class="cal_week1"><?php echo _t('목요일');?></th> <th class="cal_week1"><?php echo _t('금요일');?></th> <th class="cal_week1"><?php echo _t('토요일');?></th> </tr> </thead> <tbody> <?php $day = 0; $totalDays = $firstWeekday + $lastDay; $lastWeek = ceil($totalDays / 7); for ($week=0; $week<$lastWeek; $week++) { // 주중에 현재 날짜가 포함되어 있으면 주를 현재 주 class(tt-current-week)를 부여한다. if (($today + $firstWeekday) >= $week * 7 && ($today + $firstWeekday) < ($week + 1) * 7) { echo ' <tr class="cal_week cal_current_week">'.CRLF; } else { echo ' <tr class="cal_week">'.CRLF; } for($weekday=0; $weekday<7; $weekday++) { $day++; $dayString = isset($calendar['days'][$day]) ? '<a class="cal_click" href="'.$service['path'].'/?archive='.$current.($day > 9 ? $day : "0$day").'">'.$day.'</a>' : $day; // 일요일, 평일, 토요일별로 class를 부여한다. switch ($weekday) { case 0: $className = " cal_day cal_day_sunday"; break; case 1: case 2: case 3: case 4: case 5: case 6: $className = " cal_day"; break; } // 오늘에 현재 class(tt-current-day)를 부여한다. $className .= $day == $today ? " cal_day4" : " cal_day3"; if ($week == 0) { if ($weekday < $firstWeekday) { $day--; // 달의 첫째날이 되기 전의 빈 칸. echo ' <td class="cal_day1"> </td>'.CRLF; } else { echo ' <td class="'.$className.'">'.$dayString.'</td>'.CRLF; } } else if ($week == ($lastWeek - 1)) { if ($day <= $lastDay) { echo ' <td class="'.$className.'">'.$dayString.'</td>'.CRLF; } else { // 달의 마지막날을 넘어간 날짜 빈 칸. echo ' <td class="cal_day2"> </td>'.CRLF; } } else { echo ' <td class="'.$className.'">'.$dayString.'</td>'.CRLF; } } echo ' </tr>'.CRLF; if ($day >= $lastDay) { break; } } ?> </tbody> </table> <?php $view = ob_get_contents(); ob_end_clean(); return $view; }
private function isNotInTheFutur($day_offset) { return strtotime("+" . $day_offset . " day", $this->time_period->getStartDate()) <= $_SERVER['REQUEST_TIME']; }
/** * Retrieve a user's quota using the rollup value, if available. This method is useful for * fetching user quota data when you're unsure about whether or not the given user is a manager. * If you would like to force a direct quota, pass a false value to $should_rollup. * * @param $timeperiod_id String id of the TimePeriod to retrieve quota for * @param $user_id String value of the user id to retrieve. If NULL, the $current_user is used * @param $should_rollup boolean value indicating whether or not the quota should be a rollup calculation; false by default * * @return array [currency_id => int, amount => number, formatted_amount => String] */ public function getRollupQuota($timeperiod_id, $user_id = null, $should_rollup = false) { if (is_null($user_id)) { global $current_user; $user_id = $current_user->id; } // figure out the timeperiod // if we didn't find a time period, set the time period to be the current time period if (!is_guid($timeperiod_id) && is_numeric($timeperiod_id) && $timeperiod_id != 0) { // we have a timestamp, find timeperiod it belongs in $timeperiod_id = TimePeriod::getIdFromTimestamp($timeperiod_id); } if (!is_guid($timeperiod_id)) { $timeperiod_id = TimePeriod::getCurrentId(); } $sq = new SugarQuery(); $sq->select(array('quotas.currency_id', 'quotas.amount')); $sq->from(BeanFactory::getBean('Quotas')); $sq->where()->equals('user_id', $user_id)->equals('quota_type', $should_rollup ? 'Rollup' : 'Direct')->equals('timeperiod_id', $timeperiod_id); $sq->orderBy('date_modified', 'DESC'); $sq->limit(1); // since there is only ever one row, just shift the value off the results $row = array_shift($sq->execute()); if (empty($row)) { // This is to prevent return value of false when a given timeperiod has no quota. $row = array('currency_id' => -99, 'amount' => 0); } $row['formatted_amount'] = SugarCurrency::formatAmountUserLocale($row['amount'], $row['currency_id']); return $row; }
/** * Returns the latest TimePeriod bean instance for the given timeperiod interval type * * @param $type String value of the TimePeriod interval type * @return $bean The latest TimePeriod bean instance; null if none found */ public static function getLatest($type) { $db = DBManagerFactory::getInstance(); $result = $db->limitQuery(sprintf("SELECT * FROM timeperiods WHERE type = '%s' AND deleted = 0 ORDER BY start_date_timestamp DESC", $type), 0, 1); if ($result) { $row = $db->fetchByAssoc($result); if (!empty($row)) { return TimePeriod::getByType($type, $row['id']); } } return null; }
/** * Forecast Override since we have custom logic that needs to be ran * * {@inheritdoc} */ public function forecastsConfigSave(ServiceBase $api, array $args) { //acl check, only allow if they are module admin if (!$api->user->isAdmin() && !$api->user->isDeveloperForModule('Forecasts')) { // No create access so we construct an error message and throw the exception $failed_module_strings = return_module_language($GLOBALS['current_language'], 'forecasts'); $moduleName = $failed_module_strings['LBL_MODULE_NAME']; $args = null; if (!empty($moduleName)) { $args = array('moduleName' => $moduleName); } throw new SugarApiExceptionNotAuthorized($GLOBALS['app_strings']['EXCEPTION_CHANGE_MODULE_CONFIG_NOT_AUTHORIZED'], $args); } $admin = BeanFactory::getBean('Administration'); //track what settings have changed to determine if timeperiods need rebuilt $prior_forecasts_settings = $admin->getConfigForModule('Forecasts', $api->platform); //If this is a first time setup, default prior settings for timeperiods to 0 so we may correctly recalculate //how many timeperiods to build forward and backward. If we don't do this we would need the defaults to be 0 if (empty($prior_forecasts_settings['is_setup'])) { $prior_forecasts_settings['timeperiod_shown_forward'] = 0; $prior_forecasts_settings['timeperiod_shown_backward'] = 0; } $upgraded = 0; if (!empty($prior_forecasts_settings['is_upgrade'])) { $db = DBManagerFactory::getInstance(); // check if we need to upgrade opportunities when coming from version below 6.7.x. $upgraded = $db->getOne("SELECT count(id) AS total FROM upgrade_history\n WHERE type = 'patch' AND status = 'installed' AND version LIKE '6.7.%'"); if ($upgraded == 1) { //TODO-sfa remove this once the ability to map buckets when they get changed is implemented (SFA-215). $args['has_commits'] = true; } } if (isset($args['show_custom_buckets_options'])) { $json = getJSONobj(); $_args = array('dropdown_lang' => isset($_SESSION['authenticated_user_language']) ? $_SESSION['authenticated_user_language'] : $GLOBALS['current_language'], 'dropdown_name' => 'commit_stage_custom_dom', 'view_package' => 'studio', 'list_value' => $json->encode($args['show_custom_buckets_options']), 'skip_sync' => true); $_REQUEST['view_package'] = 'studio'; require_once 'modules/ModuleBuilder/parsers/parser.dropdown.php'; $parser = new ParserDropDown(); $parser->saveDropDown($_args); unset($args['show_custom_buckets_options']); } // we do the double check here since the front ent will send one one value if the input is empty if (empty($args['worksheet_columns']) || empty($args['worksheet_columns'][0])) { // set the defaults $args['worksheet_columns'] = array('commit_stage', 'parent_name', 'likely_case'); if ($args['show_worksheet_best'] == 1) { $args['worksheet_columns'][] = 'best_case'; } if ($args['show_worksheet_worst'] == 1) { $args['worksheet_columns'][] = 'worst_case'; } } //reload the settings to get the current settings $current_forecasts_settings = parent::configSave($api, $args); // setting are saved, reload the setting in the ForecastBean just in case. Forecast::getSettings(true); // now that we have saved the setting, we need to sync all the data if // this is being upgraded or the forecast was not setup before. if ($upgraded || empty($prior_forecasts_settings['is_setup'])) { if ($args['forecast_by'] === 'Opportunities') { SugarAutoLoader::load('include/SugarQueue/jobs/SugarJobUpdateOpportunities.php'); SugarJobUpdateOpportunities::updateOpportunitiesForForecasting(); } else { SugarAutoLoader::load('include/SugarQueue/jobs/SugarJobUpdateRevenueLineItems.php'); SugarJobUpdateRevenueLineItems::scheduleRevenueLineItemUpdateJobs(); } } // did this change? if ($prior_forecasts_settings['worksheet_columns'] !== $args['worksheet_columns']) { $this->setWorksheetColumns($api, $args['worksheet_columns'], $current_forecasts_settings['forecast_by']); } //if primary settings for timeperiods have changed, then rebuild them if ($this->timePeriodSettingsChanged($prior_forecasts_settings, $current_forecasts_settings)) { $timePeriod = TimePeriod::getByType($current_forecasts_settings['timeperiod_interval']); $timePeriod->rebuildForecastingTimePeriods($prior_forecasts_settings, $current_forecasts_settings); } return $current_forecasts_settings; }
} if (isset($_REQUEST['return_id'])) { $xtpl->assign("RETURN_ID", $_REQUEST['return_id']); } // handle Create $module then Cancel //if (empty($_REQUEST['return_id'])) { //$xtpl->assign("RETURN_ACTION", 'index'); //} $xtpl->assign("JAVASCRIPT", get_set_focus_js() . get_validate_record_js() . get_chooser_js()); $xtpl->assign("PRINT_URL", "index.php?" . $GLOBALS['request_string']); $xtpl->assign("ID", $focus->id); $xtpl->assign("NAME", $focus->name); $xtpl->assign("START_DATE", $focus->start_date); $xtpl->assign("END_DATE", $focus->end_date); if ($focus->is_fiscal_year == 1) { $xtpl->assign("FISCAL_YEAR_CHECKED", "checked"); $xtpl->assign("FISCAL_OPTIONS_DISABLED", "disabled"); } global $timedate; $xtpl->assign("CALENDAR_DATEFORMAT", $timedate->get_cal_date_format()); $xtpl->assign("USER_DATEFORMAT", '(' . $timedate->get_user_date_format() . ')'); $fiscal_year_dom = TimePeriod::get_fiscal_year_dom(); array_unshift($fiscal_year_dom, ''); if (isset($focus->parent_id)) { $xtpl->assign("FISCAL_OPTIONS", get_select_options_with_id($fiscal_year_dom, $focus->parent_id)); } else { $xtpl->assign("FISCAL_OPTIONS", get_select_options_with_id($fiscal_year_dom, '')); } $xtpl->assign("THEME", SugarThemeRegistry::current()->__toString()); $xtpl->parse("main"); $xtpl->out("main");
/** * Retrieve forecast data for user given a timeperiod. By default uses the currently logged-in * user and the current timeperiod. * * @param String $user_id * @param String $timeperiod_id * @param bool $should_rollup False to use direct numbers, true to use rollup. */ function getForecastForUser($user_id = NULL, $timeperiod_id, $should_rollup = FALSE) { global $current_user; if (is_null($user_id)) { $user_id = $current_user->id; } $where = "user_id='{$user_id}'"; if ($should_rollup) { $where .= " AND forecast_type='Rollup'"; } else { $where .= " AND forecast_type='Direct'"; } if (!is_null($timeperiod_id)) { $where .= " AND timeperiod_id='{$timeperiod_id}'"; } else { $where .= " AND timeperiod_id='" . TimePeriod::getCurrentId() . "'"; } $query = $this->create_new_list_query(NULL, $where); $result = $this->db->query($query, true, 'Error retrieving user forecast information: '); return $this->db->fetchByAssoc($result); }
/** * Roll up the data from the rep-worksheets to the manager worksheets * * @param User $reportee * @param $data * @return boolean */ public function reporteeForecastRollUp(User $reportee, $data) { /* @var $quotaSeed Quota */ $quotaSeed = BeanFactory::getBean('Quotas'); if (!isset($data['timeperiod_id']) || !is_guid($data['timeperiod_id'])) { $data['timeperiod_id'] = TimePeriod::getCurrentId(); } // handle top level managers $reports_to = $reportee->reports_to_id; if (empty($reports_to)) { $reports_to = $reportee->id; } if (isset($data['forecast_type'])) { // check forecast type to see if the assigned_user_id should be equal to the $reportee as it's their own // rep worksheet if ($data['forecast_type'] == "Direct" && User::isManager($reportee->id)) { // this is the manager committing their own data, the $reports_to should be them // and not their actual manager $reports_to = $reportee->id; } else { if ($data['forecast_type'] == "Rollup" && $reports_to == $reportee->id) { // if type is rollup and reports_to is equal to the $reportee->id (aka no top level manager), // we don't want to update their draft record so just ignore this, return false; } } } if (isset($data['draft']) && $data['draft'] == '1' && $GLOBALS['current_user']->id == $reportee->id) { // this data is for the current user, but is not a commit so we need to update their own draft record $reports_to = $reportee->id; } $this->retrieve_by_string_fields(array('user_id' => $reportee->id, 'assigned_user_id' => $reports_to, 'timeperiod_id' => $data['timeperiod_id'], 'draft' => 1, 'deleted' => 0)); $copyMap = array('currency_id', 'base_rate', 'timeperiod_id', 'opp_count', 'pipeline_opp_count', 'pipeline_amount', 'closed_amount'); if ($data["forecast_type"] == "Direct") { $copyMap[] = "likely_case"; $copyMap[] = "best_case"; $copyMap[] = "worst_case"; } else { if ($data["forecast_type"] == "Rollup") { $copyMap[] = array("likely_case" => "likely_adjusted"); $copyMap[] = array("best_case" => "best_adjusted"); $copyMap[] = array("worst_case" => "worst_adjusted"); } } if (empty($this->id) || $this->manager_saved == false) { if ($data["forecast_type"] == "Rollup") { $copyMap[] = array('likely_case_adjusted' => 'likely_adjusted'); $copyMap[] = array('best_case_adjusted' => 'best_adjusted'); $copyMap[] = array('worst_case_adjusted' => 'worst_adjusted'); } elseif ($data["forecast_type"] == "Direct") { $copyMap[] = array('likely_case_adjusted' => 'likely_case'); $copyMap[] = array('best_case_adjusted' => 'best_case'); $copyMap[] = array('worst_case_adjusted' => 'worst_case'); } } if (empty($this->id)) { if (!isset($data['quota']) || empty($data['quota'])) { // we need to get a fresh bean to store the quota if one exists $quotaSeed = BeanFactory::getBean('Quotas'); // check if we need to get the roll up amount $getRollupQuota = User::isManager($reportee->id) && isset($data['forecast_type']) && $data['forecast_type'] == 'Rollup'; $quota = $quotaSeed->getRollupQuota($data['timeperiod_id'], $reportee->id, $getRollupQuota); $data['quota'] = $quota['amount']; } $copyMap[] = "quota"; } $this->copyValues($copyMap, $data); // set the team to the default ones from the passed in user $this->team_set_id = $reportee->team_set_id; $this->team_id = $reportee->team_id; $this->name = $reportee->full_name; $this->user_id = $reportee->id; $this->assigned_user_id = $reports_to; $this->draft = 1; $this->save(); // roll up the draft value for best/likely/worst case values to the committed record if one exists $this->rollupDraftToCommittedWorksheet($this); return true; }
/** * This method implements the run function of RunnableSchedulerJob and handles processing a SchedulersJob * * @param Mixed $data parameter passed in from the job_queue.data column when a SchedulerJob is run * @return bool true on success, false on error */ public function run($data) { global $app_strings, $language; $app_strings = return_application_language($language); $admin = BeanFactory::getBean('Administration'); $config = $admin->getConfigForModule('Forecasts', 'base'); $timeperiodInterval = $config['timeperiod_interval']; $timeperiodLeafInterval = $config['timeperiod_leaf_interval']; $parentTimePeriod = TimePeriod::getLatest($timeperiodInterval); $latestTimePeriod = TimePeriod::getLatest($timeperiodLeafInterval); $currentTimePeriod = TimePeriod::getCurrentTimePeriod($timeperiodLeafInterval); if (empty($latestTimePeriod)) { $GLOBALS['log']->error(string_format($app_strings['ERR_TIMEPERIOD_TYPE_DOES_NOT_EXIST'], array($timeperiodLeafInterval)) . '[latest]'); return false; } else { if (empty($currentTimePeriod)) { $GLOBALS['log']->error(string_format($app_strings['ERR_TIMEPERIOD_TYPE_DOES_NOT_EXIST'], array($timeperiodLeafInterval)) . ' [current]'); return false; } else { if (empty($parentTimePeriod)) { $GLOBALS['log']->error(string_format($app_strings['ERR_TIMEPERIOD_TYPE_DOES_NOT_EXIST'], array($timeperiodLeafInterval)) . ' [parent]'); return false; } } } $timedate = TimeDate::getInstance(); //We run the rebuild command if the latest TimePeriod is less than the specified configuration interval //from the current TimePeriod $correctStartDate = $timedate->fromDbDate($currentTimePeriod->start_date); $latestStartDate = $timedate->fromDbDate($latestTimePeriod->start_date); $shownForward = $config['timeperiod_shown_forward']; //Move the current start date forward by the leaf period amounts for ($x = 0; $x < $shownForward; $x++) { $correctStartDate->modify($parentTimePeriod->next_date_modifier); } $leafCycle = $latestTimePeriod->leaf_cycle; //If the current start data that was modified according to the shown forward period is past the latest //leaf period we need to build more timeperiods while ($correctStartDate > $latestStartDate) { //We need to keep creating leaf periods until we are in sync. //If the leaf period we need to create is the start of the leaf cycle //then we should also create the parent TimePeriod record. $startDate = $latestStartDate->modify($latestTimePeriod->next_date_modifier); $leafCycle = $leafCycle == $parentTimePeriod->leaf_periods ? 1 : $leafCycle + 1; if ($leafCycle == 1) { $parentTimePeriod = TimePeriod::getByType($timeperiodInterval); $parentTimePeriod->setStartDate($startDate->asDbDate()); $parentTimePeriod->name = $parentTimePeriod->getTimePeriodName($leafCycle); $parentTimePeriod->save(); } $leafTimePeriod = TimePeriod::getByType($timeperiodLeafInterval); $leafTimePeriod->setStartDate($startDate->asDbDate()); $leafTimePeriod->name = $leafTimePeriod->getTimePeriodName($leafCycle, $parentTimePeriod); $leafTimePeriod->leaf_cycle = $leafCycle; $leafTimePeriod->parent_id = $parentTimePeriod->id; $leafTimePeriod->save(); } $this->job->succeedJob(); return true; }
/** * @return TimePeriod */ public function getTimeperiod() { $config = $this->getForecastConfig(); $type = $config['timeperiod_leaf_interval']; $id = $this->getArg('timeperiod_id'); if (!is_guid($id) && is_numeric($id)) { $id = TimePeriod::getIdFromTimestamp($id, $type); } return TimePeriod::getByType($type, $id); }
/** * @param String|Number $tp_id * @return TimePeriod * @throws SugarApiExceptionInvalidParameter */ protected function getTimeperiod($tp_id = '') { $forecast_settings = $this->getForecastSettings(); if ($forecast_settings['is_setup'] == 1) { // we have no timeperiod defined, so lets just pull the current one if (empty($tp_id)) { $tp_id = TimePeriod::getCurrentId(); } /* @var $tp TimePeriod */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['timeperiod_id'] is invalid $tp = BeanFactory::retrieveBean('TimePeriods', $tp_id); } else { /* @var $tp TimePeriod */ $tp = BeanFactory::retrieveBean('TimePeriods'); // generate the generic timeperiod based off the integer that was passed in. $data = $tp->getGenericStartEndByDuration($tp_id); // set the values $tp->id = 'fake_timeperiod'; foreach ($data as $key => $value) { $tp->{$key} = $value; } } // if $tp is null or the id is empty, throw an exception if (is_null($tp) || empty($tp->id)) { throw new SugarApiExceptionInvalidParameter('Provided TimePeriod is invalid'); } return $tp; }
/** * Checks to see if a worksheet item being saved has jumped timeperiods. * * @param date $worksheetDate * @param date $objDate * * @return bool */ protected function timeperiodHasMigrated($worksheetDate, $objDate) { $return = false; //if the close dates are different, we need to see if the obj is in a new timeperiod if ($worksheetDate != $objDate) { $tp1 = TimePeriod::retrieveFromDate($worksheetDate); $tp2 = TimePeriod::retrieveFromDate($objDate); if (!empty($tp1) && !empty($tp2) && $tp1->id != $tp2->id) { $return = true; } } return $return; }
function getTimePeriodsDropDown() { return TimePeriod::get_timeperiods_dom(); }
/** * Utility Method to create the filter for the filer API to use * * @param ServiceBase $api Service Api Class * @param mixed $user_id Passed in User ID, if false, it will use the current use from $api->user * @param mixed $timeperiod_id TimePeriod Id, if false, the current time period will be found an used * @param string $forecast_type Type of forecast to return, direct or rollup * @return array The Filer array to be passed back into the filerList Api * @throws SugarApiExceptionNotAuthorized * @throws SugarApiExceptionInvalidParameter */ protected function createFilter(ServiceBase $api, $user_id, $timeperiod_id, $forecast_type) { $filter = array(); // if we did not find a user in the filters array, set it to the current user's id if ($user_id == false) { // use the current user, since on one was passed in $user_id = $api->user->id; } else { // make sure that the passed in user is a valid user /* @var $user User */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['user_id'] is invalid $user = BeanFactory::retrieveBean('Users', $user_id); if (is_null($user) || is_null($user->id)) { throw new SugarApiExceptionInvalidParameter('Provided User is not valid'); } # if they are not a manager, don't show them committed number for others global $mod_strings, $current_language; $mod_strings = return_module_language($current_language, 'Forecasts'); if ($user_id != $api->user->id && !User::isManager($api->user->id)) { throw new SugarApiExceptionNotAuthorized(string_format($mod_strings['LBL_ERROR_NOT_MANAGER'], array($api->user->id, $user_id))); } } // set the assigned_user_id array_push($filter, array('user_id' => $user_id)); if ($forecast_type !== false) { // make sure $forecast_type is valid (e.g. Direct or Rollup) switch (strtolower($forecast_type)) { case 'direct': case 'rollup': break; default: throw new SugarApiExceptionInvalidParameter('Forecast Type of ' . $forecast_type . ' is not valid. Valid options Direct or Rollup.'); } // set the forecast type, make sure it's always capitalized array_push($filter, array('forecast_type' => ucfirst($forecast_type))); } // if we didn't find a time period, set the time period to be the current time period if ($timeperiod_id == false) { $timeperiod_id = TimePeriod::getCurrentId(); } // fix up the timeperiod filter /* @var $tp TimePeriod */ // we use retrieveBean so it will return NULL and not an empty bean if the $args['timeperiod_id'] is invalid $tp = BeanFactory::retrieveBean('TimePeriods', $timeperiod_id); if (is_null($tp) || is_null($tp->id)) { throw new SugarApiExceptionInvalidParameter('Provided TimePeriod is not valid'); } array_push($filter, array('timeperiod_id' => $tp->id)); return $filter; }
/** * Save any committed values * * @return array|mixed */ public function save() { global $current_user; $args = $this->getArgs(); $db = DBManagerFactory::getInstance(); if (!isset($args['timeperiod_id']) || empty($args['timeperiod_id'])) { $args['timeperiod_id'] = TimePeriod::getCurrentId(); } $commit_type = strtolower($this->getArg('commit_type')); /* @var $mgr_worksheet ForecastManagerWorksheet */ $mgr_worksheet = BeanFactory::getBean('ForecastManagerWorksheets'); /* @var $worksheet ForecastWorksheet */ $worksheet = BeanFactory::getBean('ForecastWorksheets'); $field_ext = '_case'; if ($commit_type == "manager") { $worksheet_totals = $mgr_worksheet->worksheetTotals($current_user->id, $args['timeperiod_id']); // we don't need the *_case values so lets make them the same as the *_adjusted values $field_ext = '_adjusted'; } else { $worksheet_totals = $worksheet->worksheetTotals($args['timeperiod_id'], $current_user->id); // set likely $worksheet_totals['likely_case'] = SugarMath::init($worksheet_totals['amount'], 6)->add($worksheet_totals['includedClosedAmount'])->result(); $worksheet_totals['best_case'] = SugarMath::init($worksheet_totals['best_case'], 6)->add($worksheet_totals['includedClosedBest'])->result(); $worksheet_totals['worst_case'] = SugarMath::init($worksheet_totals['worst_case'], 6)->add($worksheet_totals['includedClosedWorst'])->result(); } /* @var $forecast Forecast */ $forecast = BeanFactory::getBean('Forecasts'); $forecast->user_id = $current_user->id; $forecast->timeperiod_id = $args['timeperiod_id']; $forecast->best_case = $worksheet_totals['best' . $field_ext]; $forecast->likely_case = $worksheet_totals['likely' . $field_ext]; $forecast->worst_case = $worksheet_totals['worst' . $field_ext]; $forecast->forecast_type = $args['forecast_type']; $forecast->opp_count = $worksheet_totals['included_opp_count']; $forecast->currency_id = '-99'; $forecast->base_rate = '1'; //If we are committing a rep forecast, calculate things. Otherwise, for a manager, just use what is passed in. if ($args['commit_type'] == 'sales_rep') { $forecast->calculatePipelineData($worksheet_totals['includedClosedAmount'], $worksheet_totals['includedClosedCount']); //push the pipeline numbers back into the args $args['pipeline_opp_count'] = $forecast->pipeline_opp_count; $args['pipeline_amount'] = $forecast->pipeline_amount; $worksheet_totals['closed_amount'] = $forecast->closed_amount; } else { $forecast->pipeline_opp_count = $worksheet_totals['pipeline_opp_count']; $forecast->pipeline_amount = $worksheet_totals['pipeline_amount']; $forecast->closed_amount = $worksheet_totals['closed_amount']; } if ($worksheet_totals['likely_case'] != 0 && $worksheet_totals['included_opp_count'] != 0) { $forecast->opp_weigh_value = $worksheet_totals['likely_case'] / $worksheet_totals['included_opp_count']; } $forecast->save(); // roll up the committed forecast to that person manager view // copy the object so we can set some needed values $mgr_rollup_data = $worksheet_totals; $mgr_rollup_data['forecast_type'] = $args['forecast_type']; // pass same timeperiod as the other data to the manager's rollup $mgr_rollup_data['timeperiod_id'] = $args['timeperiod_id']; $mgr_worksheet->reporteeForecastRollUp($current_user, $mgr_rollup_data); if ($this->getArg('commit_type') == "sales_rep") { $worksheet->commitWorksheet($current_user->id, $args['timeperiod_id']); } elseif ($this->getArg('commit_type') == "manager") { $mgr_worksheet->commitManagerForecast($current_user, $args['timeperiod_id']); } //TODO-sfa remove this once the ability to map buckets when they get changed is implemented (SFA-215). $admin = BeanFactory::getBean('Administration'); $settings = $admin->getConfigForModule('Forecasts'); if (!isset($settings['has_commits']) || !$settings['has_commits']) { $admin->saveSetting('Forecasts', 'has_commits', true, 'base'); MetaDataManager::refreshModulesCache(array('Forecasts')); } $forecast->date_entered = $this->convertDateTimeToISO($db->fromConvert($forecast->date_entered, 'datetime')); $forecast->date_modified = $this->convertDateTimeToISO($db->fromConvert($forecast->date_modified, 'datetime')); return $worksheet_totals; }