summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-03-28 18:00:18 -0400
committerFrederic Guillot <fred@kanboard.net>2015-03-28 18:00:18 -0400
commit9bfab51e00608b6e008aa71f6df74104b28fc662 (patch)
treec390346c8ed384c78bf85bbdd950d70bd9a9ab51
parenteb6853c163e9059bbbc89a1a741d4a25c8e04153 (diff)
Add currency rates for budget calculation
-rw-r--r--app/Controller/Currency.php89
-rw-r--r--app/Locale/da_DK/translations.php8
-rw-r--r--app/Locale/de_DE/translations.php8
-rw-r--r--app/Locale/es_ES/translations.php8
-rw-r--r--app/Locale/fi_FI/translations.php8
-rw-r--r--app/Locale/fr_FR/translations.php8
-rw-r--r--app/Locale/hu_HU/translations.php8
-rw-r--r--app/Locale/it_IT/translations.php8
-rw-r--r--app/Locale/ja_JP/translations.php8
-rw-r--r--app/Locale/nl_NL/translations.php8
-rw-r--r--app/Locale/pl_PL/translations.php8
-rw-r--r--app/Locale/pt_BR/translations.php8
-rw-r--r--app/Locale/ru_RU/translations.php8
-rw-r--r--app/Locale/sr_Latn_RS/translations.php8
-rw-r--r--app/Locale/sv_SE/translations.php8
-rw-r--r--app/Locale/th_TH/translations.php8
-rw-r--r--app/Locale/tr_TR/translations.php8
-rw-r--r--app/Locale/zh_CN/translations.php8
-rw-r--r--app/Model/Budget.php2
-rw-r--r--app/Model/Currency.php104
-rw-r--r--app/Schema/Mysql.php12
-rw-r--r--app/Schema/Postgres.php12
-rw-r--r--app/Schema/Sqlite.php10
-rw-r--r--app/ServiceProvider/ClassProvider.php1
-rw-r--r--app/Template/budget/breakdown.php2
-rw-r--r--app/Template/config/sidebar.php3
-rw-r--r--app/Template/currency/index.php56
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').' &gt; '.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>