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); }
/** * 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; }
/** * 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; }
/** * @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; }
/** * Returns the current TimePeriod name if a TimePeriod entry is found * * @param $type String CONSTANT for the TimePeriod type; if none supplied it will use the leaf type as defined in config settings * @return String name of the current TimePeriod for given type; null if none found */ public static function getCurrentName($type = '') { if (empty($type)) { $admin = BeanFactory::getBean('Administration'); $config = $admin->getConfigForModule('Forecasts', 'base'); $type = $config['timeperiod_leaf_interval']; } $id = TimePeriod::getCurrentId($type); $tp = TimePeriod::getByType($type, $id); return !empty($tp) ? $tp->name : null; }
/** * 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; }
/** * 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); }
/** * 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; }
/** * 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; }
/** * 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; }