user profile, editing and more statistics

This commit is contained in:
Kevin Papst
2016-11-09 21:58:00 +01:00
parent 1678129fe9
commit 8401797728
42 changed files with 1441 additions and 195 deletions

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model\Statistic;
/**
* Yearly statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class Month
{
/**
* @var string
*/
protected $month;
/**
* @var int
*/
protected $totalDuration = 0;
/**
* @var int
*/
protected $totalRate = 0;
/**
* Month constructor.
* @param string $month
*/
public function __construct($month)
{
$this->month = $month;
}
/**
* @return string
*/
public function getMonth()
{
return $this->month;
}
/**
* @return int
*/
public function getTotalDuration()
{
return $this->totalDuration;
}
/**
* @param $totalDuration
* @return $this
*/
public function setTotalDuration($totalDuration)
{
$this->totalDuration = $totalDuration;
return $this;
}
/**
* @return int
*/
public function getTotalRate()
{
return $this->totalRate;
}
/**
* @param $totalRate
* @return $this
*/
public function setTotalRate($totalRate)
{
$this->totalRate = $totalRate;
return $this;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model\Statistic;
/**
* Yearly statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class Year
{
/**
* @var string
*/
protected $year;
/**
* @var Month[]
*/
protected $months;
/**
* Year constructor.
* @param string $year
*/
public function __construct($year)
{
$this->year = $year;
}
/**
* @return string
*/
public function getYear()
{
return $this->year;
}
/**
* @param Month $month
* @return $this
*/
public function setMonth(Month $month)
{
$this->months[(int)$month->getMonth()] = $month;
return $this;
}
/**
* @param $month
* @return null|Month
*/
public function getMonth($month)
{
if (isset($this->months[$month])) {
return $this->months[$month];
}
return null;
}
/**
* @return Month[]
*/
public function getMonths()
{
return array_values($this->months);
}
}

View File

@@ -11,6 +11,8 @@
namespace TimesheetBundle\Model;
use \DateTime;
/**
* Timesheet statistics
*
@@ -34,6 +36,10 @@ class TimesheetStatistic
* @var int
*/
protected $amountTotal = 0;
/**
* @var \DateTime
*/
protected $firstEntry;
/**
* @return int
@@ -98,4 +104,20 @@ class TimesheetStatistic
{
$this->amountThisMonth = $amountThisMonth;
}
/**
* @return DateTime
*/
public function getFirstEntry()
{
return $this->firstEntry;
}
/**
* @param DateTime $firstEntry
*/
public function setFirstEntry(DateTime $firstEntry)
{
$this->firstEntry = $firstEntry;
}
}

View File

@@ -18,6 +18,8 @@ use Doctrine\ORM\Query;
use Doctrine\DBAL\Types\Type;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use TimesheetBundle\Model\Statistic\Month;
use TimesheetBundle\Model\Statistic\Year;
use TimesheetBundle\Model\TimesheetGlobalStatistic;
use TimesheetBundle\Model\TimesheetStatistic;
use DateTime;
@@ -30,13 +32,29 @@ use DateTime;
class TimesheetRepository extends EntityRepository
{
/**
* @param $select
* @param User|null $user
* @return \Doctrine\ORM\QueryBuilder
*/
protected function queryThisMonth($select, User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
// FIXME
$end = new DateTime();
$begin = $end->sub(new \DateInterval("P30D"));
$begin = $end->sub(new \DateInterval("P30D")); // FIXME last month is not last 30 days
return $this->queryTimeRange($select, $begin, $end, $user);
}
/**
* @param $select
* @param DateTime $begin
* @param DateTime $end
* @param User|null $user
* @return \Doctrine\ORM\QueryBuilder
*/
protected function queryTimeRange($select, DateTime $begin, DateTime $end, User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($select)
->from('TimesheetBundle:Timesheet', 't')
@@ -52,6 +70,7 @@ class TimesheetRepository extends EntityRepository
return $qb;
}
/**
* Fetch statistic data for one user.
*
@@ -74,16 +93,67 @@ class TimesheetRepository extends EntityRepository
$durationMonth = $this->queryThisMonth('SUM(t.duration)', $user)
->getQuery()
->getSingleScalarResult();
$firstEntry = $this->getEntityManager()
->createQuery('SELECT MIN(t.begin) FROM TimesheetBundle:Timesheet t WHERE t.user = :user')
->setParameter('user', $user)
->getSingleScalarResult();
$stats = new TimesheetStatistic();
$stats->setAmountTotal($rateTotal);
$stats->setDurationTotal($durationTotal);
$stats->setAmountThisMonth($amountMonth);
$stats->setDurationThisMonth($durationMonth);
$stats->setFirstEntry(new DateTime($firstEntry));
return $stats;
}
/**
* Returns an array of Year statistics.
*
* @param User|null $user
* @return Year[]
*/
public function getMonthlyStats(User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('SUM(t.rate) as totalRate, SUM(t.duration) as totalDuration, MONTH(t.begin) as month, YEAR(t.begin) as year')
->from('TimesheetBundle:Timesheet', 't')
->where($qb->expr()->gt('t.begin', '0'))
->andWhere($qb->expr()->isNotNull('t.end'))
->orderBy('year', 'DESC')
->addOrderBy('month', 'ASC')
->groupBy('year')
->addGroupBy('month');
if (null !== $user) {
$qb->where('t.user = :user')
->setParameter('user', $user);
}
$years = [];
foreach($qb->getQuery()->execute() as $statRow) {
$curYear = $statRow['year'];
if (!isset($years[$curYear])) {
$year = new Year($curYear);
for ($i = 1; $i < 13; $i++) {
$month = $i < 10 ? '0' . $i : (string)$i;
$year->setMonth(new Month($month));
}
$years[$curYear] = $year;
}
$month = new Month($statRow['month']);
$month->setTotalDuration($statRow['totalDuration'])
->setTotalRate($statRow['totalRate']);
$years[$curYear]->setMonth($month);
}
return $years;
}
/**
* Fetch statistic data for all user.
*
@@ -133,8 +203,8 @@ class TimesheetRepository extends EntityRepository
$qb->select('t')
->from('TimesheetBundle:Timesheet', 't')
->where('t.begin > 0')
->andWhere('t.end is NULL');
->where($qb->expr()->gt('t.begin', '0'))
->andWhere($qb->expr()->isNull('t.end'));
$params = [];

View File

@@ -13,6 +13,7 @@
'label.project': 'book',
'label.comment': 'comment-o',
'label.visible': 'eye',
'label.actions': 'pencil-square-o',
}) }}
{% for entry in entries %}
@@ -22,6 +23,9 @@
<td>{{ entry.project }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.label_visible(entry.visible) }}</td>
<td>
{{ widgets.button_group({'edit': '#', 'trash': '#'}) }}
</td>
</tr>
{% endfor %}

View File

@@ -14,6 +14,7 @@
'label.activity': 'tasks',
'label.budget': 'credit-card',
'label.visible': 'eye',
'label.actions': 'pencil-square-o',
}) }}
{% for entry in entries %}
@@ -24,6 +25,9 @@
<td>{{ widgets.badge_counter(entry.activities.count) }}</td>
<td>{{ entry.budget|money}}</td>
<td>{{ widgets.label_visible(entry.visible) }}</td>
<td>
{{ widgets.button_group({'edit': '#', 'trash': '#'}) }}
</td>
</tr>
{% endfor %}

View File

@@ -1,32 +1,45 @@
{% extends 'base.html.twig' %}
{% import "macros/datatables.html.twig" as widgets %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as datatables %}
{% block page_title %}{{ 'admin_timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_timesheet.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
{{ widgets.data_table_header({
{{ datatables.data_table_header({
'label.date': 'calendar',
'label.starttime': 'hourglass-start',
'label.endtime': 'hourglass-end',
'label.duration': 'clock-o',
'label.rate': 'money',
'label.username': 'user',
'label.description': 'pencil-square-o',
'label.description': 'comment-o',
'label.actions': 'pencil-square-o',
}) }}
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date(kimai_context.date_1) }}</td>
<td>{{ entry.begin|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.user.username }}</td>
{% if entry.end %}
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.rate|money }}</td>
{% else %}
<td>&dash;</td>
<td>&dash;</td>
<td>&dash;</td>
{% endif %}
<td><a href="{{ path('profile'|route_alias, {'username' : entry.user.username}) }}">{{ entry.user.username }}</a></td>
<td>{{ entry.description }}</td>
<td>
{{ widgets.button_group({'edit': '#', 'trash': '#'}) }}
</td>
</tr>
{% endfor %}
{{ widgets.data_table_footer(entries, 'admin_timesheet_paginated') }}
{{ datatables.data_table_footer(entries, 'admin_timesheet_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}

View File

@@ -1,30 +1,43 @@
{% extends 'base.html.twig' %}
{% import "macros/datatables.html.twig" as widgets %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as datatables %}
{% block page_title %}{{ 'timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'timesheet.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
{{ widgets.data_table_header({
{{ datatables.data_table_header({
'label.date': 'calendar',
'label.starttime': 'hourglass-start',
'label.endtime': 'hourglass-end',
'label.duration': 'clock-o',
'label.description': 'pencil-square-o',
'label.rate': 'money',
'label.description': 'comment-o',
'label.actions': 'pencil-square-o',
}) }}
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date(kimai_context.date_1) }}</td>
<td>{{ entry.begin|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
{% if entry.end %}
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.rate|money }}</td>
{% else %}
<td>&dash;</td>
<td>&dash;</td>
<td>&dash;</td>
{% endif %}
<td>{{ entry.description }}</td>
<td>
{{ widgets.button_group({'repeat': '#', 'edit': '#', 'trash': '#'}) }}
</td>
</tr>
{% endfor %}
{{ widgets.data_table_footer(entries, 'timesheet_paginated') }}
{{ datatables.data_table_footer(entries, 'timesheet_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}

View File

@@ -12,7 +12,6 @@
namespace TimesheetBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use TimesheetBundle\EventListener\Menu as MenuListener;
/**
* This class defines the Bundle for all Timesheet related topics
@@ -27,8 +26,5 @@ class TimesheetBundle extends Bundle
*/
public function boot()
{
$listener = new MenuListener();
/* @var $dispatcher \Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher */
$dispatcher = $this->container->get('event_dispatcher');
}
}