Add user cost

This commit is contained in:
Frederic Guillot 2015-03-07 16:50:36 -05:00
parent 313318a40d
commit 7328995645
15 changed files with 378 additions and 8 deletions

View File

@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\Config $config
* @property \Model\DateParser $dateParser
* @property \Model\File $file
* @property \Model\HourlyRate $hourlyRate
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project

View File

@ -0,0 +1,89 @@
<?php
namespace Controller;
/**
* Hourly Rate controller
*
* @package controller
* @author Frederic Guillot
*/
class HourlyRate extends User
{
/**
* Display rate and form
*
* @access public
*/
public function index(array $values = array(), array $errors = array())
{
$user = $this->getUser();
$this->response->html($this->layout('hourlyrate/index', array(
'rates' => $this->hourlyRate->getAllByUser($user['id']),
'currencies_list' => $this->config->getCurrencies(),
'values' => $values + array('user_id' => $user['id']),
'errors' => $errors,
'user' => $user,
)));
}
/**
* Validate and save a new rate
*
* @access public
*/
public function save()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->hourlyRate->validateCreation($values);
if ($valid) {
if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) {
$this->session->flash(t('Hourly rate created successfully.'));
$this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $values['user_id'])));
}
else {
$this->session->flashError(t('Unable to save the hourly rate.'));
}
}
$this->index($values, $errors);
}
/**
* Confirmation dialag box to remove a row
*
* @access public
*/
public function confirm()
{
$user = $this->getUser();
$this->response->html($this->layout('hourlyrate/remove', array(
'rate_id' => $this->request->getIntegerParam('rate_id'),
'user' => $user,
)));
}
/**
* Remove a row
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$user = $this->getUser();
if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) {
$this->session->flash(t('Rate removed successfully.'));
}
else {
$this->session->flash(t('Unable to remove this rate.'));
}
$this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $user['id'])));
}
}

View File

@ -69,12 +69,12 @@ class User extends Base
/**
* Common layout for user views
*
* @access private
* @access protected
* @param string $template Template name
* @param array $params Template parameters
* @return string
*/
private function layout($template, array $params)
protected function layout($template, array $params)
{
$content = $this->template->render($template, $params);
$params['user_content_for_layout'] = $content;
@ -90,10 +90,10 @@ class User extends Base
/**
* Common method to get the user
*
* @access private
* @access protected
* @return array
*/
private function getUser()
protected function getUser()
{
$user = $this->user->getById($this->request->getIntegerParam('user_id'));

View File

@ -1,6 +1,8 @@
<?php
return array(
'number.decimals_separator' => ',',
'number.thousands_separator' => ' ',
'None' => 'Aucun',
'edit' => 'modifier',
'Edit' => 'Modifier',
@ -740,4 +742,15 @@ return array(
'Horizontal scrolling' => 'Défilement horizontal',
'Compact/wide view' => 'Basculer entre la vue compacte et étendue',
'No results match:' => 'Aucun résultat :',
'Remove hourly rate' => 'Supprimer un taux horaire',
'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?',
'Hourly rates' => 'Taux horaires',
'Hourly rate' => 'Taux horaire',
'Currency' => 'Devise',
'Effective date' => 'Date d\'effet',
'Add new rate' => 'Ajouter un nouveau taux horaire',
'Rate removed successfully.' => 'Taux horaire supprimé avec succès.',
'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.',
'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.',
'Hourly rate created successfully.' => 'Taux horaire créé avec succès.',
);

View File

@ -70,6 +70,7 @@ class Acl extends Base
'config' => '*',
'link' => '*',
'project' => array('remove'),
'hourlyrate' => '*',
);
/**

View File

@ -21,6 +21,26 @@ class Config extends Base
*/
const TABLE = 'settings';
/**
* Get available currencies
*
* @access public
* @return array
*/
public function getCurrencies()
{
return array(
'USD' => t('USD - US Dollar'),
'EUR' => t('EUR - Euro'),
'GBP' => t('GBP - British Pound'),
'CAD' => t('CAD - Canadian Dollar'),
'AUD' => t('AUD - Australian Dollar'),
'NZD' => t('NZD - New Zealand Dollar'),
'INR' => t('INR - Indian Rupee'),
'JPY' => t('JPY - Japanese Yen'),
);
}
/**
* Get available timezones
*

103
app/Model/HourlyRate.php Normal file
View File

@ -0,0 +1,103 @@
<?php
namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Hourly Rate
*
* @package model
* @author Frederic Guillot
*/
class HourlyRate extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'hourly_rates';
/**
* Get all rates for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAllByUser($user_id)
{
return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll();
}
/**
* Get current rate for a given user
*
* @access public
* @param integer $user_id User id
* @return float
*/
public function getCurrentRate($user_id)
{
return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0;
}
/**
* Add a new rate in the database
*
* @access public
* @param integer $user_id User id
* @param float $rate Hourly rate
* @param string $currency Currency code
* @param string $date ISO8601 date format
* @return boolean|integer
*/
public function create($user_id, $rate, $currency, $date)
{
$values = array(
'user_id' => $user_id,
'rate' => $rate,
'currency' => $currency,
'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)),
);
return $this->persist(self::TABLE, $values);
}
/**
* Remove a specific rate
*
* @access public
* @param integer $rate_id
* @return boolean
*/
public function remove($rate_id)
{
return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove();
}
/**
* Validate creation
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCreation(array $values)
{
$v = new Validator($values, array(
new Validators\Required('user_id', t('Field required')),
new Validators\Required('rate', t('Field required')),
new Validators\Numeric('rate', t('This value must be numeric')),
new Validators\Required('date_effective', t('Field required')),
new Validators\Required('currency', t('Field required')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
}

View File

@ -6,7 +6,20 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 49;
const VERSION = 50;
function version_50($pdo)
{
$pdo->exec("CREATE TABLE hourly_rates (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
rate FLOAT DEFAULT 0,
date_effective INTEGER NOT NULL,
currency TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8");
}
function version_49($pdo)
{

View File

@ -6,7 +6,19 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 30;
const VERSION = 31;
function version_31($pdo)
{
$pdo->exec("CREATE TABLE hourly_rates (
id SERIAL,
user_id INTEGER NOT NULL,
rate REAL DEFAULT 0,
date_effective INTEGER NOT NULL,
currency TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)");
}
function version_30($pdo)
{

View File

@ -6,7 +6,19 @@ use Core\Security;
use PDO;
use Model\Link;
const VERSION = 48;
const VERSION = 49;
function version_49($pdo)
{
$pdo->exec("CREATE TABLE hourly_rates (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
rate REAL DEFAULT 0,
date_effective INTEGER NOT NULL,
currency TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)");
}
function version_48($pdo)
{

View File

@ -23,6 +23,7 @@ class ClassProvider implements ServiceProviderInterface
'Config',
'DateParser',
'File',
'HourlyRate',
'LastLogin',
'Link',
'Notification',

View File

@ -0,0 +1,46 @@
<div class="page-header">
<h2><?= t('Hourly rates') ?></h2>
</div>
<?php if (! empty($rates)): ?>
<table>
<tr>
<th><?= t('Hourly rate') ?></th>
<th><?= t('Currency') ?></th>
<th><?= t('Effective date') ?></th>
<th><?= t('Action') ?></th>
</tr>
<?php foreach ($rates as $rate): ?>
<tr>
<td><?= n($rate['rate']) ?></td>
<td><?= $rate['currency'] ?></td>
<td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td>
<td>
<?= $this->a(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?>
</td>
</tr>
<?php endforeach ?>
</table>
<h3><?= t('Add new rate') ?></h3>
<?php endif ?>
<form method="post" action="<?= $this->u('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
<?= $this->formHidden('user_id', $values) ?>
<?= $this->formCsrf() ?>
<?= $this->formLabel(t('Hourly rate'), 'rate') ?>
<?= $this->formText('rate', $values, $errors, array('required'), 'form-numeric') ?>
<?= $this->formLabel(t('Currency'), 'currency') ?>
<?= $this->formSelect('currency', $currencies_list, $values, $errors, array('required')) ?>
<?= $this->formLabel(t('Effective date'), 'date_effective') ?>
<?= $this->formText('date_effective', $values, $errors, array('required'), 'form-date') ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>

View File

@ -0,0 +1,13 @@
<div class="page-header">
<h2><?= t('Remove hourly rate') ?></h2>
</div>
<div class="confirm">
<p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p>
<div class="form-actions">
<?= $this->a(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?>
<?= t('or') ?>
<?= $this->a(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
</div>
</div>

View File

@ -32,7 +32,7 @@
<?= $this->a(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php if ($this->userSession->isAdmin()): ?>
<li>
<?= $this->a(t('User dashboard'), 'app', 'dashboard', array('user_id' => $user['id'])) ?>
@ -40,6 +40,9 @@
<li>
<?= $this->a(t('User calendar'), 'user', 'calendar', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php if ($this->userSession->isAdmin() && ! $this->userSession->isCurrentUser($user['id'])): ?>

View File

@ -0,0 +1,43 @@
<?php
require_once __DIR__.'/Base.php';
use Model\User;
use Model\HourlyRate;
class HourlyRateTest extends Base
{
public function testCreation()
{
$hr = new HourlyRate($this->container);
$this->assertEquals(1, $hr->create(1, 32.4, 'EUR', '2015-01-01'));
$this->assertEquals(2, $hr->create(1, 42, 'CAD', '2015-02-01'));
$rates = $hr->getAllByUser(0);
$this->assertEmpty($rates);
$rates = $hr->getAllByUser(1);
$this->assertNotEmpty($rates);
$this->assertCount(2, $rates);
$this->assertEquals(42, $rates[0]['rate']);
$this->assertEquals('CAD', $rates[0]['currency']);
$this->assertEquals('2015-02-01', date('Y-m-d', $rates[0]['date_effective']));
$this->assertEquals(32.4, $rates[1]['rate']);
$this->assertEquals('EUR', $rates[1]['currency']);
$this->assertEquals('2015-01-01', date('Y-m-d', $rates[1]['date_effective']));
$this->assertEquals(0, $hr->getCurrentRate(0));
$this->assertEquals(42, $hr->getCurrentRate(1));
$this->assertTrue($hr->remove(2));
$this->assertEquals(32.4, $hr->getCurrentRate(1));
$this->assertTrue($hr->remove(1));
$this->assertEquals(0, $hr->getCurrentRate(1));
$rates = $hr->getAllByUser(1);
$this->assertEmpty($rates);
}
}