TTi18n::gettext( 'ENABLED' ), 20 => TTi18n::gettext( 'DISABLED' ), ]; break; case 'country_currency': //Country to primary currency mappings. $retval = [ 'AF' => 'AFA', 'AL' => 'ALL', 'DZ' => 'DZD', 'AS' => 'USD', 'AD' => 'EUR', 'AO' => 'AON', 'AI' => 'XCD', 'AQ' => 'NOK', 'AG' => 'XCD', 'AR' => 'ARA', 'AM' => 'AMD', 'AW' => 'AWG', 'AU' => 'AUD', 'AT' => 'EUR', 'AZ' => 'AZM', 'BS' => 'BSD', 'BH' => 'BHD', 'BD' => 'BDT', 'BB' => 'BBD', 'BY' => 'BYR', 'BE' => 'EUR', 'BZ' => 'BZD', 'BJ' => 'XAF', 'BM' => 'BMD', 'BT' => 'BTN', 'BO' => 'BOB', 'BA' => 'BAM', 'BW' => 'BWP', 'BV' => 'NOK', 'BR' => 'BRR', 'IO' => 'GBP', 'BN' => 'BND', 'BG' => 'BGL', 'BF' => 'XAF', 'BI' => 'BIF', 'KH' => 'KHR', 'CM' => 'XAF', 'CA' => 'CAD', 'CV' => 'CVE', 'KY' => 'KYD', 'CF' => 'XAF', 'TD' => 'XAF', 'CL' => 'CLF', 'CN' => 'CNY', 'CX' => 'AUD', 'CC' => 'AUD', 'CO' => 'COP', 'KM' => 'KMF', //'CD' => 'CDZ', //Not available in PEAR. 'CG' => 'XAF', 'CK' => 'NZD', 'CR' => 'CRC', 'HR' => 'HRK', 'CU' => 'CUP', 'CY' => 'CYP', 'CZ' => 'CZK', 'DK' => 'DKK', 'DJ' => 'DJF', 'DM' => 'XCD', 'DO' => 'DOP', 'TP' => 'TPE', 'EC' => 'USD', 'EG' => 'EGP', 'SV' => 'SVC', 'GQ' => 'XAF', 'ER' => 'ERN', 'EE' => 'EEK', 'ET' => 'ETB', 'FK' => 'FKP', 'FO' => 'DKK', 'FJ' => 'FJD', 'FI' => 'EUR', 'FR' => 'EUR', 'FX' => 'EUR', 'GF' => 'EUR', 'PF' => 'XPF', 'TF' => 'EUR', 'GA' => 'XAF', 'GM' => 'GMD', 'GE' => 'GEL', 'DE' => 'EUR', 'GH' => 'GHC', 'GI' => 'GIP', 'GR' => 'EUR', 'GL' => 'DKK', 'GD' => 'XCD', 'GP' => 'EUR', 'GU' => 'USD', 'GT' => 'GTQ', 'GN' => 'GNS', 'GW' => 'GWP', 'GY' => 'GYD', 'HT' => 'HTG', 'HM' => 'AUD', 'VA' => 'EUR', 'HN' => 'HNL', 'HK' => 'HKD', 'HU' => 'HUF', 'IS' => 'ISK', 'IN' => 'INR', 'ID' => 'IDR', 'IR' => 'IRR', 'IQ' => 'IQD', 'IE' => 'EUR', 'IL' => 'ILS', 'IT' => 'EUR', 'CI' => 'XAF', 'JM' => 'JMD', 'JP' => 'JPY', 'JO' => 'JOD', 'KZ' => 'KZT', 'KE' => 'KES', 'KI' => 'AUD', 'KP' => 'KPW', 'KR' => 'KRW', 'KW' => 'KWD', 'KG' => 'KGS', 'LA' => 'LAK', 'LV' => 'LVL', 'LB' => 'LBP', 'LS' => 'LSL', 'LR' => 'LRD', 'LY' => 'LYD', 'LI' => 'CHF', 'LT' => 'LTL', 'LU' => 'EUR', 'MO' => 'MOP', 'MK' => 'MKD', 'MG' => 'MGF', 'MW' => 'MWK', 'MY' => 'MYR', 'MV' => 'MVR', 'ML' => 'XAF', 'MT' => 'MTL', 'MH' => 'USD', 'MQ' => 'EUR', 'MR' => 'MRO', 'MU' => 'MUR', 'YT' => 'EUR', 'MX' => 'MXN', 'FM' => 'USD', 'MD' => 'MDL', 'MC' => 'EUR', 'MN' => 'MNT', 'MS' => 'XCD', 'MA' => 'MAD', 'MZ' => 'MZM', 'MM' => 'MMK', 'NA' => 'NAD', 'NR' => 'AUD', 'NP' => 'NPR', 'NL' => 'EUR', 'AN' => 'ANG', 'NC' => 'XPF', 'NZ' => 'NZD', 'NI' => 'NIC', 'NE' => 'XOF', 'NG' => 'NGN', 'NU' => 'NZD', 'NF' => 'AUD', 'MP' => 'USD', 'NO' => 'NOK', 'OM' => 'OMR', 'PK' => 'PKR', 'PW' => 'USD', 'PA' => 'PAB', 'PG' => 'PGK', 'PY' => 'PYG', 'PE' => 'PEI', 'PH' => 'PHP', 'PN' => 'NZD', 'PL' => 'PLN', 'PT' => 'EUR', 'PR' => 'USD', 'QA' => 'QAR', 'RE' => 'EUR', 'RO' => 'ROL', 'RU' => 'RUB', 'RW' => 'RWF', 'KN' => 'XCD', 'LC' => 'XCD', 'VC' => 'XCD', 'WS' => 'WST', 'SM' => 'EUR', 'ST' => 'STD', 'SA' => 'SAR', 'SN' => 'XOF', 'CS' => 'CSD', 'SC' => 'SCR', 'SL' => 'SLL', 'SG' => 'SGD', 'SK' => 'SKK', 'SI' => 'SIT', 'SB' => 'SBD', 'SO' => 'SOS', 'ZA' => 'ZAR', 'GS' => 'GBP', 'ES' => 'EUR', 'LK' => 'LKR', 'SH' => 'SHP', 'PM' => 'EUR', 'SD' => 'SDP', 'SR' => 'SRG', 'SJ' => 'NOK', 'SZ' => 'SZL', 'SE' => 'SEK', 'CH' => 'CHF', 'SY' => 'SYP', 'TW' => 'TWD', 'TJ' => 'TJR', 'TZ' => 'TZS', 'TH' => 'THB', 'TG' => 'XAF', 'TK' => 'NZD', 'TO' => 'TOP', 'TT' => 'TTD', 'TN' => 'TND', 'TR' => 'TRL', 'TM' => 'TMM', 'TC' => 'USD', 'TV' => 'AUD', 'UG' => 'UGS', 'UA' => 'UAH', 'SU' => 'SUR', 'AE' => 'AED', 'GB' => 'GBP', 'US' => 'USD', 'UM' => 'USD', 'UY' => 'UYU', 'UZ' => 'UZS', 'VU' => 'VUV', 'VE' => 'VEB', 'VN' => 'VND', 'VG' => 'USD', 'VI' => 'USD', 'WF' => 'XPF', 'XO' => 'XOF', 'EH' => 'MAD', 'YE' => 'YER', //'ZM' => 'ZMK', //Switched to ZMW in Aug 2012. 'ZM' => 'ZMW', 'ZW' => 'ZWD', ]; break; case 'round_decimal_places': $retval = [ 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, ]; break; case 'columns': $retval = [ '-1000-status' => TTi18n::gettext( 'Status' ), '-1010-name' => TTi18n::gettext( 'Name' ), '-1020-symbol' => TTi18n::gettext( 'Symbol' ), '-1020-iso_code' => TTi18n::gettext( 'ISO Code' ), '-1030-conversion_rate' => TTi18n::gettext( 'Conversion Rate' ), '-1040-auto_update' => TTi18n::gettext( 'Auto Update' ), '-1050-actual_rate' => TTi18n::gettext( 'Actual Rate' ), '-1060-actual_rate_updated_date' => TTi18n::gettext( 'Last Downloaded Date' ), '-1070-rate_modify_percent' => TTi18n::gettext( 'Rate Modify Percent' ), '-1080-is_default' => TTi18n::gettext( 'Default Currency' ), '-1090-is_base' => TTi18n::gettext( 'Base Currency' ), '-1100-round_decimal_places' => TTi18n::gettext( 'Round Decimal Places' ), '-2000-created_by' => TTi18n::gettext( 'Created By' ), '-2010-created_date' => TTi18n::gettext( 'Created Date' ), '-2020-updated_by' => TTi18n::gettext( 'Updated By' ), '-2030-updated_date' => TTi18n::gettext( 'Updated Date' ), ]; break; case 'list_columns': $retval = Misc::arrayIntersectByKey( [ 'name', 'iso_code', 'symbol', 'status' ], Misc::trimSortPrefix( $this->getOptions( 'columns' ) ) ); break; case 'default_display_columns': //Columns that are displayed by default. $retval = [ 'status', 'name', 'iso_code', 'conversion_rate', 'is_default', 'is_base', ]; break; case 'unique_columns': //Columns that are unique, and disabled for mass editing. $retval = [ 'name', 'is_default', 'is_base', ]; break; } return $retval; } /** * @param $data * @return array */ function _getVariableToFunctionMap( $data ) { $variable_function_map = [ 'id' => 'ID', 'company_id' => 'Company', 'status_id' => 'Status', 'status' => false, 'name' => 'Name', 'symbol' => false, 'iso_code' => 'ISOCode', 'conversion_rate' => 'ConversionRate', 'auto_update' => 'AutoUpdate', 'actual_rate' => 'ActualRate', 'actual_rate_updated_date' => 'ActualRateUpdatedDate', 'rate_modify_percent' => 'RateModifyPercent', 'is_default' => 'Default', 'is_base' => 'Base', 'round_decimal_places' => 'RoundDecimalPlaces', 'deleted' => 'Deleted', ]; return $variable_function_map; } /** * @return mixed */ static function getISOCodesArray() { return TTi18n::getCurrencyArray(); } /** * @return bool|mixed */ function getCompany() { return $this->getGenericDataValue( 'company_id' ); } /** * @param string $value UUID * @return bool */ function setCompany( $value ) { $value = TTUUID::castUUID( $value ); return $this->setGenericDataValue( 'company_id', $value ); } /** * @return bool|int */ function getStatus() { return $this->getGenericDataValue( 'status_id' ); } /** * @param $value * @return bool */ function setStatus( $value ) { $value = (int)trim( $value ); return $this->setGenericDataValue( 'status_id', $value ); } /** * @param $name * @return bool */ function isUniqueName( $name ) { $name = trim( $name ); if ( $name == '' ) { return false; } $ph = [ 'company_id' => TTUUID::castUUID( $this->getCompany() ), 'name' => TTi18n::strtolower( $name ), ]; $query = 'select id from ' . $this->getTable() . ' where company_id = ? AND lower(name) = ? AND deleted = 0'; $name_id = $this->db->GetOne( $query, $ph ); Debug::Arr( $name_id, 'Unique Name: ' . $name, __FILE__, __LINE__, __METHOD__, 10 ); if ( $name_id === false ) { return true; } else { if ( $name_id == $this->getId() ) { return true; } } return false; } /** * @return bool|mixed */ function getName() { return $this->getGenericDataValue( 'name' ); } /** * @param $value * @return bool */ function setName( $value ) { $value = trim( $value ); return $this->setGenericDataValue( 'name', $value ); } /** * @return bool|mixed */ function getISOCode() { return $this->getGenericDataValue( 'iso_code' ); } /** * @param $value * @return bool */ function setISOCode( $value ) { $value = trim( $value ); return $this->setGenericDataValue( 'iso_code', $value ); } /** * @return string */ function getReverseConversionRate() { return bcdiv( 1, $this->getConversionRate() ); } /** * @return bool */ function getConversionRate() { return $this->getGenericDataValue( 'conversion_rate' ); //Don't cast to (float) as it may strip some precision. } /** * @param $value * @return bool */ function setConversionRate( $value ) { //Pull out only digits and periods. $value = $this->Validator->stripNonFloat( $value ); return $this->setGenericDataValue( 'conversion_rate', $value ); } /** * @return bool */ function getAutoUpdate() { return $this->fromBool( $this->getGenericDataValue( 'auto_update' ) ); } /** * @param $value * @return bool */ function setAutoUpdate( $value ) { return $this->setGenericDataValue( 'auto_update', $this->toBool( $value ) ); } /** * @return bool|mixed */ function getActualRate() { return $this->getGenericDataValue( 'actual_rate' ); } /** * @param $value * @return bool */ function setActualRate( $value ) { $value = trim( $value ); //Pull out only digits and periods. $value = $this->Validator->stripNonFloat( $value ); //Ignore any boolean values passed in to this function. if ( is_numeric( $value ) ) { return $this->setGenericDataValue( 'actual_rate', $value ); } return false; } /** * @return bool|int */ function getActualRateUpdatedDate() { return $this->getGenericDataValue( 'actual_rate_updated_date' ); } /** * @param int $value EPOCH * @return bool */ function setActualRateUpdatedDate( $value = null ) { $value = ( !is_int( $value ) && $value !== null ) ? trim( $value ) : $value;//Dont trim integer values, as it changes them to strings. if ( $value == null ) { $value = TTDate::getTime(); } return $this->setGenericDataValue( 'actual_rate_updated_date', $value ); } /** * @param $rate * @return string */ function getPercentModifiedRate( $rate ) { if ( $this->getRateModifyPercent() == 0 ) { $percent = 1; } else { $percent = $this->getRateModifyPercent(); } return bcmul( $rate, $percent ); } /** * @return bool|mixed */ function getRateModifyPercent() { return $this->getGenericDataValue( 'rate_modify_percent' ); } /** * @param $value * @return bool */ function setRateModifyPercent( $value ) { $value = (float)trim( $value ); //Pull out only digits and periods. $value = $this->Validator->stripNonFloat( $value ); return $this->setGenericDataValue( 'rate_modify_percent', $value ); } /** * @return bool */ function isUniqueDefault() { $ph = [ 'company_id' => $this->getCompany(), ]; $query = 'select id from ' . $this->getTable() . ' where company_id = ? AND is_default = 1 AND deleted=0'; $id = $this->db->GetOne( $query, $ph ); Debug::Arr( $id, 'Unique Currency Default: ' . $id, __FILE__, __LINE__, __METHOD__, 10 ); if ( $id === false ) { return true; } else { if ( $id == $this->getId() ) { return true; } } return false; } /** * @return bool */ function getDefault() { return $this->fromBool( $this->getGenericDataValue( 'is_default' ) ); } /** * @param $value * @return bool */ function setDefault( $value ) { return $this->setGenericDataValue( 'is_default', $this->toBool( $value ) ); } /** * @return bool */ function isUniqueBase() { $ph = [ 'company_id' => $this->getCompany(), ]; $query = 'select id from ' . $this->getTable() . ' where company_id = ? AND is_base = 1 AND deleted=0'; $id = $this->db->GetOne( $query, $ph ); Debug::Arr( $id, 'Unique Currency Base: ' . $id, __FILE__, __LINE__, __METHOD__, 10 ); if ( $id === false ) { return true; } else { if ( $id == $this->getId() ) { return true; } } return false; } /** * @return bool */ function getBase() { return $this->fromBool( $this->getGenericDataValue( 'is_base' ) ); } /** * @param $value * @return bool */ function setBase( $value ) { return $this->setGenericDataValue( 'is_base', $this->toBool( $value ) ); } /** * @return mixed|string */ function getSymbol() { return TTi18n::getCurrencySymbol( $this->getISOCode() ); } /** * @return int */ function getRoundDecimalPlaces() { $value = $this->getGenericDataValue( 'round_decimal_places' ); if ( $value !== false ) { return $value; } return 2; } /** * @param $value * @return bool */ function setRoundDecimalPlaces( $value ) { if ( version_compare( APPLICATION_VERSION, 5.5, '>' ) ) { $value = trim( $value ); return $this->setGenericDataValue( 'round_decimal_places', $value ); } return false; } /** * @param $value * @return string */ function round( $value ) { //This needs to be number_format, as round strips trailing 0's. return number_format( (float)$value, (int)$this->getRoundDecimalPlaces(), '.', '' ); //return round( (float)$value, (int)$this->getRoundDecimalPlaces() ); } /** * @param $src_rate * @param $dst_rate * @return int|string */ static function getCrossConversionRate( $src_rate, $dst_rate ) { if ( $src_rate == $dst_rate ) { $retval = 1; } else { $retval = bcmul( bcdiv( 1, $src_rate ), $dst_rate ); } return $retval; } /** * @param $src_rate * @param $dst_rate * @param $amount * @param int $round_decimal_places * @return float */ static function convert( $src_rate, $dst_rate, $amount, $round_decimal_places = 2 ) { if ( $src_rate == $dst_rate ) { return round( $amount, (int)$round_decimal_places ); } else { $base_amount = bcmul( bcdiv( 1, $src_rate ), $amount ); $retval = round( bcmul( $dst_rate, $base_amount ), (int)$round_decimal_places ); return $retval; } } /** * @param string $src_currency_id UUID * @param string $dst_currency_id UUID * @param int $amount * @return bool|float|int */ static function convertCurrency( $src_currency_id, $dst_currency_id, $amount = 1 ) { //Debug::Text('Source Currency: '. $src_currency_id .' Destination Currency: '. $dst_currency_id .' Amount: '. $amount, __FILE__, __LINE__, __METHOD__, 10); if ( $src_currency_id == '' ) { return false; } if ( $dst_currency_id == '' ) { return false; } if ( $amount == '' ) { return false; } if ( $src_currency_id == $dst_currency_id ) { return $amount; } $clf = TTnew( 'CurrencyListFactory' ); /** @var CurrencyListFactory $clf */ $clf->getById( $src_currency_id ); if ( $clf->getRecordCount() > 0 ) { $src_currency_obj = $clf->getCurrent(); } else { Debug::Text( 'Source currency does not exist.', __FILE__, __LINE__, __METHOD__, 10 ); return false; } $clf->getById( $dst_currency_id ); if ( $clf->getRecordCount() > 0 ) { $dst_currency_obj = $clf->getCurrent(); } else { Debug::Text( 'Destination currency does not exist.', __FILE__, __LINE__, __METHOD__, 10 ); return false; } if ( is_object( $src_currency_obj ) && is_object( $dst_currency_obj ) ) { return self::Convert( $src_currency_obj->getConversionRate(), $dst_currency_obj->getConversionRate(), $amount, $dst_currency_obj->getRoundDecimalPlaces() ); } return false; } /** * @param $amount * @param $rate * @param bool $convert * @return string */ function getBaseCurrencyAmount( $amount, $rate, $convert = true ) { if ( $convert == true && $rate !== 1 ) { //Don't bother converting if rate is 1. $amount = bcmul( $rate, $amount ); } return $this->round( $amount ); } /** * @param string $company_id UUID * @return bool */ static function updateCurrencyRates( $company_id ) { /* Contact info@timetrex.com to request adding custom currency data feeds. */ $base_currency = false; Debug::Text( 'Begin updating Currencies...', __FILE__, __LINE__, __METHOD__, 10 ); $clf = TTnew( 'CurrencyListFactory' ); /** @var CurrencyListFactory $clf */ $clf->getByCompanyId( $company_id ); $manual_currencies = []; $active_currencies = []; if ( $clf->getRecordCount() > 0 ) { foreach ( $clf as $c_obj ) { if ( $c_obj->getBase() == true ) { $base_currency = $c_obj->getISOCode(); $manual_currencies[$c_obj->getID()] = $c_obj->getISOCode(); //Make base currency manually updated too. } else { if ( $c_obj->getStatus() == 10 && $c_obj->getAutoUpdate() == true ) { $active_currencies[$c_obj->getID()] = $c_obj->getISOCode(); } else if ( $c_obj->getStatus() == 10 && $c_obj->getAutoUpdate() == false ) { $manual_currencies[$c_obj->getID()] = $c_obj->getISOCode(); } } } } unset( $clf, $c_obj ); $ttsc = new TimeTrexSoapClient(); //Fill in any gaps or missing rates prior to today. //Get the earliest pay period date as the absolute earliest to get rates from. //Loop through each currency and get the latest date. //Download rates to fill in the gaps, if rates returned by server don't fill all gaps, manually fill them ourselves. if ( empty( $active_currencies ) == false || empty( $manual_currencies ) == false ) { $earliest_pay_period_start_date = time(); $pplf = TTNew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $pplf */ $pplf->getByCompanyId( $company_id, 1, null, null, [ 'start_date' => 'asc' ] ); if ( $pplf->getRecordCount() > 0 ) { $earliest_pay_period_start_date = $pplf->getCurrent()->getStartDate(); } unset( $pplf ); Debug::Text( ' Earliest Pay Period Date: ' . TTDate::getDATE( 'DATE', $earliest_pay_period_start_date ) . '(' . $earliest_pay_period_start_date . ')', __FILE__, __LINE__, __METHOD__, 10 ); $crlf = TTNew( 'CurrencyRateListFactory' ); /** @var CurrencyRateListFactory $crlf */ if ( empty( $active_currencies ) == false ) { Debug::Text( ' Processing Auto-Update Currencies... Total: ' . count( $active_currencies ), __FILE__, __LINE__, __METHOD__, 10 ); foreach ( $active_currencies as $active_currency_id => $active_currency_iso_code ) { $crlf->getByCurrencyId( $active_currency_id, 1, null, null, [ 'date_stamp' => 'desc' ] ); if ( $crlf->getRecordCount() > 0 ) { $latest_currency_rate_date = $crlf->getCurrent()->getDateStamp(); } else { $latest_currency_rate_date = $earliest_pay_period_start_date; } Debug::Text( ' Latest Currency Rate Date: ' . TTDate::getDATE( 'DATE', $latest_currency_rate_date ) . '(' . $latest_currency_rate_date . ') Currency ISO: ' . $active_currency_iso_code, __FILE__, __LINE__, __METHOD__, 10 ); if ( ( TTDate::getMiddleDayEpoch( time() ) - TTDate::getMiddleDayEpoch( $latest_currency_rate_date ) ) > 86400 ) { $currency_rates = $ttsc->getCurrencyExchangeRatesByDate( $company_id, [ $active_currency_iso_code ], $base_currency, $latest_currency_rate_date, ( TTDate::getMiddleDayEpoch( time() ) - 86400 ) ); //Debug::Arr($currency_rates, 'Currency Rates for: '. $active_currency_iso_code, __FILE__, __LINE__, __METHOD__, 10); if ( is_array( $currency_rates ) && isset($currency_rates[$active_currency_iso_code]) && is_array( $currency_rates[$active_currency_iso_code] ) ) { Debug::Text( 'Currency Rates for: ' . $active_currency_iso_code . ' Total: ' . count( $currency_rates ), __FILE__, __LINE__, __METHOD__, 10 ); foreach ( $currency_rates[$active_currency_iso_code] as $date_stamp => $conversion_rate ) { $crf = TTnew( 'CurrencyRateFactory' ); /** @var CurrencyRateFactory $crf */ $crf->setCurrency( $active_currency_id ); $crf->setDateStamp( strtotime( $date_stamp ) ); $crf->setConversionRate( $conversion_rate ); Debug::Text( 'Currency: ' . $active_currency_iso_code . ' Date: ' . $date_stamp . ' Rate: Raw: ' . $conversion_rate . ' Modified: ' . $crf->getCurrencyObject()->getPercentModifiedRate( $conversion_rate ), __FILE__, __LINE__, __METHOD__, 10 ); if ( $crf->isValid() ) { $crf->Save(); } } } else { Debug::Arr($currency_rates, 'No currency rates returned for: '. $active_currency_iso_code, __FILE__, __LINE__, __METHOD__, 10); } } else { Debug::Text( ' Rates not older than 24hrs, no need to backfill...', __FILE__, __LINE__, __METHOD__, 10 ); } } } else { Debug::Text( ' No Auto-Update Currencies to process...', __FILE__, __LINE__, __METHOD__, 10 ); } unset( $crlf ); $crlf = TTNew( 'CurrencyRateListFactory' ); /** @var CurrencyRateListFactory $crlf */ if ( empty( $manual_currencies ) == false ) { Debug::Text( ' Processing Manual Currencies... Total: ' . count( $manual_currencies ), __FILE__, __LINE__, __METHOD__, 10 ); foreach ( $manual_currencies as $active_currency_id => $active_currency_iso_code ) { $crlf->getByCurrencyId( $active_currency_id, 1, null, null, [ 'date_stamp' => 'desc' ] ); if ( $crlf->getRecordCount() > 0 ) { $latest_currency_rate_date = $crlf->getCurrent()->getDateStamp(); } else { $latest_currency_rate_date = $earliest_pay_period_start_date; } Debug::Text( ' Latest Currency Rate Date: ' . TTDate::getDATE( 'DATE', $latest_currency_rate_date ) . '(' . $latest_currency_rate_date . ') Currency ISO: ' . $active_currency_iso_code, __FILE__, __LINE__, __METHOD__, 10 ); $latest_currency_rate_date += 86400; //Start on next day. if ( ( TTDate::getMiddleDayEpoch( time() ) - TTDate::getMiddleDayEpoch( $latest_currency_rate_date ) ) >= 86400 ) { $clf = TTnew( 'CurrencyListFactory' ); /** @var CurrencyListFactory $clf */ $clf->getByIdAndCompanyId( $active_currency_id, $company_id ); if ( $clf->getRecordCount() > 0 ) { $last_conversion_rate = $clf->getCurrent()->getConversionRate(); //This updates right to the current date, as we won't be downloading any rates later, and no need to update the currency record itself. //As an optimization, do quick inserts if we're more than 30 days old. if ( TTDate::getDays( ( time() - $latest_currency_rate_date ) ) > 30 ) { $crf = TTnew( 'CurrencyRateFactory' ); /** @var CurrencyRateFactory $crf */ //for( $x = TTDate::getMiddleDayEpoch( $latest_currency_rate_date ); $x <= TTDate::getMiddleDayEpoch( time() ); $x += 86400 ) { foreach ( TTDate::getDatePeriod( TTDate::getMiddleDayEpoch( $latest_currency_rate_date ), TTDate::getMiddleDayEpoch( time() ), 'P1D' ) as $x ) { $crf->ExecuteSQL( 'INSERT INTO ' . $crf->getTable() . ' (id,currency_id,date_stamp,conversion_rate,created_date) VALUES(\'' . $crf->getNextInsertId() . '\',\'' . $active_currency_id . '\',\'' . $crf->db->BindDate( $x ) . '\', ' . $last_conversion_rate . ',' . time() . ')' ); } } else { //for( $x = TTDate::getMiddleDayEpoch( $latest_currency_rate_date ); $x <= TTDate::getMiddleDayEpoch( time() ); $x += 86400 ) { foreach ( TTDate::getDatePeriod( TTDate::getMiddleDayEpoch( $latest_currency_rate_date ), TTDate::getMiddleDayEpoch( time() ), 'P1D' ) as $x ) { $crf = TTnew( 'CurrencyRateFactory' ); /** @var CurrencyRateFactory $crf */ $crf->setCurrency( $active_currency_id ); $crf->setDateStamp( $x ); $crf->setConversionRate( $last_conversion_rate ); Debug::Text( ' Currency: ' . $active_currency_iso_code . ' Date: ' . $x . ' Rate: Raw: ' . $last_conversion_rate, __FILE__, __LINE__, __METHOD__, 10 ); if ( $crf->isValid() ) { $crf->Save(); } } } } } unset( $last_conversion_rate ); } } else { Debug::Text( ' No Manual Currencies to process...', __FILE__, __LINE__, __METHOD__, 10 ); } unset( $crlf ); } unset( $active_currency_id, $active_currency_iso_code, $latest_currency_rate_date, $currency_rates ); if ( $base_currency != false && empty( $active_currencies ) == false && is_array( $active_currencies ) && count( $active_currencies ) > 0 ) { $currency_rates = $ttsc->getCurrencyExchangeRates( $company_id, $active_currencies, $base_currency ); } else { Debug::Text( ' No auto-update currencies exist, no need to process further...', __FILE__, __LINE__, __METHOD__, 10 ); } if ( isset( $currency_rates ) && is_array( $currency_rates ) && count( $currency_rates ) > 0 ) { foreach ( $currency_rates as $currency => $rate ) { if ( is_numeric( $rate ) ) { $clf = TTnew( 'CurrencyListFactory' ); /** @var CurrencyListFactory $clf */ $clf->getByCompanyIdAndISOCode( $company_id, $currency ); if ( $clf->getRecordCount() == 1 ) { $c_obj = $clf->getCurrent(); if ( $c_obj->getAutoUpdate() == true ) { $c_obj->setActualRate( $rate ); $c_obj->setConversionRate( $c_obj->getPercentModifiedRate( $rate ) ); $c_obj->setActualRateUpdatedDate( time() ); if ( $c_obj->isValid() ) { $c_obj->Save(); } } } } else { Debug::Text( ' Invalid rate from data feed! Currency: ' . $currency . ' Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 ); } } unset( $ttsc, $currency_rates, $currency, $rate, $clf, $c_obj ); Debug::Text( 'Done updating Currencies...', __FILE__, __LINE__, __METHOD__, 10 ); return true; } Debug::Text( 'Updating Currency Data Complete...', __FILE__, __LINE__, __METHOD__, 10 ); unset( $ttsc ); return false; } /** * @param bool $ignore_warning * @return bool */ function Validate( $ignore_warning = true ) { // // BELOW: Validation code moved from set*() functions. // // Company if ( $this->getCompany() != false && $this->getCompany() != TTUUID::getZeroID() ) { $clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */ $this->Validator->isResultSetWithRows( 'company', $clf->getByID( $this->getCompany() ), TTi18n::gettext( 'Company is invalid' ) ); } // Status if ( $this->getStatus() != '' ) { $this->Validator->inArrayKey( 'status', $this->getStatus(), TTi18n::gettext( 'Incorrect Status' ), $this->getOptions( 'status' ) ); } // Name if ( $this->getName() !== false ) { $this->Validator->isLength( 'name', $this->getName(), TTi18n::gettext( 'Name is too short or too long' ), 2, 100 ); if ( $this->Validator->isError( 'name' ) == false ) { $this->Validator->isTrue( 'name', $this->isUniqueName( $this->getName() ), TTi18n::gettext( 'Currency already exists' ) ); } } // ISO code if ( $this->getISOCode() !== false ) { $this->Validator->inArrayKey( 'iso_code', $this->getISOCode(), TTi18n::gettext( 'ISO code is invalid' ), $this->getISOCodesArray() ); } // Conversion rate if ( $this->getConversionRate() !== false ) { $this->Validator->isTrue( 'conversion_rate', $this->getConversionRate(), TTi18n::gettext( 'Conversion rate not specified' ) ); if ( $this->Validator->isError( 'conversion_rate' ) == false ) { $this->Validator->isFloat( 'conversion_rate', $this->getConversionRate(), TTi18n::gettext( 'Incorrect Conversion Rate' ) ); } if ( $this->Validator->isError( 'conversion_rate' ) == false ) { $this->Validator->isLessThan( 'conversion_rate', $this->getConversionRate(), TTi18n::gettext( 'Conversion Rate is too high' ), 99999999 ); } if ( $this->Validator->isError( 'conversion_rate' ) == false ) { $this->Validator->isGreaterThan( 'conversion_rate', $this->getConversionRate(), TTi18n::gettext( 'Conversion Rate is too low' ), -99999999 ); } } // Actual Rate if ( $this->getActualRate() !== false && is_numeric( $this->getActualRate() ) ) { $this->Validator->isFloat( 'actual_rate', $this->getActualRate(), TTi18n::gettext( 'Incorrect Actual Rate' ) ); } // Updated Date if ( $this->getActualRateUpdatedDate() !== false ) { $this->Validator->isDate( 'actual_rate_updated_date', $this->getActualRateUpdatedDate(), TTi18n::gettext( 'Incorrect Actual Rate Updated Date' ) ); } // Modify Percent if ( $this->getRateModifyPercent() !== false ) { $this->Validator->isFloat( 'rate_modify_percent', $this->getRateModifyPercent(), TTi18n::gettext( 'Incorrect Modify Percent' ) ); } // Is Default if ( $this->getDefault() !== false ) { $this->Validator->isTrue( 'is_default', $this->isUniqueDefault(), TTi18n::gettext( 'There is already a default currency set' ) ); } // Is Base if ( $this->getBase() !== false ) { $this->Validator->isTrue( 'is_base', $this->isUniqueBase(), TTi18n::gettext( 'There is already a base currency set' ) ); } // Rounding decimal places if ( $this->getRoundDecimalPlaces() != 2 ) { $this->Validator->inArrayKey( 'round_decimal_places', $this->getRoundDecimalPlaces(), TTi18n::gettext( 'Incorrect rounding decimal places' ), $this->getOptions( 'round_decimal_places' ) ); } // // ABOVE: Validation code moved from set*() functions. // if ( $this->getDeleted() == true ) { //CHeck to make sure currency isnt in-use by paystubs/employees/wages, if so, don't delete. if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'RemittanceSourceAccountListFactory' ); /** @var RemittanceSourceAccountListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by remittance sources' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'ProductListFactory' ); /** @var ProductListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by invoice products' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'UserListFactory' ); /** @var UserListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by employees' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'InvoiceListFactory' ); /** @var InvoiceListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by invoices' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'ClientContactListFactory' ); /** @var ClientContactListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by client contacts' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'RemittanceDestinationAccountListFactory' ); /** @var RemittanceDestinationAccountListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by employee pay methods' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'TransactionListFactory' ); /** @var TransactionListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by invoice transactions' ) ); } } if ( $this->Validator->isError( 'in_use' ) == false ) { $splf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $splf */ $splf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCompany(), [ 'pay_stub_currency_id' => $this->getId() ], 1 ); if ( $splf->getRecordCount() > 0 ) { $this->Validator->isTRUE( 'in_use', false, TTi18n::gettext( 'This currency is currently in use' ) . ' ' . TTi18n::gettext( 'by pay stubs' ) ); } } } return true; } /** * @return bool */ function preSave() { if ( $this->getBase() == true ) { $this->setConversionRate( '1.00' ); $this->setRateModifyPercent( '1.00' ); } return true; } /** * @return bool */ function postSave() { $this->removeCache( $this->getId() ); $this->removeCache( $this->getCompany() . $this->getBase() ); $this->removeCache( $this->getCompany() . $this->getISOCode() ); //PayrollDeduction_Base calls into currency to get exchange rates if needed. //CompanyFactory->getEncoding() is used to determine report encodings based on data saved here. $this->removeCache( 'encoding_' . $this->getCompany(), 'company' ); if ( $this->getDeleted() == false ) { Debug::Text( 'Currency modified, update historical rate for today: ' . $this->getISOCode() . ' Date: ' . time() . ' Rate: ' . $this->getConversionRate(), __FILE__, __LINE__, __METHOD__, 10 ); $crlf = TTnew( 'CurrencyRateListFactory' ); /** @var CurrencyRateListFactory $crlf */ $crlf->getByCurrencyIdAndDateStamp( $this->getID(), time() ); if ( $crlf->getRecordCount() > 0 ) { $crf = $crlf->getCurrent(); } else { $crf = TTnew( 'CurrencyRateFactory' ); /** @var CurrencyRateFactory $crf */ } $crf->setCurrency( $this->getID() ); $crf->setDateStamp( time() ); $crf->setConversionRate( $this->getConversionRate() ); if ( $crf->isValid() ) { $crf->Save(); } } return true; } /** * Support setting created_by, updated_by especially for importing data. * Make sure data is set based on the getVariableToFunctionMap order. * @param $data * @return bool */ function setObjectFromArray( $data ) { if ( is_array( $data ) ) { $variable_function_map = $this->getVariableToFunctionMap(); foreach ( $variable_function_map as $key => $function ) { if ( isset( $data[$key] ) ) { $function = 'set' . $function; switch ( $key ) { case 'conversion_rate': $this->$function( TTi18n::parseFloat( $data[$key], 10 ) ); break; default: if ( method_exists( $this, $function ) ) { $this->$function( $data[$key] ); } break; } } } $this->setCreatedAndUpdatedColumns( $data ); return true; } return false; } /** * @param null $include_columns * @return array */ function getObjectAsArray( $include_columns = null ) { /* $include_columns = array( 'id' => TRUE, 'company_id' => TRUE, ... ) */ $data = []; $variable_function_map = $this->getVariableToFunctionMap(); if ( is_array( $variable_function_map ) ) { foreach ( $variable_function_map as $variable => $function_stub ) { if ( $include_columns == null || ( isset( $include_columns[$variable] ) && $include_columns[$variable] == true ) ) { $function = 'get' . $function_stub; switch ( $variable ) { case 'status': //case 'country_currency': $function = 'get' . $variable; if ( method_exists( $this, $function ) ) { $data[$variable] = Option::getByKey( $this->$function(), $this->getOptions( $variable ) ); } break; case 'symbol': $data[$variable] = $this->getSymbol(); break; // case 'conversion_rate': // $data[$variable] = TTi18n::formatNumber( $this->$function(), TRUE, 10, 10 ); //Don't format numbers here, as it could break scripts using the API. // break; default: if ( method_exists( $this, $function ) ) { $data[$variable] = $this->$function(); } break; } } } $this->getCreatedAndUpdatedColumns( $data, $include_columns ); } return $data; } /** * @param $log_action * @return bool */ function addLog( $log_action ) { return TTLog::addEntry( $this->getId(), $log_action, TTi18n::getText( 'Currency' ) . ': ' . $this->getISOCode() . ' ' . TTi18n::getText( 'Rate' ) . ': ' . $this->getConversionRate(), null, $this->getTable(), $this ); } } ?>