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 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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

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

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

@ -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.' => '',
);

View File

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

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 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");

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,9 @@
<li>
<?= $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>

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>