diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml
index ebc3372..58b9096 100644
--- a/.github/workflows/linting.yaml
+++ b/.github/workflows/linting.yaml
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php: ['7.2', '7.3', '7.4']
+ php: ['7.4', '8.0', '8.1']
name: Linting - PHP ${{ matrix.php }}
steps:
@@ -20,6 +20,6 @@ jobs:
coverage: none
extensions: intl
- run: composer install --no-progress
- - run: composer validate
+ - run: composer validate --strict --no-check-version
- run: composer codestyle-check
- run: composer phpstan
diff --git a/.gitignore b/.gitignore
index 661514d..6b75680 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-.gitignore
.idea
-.php_cs.cache
+.disabled
+/vendor/
+.php-cs-fixer.cache
composer.lock
-vendor/
diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php
similarity index 88%
rename from .php_cs.dist
rename to .php-cs-fixer.dist.php
index 80e5422..4a64582 100644
--- a/.php_cs.dist
+++ b/.php-cs-fixer.dist.php
@@ -1,14 +1,15 @@
setRiskyAllowed(true)
->setRules([
'encoding' => true,
@@ -20,7 +21,7 @@
'function_declaration' => true,
'indentation_type' => true,
'line_ending' => true,
- 'lowercase_constants' => true,
+ 'constant_case' => ['case' => 'lower'],
'lowercase_keywords' => true,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'],
@@ -47,7 +48,7 @@
'statements' => ['return'],
],
'cast_spaces' => true,
- 'class_attributes_separation' => ['elements' => ['method']],
+ 'class_attributes_separation' => ['elements' => ['method' => 'one']],
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => true,
'function_typehint_space' => true,
@@ -101,9 +102,10 @@
],
'phpdoc_annotation_without_dot' => true,
'phpdoc_indent' => true,
- 'phpdoc_inline_tag' => true,
+ 'phpdoc_inline_tag_normalizer' => true,
'phpdoc_no_access' => true,
'phpdoc_no_alias_tag' => true,
+ 'phpdoc_no_empty_return' => false,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_return_self_reference' => true,
@@ -130,7 +132,7 @@
'standardize_increment' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
- 'trailing_comma_in_multiline_array' => false,
+ 'trailing_comma_in_multiline' => false,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
@@ -141,6 +143,18 @@
'method',
'property',
]],
+ 'native_function_invocation' => [
+ 'include' => [
+ '@compiler_optimized'
+ ],
+ 'scope' => 'namespaced'
+ ],
+ 'native_function_type_declaration_casing' => true,
+ 'no_alias_functions' => [
+ 'sets' => [
+ '@internal'
+ ]
+ ],
])
->setFinder(
PhpCsFixer\Finder::create()
@@ -149,7 +163,10 @@
])->exclude([
__DIR__ . '/Resources/',
__DIR__ . '/vendor/',
+ __DIR__ . '/.github/',
])
)
->setFormat('checkstyle')
;
+
+return $fixer;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6410c9..6b5729b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 1.0
+
+Compatible with Kimai 1.20.1
+
+- Added "default" mode to recalculate ONLY if one of the fields is changed: customer, project, activity, user, price
+- Select recalculate mode: off, default, always
+
## 0.2
- Added GitHub actions for basic CI
diff --git a/DependencyInjection/RecalculateRatesExtension.php b/DependencyInjection/RecalculateRatesExtension.php
index 8fb6409..e6d8710 100644
--- a/DependencyInjection/RecalculateRatesExtension.php
+++ b/DependencyInjection/RecalculateRatesExtension.php
@@ -1,7 +1,7 @@
>
+ */
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ SystemConfigurationEvent::class => ['onSystemConfiguration', 100],
+ ];
+ }
+
+ public function onSystemConfiguration(SystemConfigurationEvent $event): void
+ {
+ $newConfiguration = (new Configuration())
+ ->setName('timesheet.recalculate.mode')
+ ->setLabel('recalculate.mode')
+ ->setRequired(true)
+ ->setTranslationDomain('system-configuration')
+ ->setType(RecalculateModeType::class);
+
+ foreach ($event->getConfigurations() as $configuration) {
+ if ($configuration->getSection() === 'timesheet') {
+ $configuration->addConfiguration($newConfiguration);
+
+ return;
+ }
+ }
+
+ $event->addConfiguration(
+ (new SystemConfiguration('recalculate'))->setConfiguration([$newConfiguration])
+ );
+ }
+}
diff --git a/Form/RecalculateModeType.php b/Form/RecalculateModeType.php
new file mode 100644
index 0000000..ec11aea
--- /dev/null
+++ b/Form/RecalculateModeType.php
@@ -0,0 +1,43 @@
+ null];
+
+ /* @phpstan-ignore-next-line */
+ if (\defined('App\Constants::VERSION_ID') && Constants::VERSION_ID > 12000) {
+ $modes['recalculate.mode.default'] = 'default';
+ }
+
+ $modes['recalculate.mode.always'] = 'always';
+
+ $resolver->setDefaults([
+ 'required' => true,
+ 'multiple' => false,
+ 'label' => 'kiosk.login_type',
+ 'choices' => $modes,
+ ]);
+ }
+
+ public function getParent(): string
+ {
+ return ChoiceType::class;
+ }
+}
diff --git a/README.md b/README.md
index 11b2c79..2e55579 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,54 @@
-# RecalculateRatesBundle
+# Recalculate-Rates plugin for Kimai
-A Kimai 2 plugin, which forces a recalculation of the hourly and fixed rates for timesheet records on every update.
+A Kimai plugin, which forces a recalculation of the hourly and fixed rates for timesheet records on certain updates.
-By default, Kimai will use the hourly/fixed rate which was initially found.
+There are two possible modes in which this plugin can work:
-## Warning
+1. Recalculate prices if certain fields were changed
+2. Recalculate prices on every update
-The good part is: you can change customer/project and activity and be sure, that the correct rate is used.
+The first mode is the better one, but only available from Kimai 1.20.1 on.
-The bad part: a manually entered hourly/fixed rate will be overwritten. You HAVE to work with the pre-configured rates on your activities/projects/customers.
+You can configure the mode, by default mode 1 is used, unless your Kimai version is too old, then 2 is used.
+
+## Recalculate prices if certain fields were changed
+
+This mode should be preferred.
+
+A timesheet record rate will be recalculated if it was changed in one of these fields: Customer, Project, Activity, User, Price
+
+This still might overwrite custom rates, which were applied to single timesheets.
+But this case is very rare and using custom rates for single entries should be avoided anyway.
+If you find yourself using this workflow often, consider using the [Expense plugin](https://www.kimai.org/store/expenses-bundle.html).
+
+## Recalculate prices on every update
+
+The good part is:
+- you can change customer/project and activity and be sure, that the correct rate is used.
+
+The bad part:
+- even setting the export field might change the hourly rate and render your history invalid
+- a manually entered hourly/fixed rate will be overwritten
+- you HAVE to work with the pre-configured rates on your activities/projects/customers.
## Installation
First clone it to your Kimai installation `plugins` directory:
-```
-cd /kimai/var/plugins/
+```bash
+cd var/plugins/
git clone https://github.com/Keleo/RecalculateRatesBundle.git
```
-And then rebuild the cache:
-```
-cd /kimai/
-bin/console cache:clear
-bin/console cache:warmup
-```
-
-You could also [download it as zip](https://github.com/keleo/RecalculateRatesBundle/archive/master.zip) and upload the directory via FTP:
+The file structure needs to look like this afterwards:
-```
-/kimai/var/plugins/
+```bash
+var/plugins/
├── RecalculateRatesBundle
-│ ├── RecalculateRatesBundle.php
+│ ├── RecalculateRatesBundle.php
| └ ... more files and directories follow here ...
```
+
+And then rebuild the cache:
+```bash
+bin/console kimai:reload --env=prod
+```
diff --git a/RecalculateRatesBundle.php b/RecalculateRatesBundle.php
index ada895d..e32a4b1 100644
--- a/RecalculateRatesBundle.php
+++ b/RecalculateRatesBundle.php
@@ -1,7 +1,7 @@
+
+
+
+
+
+ Preisneuberechnung
+
+
+
+ Modus für Preisneuberechnung
+
+
+
+ Aus
+
+
+
+ Bei jeder Änderung
+
+
+
+ Bei Änderung von Kunde, Projekt, Tätigkeit, Benutzer oder Preis
+
+
+
+
diff --git a/Resources/translations/system-configuration.en.xlf b/Resources/translations/system-configuration.en.xlf
new file mode 100644
index 0000000..a35f56f
--- /dev/null
+++ b/Resources/translations/system-configuration.en.xlf
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Price recalculation
+
+
+
+ Mode for price recalculation
+
+
+
+ Off
+
+
+
+ With every update
+
+
+
+ With update of customer, project, activity, user or price
+
+
+
+
diff --git a/Timesheet/Calculator/RecalculateRateCalculator.php b/Timesheet/Calculator/RecalculateRateCalculator.php
index 53f43bb..cbb213f 100644
--- a/Timesheet/Calculator/RecalculateRateCalculator.php
+++ b/Timesheet/Calculator/RecalculateRateCalculator.php
@@ -1,7 +1,7 @@
calculator = $calculator;
+ $this->systemConfiguration = $systemConfiguration;
}
/**
@@ -29,19 +34,70 @@ public function __construct(RateCalculator $calculator)
*/
public function calculate(Timesheet $record)
{
- if (null !== $record->getHourlyRate()) {
- $record->setHourlyRate(null);
+ $mode = $this->systemConfiguration->find('timesheet.recalculate.mode');
+ if ($mode === null) {
+ return;
}
- if (null !== $record->getFixedRate()) {
- $record->setFixedRate(null);
+ // this means we have Kimai > 1.20
+ // we can use the improved calculation method
+ if ($mode === 'default' && \func_num_args() === 2) {
+ /** @var array $changes */
+ $changes = func_get_arg(1);
+
+ // check if the rate was changed manually
+ $changedRate = false;
+ foreach (['hourlyRate', 'fixedRate', 'internalRate', 'rate'] as $field) {
+ if (\array_key_exists($field, $changes)) {
+ $changedRate = true;
+ break;
+ }
+ }
+
+ // if no manual rate changed was applied:
+ // check if a field changed, that is relevant for the rate calculation: if one was changed =>
+ // reset all rates, because most users do not even see their rates and would not be able
+ // to fix or empty the rate, even if they knew that the changed project has another base rate
+ if (!$changedRate) {
+ foreach (['project', 'activity', 'user'] as $field) {
+ if (\array_key_exists($field, $changes)) {
+ // this has room for minor improvements: entries with a manual rate might be changed
+ $this->resetRates($record);
+ break;
+ }
+ }
+ }
+
+ return;
+ }
+
+ if ($mode === 'always') {
+ $this->resetRates($record);
+
+ // we have to trigger it again, as there was no order defined for the calculator in earlier versions
+ /* @phpstan-ignore-next-line */
+ if (\defined('App\Constants::VERSION_ID') && Constants::VERSION_ID < 12001) {
+ $this->calculator->calculate($record);
+ }
}
+ }
+
+ private function resetRates(Timesheet $record): void
+ {
+ if (method_exists($record, 'resetRates')) {
+ $record->resetRates();
- if (null !== $record->getInternalRate()) {
- $record->setInternalRate(null);
+ return;
}
- // we have to trigger it again, as there is no order defined for the calculator
- $this->calculator->calculate($record);
+ $record->setRate(0.00);
+ $record->setInternalRate(null);
+ $record->setHourlyRate(null);
+ $record->setFixedRate(null);
+ }
+
+ public function getPriority(): int
+ {
+ return 250;
}
}
diff --git a/composer.json b/composer.json
index bc52ea6..0026138 100644
--- a/composer.json
+++ b/composer.json
@@ -1,9 +1,9 @@
{
"name": "keleo/recalculate-rates-bundle",
- "description": "A Kimai 2 plugin, which forces to recalculate the hourly/fixed rate on every timesheet update.",
+ "description": "A Kimai plugin, which forces recalculation of hourly/fixed rates on timesheet update.",
"homepage": "https://www.kimai.org/store/keleo-recalculate-rates-bundle.html",
"type": "kimai-plugin",
- "version": "0.2",
+ "version": "1.0",
"keywords": [
"kimai",
"kimai-plugin"
@@ -12,18 +12,29 @@
"authors": [
{
"name": "Kevin Papst",
- "email": "kpapst@gmx.net",
+ "email": "kevin@kevinpapst.de",
"homepage": "https://www.keleo.de"
}
],
"extra": {
"kimai": {
- "require": "0.9",
- "version": "0.2",
+ "require": "1.20.1",
"name": "RecalculateRatesBundle"
}
},
+ "autoload": {
+ "psr-4": {
+ "KimaiPlugin\\InvoiceBundle\\": ""
+ }
+ },
"config": {
+ "allow-plugins": {
+ "composer/package-versions-deprecated": false,
+ "symfony/flex": false
+ },
+ "platform": {
+ "php": "7.3"
+ },
"preferred-install": {
"*": "dist"
},
@@ -32,19 +43,19 @@
"scripts": {
"codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
"codestyle-fix": "vendor/bin/php-cs-fixer fix",
- "codestyle-check": "vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php_cs.dist --using-cache=no --show-progress=none --format=checkstyle",
- "phpstan": "vendor/bin/phpstan analyse . -c phpstan.neon --level=7",
+ "codestyle-check": "vendor/bin/php-cs-fixer fix --dry-run --verbose --using-cache=no --show-progress=none --format=checkstyle",
+ "phpstan": "vendor/bin/phpstan analyse . -c phpstan.neon --level=9",
"linting": [
- "composer validate",
+ "composer validate --strict --no-check-version",
"@codestyle-check",
"@phpstan"
]
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^2.15",
- "kevinpapst/kimai2": ">1.9",
- "phpstan/phpstan": "^0.12",
- "phpstan/phpstan-symfony": "^0.12",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "kevinpapst/kimai2": "^1.20",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-symfony": "^1.0",
"symfony/console": "^4.0",
"symfony/event-dispatcher": "^4.0"
}
diff --git a/phpstan.neon b/phpstan.neon
index 7e31e59..05226fb 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,9 +1,9 @@
includes:
- - vendor/phpstan/phpstan-symfony/extension.neon
- - vendor/phpstan/phpstan-symfony/rules.neon
+ - %rootDir%/../phpstan-symfony/extension.neon
+ - %rootDir%/../phpstan-symfony/rules.neon
parameters:
- excludes_analyse:
+ excludePaths:
- vendor/
treatPhpDocTypesAsCertain: false
inferPrivatePropertyTypeFromConstructor: true