From 5b204640d550bfe877a506eb54a8ea01f228065c Mon Sep 17 00:00:00 2001 From: Nick Breland Date: Tue, 13 Feb 2024 18:21:11 +0000 Subject: [PATCH 1/4] fix: MoneyInput hydration --- src/Forms/Components/MoneyInput.php | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Forms/Components/MoneyInput.php b/src/Forms/Components/MoneyInput.php index 6c3c125..970183f 100644 --- a/src/Forms/Components/MoneyInput.php +++ b/src/Forms/Components/MoneyInput.php @@ -15,17 +15,13 @@ protected function setUp(): void { parent::setUp(); - $this->inputMode('decimal'); - $this->step(0.01); - $this->minValue = 0; - $this->formatStateUsing(function (MoneyInput $component, $state): ?string { - $currency = $component->getCurrency()->getCode(); - $state = MoneyFormatter::parseDecimal($state, $currency, $component->getLocale()); - $this->prepare($component); + $currency = $component->getCurrency(); + $locale = $component->getLocale(); + if (is_null($state)) { return ''; } @@ -33,16 +29,16 @@ protected function setUp(): void return $state; } - return MoneyFormatter::decimalToMoneyString($state / 100, $component->getLocale()); + return MoneyFormatter::format($state, $currency, $locale); }); $this->dehydrateStateUsing(function (MoneyInput $component, $state): string { - $this->prepare($component); - $currency = $component->getCurrency()->getCode(); $state = MoneyFormatter::parseDecimal($state, $currency, $component->getLocale()); + $this->prepare($component); + return $state; }); } @@ -50,15 +46,9 @@ protected function setUp(): void protected function prepare(MoneyInput $component): void { $formattingRules = MoneyFormatter::getFormattingRules($component->getLocale()); - $this->prefix($formattingRules->currencySymbol); - if (config('filament-money-field.use_input_mask')) { $this->mask(RawJs::make('$money($input, \'' . $formattingRules->decimalSeparator . '\', \'' . $formattingRules->groupingSeparator . '\', ' . $formattingRules->fractionDigits . ')')); } - - $this->stripCharacters($formattingRules->groupingSeparator); - // OR - $this->stripCharacters([',', '.', ' ',]); } } From 562f15964520183f120b78a87bd480b2b5395fb9 Mon Sep 17 00:00:00 2001 From: Nick Breland Date: Wed, 14 Feb 2024 10:30:59 +0000 Subject: [PATCH 2/4] fix: decimal format so input works without mask --- src/Forms/Components/MoneyInput.php | 2 +- src/MoneyFormatter.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Forms/Components/MoneyInput.php b/src/Forms/Components/MoneyInput.php index 970183f..6770050 100644 --- a/src/Forms/Components/MoneyInput.php +++ b/src/Forms/Components/MoneyInput.php @@ -29,7 +29,7 @@ protected function setUp(): void return $state; } - return MoneyFormatter::format($state, $currency, $locale); + return MoneyFormatter::formatAsDecimal($state, $currency, $locale); }); $this->dehydrateStateUsing(function (MoneyInput $component, $state): string { diff --git a/src/MoneyFormatter.php b/src/MoneyFormatter.php index 7baa818..e13ec8c 100644 --- a/src/MoneyFormatter.php +++ b/src/MoneyFormatter.php @@ -24,6 +24,19 @@ public static function format($value, $currency, $locale, $monetarySeparator = n return $moneyFormatter->format($money); } + public static function formatAsDecimal($value, $currency, $locale): string + { + if (is_null($value) || $value === '') { + return ''; + } + + $numberFormatter = self::getNumberFormatter($locale, \NumberFormatter::DECIMAL); + $moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies()); + + $money = new Money($value, $currency); + return $moneyFormatter->format($money); // outputs 1.000,00 + } + public static function parseDecimal($moneyString, $currency, $locale): string { if (is_null($moneyString) || $moneyString === '') { From 45b80ba48bd9b3f0b6418efaec0489a5ccdebb01 Mon Sep 17 00:00:00 2001 From: Nick Breland Date: Wed, 14 Feb 2024 10:58:16 +0000 Subject: [PATCH 3/4] test: formatAsDecimal --- tests/MoneyFormatterTest.php | 106 +++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/tests/MoneyFormatterTest.php b/tests/MoneyFormatterTest.php index 87b5488..5e01124 100644 --- a/tests/MoneyFormatterTest.php +++ b/tests/MoneyFormatterTest.php @@ -3,8 +3,10 @@ namespace Pelmered\FilamentMoneyField\Tests; use Money\Currency; use Pelmered\FilamentMoneyField\MoneyFormatter; -use PHPUnit\Framework; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +#[CoversClass(MoneyFormatter::class)] final class MoneyFormatterTest extends TestCase { public static function provideMoneyDataSEK(): array @@ -33,6 +35,32 @@ public static function provideMoneyDataSEK(): array ]; } + public static function provideDecimalMoneyDataSEK(): array + { + return [ + 'thousands' => [ + 1000000, + '10 000,00', + ], + 'decimals' => [ + 10045, + '100,45', + ], + 'millions' => [ + 123456789, + '1 234 567,89', + ], + 'empty_string' => [ + '', + '', + ], + 'null' => [ + null, + '', + ], + ]; + } + public static function provideMoneyDataUSD(): array { return [ @@ -59,6 +87,32 @@ public static function provideMoneyDataUSD(): array ]; } + public static function provideDecimalMoneyDataUSD(): array + { + return [ + 'thousands' => [ + 1000000, + '10,000.00', + ], + 'decimals' => [ + 10045, + '100.45', + ], + 'millions' => [ + 123456789, + '1,234,567.89', + ], + 'empty_string' => [ + '', + '', + ], + 'null' => [ + null, + '', + ], + ]; + } + public static function provideDecimalDataSEK(): array { return [ @@ -110,13 +164,17 @@ public static function provideDecimalDataUSD(): array ], ]; } + + #[DataProvider('provideMoneyDataUSD')] + public function testMoneyFormatterUSD(mixed $input, string $expectedOutput) + { + self::assertSame( + static::replaceNonBreakingSpaces($expectedOutput), + MoneyFormatter::format($input, new Currency('USD'), 'en_US') + ); + } - - /** - * @covers MoneyFormatter::format - * @dataProvider provideMoneyDataSEK - */ - #[Framework\CoversClass(MoneyFormatter::class)] + #[DataProvider('provideMoneyDataSEK')] public function testMoneyFormatterSEK(mixed $input, string $expectedOutput) { self::assertSame( @@ -125,24 +183,28 @@ public function testMoneyFormatterSEK(mixed $input, string $expectedOutput) ); } - /** - * @covers MoneyFormatter::format - * @dataProvider provideMoneyDataUSD - */ - #[Framework\CoversClass(MoneyFormatter::class)] - public function testMoneyFormatterUSD(mixed $input, string $expectedOutput) + #[DataProvider('provideDecimalMoneyDataUSD')] + //#[CoversClass(MoneyFormatter::class)] + public function testMoneyDecimalFormatterUSD(mixed $input, string $expectedOutput) { self::assertSame( static::replaceNonBreakingSpaces($expectedOutput), - MoneyFormatter::format($input, new Currency('USD'), 'en_US') + MoneyFormatter::formatAsDecimal($input, new Currency('USD'), 'en_US') ); } - /** - * @covers MoneyFormatter::parseDecimal - * @dataProvider provideDecimalDataSEK - */ - #[Framework\CoversClass(MoneyFormatter::class)] + #[DataProvider('provideDecimalMoneyDataSEK')] + //#[CoversClass(MoneyFormatter::class)] + public function testMoneyDecimalFormatterSEK(mixed $input, string $expectedOutput) + { + self::assertSame( + static::replaceNonBreakingSpaces($expectedOutput), + MoneyFormatter::formatAsDecimal($input, new Currency('SEK'), 'sv_SE') + ); + } + + #[DataProvider('provideDecimalDataSEK')] + //#[CoversClass(MoneyFormatter::class)] public function testMoneyParserDecimalSEK(mixed $input, string $expectedOutput) { self::assertSame( @@ -151,11 +213,7 @@ public function testMoneyParserDecimalSEK(mixed $input, string $expectedOutput) ); } - /** - * @covers MoneyFormatter::parseDecimal - * @dataProvider provideDecimalDataUSD - */ - #[Framework\CoversClass(MoneyFormatter::class)] + #[DataProvider('provideDecimalDataUSD')] public function testMoneyParserDecimalUSD(mixed $input, string $expectedOutput) { self::assertSame( From b6bc755a8c5c3f8e6cb10ff281f4388236162f05 Mon Sep 17 00:00:00 2001 From: Nick Breland Date: Wed, 14 Feb 2024 12:41:10 +0000 Subject: [PATCH 4/4] feat: max & min validation --- src/Forms/Components/MoneyInput.php | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Forms/Components/MoneyInput.php b/src/Forms/Components/MoneyInput.php index 6770050..0fa18b1 100644 --- a/src/Forms/Components/MoneyInput.php +++ b/src/Forms/Components/MoneyInput.php @@ -47,8 +47,49 @@ protected function prepare(MoneyInput $component): void { $formattingRules = MoneyFormatter::getFormattingRules($component->getLocale()); $this->prefix($formattingRules->currencySymbol); + if (config('filament-money-field.use_input_mask')) { $this->mask(RawJs::make('$money($input, \'' . $formattingRules->decimalSeparator . '\', \'' . $formattingRules->groupingSeparator . '\', ' . $formattingRules->fractionDigits . ')')); } } + + public function minValue(mixed $min): static + { + $this->rule(static function (MoneyInput $component, mixed $state) use ($min) { + return function (string $attribute, mixed $value, \Closure $fail) use ($component, $state, $min) { + + $value = MoneyFormatter::parseDecimal( + $state, + $component->getCurrency()->getCode(), + $component->getLocale() + ); + + if ($value < $min) { + $fail('The :attribute must be greater than or equal to ' . $min . '.'); + } + }; + }); + + return $this; + } + + public function maxValue(mixed $max): static + { + $this->rule(static function (MoneyInput $component, mixed $state) use ($max) { + return function (string $attribute, mixed $value, \Closure $fail) use ($component, $state, $max) { + + $value = MoneyFormatter::parseDecimal( + $state, + $component->getCurrency()->getCode(), + $component->getLocale() + ); + + if ($value > $max) { + $fail('The :attribute must be less than or equal to ' . $max . '.'); + } + }; + }); + + return $this; + } }