summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Controller/Budget.php111
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/Base.php1
-rw-r--r--app/Model/Budget.php101
-rw-r--r--app/Schema/Mysql.php15
-rw-r--r--app/Schema/Postgres.php14
-rw-r--r--app/Schema/Sqlite.php14
-rw-r--r--app/ServiceProvider/ClassProvider.php1
-rw-r--r--app/Template/budget/create.php51
-rw-r--r--app/Template/budget/index.php9
-rw-r--r--app/Template/budget/remove.php13
-rw-r--r--app/Template/project/sidebar.php5
12 files changed, 332 insertions, 4 deletions
diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php
new file mode 100644
index 00000000..01090550
--- /dev/null
+++ b/app/Controller/Budget.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Budget
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Budget extends Base
+{
+ /**
+ * Budget index page
+ *
+ * @access public
+ */
+ public function index()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('budget/index', array(
+ 'total' => $this->budget->getTotal($project['id']),
+ 'project' => $project,
+ 'title' => t('Budget')
+ )));
+ }
+
+ /**
+ * Create budget lines
+ *
+ * @access public
+ */
+ public function create(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+
+ if (empty($values)) {
+ $values['date'] = date('Y-m-d');
+ }
+
+ $this->response->html($this->projectLayout('budget/create', array(
+ 'lines' => $this->budget->getAll($project['id']),
+ 'values' => $values + array('project_id' => $project['id']),
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Budget')
+ )));
+ }
+
+ /**
+ * Validate and save a new budget
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->budget->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) {
+ $this->session->flash(t('The budget line have been created successfully.'));
+ $this->response->redirect($this->helper->url('budget', 'create', array('project_id' => $project['id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to create the budget line.'));
+ }
+ }
+
+ $this->create($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a budget
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('budget/remove', array(
+ 'project' => $project,
+ 'budget_id' => $this->request->getIntegerParam('budget_id'),
+ 'title' => t('Remove a budget line'),
+ )));
+ }
+
+ /**
+ * Remove a budget
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+
+ if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) {
+ $this->session->flash(t('Budget line removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this budget line.'));
+ }
+
+ $this->response->redirect($this->helper->url('budget', 'create', array('project_id' => $project['id'])));
+ }
+}
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 56938f9d..b52a7864 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -56,6 +56,7 @@ class Acl extends Base
'export' => array('tasks', 'subtasks', 'summary'),
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
+ 'budget' => '*',
);
/**
diff --git a/app/Model/Base.php b/app/Model/Base.php
index f836231c..8a90e286 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -16,6 +16,7 @@ use Pimple\Container;
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
+ * @property \Model\Budget $budget
* @property \Model\Category $category
* @property \Model\Comment $comment
* @property \Model\CommentHistory $commentHistory
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
new file mode 100644
index 00000000..03a90f7f
--- /dev/null
+++ b/app/Model/Budget.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Model;
+
+use SimpleValidator\Validator;
+use SimpleValidator\Validators;
+
+/**
+ * Budget
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class Budget extends Base
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'budget_lines';
+
+ /**
+ * Get all budget lines for a project
+ *
+ * @access public
+ * @param integer $project_id
+ * @return array
+ */
+ public function getAll($project_id)
+ {
+ return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll();
+ }
+
+ /**
+ * Get the current total of the budget
+ *
+ * @access public
+ * @param integer $project_id
+ * @return float
+ */
+ public function getTotal($project_id)
+ {
+ $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne();
+ return isset($result['total']) ? (float) $result['total'] : 0;
+ }
+
+ /**
+ * Add a new budget line in the database
+ *
+ * @access public
+ * @param integer $project_id
+ * @param float $amount
+ * @param string $comment
+ * @param string $date
+ * @return boolean|integer
+ */
+ public function create($project_id, $amount, $comment, $date = '')
+ {
+ $values = array(
+ 'project_id' => $project_id,
+ 'amount' => $amount,
+ 'comment' => $comment,
+ 'date' => $date ?: date('Y-m-d'),
+ );
+
+ return $this->persist(self::TABLE, $values);
+ }
+
+ /**
+ * Remove a specific budget line
+ *
+ * @access public
+ * @param integer $budget_id
+ * @return boolean
+ */
+ public function remove($budget_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $budget_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('project_id', t('Field required')),
+ new Validators\Required('amount', t('Field required')),
+ ));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+} \ No newline at end of file
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 44ca7fd4..03868748 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,20 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 51;
+const VERSION = 52;
+
+function version_52($pdo)
+{
+ $pdo->exec('CREATE TABLE budget_lines (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `project_id` INT NOT NULL,
+ `amount` FLOAT NOT NULL,
+ `date` VARCHAR(10) NOT NULL,
+ `comment` TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ PRIMARY KEY(id)
+ ) ENGINE=InnoDB CHARSET=utf8');
+}
function version_51($pdo)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index fe01b1e9..124aec76 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,19 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 32;
+const VERSION = 33;
+
+function version_33($pdo)
+{
+ $pdo->exec('CREATE TABLE budget_lines (
+ "id" SERIAL PRIMARY KEY,
+ "project_id" INTEGER NOT NULL,
+ "amount" REAL NOT NULL,
+ "date" VARCHAR(10) NOT NULL,
+ "comment" TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+ )');
+}
function version_32($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index eaa5c734..818ed78d 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,19 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 50;
+const VERSION = 51;
+
+function version_51($pdo)
+{
+ $pdo->exec('CREATE TABLE budget_lines (
+ "id" INTEGER PRIMARY KEY,
+ "project_id" INTEGER NOT NULL,
+ "amount" REAL NOT NULL,
+ "date" TEXT NOT NULL,
+ "comment" TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+ )');
+}
function version_50($pdo)
{
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index a94bb745..6f597a5c 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -17,6 +17,7 @@ class ClassProvider implements ServiceProviderInterface
'Action',
'Authentication',
'Board',
+ 'Budget',
'Category',
'Color',
'Comment',
diff --git a/app/Template/budget/create.php b/app/Template/budget/create.php
new file mode 100644
index 00000000..0ff395c9
--- /dev/null
+++ b/app/Template/budget/create.php
@@ -0,0 +1,51 @@
+<div class="page-header">
+ <h2><?= t('Budget') ?></h2>
+ <ul>
+ <li><?= $this->a(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?></li>
+ <li><?= $this->a(t('Burn rate'), 'budget', 'index', array('project_id' => $project['id'])) ?></li>
+ </ul>
+</div>
+
+<?php if (! empty($lines)): ?>
+<table class="table-fixed table-stripped">
+ <tr>
+ <th class="column-20"><?= t('Budget line') ?></th>
+ <th class="column-20"><?= t('Date') ?></th>
+ <th><?= t('Comment') ?></th>
+ <th><?= t('Action') ?></th>
+ </tr>
+ <?php foreach ($lines as $line): ?>
+ <tr>
+ <td><?= n($line['amount']) ?></td>
+ <td><?= $this->e($line['date']) ?></td>
+ <td><?= $this->e($line['comment']) ?></td>
+ <td>
+ <?= $this->a(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+</table>
+
+<h3><?= t('New budget line') ?></h3>
+<?php endif ?>
+
+<form method="post" action="<?= $this->u('budget', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off">
+
+ <?= $this->formCsrf() ?>
+
+ <?= $this->formHidden('id', $values) ?>
+ <?= $this->formHidden('project_id', $values) ?>
+
+ <?= $this->formLabel(t('Amount'), 'amount') ?>
+ <?= $this->formText('amount', $values, $errors, array('required'), 'form-numeric') ?>
+
+ <?= $this->formLabel(t('Date'), 'date') ?>
+ <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?>
+
+ <?= $this->formLabel(t('Comment'), 'comment') ?>
+ <?= $this->formText('comment', $values, $errors) ?>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ </div>
+</form> \ No newline at end of file
diff --git a/app/Template/budget/index.php b/app/Template/budget/index.php
new file mode 100644
index 00000000..8bdf1a57
--- /dev/null
+++ b/app/Template/budget/index.php
@@ -0,0 +1,9 @@
+<div class="page-header">
+ <h2><?= t('Budget') ?></h2>
+ <ul>
+ <li><?= $this->a(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?></li>
+ <li><?= $this->a(t('Burn rate'), 'budget', 'index', array('project_id' => $project['id'])) ?></li>
+ </ul>
+</div>
+
+<p><?= t('Current budget: ') ?><strong><?= n($total) ?></strong></p>
diff --git a/app/Template/budget/remove.php b/app/Template/budget/remove.php
new file mode 100644
index 00000000..97f9c3dc
--- /dev/null
+++ b/app/Template/budget/remove.php
@@ -0,0 +1,13 @@
+<div class="page-header">
+ <h2><?= t('Remove budget line') ?></h2>
+</div>
+
+<div class="confirm">
+ <p class="alert alert-info"><?= t('Do you really want to remove this budget line?') ?></p>
+
+ <div class="form-actions">
+ <?= $this->a(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?>
+ <?= t('or') ?>
+ <?= $this->a(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?>
+ </div>
+</div> \ No newline at end of file
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php
index f4809fde..4afc8ba9 100644
--- a/app/Template/project/sidebar.php
+++ b/app/Template/project/sidebar.php
@@ -33,7 +33,10 @@
<?= $this->a(t('Automatic actions'), 'action', 'index', array('project_id' => $project['id'])) ?>
</li>
<li>
- <?= $this->a(t('Duplicate'), 'project', 'duplicate', array('project_id' => $project['id']), true) ?>
+ <?= $this->a(t('Duplicate'), 'project', 'duplicate', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <?= $this->a(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?>
</li>
<li>
<?php if ($project['is_active']): ?>