Skip to content

Commit

Permalink
Preparations for coming features (kimai#1818)
Browse files Browse the repository at this point in the history
* removed unused convert_tz code
* added timestampable listener to support automatic updates of change columns
* added modified_at, billable and category timesheet fields
* new timesheet API query parameter modified_after
  • Loading branch information
kevinpapst authored and Sergii committed Sep 29, 2020
1 parent 3a70798 commit 5edb097
Show file tree
Hide file tree
Showing 18 changed files with 381 additions and 147 deletions.
1 change: 0 additions & 1 deletion config/packages/doctrine/mysql.yaml
Expand Up @@ -3,7 +3,6 @@ doctrine:
dql:
datetime_functions:
addtime: DoctrineExtensions\Query\Mysql\AddTime
convert_tz: DoctrineExtensions\Query\Mysql\ConvertTz
date: DoctrineExtensions\Query\Mysql\Date
date_format: DoctrineExtensions\Query\Mysql\DateFormat
dateadd: DoctrineExtensions\Query\Mysql\DateAdd
Expand Down
1 change: 0 additions & 1 deletion config/packages/doctrine/sqlite.yaml
Expand Up @@ -2,7 +2,6 @@ doctrine:
orm:
dql:
datetime_functions:
convert_tz: App\Doctrine\Query\Sqlite\ConvertTz
date: DoctrineExtensions\Query\Sqlite\Date
date_format: DoctrineExtensions\Query\Sqlite\DateFormat
#dateadd: DoctrineExtensions\Query\Sqlite\DateAdd
Expand Down
8 changes: 8 additions & 0 deletions config/services.yaml
Expand Up @@ -101,6 +101,14 @@ services:
tags:
- { name: doctrine.event_subscriber, priority: 50 }

# updates timestampable columns (higher priority, so the TimesheetSubscriber will be executed later)
Gedmo\Timestampable\TimestampableListener:
class: Gedmo\Timestampable\TimestampableListener
tags:
- { name: doctrine.event_subscriber, priority: 60 }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]

# make sure, that sqlite supports foreign keys and cascade deletes
App\Doctrine\SqliteSessionInitSubscriber:
class: App\Doctrine\SqliteSessionInitSubscriber
Expand Down
5 changes: 5 additions & 0 deletions src/API/TimesheetController.php
Expand Up @@ -145,6 +145,7 @@ protected function getTrackingMode(): TrackingModeInterface
* @Rest\QueryParam(name="active", requirements="0|1", strict=true, nullable=true, description="Filter for running/active records. Allowed values: 0=stopped, 1=active (default: all)")
* @Rest\QueryParam(name="full", requirements="true", strict=true, nullable=true, description="Allows to fetch fully serialized objects including subresources. Allowed values: true (default: false)")
* @Rest\QueryParam(name="term", description="Free search term")
* @Rest\QueryParam(name="modified_after", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only records changed after this date will be included (format: HTML5). Available since Kimai 1.10 and works only for records that were created/updated since then.")
*
* @Security("is_granted('view_own_timesheet') or is_granted('view_other_timesheet')")
*
Expand Down Expand Up @@ -255,6 +256,10 @@ public function cgetAction(ParamFetcherInterface $paramFetcher): Response
$query->setSearchTerm(new SearchTerm($term));
}

if (!empty($modifiedAfter = $paramFetcher->get('modified_after'))) {
$query->setModifiedAfter($this->dateTime->createDateTime($modifiedAfter));
}

/** @var Pagerfanta $data */
$data = $this->repository->getPagerfantaForQuery($query);
$data = (array) $data->getCurrentPageResults();
Expand Down
57 changes: 0 additions & 57 deletions src/Doctrine/Query/Sqlite/ConvertTz.php

This file was deleted.

8 changes: 7 additions & 1 deletion src/Entity/Invoice.php
Expand Up @@ -102,6 +102,7 @@ class Invoice
* @var string
*
* @ORM\Column(name="currency", type="string", length=3, nullable=false)
* @Assert\NotNull()
* @Assert\Length(max=3)
*/
private $currency;
Expand All @@ -110,6 +111,7 @@ class Invoice
* @var int
*
* @ORM\Column(name="due_days", type="integer", length=3, nullable=false)
* @Assert\NotNull()
* @Assert\Range(min = 0, max = 999)
*/
private $dueDays = 30;
Expand All @@ -118,6 +120,7 @@ class Invoice
* @var float
*
* @ORM\Column(name="vat", type="float", nullable=false)
* @Assert\NotNull()
* @Assert\Range(min = 0.0, max = 99.99)
*/
private $vat = 0.00;
Expand All @@ -126,13 +129,16 @@ class Invoice
* @var string
*
* @ORM\Column(name="status", type="string", length=20, nullable=false)
* @Assert\NotNull()
*/
private $status = self::STATUS_NEW;

/**
* @var string
*
* @ORM\Column(name="invoice_filename", type="string", length=100, nullable=false)
* @ORM\Column(name="invoice_filename", type="string", length=150, nullable=false)
* @Assert\NotNull()
* @Assert\Length(min=1, max=150)
*/
private $invoiceFilename;

Expand Down
97 changes: 84 additions & 13 deletions src/Entity/Timesheet.php
Expand Up @@ -15,6 +15,7 @@
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;

/**
Expand All @@ -31,18 +32,29 @@
* @ORM\Entity(repositoryClass="App\Repository\TimesheetRepository")
* @ORM\HasLifecycleCallbacks()
* @App\Validator\Constraints\Timesheet
*
* columns={"user"} => IDX_4F60C6B18D93D649 => count results for user timesheets
* columns={"activity_id"} => IDX_4F60C6B181C06096 => ???
* columns={"user","start_time"} => IDX_4F60C6B18D93D649502DF587 => recent activities, user timesheet with date filzer
* columns={"start_time"} => IDX_4F60C6B1502DF587 => team timesheets with timerange filter only
* columns={"start_time","end_time"} => IDX_4F60C6B1502DF58741561401 => ???
* columns={"start_time","end_time","user"} => IDX_4F60C6B1502DF587415614018D93D649 => ???
*/
class Timesheet implements EntityWithMetaFields, ExportItemInterface
{
public const TYPE_TIMESHEET = 'timesheet';
public const CATEGORY_WORK = 'work';
/**
* Category: Normal work-time (default category)
*/
public const WORK = 'work';
/**
* Category: Holiday
*/
public const HOLIDAY = 'holiday';
/**
* Category: Sickness
*/
public const SICKNESS = 'sickness';
/**
* Category: Parental leave
*/
public const PARENTAL = 'parental';
/**
* Category: Overtime reduction
*/
public const OVERTIME = 'overtime';

/**
* @var int|null
Expand Down Expand Up @@ -161,6 +173,30 @@ class Timesheet implements EntityWithMetaFields, ExportItemInterface
*/
private $exported = false;

/**
* @var bool
*
* @ORM\Column(name="billable", type="boolean", nullable=false, options={"default": true})
* @Assert\NotNull()
*/
private $billable = true;

/**
* @var string
*
* @ORM\Column(name="category", type="string", length=10, nullable=false, options={"default": "work"})
* @Assert\NotNull()
*/
private $category = self::WORK;

/**
* @var DateTime|null
*
* @Gedmo\Timestampable
* @ORM\Column(name="modified_at", type="datetime", nullable=true)
*/
private $modifiedAt;

/**
* @var Tag[]|ArrayCollection
*
Expand All @@ -185,6 +221,7 @@ class Timesheet implements EntityWithMetaFields, ExportItemInterface
*/
private $meta;


/**
* @var Tasks[]|Collection
*
Expand All @@ -204,6 +241,7 @@ class Timesheet implements EntityWithMetaFields, ExportItemInterface
/**
* Default constructor, initializes collections
*/

public function __construct()
{
$this->tags = new ArrayCollection();
Expand Down Expand Up @@ -493,16 +531,44 @@ public function setTimezone(string $timezone): Timesheet
return $this;
}

/**
* This method returns ALWAYS: "timesheet"
*
* @return string
*/
public function getType(): string
{
// this will be improved in a future version
return self::TYPE_TIMESHEET;
return 'timesheet';
}

public function getCategory(): string
{
// this will be improved in a future version
return self::CATEGORY_WORK;
return $this->category;
}

public function setCategory(string $category): Timesheet
{
$allowed = [self::WORK, self::HOLIDAY, self::SICKNESS, self::PARENTAL, self::OVERTIME];

if (!\in_array($category, $allowed)) {
throw new \InvalidArgumentException(sprintf('Invalid timesheet category "%s" given, expected one of: %s', $category, implode(', ', $allowed)));
}

$this->category = $category;

return $this;
}

public function isBillable(): bool
{
return $this->billable;
}

public function setBillable(bool $billable): Timesheet
{
$this->billable = $billable;

return $this;
}

public function getFixedRate(): ?float
Expand All @@ -529,6 +595,11 @@ public function setHourlyRate(?float $hourlyRate): Timesheet
return $this;
}

public function getModifiedAt(): ?DateTime
{
return $this->modifiedAt;
}

/**
* @return Collection|MetaTableTypeInterface[]
*/
Expand Down
4 changes: 3 additions & 1 deletion src/EventSubscriber/UserEnvironmentSubscriber.php
Expand Up @@ -52,9 +52,11 @@ public function prepareEnvironment(RequestEvent $event)

$user = $this->storage->getToken()->getUser();

// the locale depends on the request, not on the user configuration
\Locale::setDefault($event->getRequest()->getLocale());

if ($user instanceof User) {
date_default_timezone_set($user->getTimezone());
\Locale::setDefault($user->getLocale());
$user->initCanSeeAllData($this->auth->isGranted('view_all_data'));
}
}
Expand Down
63 changes: 63 additions & 0 deletions src/Migrations/Version20200705152310.php
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace DoctrineMigrations;

use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

/**
* @version 1.10
*/
final class Version20200705152310 extends AbstractMigration
{
public function getDescription(): string
{
return 'Updated invoice and added timesheet columns';
}

public function up(Schema $schema): void
{
$invoices = $schema->getTable('kimai2_invoices');
$invoices->getColumn('invoice_filename')->setLength(150);

$timesheet = $schema->getTable('kimai2_timesheet');
$timesheet->addColumn('billable', 'boolean', ['notnull' => false, 'default' => true]);
$timesheet->addColumn('category', 'string', ['length' => 10, 'notnull' => true, 'default' => 'work']);
$timesheet->addColumn('modified_at', 'datetime', ['notnull' => false]);
}

public function down(Schema $schema): void
{
$invoices = $schema->getTable('kimai2_invoices');
$invoices->getColumn('invoice_filename')->setLength(100);

$timesheet = $schema->getTable('kimai2_timesheet');
$timesheet->dropColumn('billable');
$timesheet->dropColumn('category');
$timesheet->dropColumn('modified_at');
}

protected function isSupportingForeignKeys(): bool
{
return false;
}

public function isTransactional(): bool
{
if ($this->isPlatformSqlite()) {
// does fail if we use transactions, as tables are re-created and foreign keys would fail
return false;
}

return true;
}
}
6 changes: 6 additions & 0 deletions src/Repository/Query/InvoiceQuery.php
Expand Up @@ -22,6 +22,12 @@ class InvoiceQuery extends TimesheetQuery
*/
private $markAsExported = false;

public function __construct()
{
parent::__construct();
$this->setBillable(InvoiceQuery::STATE_BILLABLE);
}

public function getTemplate(): ?InvoiceTemplate
{
return $this->template;
Expand Down

0 comments on commit 5edb097

Please sign in to comment.