Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added custom field docu and examples #29

Merged
merged 6 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _data/menu-documentation.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
- title: Administration
pages: [installation, updates, docker, backups]
- title: User manual
pages: [timesheet, invoices, export, users, customer, project, activity, tags]
pages: [timesheet, invoices, export, users, customer, project, activity, tags, meta-fields]
- title: Configuration
pages: [configurations, user-preferences, calendar, dashboard, emails, permissions, ldap]
- title: Developer
Expand Down
38 changes: 18 additions & 20 deletions _documentation/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ To rebuild all assets you have to execute:
yarn run prod
```

You can find more information at:

- https://symfony.com/doc/current/frontend/encore/installation.html
- https://symfony.com/doc/current/frontend.html
You can find more information [here](https://symfony.com/doc/current/frontend/encore/installation.html) and [here](https://symfony.com/doc/current/frontend.html).

## local.yaml

Expand All @@ -69,30 +66,26 @@ composer kimai:tests-unit
composer kimai:tests-integration
```

Or you simply run all tests with:
```bash
composer kimai:tests
vendor/bin/phpunit
```
Or you simply run all tests with one of:
- `composer kimai:tests`
- `vendor/bin/phpunit`

## Static code analysis via PHPStan

Besides automated tests Kimai relies on PHPStan to detect code problems.

You can run the checks before CI process kicks in via:

```bash
composer kimai:phpstan
```
## Coding styles

You can run the code formatter with the built-in command like that:
You can run the code sniffer with the built-in command like that:

```bash
composer kimai:codestyle
```

You can also automatically fix the violations by running:
And you can also automatically fix the violations by running:

```bash
composer kimai:codestyle-fix
Expand Down Expand Up @@ -216,14 +209,14 @@ In the config `kimai.invoice.documents`, you can add a list of directories with
### Adding invoice calculator

An invoice calculator is a class implementing `App\Invoice\CalculatorInterface` and it is responsible for calculating
invoice rates, taxes and taking care of all timesheet entries that should be displayed.
invoice rates, taxes and taking care to aggregate all timesheet entries that should be displayed.

Every invoice calculator class will be automatically available, after refreshing the application cache with `bin/console cache:clear`.
This "magic" happens in the [InvoiceServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/InvoiceServiceCompilerPass.php),
which finds the classes by the interface `CalculatorInterface`.

The ID of the calculator must be unique, please prefix it with your vendor or bundle name and make sure it only contains
character as it will be stored in a database column.
alpha-numeric characters, as it will be stored in a database column.

Translations are stored in the `invoice-calculator.xx.xliff`.

Expand All @@ -237,7 +230,7 @@ This "magic" happens in the [InvoiceServiceCompilerPass]({{ site.kimai_v2_file }
which finds the classes by the interface `NumberGeneratorInterface`.

The ID of the number generator must be unique, please prefix it with your vendor or bundle name and make sure it only contains
character as it will be stored in a database column.
alpha-numeric characters, as it will be stored in a database column.

Translations are stored in the `invoice-numbergenerator.xx.xliff`.

Expand All @@ -252,10 +245,7 @@ which finds the classes by the interface `RendererInterface`.

## Adding export renderer

An export renderer is a class implementing `App\Export\RendererInterface` and it is responsible to convert ar array of `Timesheet` objects
into a downloadable/printable document.

Every export renderer class will be automatically available when refreshing the application cache by the [ExportServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php):
See [export]({% link _documentation/export.md %}) documentation.

## Adding timesheet calculator

Expand All @@ -271,3 +261,11 @@ You can apply several rules in your config file [local.yaml]({% link _documentat
The configuration for "rounding rules" can be fetched from the container parameter `kimai.timesheet.rounding`.

The configuration for "hourly-rates multiplication factors" can be fetched from the container parameter `kimai.timesheet.rates`.

## Adding custom fields (meta fields)

See [meta fields]({% link _documentation/meta-fields.md %}) documentation.

## Adding UserPreference

See [user preferences]({% link _documentation/user-preferences.md %}) documentation.
52 changes: 52 additions & 0 deletions _documentation/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Export
description: Export your timesheet data with Kimai into several different formats
since_version: 0.8
toc: true
---

The export module allows you to export filtered timesheet data into several formats.
Expand All @@ -26,3 +27,54 @@ So all customer, projects, activities, all hourly rates, the personal time worke

Exported records cannot be edited or deleted any longer.
For further information read the [timesheet documentation]({% link _documentation/timesheet.md %}).

## Adding export renderer

An export renderer is a class implementing `App\Export\RendererInterface` and it is responsible to convert an array of
`Timesheet` objects into a downloadable/printable document.

Every export renderer class will be automatically available when refreshing the application cache, thanks to the
[ExportServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php).

Each renderer is represented by a "button" below the datatable on the export screen.

A simple example, which only shows the IDs of the included timesheet records could look like this:

```php
use App\Entity\Timesheet;
use App\Export\RendererInterface;
use App\Repository\Query\TimesheetQuery;
use Symfony\Component\HttpFoundation\Response;

final class TimesheetIdRenderer implements RendererInterface
{
public function render(array $timesheets, TimesheetQuery $query): Response
{
$ids = array_map(function(Timesheet $timesheet) {
return $timesheet->getId();
}, $timesheets);

$response = new Response();
$response->setContent(sprintf('Included IDs: %s', implode(', ', $ids)));

return $response;
}

public function getId(): string
{
return 'ext_array_dump';
}

public function getIcon(): string
{
return 'fas fa-file-code';
}

public function getTitle(): string
{
return 'Show IDs';
}
}
```

All you need to do is to register it as a service in the Symfony DI container.
12 changes: 12 additions & 0 deletions _documentation/invoices.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ If a customer was selected the following values exist as well:
| ${customer.country} | The customer country |
| ${customer.homepage} | The customer homepage |
| ${customer.comment} | The customer comment |
| ${customer.meta.X} | The customer [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) |

If a project was selected the following values exist as well:

Expand All @@ -156,6 +157,16 @@ If a project was selected the following values exist as well:
| ${project.name} | The project name |
| ${project.comment} | The project name |
| ${project.order_number} | The project order number |
| ${project.meta.X} | The project [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) |

If an activity was selected the following values exist as well:

| Key | Description |
|---|---|
| ${activity.id} | The activity ID |
| ${activity.name} | The activity name |
| ${activity.comment} | The activity name |
| ${activity.meta.X} | The activity [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) |

### Timesheet entry variables

Expand Down Expand Up @@ -187,6 +198,7 @@ For each timesheet entry you can use the variables from the following table.
| ${entry.project_id} | Project ID | 10 |
| ${entry.customer} | Customer name | Acme Studios |
| ${entry.customer_id} | Customer ID | 3 |
| ${entry.meta.X} | The [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) |

## Configure search path

Expand Down
116 changes: 116 additions & 0 deletions _documentation/meta-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: Custom fields
description: Use your own custom/meta fields
toc: true
since_version: 1.0
---

Kimai supports custom fields for the following object types:

- `User` via `UserPreference` (see [user preferences]({% link _documentation/user-preferences.md %}))
- `Timesheet` via `TimesheetMeta`
- `Customer` via `CustomerMeta`
- `Project` via `ProjectMeta`
- `Activity` via `ActivityMeta`

## Custom fields

Using the fields for internal reasons (eg. importing and linking to IDs of external apps) is simple.
You can add these fields programmatically at any time:
```php
$externalId = (new TimesheetMeta())->setName('externalID')->setValue(1);
$timesheet = new Timesheet();
$timesheet->setMetaField($externalId);
```

But what if you want the field to be editable by users?

Well, this is possible through the registration via an EventSubscriber, where you add your custom fields.

## Add editable custom fields

The following example adds a custom fields to each entity types "edit" and "create" forms:
- `Timesheet` via `TimesheetMeta`
- `Customer` via `CustomerMeta`
- `Project` via `ProjectMeta`
- `Activity` via `ActivityMeta`

This example might seem a little awkward first, as I wanted to add only one example for all
possible entity types and that definitely doesn't make the code prettier ;-)
But I hope you get the point and see in `prepareEntity` what needs to be done to setup new
custom fields, which can be edited by the user.

```php
use App\Entity\ActivityMeta;
use App\Entity\CustomerMeta;
use App\Entity\EntityWithMetaFields;
use App\Entity\MetaTableTypeInterface;
use App\Entity\ProjectMeta;
use App\Entity\TimesheetMeta;
use App\Event\ActivityMetaDefinitionEvent;
use App\Event\CustomerMetaDefinitionEvent;
use App\Event\ProjectMetaDefinitionEvent;
use App\Event\TimesheetMetaDefinitionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Length;

class MetaFieldSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
TimesheetMetaDefinitionEvent::class => ['loadTimesheetMeta', 200],
CustomerMetaDefinitionEvent::class => ['loadCustomerMeta', 200],
ProjectMetaDefinitionEvent::class => ['loadProjectMeta', 200],
ActivityMetaDefinitionEvent::class => ['loadActivityMeta', 200],
];
}

public function loadTimesheetMeta(TimesheetMetaDefinitionEvent $event)
{
$this->prepareEntity($event->getEntity(), new TimesheetMeta());
}

public function loadCustomerMeta(CustomerMetaDefinitionEvent $event)
{
$this->prepareEntity($event->getEntity(), new CustomerMeta());
}

public function loadProjectMeta(ProjectMetaDefinitionEvent $event)
{
$this->prepareEntity($event->getEntity(), new ProjectMeta());
}

public function loadActivityMeta(ActivityMetaDefinitionEvent $event)
{
$this->prepareEntity($event->getEntity(), new ActivityMeta());
}

private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $definition)
{
$definition
->setName('Location')
->setType(TextType::class)
->addConstraint(new Length(['max' => 255]))
->setIsVisible(true);

$entity->setMetaField($definition);
}
}
```

## Visibility

Each meta field has its own visibility, which determines whether the field will be exposed
in the following places:

- Export
- Invoice
- API

The default visibility is `false` (hidden). If you want to use the meta fields value
in your invoices, then you have to set its visibility to true (see EventSubscriber example above).

Be aware: the visibility is stored with the meta field, so changing its value via the EventSubscriber
does NOT change the visibility of already saved meta fields, just for new ones.
4 changes: 2 additions & 2 deletions _documentation/timesheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,5 @@ kimai:

Exported records will be locked to prevent manipulation of cleared data.

There is the permission `edit_exported_timesheet` which allows to edit and delete these locked entries nevertheless,
which by default is given to users with `ROLE_ADMIN` and `ROLE_SUPER_ADMIN`.
The [permission]({% link _documentation/permissions.md %}) `edit_exported_timesheet` does allow to edit and delete these
locked entries nevertheless, which by default is given to users with `ROLE_ADMIN` and `ROLE_SUPER_ADMIN`.
38 changes: 38 additions & 0 deletions _documentation/user-preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,41 @@ on mobile devices will be replaced by a link to the calendar.
## Show daily statistics in timesheet

If activated, the personal timesheet visually groups and shows statistics for all records within one day.

## Adding new UserPreference

Developers can register new user preferences from within [their plugin]({% link _documentation/plugins.md %}) as easy as that:

```php
use App\Entity\UserPreference;
use App\Event\UserPreferenceEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;

class UserProfileSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UserPreferenceEvent::CONFIGURE => ['loadUserPreferences', 200]
];
}

public function loadUserPreferences(UserPreferenceEvent $event)
{
if (null === ($user = $event->getUser())) {
return;
}

// You attach every field to the event and all the heavy lifting is done by Kimai.
// The value is the default as long as the user has not yet updated his preferences,
// otherwise it will be overwritten with the users choice, stored in the database.
$event->addUserPreference(
(new UserPreference())
->setName('fooooo-bar')
->setValue(false)
->setType(CheckboxType::class)
);
}
}
```