user profile, editing and more statistics
This commit is contained in:
86
src/TimesheetBundle/Model/Statistic/Month.php
Normal file
86
src/TimesheetBundle/Model/Statistic/Month.php
Normal 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;
|
||||
}
|
||||
}
|
||||
78
src/TimesheetBundle/Model/Statistic/Year.php
Normal file
78
src/TimesheetBundle/Model/Statistic/Year.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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>‐</td>
|
||||
<td>‐</td>
|
||||
<td>‐</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 %}
|
||||
|
||||
@@ -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>‐</td>
|
||||
<td>‐</td>
|
||||
<td>‐</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 %}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user