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
|
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:
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. See error
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
**Logfile**
|
||||||
A clear and concise description of what you expected to happen.
|
```
|
||||||
|
Add the last lines from your logfile at var/log/prod.log, around the time when the problem happened.
|
||||||
|
```
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
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]
|
- OS: [e.g. iOS]
|
||||||
- Browser [e.g. chrome, safari]
|
- Browser [e.g. chrome, safari]
|
||||||
- Version [e.g. 22]
|
- 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**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
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
|
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
|
## Description
|
||||||
|
A clear and concise description of what this pull request changes.
|
||||||
|
|
||||||
## Types of changes
|
## Types of changes
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] 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)
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
- [ ] My code follows the code style
|
- [ ] I have executed `bin/console kimai:phpcs --fix` to make sure my changes adopt the correct code style
|
||||||
- [ ] All files have a license header
|
|
||||||
- [ ] All methods have a doc header with type declarations
|
|
||||||
- [ ] I have updated the documentation accordingly
|
- [ ] I have updated the documentation accordingly
|
||||||
- [ ] I have added tests to cover my changes
|
- [ ] 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
|
unreleased=true
|
||||||
future-release=0.7
|
future-release=0.8
|
||||||
exclude-labels=duplicate,question,invalid,wontfix,release
|
exclude-labels=duplicate,question,invalid,wontfix,release
|
||||||
enhancement_labels=>enhancement,Enhancement,feature request
|
enhancement_labels=>enhancement,Enhancement,feature request
|
||||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,5 +1,63 @@
|
|||||||
# Change Log
|
# 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)
|
## [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)
|
[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)
|
- 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)
|
- 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)
|
- 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:**
|
**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)
|
- 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)
|
- 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)
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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
|
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.
|
`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
|
```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 composer install --no-dev --optimize-autoloader
|
||||||
sudo -u www-data bin/console cache:clear --env=prod
|
sudo -u www-data bin/console cache:clear --env=prod
|
||||||
sudo -u www-data bin/console cache:warmup --env=prod
|
sudo -u www-data bin/console cache:warmup --env=prod
|
||||||
bin/console doctrine:migrations:migrate
|
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
|
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.
|
if your updated version is mentioned below.
|
||||||
|
|
||||||
## [0.8](https://github.com/kevinpapst/kimai2/releases/tag/0.8) (unreleased)
|
## [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.
|
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`.
|
||||||
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`.
|
|
||||||
|
|
||||||
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)
|
## [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
|
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)
|
## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22)
|
||||||
|
|
||||||
**Update from 0.2:**
|
**Update from 0.2:**
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ $(function() {
|
|||||||
$('input[data-daterangepickerenable="on"]').each(function(index) {
|
$('input[data-daterangepickerenable="on"]').each(function(index) {
|
||||||
var localeFormat = $(this).data('format');
|
var localeFormat = $(this).data('format');
|
||||||
var separator = $(this).data('separator');
|
var separator = $(this).data('separator');
|
||||||
var transToday = $.kimai.settings['today'];
|
|
||||||
var rangesList = {};
|
var rangesList = {};
|
||||||
rangesList[$.kimai.settings['today']] = [moment(), moment()];
|
rangesList[$.kimai.settings['today']] = [moment(), moment()];
|
||||||
rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
|
rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
|
||||||
|
|||||||
@@ -83,11 +83,11 @@ class ActivityController extends BaseApiController
|
|||||||
$query->setOrderBy($orderBy);
|
$query->setOrderBy($orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== ($globals = $paramFetcher->get('globals'))) {
|
if (null !== $paramFetcher->get('globals')) {
|
||||||
$query->setGlobalsOnly(true);
|
$query->setGlobalsOnly(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('false' === ($globalsFirst = $paramFetcher->get('globalsFirst'))) {
|
if ('false' === $paramFetcher->get('globalsFirst')) {
|
||||||
$query->setOrderGlobalsFirst(false);
|
$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.
|
* 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!
|
* Please read https://github.com/kevinpapst/kimai2/pull/372 to find out more!
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
class ConvertTimezoneCommand extends Command
|
class ConvertTimezoneCommand extends Command
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|||||||
/**
|
/**
|
||||||
* Command used to import data from a Kimai v1 installation.
|
* 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.
|
* Getting help in improving this script would be fantastic, it currently only handles the most basic use-cases.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
class KimaiImporterCommand extends Command
|
class KimaiImporterCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -962,7 +964,6 @@ class KimaiImporterCommand extends Command
|
|||||||
|
|
||||||
// ----------------------- unknown user, damned missing data integrity in Kimai v1 -----------------------
|
// ----------------------- unknown user, damned missing data integrity in Kimai v1 -----------------------
|
||||||
if (!isset($this->users[$oldRecord['userID']])) {
|
if (!isset($this->users[$oldRecord['userID']])) {
|
||||||
|
|
||||||
$tempUserName = uniqid();
|
$tempUserName = uniqid();
|
||||||
$tempPassword = uniqid() . uniqid();
|
$tempPassword = uniqid() . uniqid();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '0.7';
|
public const VERSION = '0.8';
|
||||||
/**
|
/**
|
||||||
* The release name, will only change for new major version
|
* The release name, will only change for new major version
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -74,5 +74,4 @@ class TimezoneSubscriber implements EventSubscriberInterface
|
|||||||
|
|
||||||
return ($user instanceof User);
|
return ($user instanceof User);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Activity;
|
||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Model\Statistic\Month;
|
use App\Model\Statistic\Month;
|
||||||
@@ -364,8 +365,7 @@ class TimesheetRepository extends AbstractRepository
|
|||||||
->setParameter('activity', $query->getActivity());
|
->setParameter('activity', $query->getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO if activity is an int, this will fail
|
if (null === $query->getActivity() || ($query->getActivity() instanceof Activity && null === $query->getActivity()->getProject())) {
|
||||||
if (null === $query->getActivity() || null === $query->getActivity()->getProject()) {
|
|
||||||
if (null !== $query->getProject()) {
|
if (null !== $query->getProject()) {
|
||||||
$qb->andWhere('t.project = :project')
|
$qb->andWhere('t.project = :project')
|
||||||
->setParameter('project', $query->getProject());
|
->setParameter('project', $query->getProject());
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ActivityControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(count($expected), count($result));
|
$this->assertEquals(count($expected), count($result));
|
||||||
for ($i = 0; $i < count($result); $i++) {
|
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]]];
|
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()
|
public function testGetEntity()
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/activities/1');
|
$this->assertAccessIsGranted($client, '/api/activities/1');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
|
|
||||||
$expectedKeys = ['id', 'name', 'comment', 'visible'];
|
$expectedKeys = ['id', 'name', 'comment', 'visible'];
|
||||||
$actual = array_keys($result);
|
$actual = array_keys($result);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class ApiDocControllerTest extends ControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/doc.json');
|
$this->assertAccessIsGranted($client, '/api/doc.json');
|
||||||
$this->assertContains('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent());
|
$this->assertContains('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,20 @@ class CustomerControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/customers');
|
$this->assertAccessIsGranted($client, '/api/customers');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$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->assertNotEmpty($result);
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertStructure($result[0], false);
|
$this->assertStructure($result[0], false);
|
||||||
@@ -40,7 +53,7 @@ class CustomerControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/customers/1');
|
$this->assertAccessIsGranted($client, '/api/customers/1');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertStructure($result, true);
|
$this->assertStructure($result, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/ping');
|
$this->assertAccessIsGranted($client, '/api/ping');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertEquals(['message' => 'pong'], $result);
|
$this->assertEquals(['message' => 'pong'], $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/version');
|
$this->assertAccessIsGranted($client, '/api/version');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
|
|
||||||
$this->assertArrayHasKey('version', $result);
|
$this->assertArrayHasKey('version', $result);
|
||||||
$this->assertArrayHasKey('candidate', $result);
|
$this->assertArrayHasKey('candidate', $result);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/projects');
|
$this->assertAccessIsGranted($client, '/api/projects');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertStructure($result[0], false);
|
$this->assertStructure($result[0], false);
|
||||||
@@ -81,7 +81,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
$this->assertAccessIsGranted($client, $url, 'GET', $parameters);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$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');
|
$this->assertEquals(count($expected), count($result), 'Found wrong amount of projects');
|
||||||
|
|
||||||
for ($i = 0; $i < count($expected); $i++) {
|
for ($i = 0; $i < count($expected); $i++) {
|
||||||
@@ -111,7 +111,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/projects/1');
|
$this->assertAccessIsGranted($client, '/api/projects/1');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertStructure($result);
|
$this->assertStructure($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use App\Entity\Customer;
|
|||||||
use App\Entity\Project;
|
use App\Entity\Project;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @coversDefaultClass \App\API\TimesheetController
|
* @coversDefaultClass \App\API\TimesheetController
|
||||||
@@ -118,7 +119,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
public function testGetCollectionWithQuery()
|
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);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
@@ -218,24 +219,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiCallValidationError($client->getResponse(), ['activity']);
|
$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()
|
public function testNotFound()
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/20');
|
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/20');
|
||||||
@@ -263,6 +246,38 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertEquals(1, $result['exported']);
|
$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()
|
public function testInvalidPatchAction()
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class UserControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/users');
|
$this->assertAccessIsGranted($client, '/api/users');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(6, count($result));
|
$this->assertEquals(6, count($result));
|
||||||
$this->assertStructure($result[0], false);
|
$this->assertStructure($result[0], false);
|
||||||
@@ -41,7 +41,7 @@ class UserControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/api/users/1');
|
$this->assertAccessIsGranted($client, '/api/users/1');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertStructure($result);
|
$this->assertStructure($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CalendarControllerTest extends ControllerBaseTest
|
|||||||
$response = $client->getResponse();
|
$response = $client->getResponse();
|
||||||
$this->assertTrue($response->isSuccessful());
|
$this->assertTrue($response->isSuccessful());
|
||||||
$json = json_decode($response->getContent(), true);
|
$json = json_decode($response->getContent(), true);
|
||||||
$this->assertInternalType('array', $json);
|
$this->assertIsArray($json);
|
||||||
$this->assertEmpty($json);
|
$this->assertEmpty($json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +68,11 @@ class CalendarControllerTest extends ControllerBaseTest
|
|||||||
$response = $client->getResponse();
|
$response = $client->getResponse();
|
||||||
$this->assertTrue($response->isSuccessful());
|
$this->assertTrue($response->isSuccessful());
|
||||||
$json = json_decode($response->getContent(), true);
|
$json = json_decode($response->getContent(), true);
|
||||||
$this->assertInternalType('array', $json);
|
$this->assertIsArray($json);
|
||||||
$this->assertNotEmpty($json);
|
$this->assertNotEmpty($json);
|
||||||
$this->assertEquals(10, count($json));
|
$this->assertEquals(10, count($json));
|
||||||
foreach ($json as $result) {
|
foreach ($json as $result) {
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
$this->assertCalendarStructure($result);
|
$this->assertCalendarStructure($result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class TimesheetFixtures extends Fixture
|
|||||||
public function setAllowEmptyDescriptions(bool $allowEmptyDescriptions)
|
public function setAllowEmptyDescriptions(bool $allowEmptyDescriptions)
|
||||||
{
|
{
|
||||||
$this->allowEmptyDescriptions = $allowEmptyDescriptions;
|
$this->allowEmptyDescriptions = $allowEmptyDescriptions;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ class ServiceInvoiceTest extends TestCase
|
|||||||
$sut = new ServiceInvoice($repo);
|
$sut = new ServiceInvoice($repo);
|
||||||
|
|
||||||
$this->assertEmpty($sut->getCalculator());
|
$this->assertEmpty($sut->getCalculator());
|
||||||
$this->assertInternalType('array', $sut->getCalculator());
|
$this->assertIsArray($sut->getCalculator());
|
||||||
$this->assertEmpty($sut->getRenderer());
|
$this->assertEmpty($sut->getRenderer());
|
||||||
$this->assertInternalType('array', $sut->getRenderer());
|
$this->assertIsArray($sut->getRenderer());
|
||||||
$this->assertEmpty($sut->getNumberGenerator());
|
$this->assertEmpty($sut->getNumberGenerator());
|
||||||
$this->assertInternalType('array', $sut->getNumberGenerator());
|
$this->assertIsArray($sut->getNumberGenerator());
|
||||||
$this->assertEmpty($sut->getDocuments());
|
$this->assertEmpty($sut->getDocuments());
|
||||||
$this->assertInternalType('array', $sut->getDocuments());
|
$this->assertIsArray($sut->getDocuments());
|
||||||
|
|
||||||
$this->assertNull($sut->getCalculatorByName('default'));
|
$this->assertNull($sut->getCalculatorByName('default'));
|
||||||
$this->assertNull($sut->getDocumentByName('default'));
|
$this->assertNull($sut->getDocumentByName('default'));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class InvoiceModelTest extends TestCase
|
|||||||
$this->assertNull($sut->getQuery());
|
$this->assertNull($sut->getQuery());
|
||||||
$this->assertNull($sut->getDueDate());
|
$this->assertNull($sut->getDueDate());
|
||||||
$this->assertEmpty($sut->getEntries());
|
$this->assertEmpty($sut->getEntries());
|
||||||
$this->assertInternalType('array', $sut->getEntries());
|
$this->assertIsArray($sut->getEntries());
|
||||||
$this->assertInstanceOf(\DateTime::class, $sut->getInvoiceDate());
|
$this->assertInstanceOf(\DateTime::class, $sut->getInvoiceDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class YearTest extends TestCase
|
|||||||
$sut = new Year('1999');
|
$sut = new Year('1999');
|
||||||
$this->assertNull($sut->getMonth('01'));
|
$this->assertNull($sut->getMonth('01'));
|
||||||
$this->assertEmpty($sut->getMonths());
|
$this->assertEmpty($sut->getMonths());
|
||||||
$this->assertInternalType('array', $sut->getMonths());
|
$this->assertIsArray($sut->getMonths());
|
||||||
$this->assertEquals('1999', $sut->getYear());
|
$this->assertEquals('1999', $sut->getYear());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class InvoiceDocumentRepositoryTest extends TestCase
|
|||||||
{
|
{
|
||||||
$sut = new InvoiceDocumentRepository([]);
|
$sut = new InvoiceDocumentRepository([]);
|
||||||
$this->assertEmpty($sut->findAll());
|
$this->assertEmpty($sut->findAll());
|
||||||
$this->assertInternalType('array', $sut->findAll());
|
$this->assertIsArray($sut->findAll());
|
||||||
$this->assertNull($sut->findByName('default'));
|
$this->assertNull($sut->findByName('default'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
|
|||||||
|
|
||||||
$query->setResultType(BaseQuery::RESULT_TYPE_OBJECTS);
|
$query->setResultType(BaseQuery::RESULT_TYPE_OBJECTS);
|
||||||
$result = $repository->findByQuery($query);
|
$result = $repository->findByQuery($query);
|
||||||
$this->assertInternalType('array', $result);
|
$this->assertIsArray($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStoppedEntriesCannotBeStoppedAgain()
|
public function testStoppedEntriesCannotBeStoppedAgain()
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class ExtensionsTest extends TestCase
|
|||||||
foreach ($icons as $icon) {
|
foreach ($icons as $icon) {
|
||||||
$result = $sut->icon($icon);
|
$result = $sut->icon($icon);
|
||||||
$this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon);
|
$this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon);
|
||||||
$this->assertInternalType('string', $result);
|
$this->assertIsString($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test fallback will be returned
|
// test fallback will be returned
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class DurationTest extends TestCase
|
|||||||
public function testFormat()
|
public function testFormat()
|
||||||
{
|
{
|
||||||
$sut = new Duration();
|
$sut = new Duration();
|
||||||
|
|
||||||
|
$this->assertNull($sut->format(null));
|
||||||
$this->assertEquals('02:38', $sut->format(9494));
|
$this->assertEquals('02:38', $sut->format(9494));
|
||||||
$this->assertEquals('02:38:14', $sut->format(9494, Duration::FORMAT_WITH_SECONDS));
|
$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', Duration::FORMAT_COLON],
|
||||||
['13-13', Duration::FORMAT_COLON],
|
['13-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
|
// invalid modes
|
||||||
[17, 'foo'],
|
[17, 'foo'],
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ class LocaleSettingsTest extends TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \InvalidArgumentException
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Unknown locale given: xx
|
||||||
*/
|
*/
|
||||||
public function testInvalidLocaleWithGivenLocale()
|
public function testInvalidLocaleWithGivenLocale()
|
||||||
{
|
{
|
||||||
@@ -197,4 +198,16 @@ class LocaleSettingsTest extends TestCase
|
|||||||
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
|
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
|
||||||
$this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de'));
|
$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\Markdown
|
||||||
|
* @covers \App\Utils\ParsedownExtension
|
||||||
*/
|
*/
|
||||||
class MarkdownTest extends TestCase
|
class MarkdownTest extends TestCase
|
||||||
{
|
{
|
||||||
@@ -51,6 +52,26 @@ sdfsdf [asdfasdf](#test-1) asdfasdf
|
|||||||
|
|
||||||
# test
|
# test
|
||||||
aasdfasdf
|
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;
|
EOT;
|
||||||
$this->assertEquals($html, $sut->toHtml($markdown));
|
$this->assertEquals($html, $sut->toHtml($markdown));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# Configurations
|
# 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)
|
## Environment specific settings (.env)
|
||||||
|
|
||||||
The most basic settings, which need always be adjusted are stored in the `.env` file:
|
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/kimai.yaml` - Kimai specific settings
|
||||||
- `config/packages/admin_lte.yaml` - Kimai base theme
|
- `config/packages/admin_lte.yaml` - Kimai base theme
|
||||||
- `config/packages/fos_user.yaml` - user management and email settings
|
- `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/).
|
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).
|
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)
|
## 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.
|
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,
|
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.
|
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
|
## I have a problem
|
||||||
|
|
||||||
Please open a new issue at [GitHub](https://github.com/kevinpapst/kimai2/issues/).
|
Please check your logfile at `var/log/prod.log`. Many problems reveal themselves after checking it.
|
||||||
Add the last entries from your logfile at `var/log/prod.log`.
|
|
||||||
|
|
||||||
## 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?
|
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).
|
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 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:
|
Further readings:
|
||||||
|
|
||||||
- [MariaDB - JSON support was added with 10.2.7](https://mariadb.com/kb/en/library/json-data-type/)
|
- [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)
|
- [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:
|
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 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, which can lead to critical bugs when deleting activities/projects/customers
|
- 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
|
## 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:
|
First clone this repo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/kevinpapst/kimai2.git
|
git clone -b 0.8 --depth 1 https://github.com/kevinpapst/kimai2.git
|
||||||
cd kimai2/
|
cd kimai2/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Internal documentation for project maintainers
|
|||||||
- Change .github_changelog_generator config accordingly to new release tag (increase future release)
|
- Change .github_changelog_generator config accordingly to new release tag (increase future release)
|
||||||
- Change version constants in `src/Constants.php`
|
- 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`
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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
|
## Stopping records
|
||||||
|
|
||||||
You can stop timesheet records like so:
|
You can stop timesheet records like so:
|
||||||
@@ -22,7 +20,24 @@ You can stop timesheet records like so:
|
|||||||
|
|
||||||
## Duration only mode
|
## 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:
|
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.
|
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
|
## Rate calculation
|
||||||
|
|
||||||
There are two rate types:
|
There are two rate types:
|
||||||
@@ -63,5 +150,35 @@ The calculation is based on the following formula:
|
|||||||
- __Fixed rate__: `$fixedRate`
|
- __Fixed rate__: `$fixedRate`
|
||||||
- __Hourly rate__: `$hourlyRate * ($durationInSeconds / 3600) * $factor`
|
- __Hourly rate__: `$hourlyRate * ($durationInSeconds / 3600) * $factor`
|
||||||
|
|
||||||
Please see also the configuration chapter about [hourly rates for timesheet records](configurations.md) to see how you
|
Please see below to see how you can apply configurable multiplying factors based on day and time.
|
||||||
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:
|
Change into your Kimai 2 installation directory, then fetch the latest code and install all dependencies:
|
||||||
|
|
||||||
```bash
|
```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 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
|
- 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
|
- 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
|
### Remember me login
|
||||||
|
|
||||||
If you have chosen to login with the `Remember me` option, your login will be extended to one week (default value).
|
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
|
form when you try to access them. This is a security feature to prevent abuse in case you forgot to logout in public
|
||||||
environments.
|
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
|
||||||
|
|
||||||
User registration with instant approval is activated by default, so users can register and will be able to login and start time-tracking instantly.
|
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
|
## 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))
|
- 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)
|
- 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