diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock index 10dbc4c8..d8a944d9 100644 --- a/.ci/php-cs-fixer/composer.lock +++ b/.ci/php-cs-fixer/composer.lock @@ -226,16 +226,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.46.0", + "version": "v3.47.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2" + "reference": "173c60d1eff911c9c54322704623a45561d3241d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/173c60d1eff911c9c54322704623a45561d3241d", + "reference": "173c60d1eff911c9c54322704623a45561d3241d", "shasum": "" }, "require": { @@ -305,7 +305,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.47.1" }, "funding": [ { @@ -313,7 +313,7 @@ "type": "github" } ], - "time": "2024-01-03T21:38:46+00:00" + "time": "2024-01-16T18:54:21+00:00" }, { "name": "psr/container", diff --git a/app/Http/Controllers/Import/ConversionController.php b/app/Http/Controllers/Import/ConversionController.php index befede46..1fd5c882 100644 --- a/app/Http/Controllers/Import/ConversionController.php +++ b/app/Http/Controllers/Import/ConversionController.php @@ -108,7 +108,7 @@ class ConversionController extends Controller } if ('camt' === $contentType) { app('log')->debug('Create CAMT routine manager.'); - $routine = new CAMTRoutineManager($identifier); + $routine = new CamtRoutineManager($identifier); } } if ('nordigen' === $flow) { @@ -158,7 +158,7 @@ class ConversionController extends Controller $routine = new CSVRoutineManager($identifier); } if ('camt' === $contentType) { - $routine = new CAMTRoutineManager($identifier); // why do we need this one? + $routine = new CamtRoutineManager($identifier); // why do we need this one? } } if ('nordigen' === $flow) { diff --git a/app/Services/CSV/Converter/Amount.php b/app/Services/CSV/Converter/Amount.php index 6e5015d1..504e8c5b 100644 --- a/app/Services/CSV/Converter/Amount.php +++ b/app/Services/CSV/Converter/Amount.php @@ -81,7 +81,9 @@ class Amount implements ConverterInterface // decimal character still null? Search from the left for '.',',' or ' '. if (null === $decimal) { - $decimal = $this->findFromLeft($value); + // See issue #8404 + // $decimal = $this->findFromLeft($value); + app('log')->debug('Disabled "findFromLeft" because it happens more often that "1.000" is thousand than "1.000" is 1 with three zeroes.'); } // if decimal is dot, replace all comma's and spaces with nothing diff --git a/app/Services/Camt/Conversion/TransactionMapper.php b/app/Services/Camt/Conversion/TransactionMapper.php index ede2ba0d..eaa17e00 100644 --- a/app/Services/Camt/Conversion/TransactionMapper.php +++ b/app/Services/Camt/Conversion/TransactionMapper.php @@ -499,12 +499,19 @@ class TransactionMapper break; case 'amount': - $current['amount'] = null; + // #8367 default amount = 0 + $current['amount'] = 0; + app('log')->debug(sprintf('Start with amount at zero ("%s")', $current['amount'])); if ('group' === $groupHandling || 'split' === $groupHandling) { // if multiple values, use biggest (... at index 0?) // TODO this will never work because $current['amount'] is NULL the first time and abs() can't handle that. foreach ($data['data'] as $amount) { - if (abs($current['amount']) < abs($amount) || null === $current['amount']) { + if(null === $amount) { + // #8367 skip null values + continue; + } + if (abs($current['amount']) < abs($amount)) { + app('log')->debug(sprintf('Amount is now "%s" instead of "%s"', $amount, $current['amount'])); $current['amount'] = $amount; } } @@ -512,12 +519,22 @@ class TransactionMapper if ('single' === $groupHandling) { // if multiple values, use smallest (... at index 1?) foreach ($data['data'] as $amount) { + // #8367 skip null values + if(null === $amount) { + continue; + } // check for null first, should prevent null pointers in abs() - if (null === $current['amount'] && null !== $amount && abs($current['amount']) > abs($amount)) { + if (abs($current['amount']) < abs($amount)) { + app('log')->debug(sprintf('Amount is now "%s" instead of "%s"', $amount, $current['amount'])); $current['amount'] = $amount; } } } + if(0 === bccomp('0', (string)$current['amount'])) { + app('log')->debug('Amount is ZERO, zet to NULL'); + $current['amount'] = null; + } + app('log')->debug(sprintf('Final amount is "%s"', $amount)); break; diff --git a/config/app.php b/config/app.php index 95468cf2..91f074ca 100644 --- a/config/app.php +++ b/config/app.php @@ -22,7 +22,69 @@ declare(strict_types=1); +use App\Providers\AppServiceProvider; +use App\Providers\EventServiceProvider; +use App\Providers\RouteServiceProvider; use App\Support\Facades\Steam; +use Illuminate\Auth\AuthServiceProvider; +use Illuminate\Auth\Passwords\PasswordResetServiceProvider; +use Illuminate\Broadcasting\BroadcastServiceProvider; +use Illuminate\Bus\BusServiceProvider; +use Illuminate\Cache\CacheServiceProvider; +use Illuminate\Cookie\CookieServiceProvider; +use Illuminate\Database\DatabaseServiceProvider; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Encryption\EncryptionServiceProvider; +use Illuminate\Filesystem\FilesystemServiceProvider; +use Illuminate\Foundation\Providers\ConsoleSupportServiceProvider; +use Illuminate\Foundation\Providers\FoundationServiceProvider; +use Illuminate\Hashing\HashServiceProvider; +use Illuminate\Mail\MailServiceProvider; +use Illuminate\Notifications\NotificationServiceProvider; +use Illuminate\Pagination\PaginationServiceProvider; +use Illuminate\Pipeline\PipelineServiceProvider; +use Illuminate\Queue\QueueServiceProvider; +use Illuminate\Redis\RedisServiceProvider; +use Illuminate\Session\SessionServiceProvider; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Blade; +use Illuminate\Support\Facades\Broadcast; +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Cookie; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Lang; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Facades\Password; +use Illuminate\Support\Facades\Queue; +use Illuminate\Support\Facades\Redirect; +use Illuminate\Support\Facades\Redis; +use Illuminate\Support\Facades\Request; +use Illuminate\Support\Facades\Response; +use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Session; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\URL; +use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Str; +use Illuminate\Translation\TranslationServiceProvider; +use Illuminate\Validation\ValidationServiceProvider; +use Illuminate\View\ViewServiceProvider; +use TwigBridge\Facade\Twig; +use TwigBridge\ServiceProvider; return [ /* @@ -159,38 +221,38 @@ return [ 'providers' => [ // Laravel Framework Service Providers... - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, + AuthServiceProvider::class, + BroadcastServiceProvider::class, + BusServiceProvider::class, + CacheServiceProvider::class, + ConsoleSupportServiceProvider::class, + CookieServiceProvider::class, + DatabaseServiceProvider::class, + EncryptionServiceProvider::class, + FilesystemServiceProvider::class, + FoundationServiceProvider::class, + HashServiceProvider::class, + MailServiceProvider::class, + NotificationServiceProvider::class, + PaginationServiceProvider::class, + PipelineServiceProvider::class, + QueueServiceProvider::class, + RedisServiceProvider::class, + PasswordResetServiceProvider::class, + SessionServiceProvider::class, + TranslationServiceProvider::class, + ValidationServiceProvider::class, + ViewServiceProvider::class, // Package Service Providers... - TwigBridge\ServiceProvider::class, + ServiceProvider::class, // Application Service Providers... - App\Providers\AppServiceProvider::class, + AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, - App\Providers\EventServiceProvider::class, - App\Providers\RouteServiceProvider::class, + EventServiceProvider::class, + RouteServiceProvider::class, ], /* @@ -206,42 +268,42 @@ return [ 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, - 'Arr' => Illuminate\Support\Arr::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Http' => Illuminate\Support\Facades\Http::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'Str' => Illuminate\Support\Str::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - 'Twig' => TwigBridge\Facade\Twig::class, + 'Arr' => Arr::class, + 'Artisan' => Artisan::class, + 'Auth' => Auth::class, + 'Blade' => Blade::class, + 'Broadcast' => Broadcast::class, + 'Bus' => Bus::class, + 'Cache' => Cache::class, + 'Config' => Config::class, + 'Cookie' => Cookie::class, + 'Crypt' => Crypt::class, + 'DB' => DB::class, + 'Eloquent' => Model::class, + 'Event' => Event::class, + 'File' => File::class, + 'Gate' => Gate::class, + 'Hash' => Hash::class, + 'Http' => Http::class, + 'Lang' => Lang::class, + 'Log' => Log::class, + 'Mail' => Mail::class, + 'Notification' => Notification::class, + 'Password' => Password::class, + 'Queue' => Queue::class, + 'Redirect' => Redirect::class, + 'Redis' => Redis::class, + 'Request' => Request::class, + 'Response' => Response::class, + 'Route' => Route::class, + 'Schema' => Schema::class, + 'Session' => Session::class, + 'Storage' => Storage::class, + 'Str' => Str::class, + 'URL' => URL::class, + 'Validator' => Validator::class, + 'View' => View::class, + 'Twig' => Twig::class, 'Steam' => Steam::class, ], ]; diff --git a/config/auth.php b/config/auth.php index 379b21cf..65d536a2 100644 --- a/config/auth.php +++ b/config/auth.php @@ -21,6 +21,7 @@ */ declare(strict_types=1); +use App\User; return [ /* @@ -96,7 +97,7 @@ return [ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => App\User::class, + 'model' => User::class, ], // 'users' => [ diff --git a/phpunit.xml b/phpunit.xml index 53bcd9f3..c5f0d889 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,41 +19,31 @@ ~ You should have received a copy of the GNU Affero General Public License ~ along with this program. If not, see . --> - - - - - ./tests/Unit - - - - ./tests/Feature - - - - - ./app - - - - - - - - - - - - - - - + + + + ./tests/Unit + + + ./tests/Feature + + + + + + + + + + + + + + + + + + ./app + + diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php index 0d14cca5..84b2eca3 100644 --- a/tests/Feature/ExampleTest.php +++ b/tests/Feature/ExampleTest.php @@ -40,6 +40,6 @@ final class ExampleTest extends TestCase { $response = $this->get('/'); - $response->assertStatus(200); + $response->assertStatus(302); } } diff --git a/tests/Unit/Services/CSV/Converter/AmountTest.php b/tests/Unit/Services/CSV/Converter/AmountTest.php new file mode 100644 index 00000000..1bb8cfb6 --- /dev/null +++ b/tests/Unit/Services/CSV/Converter/AmountTest.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Services\CSV\Converter; + +use App\Services\CSV\Converter\Amount; +use Tests\TestCase; + +/** + * @internal + * + * @coversNothing + */ +final class AmountTest extends TestCase +{ + /** + * A basic test example. + */ + public function testBasicTest(): void + { + $amount = new Amount(); + + self::assertSame('0', $amount->convert('0')); + self::assertSame('0.0', $amount->convert('0.0')); + self::assertSame('0.1', $amount->convert('0.1')); + self::assertSame('0.1', $amount->convert(0.1)); + self::assertSame('1', $amount->convert(1)); + self::assertSame('1', $amount->convert('1')); + self::assertSame('1.0', $amount->convert('1.0')); + + self::assertSame('1000.000000000000', $amount->convert('1000,-')); + self::assertSame('1000.00', $amount->convert('1000,00')); + self::assertSame('1000.00', $amount->convert('1.000,00')); + self::assertSame('1000', $amount->convert('1.000,')); + self::assertSame('1000', $amount->convert('1.000')); + self::assertSame('1.00', $amount->convert('1.00')); + } +}