diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-03-28 18:00:18 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-03-28 18:00:18 -0400 |
commit | 9bfab51e00608b6e008aa71f6df74104b28fc662 (patch) | |
tree | c390346c8ed384c78bf85bbdd950d70bd9a9ab51 | |
parent | eb6853c163e9059bbbc89a1a741d4a25c8e04153 (diff) |
Add currency rates for budget calculation
27 files changed, 420 insertions, 7 deletions
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 @@ +<?php + +namespace Controller; + +/** + * Currency controller + * + * @package controller + * @author Frederic Guillot + */ +class Currency extends Base +{ + /** + * Common layout for config views + * + * @access private + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + private function layout($template, array $params) + { + $params['board_selector'] = $this->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 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Currency + * + * @package model + * @author Frederic Guillot + */ +class Currency extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'currencies'; + + /** + * Get all currency rates + * + * @access public + * @return array + */ + public function getAll() + { + return $this->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 @@ <tr> <td><?= $this->a($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td> <td><?= $this->a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td> - <td><?= $this->e($record['name'] ?: $record['username']) ?></td> + <td><?= $this->a($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?></td> <td><?= n($record['cost']) ?></td> <td><?= n($record['time_spent']).' '.t('hours') ?></td> <td><?= dt('%B %e, %Y', $record['start']) ?></td> 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 @@ -14,6 +14,9 @@ <?= $this->a(t('Link settings'), 'link', 'index') ?> </li> <li> + <?= $this->a(t('Currency rates'), 'currency', 'index') ?> + </li> + <li> <?= $this->a(t('Webhooks'), 'config', 'webhook') ?> </li> <li> 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 @@ +<div class="page-header"> + <h2><?= t('Currency rates') ?></h2> +</div> + +<?php if (! empty($rates)): ?> + +<table class="table-stripped"> + <tr> + <th class="column-35"><?= t('Currency') ?></th> + <th><?= t('Rate') ?></th> + </tr> + <?php foreach ($rates as $rate): ?> + <tr> + <td> + <strong><?= $this->e($rate['currency']) ?></strong> + </td> + <td> + <?= n($rate['rate']) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<hr/> +<h3><?= t('Change reference currency') ?></h3> +<?php endif ?> +<form method="post" action="<?= $this->u('currency', 'reference') ?>" autocomplete="off"> + + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Reference currency'), 'application_currency') ?> + <?= $this->formSelect('application_currency', $currencies, $config_values, $errors) ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<hr/> +<h3><?= t('Add a new currency rate') ?></h3> +<form method="post" action="<?= $this->u('currency', 'create') ?>" autocomplete="off"> + + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Currency'), 'currency') ?> + <?= $this->formSelect('currency', $currencies, $values, $errors) ?><br/> + + <?= $this->formLabel(t('Rate'), 'rate') ?> + <?= $this->formText('rate', $values, $errors, array(), 'form-numeric') ?><br/> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<p class="alert alert-info"><?= t('Currency rates are used to calculate project budget.') ?></p> |