release 0.8 (#567)
This commit is contained in:
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
||||
## Description
|
||||
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
|
||||
## Possible Fix
|
||||
|
||||
|
||||
## Steps to Reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Your Environment
|
||||
* Version:
|
||||
* Browser Name and version:
|
||||
* Operating System and version (desktop or mobile):
|
||||
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
about: Create a report to help us improve Kimai
|
||||
|
||||
---
|
||||
|
||||
@@ -11,25 +11,24 @@ A clear and concise description of what the bug is.
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
3. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
**Logfile**
|
||||
```
|
||||
Add the last lines from your logfile at var/log/prod.log, around the time when the problem happened.
|
||||
```
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
**Desktop/Smartphone**
|
||||
Please complete the following information if this seems to be a frontend issue:
|
||||
- Device: [e.g. iPhone6s]
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
- Kimai version
|
||||
- PHP version
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
about: Suggest an idea to make Kimai better
|
||||
|
||||
---
|
||||
|
||||
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,5 @@
|
||||
## Description
|
||||
|
||||
A clear and concise description of what this pull request changes.
|
||||
|
||||
## Types of changes
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
@@ -7,8 +7,7 @@
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist
|
||||
- [ ] My code follows the code style
|
||||
- [ ] All files have a license header
|
||||
- [ ] All methods have a doc header with type declarations
|
||||
- [ ] I have executed `bin/console kimai:phpcs --fix` to make sure my changes adopt the correct code style
|
||||
- [ ] I have updated the documentation accordingly
|
||||
- [ ] I have added tests to cover my changes
|
||||
- [ ] I agree that this code is used in Kimai and will be published under the [MIT license](https://github.com/kevinpapst/kimai2/blob/master/LICENSE)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
unreleased=true
|
||||
future-release=0.7
|
||||
future-release=0.8
|
||||
exclude-labels=duplicate,question,invalid,wontfix,release
|
||||
enhancement_labels=>enhancement,Enhancement,feature request
|
||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,5 +1,63 @@
|
||||
# Change Log
|
||||
|
||||
## [0.8](https://github.com/kevinpapst/kimai2/tree/0.8) (2019-02-19)
|
||||
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.7...0.8)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Export other users' timesheets using the API [\#562](https://github.com/kevinpapst/kimai2/issues/562)
|
||||
- Some importer bugs/improvements [\#553](https://github.com/kevinpapst/kimai2/issues/553)
|
||||
- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534)
|
||||
- On the last activities dropdown show tha last ten DIFFERENT activities [\#533](https://github.com/kevinpapst/kimai2/issues/533)
|
||||
- Better user expierence on "My Times" [\#526](https://github.com/kevinpapst/kimai2/issues/526)
|
||||
- Highlight current day in datepickers and not only the selected day [\#522](https://github.com/kevinpapst/kimai2/issues/522)
|
||||
- Configuration of first page after login [\#501](https://github.com/kevinpapst/kimai2/issues/501)
|
||||
- Visual Grouping of entry from same day or same week with some stats of that group [\#495](https://github.com/kevinpapst/kimai2/issues/495)
|
||||
- add currency field to invoice template [\#422](https://github.com/kevinpapst/kimai2/issues/422)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Error 404 on 'My times' page 2+ if the time range is changed to something smaller [\#558](https://github.com/kevinpapst/kimai2/issues/558)
|
||||
- Timezone is not working correctly [\#554](https://github.com/kevinpapst/kimai2/issues/554)
|
||||
- New time picker not working on languages without updated translation [\#546](https://github.com/kevinpapst/kimai2/issues/546)
|
||||
- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534)
|
||||
- Kimai2 will not allow me to enter times in the future [\#531](https://github.com/kevinpapst/kimai2/issues/531)
|
||||
- No projects in filter section [\#525](https://github.com/kevinpapst/kimai2/issues/525)
|
||||
- This value should be greater than or equal to zero [\#511](https://github.com/kevinpapst/kimai2/issues/511)
|
||||
- Wrong time after migration kimai v1 data [\#507](https://github.com/kevinpapst/kimai2/issues/507)
|
||||
- SQL-Error for getRecentActivities\(\) caused by GROUP BY [\#488](https://github.com/kevinpapst/kimai2/issues/488)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Feedback for improving installation setup [\#541](https://github.com/kevinpapst/kimai2/issues/541)
|
||||
- template entry.end\_time shows empty field [\#535](https://github.com/kevinpapst/kimai2/issues/535)
|
||||
- Global Projects [\#413](https://github.com/kevinpapst/kimai2/issues/413)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- handle deleted user during import from v1 [\#569](https://github.com/kevinpapst/kimai2/pull/569) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fix pagination in combination with daterange picker [\#568](https://github.com/kevinpapst/kimai2/pull/568) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- allow to query other users timesheets via api [\#563](https://github.com/kevinpapst/kimai2/pull/563) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Updated HTML invoice templates [\#560](https://github.com/kevinpapst/kimai2/pull/560) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- updated composer packages [\#559](https://github.com/kevinpapst/kimai2/pull/559) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fix timezone problems in timesheet forms [\#555](https://github.com/kevinpapst/kimai2/pull/555) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Daily stats in timesheet [\#552](https://github.com/kevinpapst/kimai2/pull/552) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Added more php requirements to documentation [\#551](https://github.com/kevinpapst/kimai2/pull/551) ([infeeeee](https://github.com/infeeeee))
|
||||
- improve recent activities [\#550](https://github.com/kevinpapst/kimai2/pull/550) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- scss fixes: year selector width, dropdown menu width, navbar refactoring [\#549](https://github.com/kevinpapst/kimai2/pull/549) ([infeeeee](https://github.com/infeeeee))
|
||||
- improved installation docs [\#548](https://github.com/kevinpapst/kimai2/pull/548) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fix daterange-picker for fr, hu and ar [\#547](https://github.com/kevinpapst/kimai2/pull/547) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- do not limit users timesheet export page size [\#545](https://github.com/kevinpapst/kimai2/pull/545) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Use Symfony formatter for currency symbol placement [\#542](https://github.com/kevinpapst/kimai2/pull/542) ([sanjitlpatel](https://github.com/sanjitlpatel))
|
||||
- Localized date-inputs and daterange-picker [\#540](https://github.com/kevinpapst/kimai2/pull/540) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- added export module [\#538](https://github.com/kevinpapst/kimai2/pull/538) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- updated documentation [\#536](https://github.com/kevinpapst/kimai2/pull/536) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- support remote data in beta-test selectpicker [\#529](https://github.com/kevinpapst/kimai2/pull/529) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Improve allowed running records [\#528](https://github.com/kevinpapst/kimai2/pull/528) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Improve daterangepicker [\#527](https://github.com/kevinpapst/kimai2/pull/527) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- added configurable view after login [\#523](https://github.com/kevinpapst/kimai2/pull/523) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- convert timesheets to UTC with support for user timezone [\#372](https://github.com/kevinpapst/kimai2/pull/372) ([kevinpapst](https://github.com/kevinpapst))
|
||||
|
||||
## [0.7](https://github.com/kevinpapst/kimai2/tree/0.7) (2019-01-28)
|
||||
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6.1...0.7)
|
||||
|
||||
@@ -18,11 +76,10 @@
|
||||
- Configuration option to disable fixed rate and hourly rate from "edit timesheet" [\#330](https://github.com/kevinpapst/kimai2/issues/330)
|
||||
- Set other users hourly rate [\#303](https://github.com/kevinpapst/kimai2/issues/303)
|
||||
- Feature request - Make "Rate" hideable [\#217](https://github.com/kevinpapst/kimai2/issues/217)
|
||||
- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- This value should be greater than or equal to zero [\#511](https://github.com/kevinpapst/kimai2/issues/511)
|
||||
- Timesheet Export for Admins [\#503](https://github.com/kevinpapst/kimai2/issues/503)
|
||||
- admin activity: visibility "none" \(no filter\) causes sql-error [\#491](https://github.com/kevinpapst/kimai2/issues/491)
|
||||
- login-screen optimizations [\#483](https://github.com/kevinpapst/kimai2/issues/483)
|
||||
@@ -51,7 +108,7 @@
|
||||
|
||||
- pagination without reload while keeping filters applied [\#521](https://github.com/kevinpapst/kimai2/pull/521) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- go back to calendar after editing and creation of time-records [\#519](https://github.com/kevinpapst/kimai2/pull/519) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fetch toolbar results without page reload [\#518](https://github.com/kevinpapst/kimai2/pull/518) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- fetch toolbar results without page reload [\#518](https://github.com/kevinpapst/kimai2/pull/518) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- Form and theme improvements [\#513](https://github.com/kevinpapst/kimai2/pull/513) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- validation for future and negative times [\#512](https://github.com/kevinpapst/kimai2/pull/512) ([kevinpapst](https://github.com/kevinpapst))
|
||||
- alphabetical order for selectboxes [\#510](https://github.com/kevinpapst/kimai2/pull/510) ([kevinpapst](https://github.com/kevinpapst))
|
||||
|
||||
26
UPGRADING.md
26
UPGRADING.md
@@ -5,26 +5,30 @@ Database upgrades are currently ONLY provided for MySQL/MariaDB and SQLite.
|
||||
If you plan on using e.g. PostgreSQL, please read more about the `bin/console doctrine:migrations:diff` and
|
||||
`bin/console doctrine:migrations:migrate` commands and contact us, so we can integrate them into the official releases.
|
||||
|
||||
A normal upgrade can be executed with these commands:
|
||||
Upgrading to the latest available version can be achieved with these commands:
|
||||
|
||||
```bash
|
||||
git pull origin master
|
||||
git fetch --tags
|
||||
git checkout 0.8
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
sudo -u www-data bin/console cache:clear --env=prod
|
||||
sudo -u www-data bin/console cache:warmup --env=prod
|
||||
bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
Be careful when upgrading multiple versions at once: follow each version specific information!
|
||||
|
||||
There might be version specific tasks that need to be executed before or after these steps, please see below
|
||||
if your updated version is mentioned below.
|
||||
|
||||
## [0.8](https://github.com/kevinpapst/kimai2/releases/tag/0.8) (unreleased)
|
||||
|
||||
There was a change introduced regarding the storage of the date-time objects in timesheet records.
|
||||
Please read this [Pull request](https://github.com/kevinpapst/kimai2/pull/372) carefully before you follow the instructions
|
||||
to convert the timezones in your existing time records with `bin/console kimai:convert-timezone --help`.
|
||||
After you followed the normal update and database migration process (see above), you need to execute a bash command (see below) and check if you want to apply changes to your `local.yaml`.
|
||||
|
||||
If you don't do that, you will end up with wrong times in your database. Be especially careful, when you previously imported data from Kimai v1.
|
||||
- An important change was introduced regarding the storage of date-time objects in the database. Please read this [pull request](https://github.com/kevinpapst/kimai2/pull/372) BEFORE you follow the instructions
|
||||
to convert the timezones in your existing time records with `bin/console kimai:convert-timezone`. Without that, you will end up with wrong times in your database.
|
||||
- A new boolean setting `kimai.timesheet.rules.allow_future_times` was introduced
|
||||
- New [permissions](var/docs/permissions.md) are available: `view_export,create_export,edit_export_own_timesheet,edit_export_other_timesheet,system_information`
|
||||
|
||||
## [0.7](https://github.com/kevinpapst/kimai2/releases/tag/0.7) (2019-01-28)
|
||||
|
||||
@@ -68,16 +72,6 @@ sudo composer self-update
|
||||
sudo -u www-data composer update symfony/flex --no-plugins --no-scripts
|
||||
```
|
||||
|
||||
Then the full update can be executed as usual:
|
||||
|
||||
```bash
|
||||
git pull origin master
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
sudo -u www-data bin/console cache:clear --env=prod
|
||||
sudo -u www-data bin/console cache:warmup --env=prod
|
||||
bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22)
|
||||
|
||||
**Update from 0.2:**
|
||||
|
||||
@@ -62,7 +62,6 @@ $(function() {
|
||||
$('input[data-daterangepickerenable="on"]').each(function(index) {
|
||||
var localeFormat = $(this).data('format');
|
||||
var separator = $(this).data('separator');
|
||||
var transToday = $.kimai.settings['today'];
|
||||
var rangesList = {};
|
||||
rangesList[$.kimai.settings['today']] = [moment(), moment()];
|
||||
rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
|
||||
|
||||
@@ -83,11 +83,11 @@ class ActivityController extends BaseApiController
|
||||
$query->setOrderBy($orderBy);
|
||||
}
|
||||
|
||||
if (null !== ($globals = $paramFetcher->get('globals'))) {
|
||||
if (null !== $paramFetcher->get('globals')) {
|
||||
$query->setGlobalsOnly(true);
|
||||
}
|
||||
|
||||
if ('false' === ($globalsFirst = $paramFetcher->get('globalsFirst'))) {
|
||||
if ('false' === $paramFetcher->get('globalsFirst')) {
|
||||
$query->setOrderGlobalsFirst(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
* You should convert all timesheet records that were saved with Kimai 2 directly, but NOT the ones migrated from Kimai v1.
|
||||
*
|
||||
* Please read https://github.com/kevinpapst/kimai2/pull/372 to find out more!
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class ConvertTimezoneCommand extends Command
|
||||
{
|
||||
|
||||
@@ -35,6 +35,8 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
/**
|
||||
* Command used to import data from a Kimai v1 installation.
|
||||
* Getting help in improving this script would be fantastic, it currently only handles the most basic use-cases.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class KimaiImporterCommand extends Command
|
||||
{
|
||||
@@ -962,7 +964,6 @@ class KimaiImporterCommand extends Command
|
||||
|
||||
// ----------------------- unknown user, damned missing data integrity in Kimai v1 -----------------------
|
||||
if (!isset($this->users[$oldRecord['userID']])) {
|
||||
|
||||
$tempUserName = uniqid();
|
||||
$tempPassword = uniqid() . uniqid();
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '0.7';
|
||||
public const VERSION = '0.8';
|
||||
/**
|
||||
* The release name, will only change for new major version
|
||||
*/
|
||||
|
||||
@@ -74,5 +74,4 @@ class TimezoneSubscriber implements EventSubscriberInterface
|
||||
|
||||
return ($user instanceof User);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Model\Statistic\Month;
|
||||
@@ -364,8 +365,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
->setParameter('activity', $query->getActivity());
|
||||
}
|
||||
|
||||
// TODO if activity is an int, this will fail
|
||||
if (null === $query->getActivity() || null === $query->getActivity()->getProject()) {
|
||||
if (null === $query->getActivity() || ($query->getActivity() instanceof Activity && null === $query->getActivity()->getProject())) {
|
||||
if (null !== $query->getProject()) {
|
||||
$qb->andWhere('t.project = :project')
|
||||
->setParameter('project', $query->getProject());
|
||||
|
||||
@@ -67,7 +67,7 @@ class ActivityControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(count($expected), count($result));
|
||||
for ($i = 0; $i < count($result); $i++) {
|
||||
@@ -93,13 +93,32 @@ class ActivityControllerTest extends APIControllerBaseTest
|
||||
yield ['/api/activities', ['project' => '2', 'visible' => VisibilityQuery::SHOW_HIDDEN], [[false], [true, 2]]];
|
||||
}
|
||||
|
||||
public function testGetCollectionWithQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$this->loadActivityTestData($client);
|
||||
|
||||
$query = ['order' => 'ASC', 'orderBy' => 'project', 'globalsFirst' => 'false'];
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$this->assertAccessIsGranted($client, '/api/activities', 'GET', $query);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(5, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
$this->assertEquals(1, $result[2]['project']);
|
||||
$this->assertEquals(2, $result[3]['project']);
|
||||
$this->assertEquals(2, $result[4]['project']);
|
||||
}
|
||||
|
||||
public function testGetEntity()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$this->assertAccessIsGranted($client, '/api/activities/1');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
|
||||
$expectedKeys = ['id', 'name', 'comment', 'visible'];
|
||||
$actual = array_keys($result);
|
||||
|
||||
@@ -35,7 +35,7 @@ class ApiDocControllerTest extends ControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/doc.json');
|
||||
$this->assertContains('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,20 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/customers');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
}
|
||||
|
||||
public function testGetCollectionWithQuery()
|
||||
{
|
||||
$query = ['order' => 'ASC', 'orderBy' => 'name', 'visible' => 3];
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$this->assertAccessIsGranted($client, '/api/customers', 'GET', $query);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
@@ -40,7 +53,7 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/customers/1');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/ping');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEquals(['message' => 'pong'], $result);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/version');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
|
||||
$this->assertArrayHasKey('version', $result);
|
||||
$this->assertArrayHasKey('candidate', $result);
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/projects');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
@@ -81,7 +81,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEquals(count($expected), count($result), 'Found wrong amount of projects');
|
||||
|
||||
for ($i = 0; $i < count($expected); $i++) {
|
||||
@@ -111,7 +111,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/projects/1');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \App\API\TimesheetController
|
||||
@@ -118,7 +119,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
|
||||
public function testGetCollectionWithQuery()
|
||||
{
|
||||
$query = ['customer' => 1, 'project' => 1, 'page' => 2, 'size' => 5, 'order' => 'DESC', 'orderBy' => 'rate'];
|
||||
$query = ['customer' => 1, 'project' => 1, 'activity' => 1, 'page' => 2, 'size' => 5, 'order' => 'DESC', 'orderBy' => 'rate'];
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
@@ -218,24 +219,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$this->assertApiCallValidationError($client->getResponse(), ['activity']);
|
||||
}
|
||||
|
||||
public function testPostActionWithIdIsNotAllowed()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$data = [
|
||||
'id' => 1,
|
||||
'activity' => 1,
|
||||
'project' => 1,
|
||||
'begin' => (new \DateTime('- 8 hours'))->format('Y-m-d H:m'),
|
||||
'end' => (new \DateTime())->format('Y-m-d H:m'),
|
||||
'description' => 'foo',
|
||||
'fixedRate' => 2016,
|
||||
'hourlyRate' => 127
|
||||
];
|
||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
$this->assertEquals(400, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testNotFound()
|
||||
{
|
||||
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/20');
|
||||
@@ -263,6 +246,38 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$this->assertEquals(1, $result['exported']);
|
||||
}
|
||||
|
||||
public function testPatchActionWithInvalidUser()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture
|
||||
->setFixedRate(true)
|
||||
->setHourlyRate(true)
|
||||
->setAmount(10)
|
||||
->setUser($this->getUserByRole($em, User::ROLE_TEAMLEAD))
|
||||
->setStartDate(new \DateTime('-10 days'))
|
||||
->setAllowEmptyDescriptions(false)
|
||||
;
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$data = [
|
||||
'activity' => 1,
|
||||
'project' => 1,
|
||||
'begin' => (new \DateTime('- 7 hours'))->format('Y-m-d H:m'),
|
||||
'end' => (new \DateTime())->format('Y-m-d H:m'),
|
||||
'description' => 'foo',
|
||||
'exported' => true,
|
||||
];
|
||||
$this->request($client, '/api/timesheets/15', 'PATCH', [], json_encode($data));
|
||||
$response = $client->getResponse();
|
||||
$this->assertFalse($response->isSuccessful());
|
||||
$this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode());
|
||||
$json = json_decode($response->getContent(), true);
|
||||
$this->assertEquals('User cannot update timesheet', $json['message']);
|
||||
}
|
||||
|
||||
public function testInvalidPatchAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
@@ -29,7 +29,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/users');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(6, count($result));
|
||||
$this->assertStructure($result[0], false);
|
||||
@@ -41,7 +41,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/api/users/1');
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertStructure($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class CalendarControllerTest extends ControllerBaseTest
|
||||
$response = $client->getResponse();
|
||||
$this->assertTrue($response->isSuccessful());
|
||||
$json = json_decode($response->getContent(), true);
|
||||
$this->assertInternalType('array', $json);
|
||||
$this->assertIsArray($json);
|
||||
$this->assertEmpty($json);
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ class CalendarControllerTest extends ControllerBaseTest
|
||||
$response = $client->getResponse();
|
||||
$this->assertTrue($response->isSuccessful());
|
||||
$json = json_decode($response->getContent(), true);
|
||||
$this->assertInternalType('array', $json);
|
||||
$this->assertIsArray($json);
|
||||
$this->assertNotEmpty($json);
|
||||
$this->assertEquals(10, count($json));
|
||||
foreach ($json as $result) {
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCalendarStructure($result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class TimesheetFixtures extends Fixture
|
||||
public function setAllowEmptyDescriptions(bool $allowEmptyDescriptions)
|
||||
{
|
||||
$this->allowEmptyDescriptions = $allowEmptyDescriptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ class ServiceInvoiceTest extends TestCase
|
||||
$sut = new ServiceInvoice($repo);
|
||||
|
||||
$this->assertEmpty($sut->getCalculator());
|
||||
$this->assertInternalType('array', $sut->getCalculator());
|
||||
$this->assertIsArray($sut->getCalculator());
|
||||
$this->assertEmpty($sut->getRenderer());
|
||||
$this->assertInternalType('array', $sut->getRenderer());
|
||||
$this->assertIsArray($sut->getRenderer());
|
||||
$this->assertEmpty($sut->getNumberGenerator());
|
||||
$this->assertInternalType('array', $sut->getNumberGenerator());
|
||||
$this->assertIsArray($sut->getNumberGenerator());
|
||||
$this->assertEmpty($sut->getDocuments());
|
||||
$this->assertInternalType('array', $sut->getDocuments());
|
||||
$this->assertIsArray($sut->getDocuments());
|
||||
|
||||
$this->assertNull($sut->getCalculatorByName('default'));
|
||||
$this->assertNull($sut->getDocumentByName('default'));
|
||||
|
||||
@@ -33,7 +33,7 @@ class InvoiceModelTest extends TestCase
|
||||
$this->assertNull($sut->getQuery());
|
||||
$this->assertNull($sut->getDueDate());
|
||||
$this->assertEmpty($sut->getEntries());
|
||||
$this->assertInternalType('array', $sut->getEntries());
|
||||
$this->assertIsArray($sut->getEntries());
|
||||
$this->assertInstanceOf(\DateTime::class, $sut->getInvoiceDate());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class YearTest extends TestCase
|
||||
$sut = new Year('1999');
|
||||
$this->assertNull($sut->getMonth('01'));
|
||||
$this->assertEmpty($sut->getMonths());
|
||||
$this->assertInternalType('array', $sut->getMonths());
|
||||
$this->assertIsArray($sut->getMonths());
|
||||
$this->assertEquals('1999', $sut->getYear());
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class InvoiceDocumentRepositoryTest extends TestCase
|
||||
{
|
||||
$sut = new InvoiceDocumentRepository([]);
|
||||
$this->assertEmpty($sut->findAll());
|
||||
$this->assertInternalType('array', $sut->findAll());
|
||||
$this->assertIsArray($sut->findAll());
|
||||
$this->assertNull($sut->findByName('default'));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
|
||||
|
||||
$query->setResultType(BaseQuery::RESULT_TYPE_OBJECTS);
|
||||
$result = $repository->findByQuery($query);
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
public function testStoppedEntriesCannotBeStoppedAgain()
|
||||
|
||||
@@ -220,7 +220,7 @@ class ExtensionsTest extends TestCase
|
||||
foreach ($icons as $icon) {
|
||||
$result = $sut->icon($icon);
|
||||
$this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon);
|
||||
$this->assertInternalType('string', $result);
|
||||
$this->assertIsString($result);
|
||||
}
|
||||
|
||||
// test fallback will be returned
|
||||
|
||||
@@ -20,6 +20,8 @@ class DurationTest extends TestCase
|
||||
public function testFormat()
|
||||
{
|
||||
$sut = new Duration();
|
||||
|
||||
$this->assertNull($sut->format(null));
|
||||
$this->assertEquals('02:38', $sut->format(9494));
|
||||
$this->assertEquals('02:38:14', $sut->format(9494, Duration::FORMAT_WITH_SECONDS));
|
||||
}
|
||||
@@ -73,7 +75,7 @@ class DurationTest extends TestCase
|
||||
['13', Duration::FORMAT_COLON],
|
||||
['13-13', Duration::FORMAT_COLON],
|
||||
['13.13', Duration::FORMAT_COLON],
|
||||
[1111, 1111, Duration::FORMAT_NATURAL],
|
||||
[1111, Duration::FORMAT_NATURAL],
|
||||
|
||||
// invalid modes
|
||||
[17, 'foo'],
|
||||
|
||||
@@ -142,6 +142,7 @@ class LocaleSettingsTest extends TestCase
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedExceptionMessage Unknown locale given: xx
|
||||
*/
|
||||
public function testInvalidLocaleWithGivenLocale()
|
||||
{
|
||||
@@ -197,4 +198,16 @@ class LocaleSettingsTest extends TestCase
|
||||
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
|
||||
$this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
* @expectedExceptionMessage Unknown setting for locale en: date_time_picker
|
||||
*/
|
||||
public function testUnknownSetting()
|
||||
{
|
||||
$sut = $this->getSut('en', ['en' => [
|
||||
'xxx' => 'dd.MM.yyyy HH:mm',
|
||||
]]);
|
||||
$sut->getDateTimePickerFormat('en');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Utils\Markdown
|
||||
* @covers \App\Utils\ParsedownExtension
|
||||
*/
|
||||
class MarkdownTest extends TestCase
|
||||
{
|
||||
@@ -51,6 +52,26 @@ sdfsdf [asdfasdf](#test-1) asdfasdf
|
||||
|
||||
# test
|
||||
aasdfasdf
|
||||
EOT;
|
||||
$this->assertEquals($html, $sut->toHtml($markdown));
|
||||
}
|
||||
|
||||
public function testDuplicateIds()
|
||||
{
|
||||
$sut = new Markdown();
|
||||
|
||||
$html = <<<'EOT'
|
||||
<h1 id="test">test</h1>
|
||||
<h2 id="test-1">test</h2>
|
||||
<h3 id="test-2">test</h3>
|
||||
<h1 id="test-3">test</h1>
|
||||
EOT;
|
||||
|
||||
$markdown = <<<EOT
|
||||
# test
|
||||
## test
|
||||
### test
|
||||
# test
|
||||
EOT;
|
||||
$this->assertEquals($html, $sut->toHtml($markdown));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Configurations
|
||||
|
||||
This is an introduction into the configuration options and files, which are used by Kimai and an explanantion on how to change them.
|
||||
|
||||
Specific configurations are explained in the detailed feature docs:
|
||||
|
||||
- [Timesheet](timesheet.md)
|
||||
- [Permissions](permissions.md)
|
||||
- [Invoice](invoices.md)
|
||||
- [Calendar](calendar.md)
|
||||
- [Customer](customer.md)
|
||||
- [Emails](emails.md)
|
||||
- [Dashboard widgets](dashboard.md)
|
||||
- [Theme](theme.md)
|
||||
|
||||
## Environment specific settings (.env)
|
||||
|
||||
The most basic settings, which need always be adjusted are stored in the `.env` file:
|
||||
@@ -19,20 +32,12 @@ Configuration of Kimai is spread in all files in the `config/`directory but main
|
||||
- `config/packages/kimai.yaml` - Kimai specific settings
|
||||
- `config/packages/admin_lte.yaml` - Kimai base theme
|
||||
- `config/packages/fos_user.yaml` - user management and email settings
|
||||
- `config/packages/local.yaml` - your local configuration settings
|
||||
- `config/packages/local.yaml` - your configuration settings (file needs to be created by yourself)
|
||||
|
||||
There are several other configurations that could potentially be interesting for you in [config/packages/*.yaml](../../config/packages/).
|
||||
|
||||
If you want to adjust a setting from any of these files, use `local.yaml` (see below).
|
||||
|
||||
#### Other topics
|
||||
|
||||
- [Theme settings](theme.md) - in `kimai.yaml` and `admin_lte.yaml`
|
||||
- [Email configuration](emails.md) - in `swiftmailer.yaml`
|
||||
- [Dashboard widgets](dashboard.md) - in `kimai.yaml`
|
||||
- [Calendar](calendar.md) - in `kimai.yaml`
|
||||
- [Invoices](invoices.md) - in `kimai.yaml`
|
||||
|
||||
## Overwriting local configs (local.yaml)
|
||||
|
||||
You should NOT edit the file `config/packages/kimai.yaml` directly, as it contains default settings and will be overwritten during an update.
|
||||
@@ -71,202 +76,3 @@ bin/console cache:warmup --env=prod
|
||||
|
||||
Depending on your setup it might be necessary to execute these commands as webserver user,
|
||||
please read the [UPGRADING guide](../../UPGRADING.md) for more details.
|
||||
|
||||
## Security
|
||||
|
||||
Kimai uses the FOSUserBundle for security related tasks like user management. Its configuration can be found in [fos_user.yaml](../../config/packages/fos_user.yaml).
|
||||
|
||||
### User management emails (fos_user.yaml)
|
||||
|
||||
Read more about [email configuration](emails.md).
|
||||
|
||||
### Remember me login (security.yaml)
|
||||
|
||||
The default period for the `Remember me` option can be changed in the config file [security.yaml](../../config/packages/security.yaml).
|
||||
|
||||
### User registration
|
||||
|
||||
If you want your new users to use [email](emails.md) based activation add this to your `local.yaml`:
|
||||
|
||||
```yaml
|
||||
fos_user:
|
||||
registration:
|
||||
confirmation:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
#### Disable user registration
|
||||
|
||||
If you want to disable the user registration, add this your `local.yaml`:
|
||||
```yaml
|
||||
kimai:
|
||||
user:
|
||||
registration: false
|
||||
```
|
||||
|
||||
If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`:
|
||||
```yaml
|
||||
admin_lte:
|
||||
routes:
|
||||
adminlte_registration: ~
|
||||
```
|
||||
|
||||
### Password reset
|
||||
|
||||
If you want to configure the behaviour (like the allowed time between multiple retries) then configure the settings:
|
||||
|
||||
- in `config/packages/fos_user.yaml` the key below `fos_user.registration.resetting` (see [documentation](https://symfony.com/doc/current/bundles/FOSUserBundle/configuration_reference.html))
|
||||
- the values `retry_ttl` and `token_ttl` are configured in seconds (7220 = 2 hours)
|
||||
|
||||
#### Disable password reset
|
||||
|
||||
If you want to disable the password reset, add this your `local.yaml`:
|
||||
```yaml
|
||||
kimai:
|
||||
user:
|
||||
password_reset: false
|
||||
```
|
||||
|
||||
If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`:
|
||||
```yaml
|
||||
admin_lte:
|
||||
routes:
|
||||
adminlte_password_reset: ~
|
||||
```
|
||||
|
||||
## Timesheets (kimai.yaml)
|
||||
|
||||
### Limit active entries
|
||||
|
||||
To limit the amount of active entries each user can have, the configuration `active_entries` can be changed:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
active_entries:
|
||||
soft_limit: 1
|
||||
hard_limit: 3
|
||||
```
|
||||
|
||||
The `soft_limit` is used as theme setting (formerly "kimai.theme.active_warning") to display a warning if the user has at least X active recordings.
|
||||
|
||||
The `hard_limit` is used to detect how many active records are allowed per user (by default 3 active time-records are allowed).
|
||||
If `hard_limit` is 1, the active record is automatically stopped when a new one is started.
|
||||
When `hard_limit` is greater than 1 and as soon as the limit is reached, the user has to manually stop at least one active
|
||||
entry (an error message is shown, indicating why it is not possible to start another one).
|
||||
|
||||
|
||||
### Descriptions with Markdown
|
||||
|
||||
The description for every timesheet entry can be formatted in two different ways, configured with the `markdown_content` setting.
|
||||
|
||||
- `false` - simple newlines in the description box will be displayed in the frontend as well (default)
|
||||
- `true` - description will be rendered with a markdown engine, supporting simple lists and other HTML content
|
||||
|
||||
Allowing Markdown in timesheet descriptions is beautiful, but also could be a [security risk](https://github.com/erusev/parsedown/blob/master/README.md#security).
|
||||
Kimai will only apply the markdown in the user timesheet and not in the admin section as additional security measure.
|
||||
|
||||
### Duration only
|
||||
|
||||
Kimai supports two modes for displaying and recording timesheet entries:
|
||||
|
||||
- `begin` and `end` time (default)
|
||||
- `date` and `duration` (the so called `duration_only` mode)
|
||||
|
||||
When activating the `duration_only` mode all timesheet tables will only display the `date` and `duration` of all records.
|
||||
In addition, the "edit timesheet" forms will be changed and instead of displaying the `end` date you will see a field for `duration`.
|
||||
The `start` date is only visible in these forms when editing an active or starting a new record.
|
||||
|
||||
You can activate the `duration_only` mode by switching the configuration key `kimai.timesheet.duration_only` to `true` in the file [kimai.yaml](../../config/packages/kimai.yaml).
|
||||
|
||||
For supported formats while entering the `duration` please see the [timesheet chapter](timesheet.md)
|
||||
|
||||
### Rounding of begin, end and duration for timesheet records
|
||||
|
||||
Rounding rules are used to round the begin & end dates and the duration for timesheet records.
|
||||
|
||||
1. You can define as many rules as you want ("default" is only an example)
|
||||
2. Every matching rule will be applied, so be careful with overlapping rules
|
||||
3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight)
|
||||
4. If you set one of "begin", "end", "duration" to 0 no rounding will be applied for that field and the exact time (including seconds) is used for calculation
|
||||
5. The values of the rules are minutes (not the minute of an hour), so 5 for "begin" means we round down to the previous multiple of five
|
||||
6. You can define different rules for different days of the week
|
||||
7. "begin" will always be rounded to the floor (down) and "end" & "duration" to the ceiling (up)
|
||||
8. Rounding rules will be applied on stopped timesheet records only, so you might see an un-rounded value for the start time and duration until you stop the record
|
||||
|
||||
You can configure your `rounding` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml).
|
||||
|
||||
#### Examples
|
||||
|
||||
A simple example to always charge at least 1 hour for weekend work (even if you only worked for 5 minutes) could look like this:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rounding:
|
||||
weekend:
|
||||
days: ['saturday','sunday']
|
||||
begin: 1
|
||||
end: 1
|
||||
duration: 60
|
||||
```
|
||||
|
||||
A rule which is often used is to round up to a mulitple of 10:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rounding:
|
||||
workdays:
|
||||
days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
|
||||
begin: 10
|
||||
end: 10
|
||||
duration: 0
|
||||
```
|
||||
|
||||
### Hourly rates for timesheet records
|
||||
|
||||
If you want to apply different hourly rates multiplication `factor` for specific weekdays, you can use this `rates` configuration.
|
||||
|
||||
1. You can define as many rules as you want ("workdays" and "weekend" are only examples)
|
||||
2. Every matching rule will be applied, so be careful with overlapping rules
|
||||
3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight)
|
||||
4. "days" is an array of weekdays, where the days need to be written in english and in lowercase
|
||||
5. "factor" will be used as multiplier for the applied hourly rate
|
||||
6. Rate rules will be applied on stopped timesheet records only, as it can't be calculated before
|
||||
7. There is no default rule active, by default the users hourly-rate is used for calculation
|
||||
|
||||
You can configure the `hourly_rate` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml).
|
||||
|
||||
#### Examples
|
||||
|
||||
1. The "workdays" rule will use the default "hourly rate" for each timesheet entry recorded between "monday" to "friday" as a multiplication with 1 will not change the result
|
||||
2. The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays"
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rates:
|
||||
workdays:
|
||||
days: ['monday','tuesday','wednesday','thursday','friday']
|
||||
factor: 1
|
||||
weekend:
|
||||
days: ['saturday','sunday']
|
||||
factor: 1.5
|
||||
```
|
||||
|
||||
## Forms (kimai.yaml)
|
||||
|
||||
You can set some defaults for various forms within Kimai, to easify the creation process.
|
||||
|
||||
### Customer creation
|
||||
|
||||
Define the default values for a customer like this:
|
||||
```yaml
|
||||
kimai:
|
||||
defaults:
|
||||
customer:
|
||||
timezone: Europe/London
|
||||
country: GB
|
||||
currency: GBP
|
||||
```
|
||||
17
var/docs/customer.md
Normal file
17
var/docs/customer.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Customer
|
||||
|
||||
Customers in Kimai are used to manage project and activities, which are then used for time-records.
|
||||
|
||||
It is very common to have a _customer_ for your own company, to track times for administration and other internal work.
|
||||
|
||||
## Creating customer
|
||||
|
||||
Define the default values for a customer like this:
|
||||
```yaml
|
||||
kimai:
|
||||
defaults:
|
||||
customer:
|
||||
timezone: Europe/London
|
||||
country: GB
|
||||
currency: GBP
|
||||
```
|
||||
@@ -2,10 +2,22 @@
|
||||
|
||||
## I have a problem
|
||||
|
||||
Please open a new issue at [GitHub](https://github.com/kevinpapst/kimai2/issues/).
|
||||
Add the last entries from your logfile at `var/log/prod.log`.
|
||||
Please check your logfile at `var/log/prod.log`. Many problems reveal themselves after checking it.
|
||||
|
||||
## I have only FTP available ...
|
||||
If that doesn't help, open a new issue at [GitHub](https://github.com/kevinpapst/kimai2/issues/) and we try to find a solution.
|
||||
|
||||
## Changed configs/templates do not load
|
||||
|
||||
Kimai is built on top of Symfony, a framework that optimizes its speed by caching most files.
|
||||
Therefor, if you are running Kimai in `production`, you have to clear the cache before changes will show up:
|
||||
|
||||
```bash
|
||||
bin/console cache:clear
|
||||
```
|
||||
|
||||
See also the [configurations docs](configurations.md).
|
||||
|
||||
## I have only FTP available
|
||||
|
||||
So you want to install Kimai v2 but have no SSH access to your server?
|
||||
There is a workaround available, read the additional [installation instructions](installation.md).
|
||||
@@ -26,22 +38,17 @@ used by executing `mysql --version` or by checking the server information e.g. w
|
||||
|
||||
There is a [discussion in the issue tracker](https://github.com/kevinpapst/kimai2/issues/191) about this topic.
|
||||
|
||||
There are two solution for this:
|
||||
|
||||
- Update your MariaDB server to at least 10.2.7
|
||||
- Switch to SQLite (that can be changed in your `.env` file)
|
||||
|
||||
Further readings:
|
||||
|
||||
- [MariaDB - JSON support was added with 10.2.7](https://mariadb.com/kb/en/library/json-data-type/)
|
||||
- [Using JSON fields with Doctrine ORM on PostgreSQL & MySQL](https://symfony.fi/entry/using-json-fields-with-doctrine-orm-on-postgresql-mysql)
|
||||
|
||||
## Why is SQLite not recommended for production usage
|
||||
## SQLite not recommended for production usage
|
||||
|
||||
SQLite is a great database engine for testing, but when it comes to production usage it fails due to several reasons:
|
||||
|
||||
- It does not support ALTER TABLE commands and makes update procedures very clunky and problematic/errorsome (we still try to support updates, but they are heavy on large databases)
|
||||
- It does not support FOREIGN KEY constraints out of the box, which can lead to critical bugs when deleting activities/projects/customers
|
||||
- It does not support ALTER TABLE commands and makes update procedures very clunky and problematic (we still try to support updates, but they are heavy on large databases)
|
||||
- It does not support FOREIGN KEY constraints [out of the box](https://www.sqlite.org/foreignkeys.html#fk_enable), which can lead to critical bugs when deleting users/activities/projects/customers
|
||||
|
||||
## Dotenv::populate() must be an instance of Symfony\\Component\\Dotenv\\void
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ You need to install Git and [Composer](https://getcomposer.org/doc/00-intro.md)
|
||||
First clone this repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/kevinpapst/kimai2.git
|
||||
git clone -b 0.8 --depth 1 https://github.com/kevinpapst/kimai2.git
|
||||
cd kimai2/
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ Internal documentation for project maintainers
|
||||
- Prepare a GitHub release-draft
|
||||
- Change .github_changelog_generator config accordingly to new release tag (increase future release)
|
||||
- Change version constants in `src/Constants.php`
|
||||
- Adjust the file `UPGRADING.md` with version specific information
|
||||
- Adjust the file `UPGRADING.md` with version specific information
|
||||
- Change version string in git command in `UPGRADING.md` and `var/docs/installation.md`
|
||||
- Create CHANGELOG.md with [github-changelog-generator](https://github.com/github-changelog-generator/github-changelog-generator]) by running `github_changelog_generator kevinpapst/kimai2`
|
||||
- Edit the release-draft and add the "Full changelog" link + everything from CHANGELOG.md related to the new version
|
||||
- Push a release branch and merge it as last PR into master
|
||||
|
||||
@@ -11,8 +11,6 @@ You can start new timesheet records like so:
|
||||
- Click the **redo** button from one of the activities in your timesheet
|
||||
- Start a completely new activity, by clicking the big **play** button in the toolbar
|
||||
|
||||
The same goes for running time-records: top them with one-click from the timesheet or from the "running entries" dropdown.
|
||||
|
||||
## Stopping records
|
||||
|
||||
You can stop timesheet records like so:
|
||||
@@ -22,7 +20,24 @@ You can stop timesheet records like so:
|
||||
|
||||
## Duration only mode
|
||||
|
||||
When the `duration_only` mode is activated, you will only see the `date` and `duration` fields (see [configurations chapter](configurations.md)).
|
||||
Kimai supports two modes for displaying and recording timesheet entries:
|
||||
|
||||
- `begin` and `end` time (default)
|
||||
- `date` and `duration` (the so called `duration_only` mode)
|
||||
|
||||
When activating the `duration_only` mode all timesheet tables will only display the `date` and `duration` of all records.
|
||||
In addition, the "edit timesheet" forms will be changed and instead of displaying the `end` date you will see a field for `duration`.
|
||||
The `start` date is only visible in these forms when editing an active or starting a new record.
|
||||
|
||||
You can activate the `duration_only` mode by switching the configuration key `kimai.timesheet.duration_only` to `true` in your `local.yaml`:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
duration_only: true
|
||||
```
|
||||
|
||||
### Duration format
|
||||
|
||||
The `duration` field supports entering data in the following formats:
|
||||
|
||||
@@ -34,6 +49,78 @@ The `duration` field supports entering data in the following formats:
|
||||
|
||||
Please note: if time rounding is activated (which is the default behaviour), then your entered seconds might be removed after submitting the form.
|
||||
|
||||
## Limit active entries
|
||||
|
||||
To limit the amount of active entries each user can have, the configuration `active_entries` can be changed:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
active_entries:
|
||||
soft_limit: 1
|
||||
hard_limit: 3
|
||||
```
|
||||
|
||||
The `soft_limit` is used as theme setting (formerly "kimai.theme.active_warning") to display a warning if the user has at least X active recordings.
|
||||
|
||||
The `hard_limit` is used to detect how many active records are allowed per user (by default 3 active time-records are allowed).
|
||||
If `hard_limit` is 1, the active record is automatically stopped when a new one is started.
|
||||
When `hard_limit` is greater than 1 and as soon as the limit is reached, the user has to manually stop at least one active
|
||||
entry (an error message is shown, indicating why it is not possible to start another one).
|
||||
|
||||
## Descriptions with Markdown
|
||||
|
||||
The description for every timesheet entry can be formatted in two different ways, configured with the `markdown_content` setting.
|
||||
|
||||
- `false` - simple newlines in the description box will be displayed in the frontend as well (default)
|
||||
- `true` - description will be rendered with a markdown engine, supporting simple lists and other HTML content
|
||||
|
||||
Allowing Markdown in timesheet descriptions is beautiful, but also could be a [security risk](https://github.com/erusev/parsedown/blob/master/README.md#security).
|
||||
Kimai will only apply the markdown in the user timesheet and not in the admin section as additional security measure.
|
||||
|
||||
## Rounding of begin, end and duration for timesheet records
|
||||
|
||||
Rounding rules are used to round the begin & end dates and the duration for timesheet records.
|
||||
|
||||
1. You can define as many rules as you want ("default" is only an example)
|
||||
2. Every matching rule will be applied, so be careful with overlapping rules
|
||||
3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight)
|
||||
4. If you set one of "begin", "end", "duration" to 0 no rounding will be applied for that field and the exact time (including seconds) is used for calculation
|
||||
5. The values of the rules are minutes (not the minute of an hour), so 5 for "begin" means we round down to the previous multiple of five
|
||||
6. You can define different rules for different days of the week
|
||||
7. "begin" will always be rounded to the floor (down) and "end" & "duration" to the ceiling (up)
|
||||
8. Rounding rules will be applied on stopped timesheet records only, so you might see an un-rounded value for the start time and duration until you stop the record
|
||||
|
||||
You can configure your `rounding` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml).
|
||||
|
||||
### Examples
|
||||
|
||||
A simple example to always charge at least 1 hour for weekend work (even if you only worked for 5 minutes) could look like this:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rounding:
|
||||
weekend:
|
||||
days: ['saturday','sunday']
|
||||
begin: 1
|
||||
end: 1
|
||||
duration: 60
|
||||
```
|
||||
|
||||
A rule which is often used is to round up to a mulitple of 10:
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rounding:
|
||||
workdays:
|
||||
days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
|
||||
begin: 10
|
||||
end: 10
|
||||
duration: 0
|
||||
```
|
||||
|
||||
## Rate calculation
|
||||
|
||||
There are two rate types:
|
||||
@@ -63,5 +150,35 @@ The calculation is based on the following formula:
|
||||
- __Fixed rate__: `$fixedRate`
|
||||
- __Hourly rate__: `$hourlyRate * ($durationInSeconds / 3600) * $factor`
|
||||
|
||||
Please see also the configuration chapter about [hourly rates for timesheet records](configurations.md) to see how you
|
||||
can apply configurable multiplying factors based on day and time.
|
||||
Please see below to see how you can apply configurable multiplying factors based on day and time.
|
||||
|
||||
### Rate multiplier for specific weekdays
|
||||
|
||||
If you want to apply different hourly rates multiplication `factor` for specific weekdays, you can use this `rates` configuration.
|
||||
|
||||
1. You can define as many rules as you want ("workdays" and "weekend" are only examples)
|
||||
2. Every matching rule will be applied, so be careful with overlapping rules
|
||||
3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight)
|
||||
4. "days" is an array of weekdays, where the days need to be written in english and in lowercase
|
||||
5. "factor" will be used as multiplier for the applied hourly rate
|
||||
6. Rate rules will be applied on stopped timesheet records only, as it can't be calculated before
|
||||
7. There is no default rule active, by default the users hourly-rate is used for calculation
|
||||
|
||||
You can configure the `hourly_rate` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml).
|
||||
|
||||
#### Examples
|
||||
|
||||
1. The "workdays" rule will use the default "hourly rate" for each timesheet entry recorded between "monday" to "friday" as a multiplication with 1 will not change the result
|
||||
2. The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays"
|
||||
|
||||
```yaml
|
||||
kimai:
|
||||
timesheet:
|
||||
rates:
|
||||
workdays:
|
||||
days: ['monday','tuesday','wednesday','thursday','friday']
|
||||
factor: 1
|
||||
weekend:
|
||||
days: ['saturday','sunday']
|
||||
factor: 1.5
|
||||
```
|
||||
|
||||
@@ -16,7 +16,8 @@ personal computer instead of a server. Read the [installation docu](installation
|
||||
Change into your Kimai 2 installation directory, then fetch the latest code and install all dependencies:
|
||||
|
||||
```bash
|
||||
git pull origin master
|
||||
git fetch --tags
|
||||
git checkout 0.8
|
||||
sudo -u www-data composer install --no-dev --optimize-autoloader
|
||||
```
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ The permission system is configurable through a configuration file. You can find
|
||||
- User can login with username or email
|
||||
- If you activate the `Remember me` option, you can use use the most common functions within the next days without a new login
|
||||
|
||||
Kimai uses the FOSUserBundle for security related tasks like user management.
|
||||
Its configuration can be found in [fos_user.yaml](../../config/packages/fos_user.yaml).
|
||||
|
||||
### Remember me login
|
||||
|
||||
If you have chosen to login with the `Remember me` option, your login will be extended to one week (default value).
|
||||
@@ -34,13 +37,39 @@ If you are an administrator, you will see all your allowed options in the menu,
|
||||
form when you try to access them. This is a security feature to prevent abuse in case you forgot to logout in public
|
||||
environments.
|
||||
|
||||
Read the [configurations chapter](configurations.md) if you want to change the value.
|
||||
The default period for the `Remember me` option can be changed in the config file [security.yaml](../../config/packages/security.yaml).
|
||||
|
||||
## User registration
|
||||
|
||||
User registration with instant approval is activated by default, so users can register and will be able to login and start time-tracking instantly.
|
||||
|
||||
Read the [configurations chapter](configurations.md) if you want to disable the registration or enable email verification.
|
||||
If you want to disable the registration or enable email verification, read further below under _Configurations_.
|
||||
|
||||
### Email activation
|
||||
If you want your new users to use [email](emails.md) based activation add this to your `local.yaml`:
|
||||
|
||||
```yaml
|
||||
fos_user:
|
||||
registration:
|
||||
confirmation:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### Disable user registration
|
||||
|
||||
If you want to disable the user registration, add this your `local.yaml`:
|
||||
```yaml
|
||||
kimai:
|
||||
user:
|
||||
registration: false
|
||||
```
|
||||
|
||||
If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`:
|
||||
```yaml
|
||||
admin_lte:
|
||||
routes:
|
||||
adminlte_registration: ~
|
||||
```
|
||||
|
||||
## Password reset
|
||||
|
||||
@@ -56,4 +85,19 @@ If you want to configure the behaviour (like the allowed time between multiple r
|
||||
- in `config/packages/fos_user.yaml` the key below `fos_user.registration.resetting` (see [documentation](https://symfony.com/doc/current/bundles/FOSUserBundle/configuration_reference.html))
|
||||
- the values `retry_ttl` and `token_ttl` are configured in seconds (7220 = 2 hours)
|
||||
|
||||
Read the [configurations chapter](configurations.md) if you want to reload the changed configuration files.
|
||||
### Disable password reset
|
||||
|
||||
If you want to disable the password reset, add this your `local.yaml`:
|
||||
```yaml
|
||||
kimai:
|
||||
user:
|
||||
password_reset: false
|
||||
```
|
||||
|
||||
If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`:
|
||||
```yaml
|
||||
admin_lte:
|
||||
routes:
|
||||
adminlte_password_reset: ~
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user