From 9bfab51e00608b6e008aa71f6df74104b28fc662 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 28 Mar 2015 18:00:18 -0400 Subject: Add currency rates for budget calculation --- app/Controller/Currency.php | 89 ++++++++++++++++++++++++++++ app/Locale/da_DK/translations.php | 8 +++ app/Locale/de_DE/translations.php | 8 +++ app/Locale/es_ES/translations.php | 8 +++ app/Locale/fi_FI/translations.php | 8 +++ app/Locale/fr_FR/translations.php | 8 +++ app/Locale/hu_HU/translations.php | 8 +++ app/Locale/it_IT/translations.php | 8 +++ app/Locale/ja_JP/translations.php | 8 +++ app/Locale/nl_NL/translations.php | 8 +++ app/Locale/pl_PL/translations.php | 8 +++ app/Locale/pt_BR/translations.php | 8 +++ app/Locale/ru_RU/translations.php | 8 +++ app/Locale/sr_Latn_RS/translations.php | 8 +++ app/Locale/sv_SE/translations.php | 8 +++ app/Locale/th_TH/translations.php | 8 +++ app/Locale/tr_TR/translations.php | 8 +++ app/Locale/zh_CN/translations.php | 8 +++ app/Model/Budget.php | 2 +- app/Model/Currency.php | 104 +++++++++++++++++++++++++++++++++ app/Schema/Mysql.php | 12 +++- app/Schema/Postgres.php | 12 +++- app/Schema/Sqlite.php | 10 +++- app/ServiceProvider/ClassProvider.php | 1 + app/Template/budget/breakdown.php | 2 +- app/Template/config/sidebar.php | 3 + app/Template/currency/index.php | 56 ++++++++++++++++++ 27 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 app/Controller/Currency.php create mode 100644 app/Model/Currency.php create mode 100644 app/Template/currency/index.php (limited to 'app') diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php new file mode 100644 index 00000000..fac34a30 --- /dev/null +++ b/app/Controller/Currency.php @@ -0,0 +1,89 @@ +projectPermission->getAllowedProjects($this->userSession->getId()); + $params['config_content_for_layout'] = $this->template->render($template, $params); + + return $this->template->layout('config/layout', $params); + } + + /** + * Display all currency rates and form + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $this->response->html($this->layout('currency/index', array( + 'config_values' => array('application_currency' => $this->config->get('application_currency')), + 'values' => $values, + 'errors' => $errors, + 'rates' => $this->currency->getAll(), + 'currencies' => $this->config->getCurrencies(), + 'title' => t('Settings').' > '.t('Currency rates'), + ))); + } + + /** + * Validate and save a new currency rate + * + * @access public + */ + public function create() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->currency->validate($values); + + if ($valid) { + + if ($this->currency->create($values['currency'], $values['rate'])) { + $this->session->flash(t('The currency rate have been added successfully.')); + $this->response->redirect($this->helper->url('currency', 'index')); + } + else { + $this->session->flashError(t('Unable to add this currency rate.')); + } + } + + $this->index($values, $errors); + } + + /** + * Save reference currency + * + * @access public + */ + public function reference() + { + $values = $this->request->getValues(); + + if ($this->config->save($values)) { + $this->config->reload(); + $this->session->flash(t('Settings saved successfully.')); + } + else { + $this->session->flashError(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url('currency', 'index')); + } +} diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 17b8362d..e11f01ce 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index e3eeea52..ac7fb8ee 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index ac76ff74..6abd3e77 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index abf53362..39b85832 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 15705106..fec0f8b8 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -817,4 +817,12 @@ return array( 'Task transitions' => 'Transitions des tâches', 'Task transitions export' => 'Export des transitions des tâches', 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ce rapport contient tous les mouvements de colonne pour chaque tâche avec la date, l\'utilisateur et le temps passé pour chaque transition.', + 'Currency rates' => 'Taux de change des devises', + 'Rate' => 'Taux', + 'Change reference currency' => 'Changer la monnaie de référence', + 'Add a new currency rate' => 'Ajouter un nouveau taux pour une devise', + 'Currency rates are used to calculate project budget.' => 'Le cours des devises est utilisé pour calculer le budget des projets.', + 'Reference currency' => 'Devise de référence', + 'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.', + 'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index ecf7b6e3..8feab08e 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 511f1d44..3c059854 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 014ca0f4..2b9a1e45 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 199c33b8..839b0155 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 3d9fcb25..2a04375a 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 9d0de537..79bda55b 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 12eb1a38..23f4dd11 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index ec30a3b2..594abb31 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 5f460a25..0bcecdb4 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index b017a255..5a52c9e1 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index fc89f1c5..b556595e 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 03cfafe4..eb0f5126 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -815,4 +815,12 @@ return array( // 'Task transitions' => '', // 'Task transitions export' => '', // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // 'Rate' => '', + // 'Change reference currency' => '', + // 'Add a new currency rate' => '', + // 'Currency rates are used to calculate project budget.' => '', + // 'Reference currency' => '', + // 'The currency rate have been added successfully.' => '', + // 'Unable to add this currency rate.' => '', ); diff --git a/app/Model/Budget.php b/app/Model/Budget.php index fe8457bd..d74dd870 100644 --- a/app/Model/Budget.php +++ b/app/Model/Budget.php @@ -147,7 +147,7 @@ class Budget extends Base foreach ($rates as $rate) { if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) { - $hourly_price = $rate['rate']; + $hourly_price = $this->currency->getPrice($rate['currency'], $rate['rate']); break; } } diff --git a/app/Model/Currency.php b/app/Model/Currency.php new file mode 100644 index 00000000..bc423337 --- /dev/null +++ b/app/Model/Currency.php @@ -0,0 +1,104 @@ +db->table(self::TABLE)->findAll(); + } + + /** + * Calculate the price for the reference currency + * + * @access public + * @return array + */ + public function getPrice($currency, $price) + { + static $rates = null; + $reference = $this->config->get('application_currency', 'USD'); + + if ($reference !== $currency) { + $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : array(); + $rate = isset($rates[$currency]) ? $rates[$currency] : 1; + + return $rate * $price; + } + + return $price; + } + + /** + * Add a new currency rate + * + * @access public + * @param string $currency + * @param float $rate + * @return boolean|integer + */ + public function create($currency, $rate) + { + if ($this->db->table(self::TABLE)->eq('currency', $currency)->count() === 1) { + return $this->update($currency, $rate); + } + + return $this->persist(self::TABLE, compact('currency', 'rate')); + } + + /** + * Update a currency rate + * + * @access public + * @param string $currency + * @param float $rate + * @return boolean + */ + public function update($currency, $rate) + { + return $this->db->table(self::TABLE)->eq('currency', $currency)->update(array('rate' => $rate)); + } + + /** + * Validate + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validate(array $values) + { + $v = new Validator($values, array( + new Validators\Required('currency', t('Field required')), + new Validators\Required('rate', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 30fccbfa..bac65ec4 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,15 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 56; +const VERSION = 57; + +function version_57($pdo) +{ + $pdo->exec('CREATE TABLE currencies (`currency` CHAR(3) NOT NULL UNIQUE, `rate` FLOAT DEFAULT 0) ENGINE=InnoDB CHARSET=utf8'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} function version_56($pdo) { @@ -115,7 +123,7 @@ function version_50($pdo) user_id INT NOT NULL, rate FLOAT DEFAULT 0, date_effective INTEGER NOT NULL, - currency TEXT NOT NULL, + currency CHAR(3) NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, PRIMARY KEY(id) ) ENGINE=InnoDB CHARSET=utf8"); diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index ecf8664d..dc4afff3 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,15 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 37; +const VERSION = 38; + +function version_38($pdo) +{ + $pdo->exec('CREATE TABLE currencies ("currency" CHAR(3) NOT NULL UNIQUE, "rate" REAL DEFAULT 0)'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} function version_37($pdo) { @@ -109,7 +117,7 @@ function version_31($pdo) user_id INTEGER NOT NULL, rate REAL DEFAULT 0, date_effective INTEGER NOT NULL, - currency TEXT NOT NULL, + currency CHAR(3) NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE )"); } diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 19af5dd3..3e3366a4 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,15 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 55; +const VERSION = 56; + +function version_56($pdo) +{ + $pdo->exec('CREATE TABLE currencies ("currency" TEXT NOT NULL UNIQUE, "rate" REAL DEFAULT 0)'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} function version_55($pdo) { diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index c25ceb62..9ee4c18f 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -22,6 +22,7 @@ class ClassProvider implements ServiceProviderInterface 'Color', 'Comment', 'Config', + 'Currency', 'DateParser', 'File', 'HourlyRate', diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php index d4168406..0a3c63d7 100644 --- a/app/Template/budget/breakdown.php +++ b/app/Template/budget/breakdown.php @@ -22,7 +22,7 @@ a($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?> a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?> - e($record['name'] ?: $record['username']) ?> + a($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?> diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 89f2c203..2e81d48a 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -13,6 +13,9 @@
  • a(t('Link settings'), 'link', 'index') ?>
  • +
  • + a(t('Currency rates'), 'currency', 'index') ?> +
  • a(t('Webhooks'), 'config', 'webhook') ?>
  • diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php new file mode 100644 index 00000000..7839a142 --- /dev/null +++ b/app/Template/currency/index.php @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + +
    + e($rate['currency']) ?> + + +
    + +
    +

    + +
    + + formCsrf() ?> + + formLabel(t('Reference currency'), 'application_currency') ?> + formSelect('application_currency', $currencies, $config_values, $errors) ?>
    + +
    + +
    +
    + +
    +

    +
    + + formCsrf() ?> + + formLabel(t('Currency'), 'currency') ?> + formSelect('currency', $currencies, $values, $errors) ?>
    + + formLabel(t('Rate'), 'rate') ?> + formText('rate', $values, $errors, array(), 'form-numeric') ?>
    + +
    + +
    +
    + +

    -- cgit v1.2.3