Add currency rates for budget calculation

This commit is contained in:
Frederic Guillot
2015-03-28 18:00:18 -04:00
parent eb6853c163
commit 9bfab51e00
27 changed files with 420 additions and 7 deletions

View File

@@ -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'));
}
}

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -817,4 +817,12 @@ return array(
'Task transitions' => 'Transitions des tâches', 'Task transitions' => 'Transitions des tâches',
'Task transitions export' => 'Export des 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.', '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',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -815,4 +815,12 @@ return array(
// 'Task transitions' => '', // 'Task transitions' => '',
// 'Task transitions export' => '', // 'Task transitions export' => '',
// 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', // '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.' => '',
); );

View File

@@ -147,7 +147,7 @@ class Budget extends Base
foreach ($rates as $rate) { foreach ($rates as $rate) {
if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) { 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; break;
} }
} }

104
app/Model/Currency.php Normal file
View File

@@ -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()
);
}
}

View File

@@ -6,7 +6,15 @@ use PDO;
use Core\Security; use Core\Security;
use Model\Link; 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) function version_56($pdo)
{ {
@@ -115,7 +123,7 @@ function version_50($pdo)
user_id INT NOT NULL, user_id INT NOT NULL,
rate FLOAT DEFAULT 0, rate FLOAT DEFAULT 0,
date_effective INTEGER NOT NULL, date_effective INTEGER NOT NULL,
currency TEXT NOT NULL, currency CHAR(3) NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY(id) PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8"); ) ENGINE=InnoDB CHARSET=utf8");

View File

@@ -6,7 +6,15 @@ use PDO;
use Core\Security; use Core\Security;
use Model\Link; 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) function version_37($pdo)
{ {
@@ -109,7 +117,7 @@ function version_31($pdo)
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
rate REAL DEFAULT 0, rate REAL DEFAULT 0,
date_effective INTEGER NOT NULL, date_effective INTEGER NOT NULL,
currency TEXT NOT NULL, currency CHAR(3) NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)"); )");
} }

View File

@@ -6,7 +6,15 @@ use Core\Security;
use PDO; use PDO;
use Model\Link; 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) function version_55($pdo)
{ {

View File

@@ -22,6 +22,7 @@ class ClassProvider implements ServiceProviderInterface
'Color', 'Color',
'Comment', 'Comment',
'Config', 'Config',
'Currency',
'DateParser', 'DateParser',
'File', 'File',
'HourlyRate', 'HourlyRate',

View File

@@ -22,7 +22,7 @@
<tr> <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['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->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['cost']) ?></td>
<td><?= n($record['time_spent']).' '.t('hours') ?></td> <td><?= n($record['time_spent']).' '.t('hours') ?></td>
<td><?= dt('%B %e, %Y', $record['start']) ?></td> <td><?= dt('%B %e, %Y', $record['start']) ?></td>

View File

@@ -13,6 +13,9 @@
<li> <li>
<?= $this->a(t('Link settings'), 'link', 'index') ?> <?= $this->a(t('Link settings'), 'link', 'index') ?>
</li> </li>
<li>
<?= $this->a(t('Currency rates'), 'currency', 'index') ?>
</li>
<li> <li>
<?= $this->a(t('Webhooks'), 'config', 'webhook') ?> <?= $this->a(t('Webhooks'), 'config', 'webhook') ?>
</li> </li>

View File

@@ -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>