浏览代码

feat: almost done with release

3.x
wallo 1年前
父节点
当前提交
05976e1a90
共有 32 个文件被更改,包括 675 次插入457 次删除
  1. 7
    7
      app/Actions/OptionAction/CreateCurrency.php
  2. 39
    0
      app/Casts/JournalEntryCast.php
  3. 2
    2
      app/Casts/MoneyCast.php
  4. 18
    2
      app/Casts/TransactionAmountCast.php
  5. 47
    0
      app/Collections/Accounting/JournalEntryCollection.php
  6. 114
    42
      app/Concerns/HasJournalEntryActions.php
  7. 7
    7
      app/Contracts/AccountHandler.php
  8. 3
    0
      app/Enums/Banking/BankAccountType.php
  9. 7
    7
      app/Facades/Accounting.php
  10. 0
    10
      app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php
  11. 47
    36
      app/Filament/Company/Pages/Accounting/Transactions.php
  12. 1
    1
      app/Filament/Company/Pages/Reports/AccountBalances.php
  13. 13
    74
      app/Filament/Company/Resources/Banking/AccountResource.php
  14. 0
    11
      app/Filament/Company/Resources/Banking/AccountResource/Pages/CreateAccount.php
  15. 70
    0
      app/Filament/Forms/Components/CreateCurrencySelect.php
  16. 1
    1
      app/Filament/Forms/Components/DateRangeSelect.php
  17. 2
    2
      app/Filament/Forms/Components/JournalEntryRepeater.php
  18. 1
    1
      app/Listeners/UpdateAccountBalances.php
  19. 7
    12
      app/Models/Accounting/JournalEntry.php
  20. 8
    0
      app/Models/Accounting/Transaction.php
  21. 6
    14
      app/Observers/JournalEntryObserver.php
  22. 8
    3
      app/Observers/TransactionObserver.php
  23. 14
    0
      app/Providers/MacroServiceProvider.php
  24. 21
    21
      app/Services/AccountService.php
  25. 2
    1
      app/Utilities/Currency/ConfigureCurrencies.php
  26. 17
    0
      app/Utilities/Currency/CurrencyConverter.php
  27. 0
    69
      app/ValueObjects/BalanceValue.php
  28. 79
    0
      app/ValueObjects/Money.php
  29. 54
    49
      composer.lock
  30. 7
    12
      database/migrations/2023_09_08_040159_create_company_defaults_table.php
  31. 73
    73
      package-lock.json
  32. 0
    0
      resources/views/filament/forms/components/journal-entry-repeater.blade.php

+ 7
- 7
app/Actions/OptionAction/CreateCurrency.php 查看文件

7
 
7
 
8
 class CreateCurrency
8
 class CreateCurrency
9
 {
9
 {
10
-    public function create(string $code, string $name, string $rate): Currency
10
+    public static function create(string $code, string $name, string $rate): Currency
11
     {
11
     {
12
         $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
12
         $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
13
 
13
 
14
         $hasDefaultCurrency = $defaultCurrency !== null;
14
         $hasDefaultCurrency = $defaultCurrency !== null;
15
-        $currency_code = currency($code);
15
+        $currency = currency($code);
16
 
16
 
17
         return Currency::create([
17
         return Currency::create([
18
             'name' => $name,
18
             'name' => $name,
19
             'code' => $code,
19
             'code' => $code,
20
             'rate' => $rate,
20
             'rate' => $rate,
21
-            'precision' => $currency_code->getPrecision(),
22
-            'symbol' => $currency_code->getSymbol(),
23
-            'symbol_first' => $currency_code->isSymbolFirst(),
24
-            'decimal_mark' => $currency_code->getDecimalMark(),
25
-            'thousands_separator' => $currency_code->getThousandsSeparator(),
21
+            'precision' => $currency->getPrecision(),
22
+            'symbol' => $currency->getSymbol(),
23
+            'symbol_first' => $currency->isSymbolFirst(),
24
+            'decimal_mark' => $currency->getDecimalMark(),
25
+            'thousands_separator' => $currency->getThousandsSeparator(),
26
             'enabled' => ! $hasDefaultCurrency,
26
             'enabled' => ! $hasDefaultCurrency,
27
         ]);
27
         ]);
28
     }
28
     }

+ 39
- 0
app/Casts/JournalEntryCast.php 查看文件

1
+<?php
2
+
3
+namespace App\Casts;
4
+
5
+use App\Utilities\Currency\CurrencyAccessor;
6
+use App\Utilities\Currency\CurrencyConverter;
7
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
8
+use Illuminate\Database\Eloquent\Model;
9
+use UnexpectedValueException;
10
+
11
+class JournalEntryCast implements CastsAttributes
12
+{
13
+    public function get(Model $model, string $key, mixed $value, array $attributes): string
14
+    {
15
+        $currency_code = CurrencyAccessor::getDefaultCurrency();
16
+
17
+        if ($value !== null) {
18
+            return CurrencyConverter::prepareForMutator($value, $currency_code);
19
+        }
20
+
21
+        return '';
22
+    }
23
+
24
+    /**
25
+     * @throws UnexpectedValueException
26
+     */
27
+    public function set(Model $model, string $key, mixed $value, array $attributes): int
28
+    {
29
+        $currency_code = CurrencyAccessor::getDefaultCurrency();
30
+
31
+        if (is_numeric($value)) {
32
+            $value = (string) $value;
33
+        } elseif (! is_string($value)) {
34
+            throw new UnexpectedValueException('Expected string or numeric value for money cast');
35
+        }
36
+
37
+        return CurrencyConverter::prepareForAccessor($value, $currency_code);
38
+    }
39
+}

+ 2
- 2
app/Casts/MoneyCast.php 查看文件

12
 {
12
 {
13
     public function get(Model $model, string $key, mixed $value, array $attributes): string
13
     public function get(Model $model, string $key, mixed $value, array $attributes): string
14
     {
14
     {
15
-        $currency_code = $model->getAttribute('currency_code') ?? CurrencyAccessor::getDefaultCurrency();
15
+        $currency_code = $attributes['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
16
 
16
 
17
         if ($value !== null) {
17
         if ($value !== null) {
18
             return CurrencyConverter::prepareForMutator($value, $currency_code);
18
             return CurrencyConverter::prepareForMutator($value, $currency_code);
26
      */
26
      */
27
     public function set(Model $model, string $key, mixed $value, array $attributes): int
27
     public function set(Model $model, string $key, mixed $value, array $attributes): int
28
     {
28
     {
29
-        $currency_code = $model->getAttribute('currency_code') ?? CurrencyAccessor::getDefaultCurrency();
29
+        $currency_code = $attributes['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
30
 
30
 
31
         if (is_numeric($value)) {
31
         if (is_numeric($value)) {
32
             $value = (string) $value;
32
             $value = (string) $value;

+ 18
- 2
app/Casts/TransactionAmountCast.php 查看文件

2
 
2
 
3
 namespace App\Casts;
3
 namespace App\Casts;
4
 
4
 
5
+use App\Models\Banking\BankAccount;
5
 use App\Utilities\Currency\CurrencyAccessor;
6
 use App\Utilities\Currency\CurrencyAccessor;
6
 use App\Utilities\Currency\CurrencyConverter;
7
 use App\Utilities\Currency\CurrencyConverter;
7
 use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
8
 use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
13
     public function get(Model $model, string $key, mixed $value, array $attributes): string
14
     public function get(Model $model, string $key, mixed $value, array $attributes): string
14
     {
15
     {
15
         // Attempt to retrieve the currency code from the related bankAccount->account model
16
         // Attempt to retrieve the currency code from the related bankAccount->account model
16
-        $currency_code = $model->bankAccount?->account?->currency_code ?? CurrencyAccessor::getDefaultCurrency();
17
+        $currency_code = $this->getCurrencyCodeFromBankAccountId($attributes['bank_account_id'] ?? null);
17
 
18
 
18
         if ($value !== null) {
19
         if ($value !== null) {
19
             return CurrencyConverter::prepareForMutator($value, $currency_code);
20
             return CurrencyConverter::prepareForMutator($value, $currency_code);
27
      */
28
      */
28
     public function set(Model $model, string $key, mixed $value, array $attributes): int
29
     public function set(Model $model, string $key, mixed $value, array $attributes): int
29
     {
30
     {
30
-        $currency_code = $model->bankAccount?->account?->currency_code ?? CurrencyAccessor::getDefaultCurrency();
31
+        $currency_code = $this->getCurrencyCodeFromBankAccountId($attributes['bank_account_id'] ?? null);
31
 
32
 
32
         if (is_numeric($value)) {
33
         if (is_numeric($value)) {
33
             $value = (string) $value;
34
             $value = (string) $value;
37
 
38
 
38
         return CurrencyConverter::prepareForAccessor($value, $currency_code);
39
         return CurrencyConverter::prepareForAccessor($value, $currency_code);
39
     }
40
     }
41
+
42
+    /**
43
+     * Using this is necessary because the relationship is not always loaded into memory when the cast is called
44
+     * Instead of using: $model->bankAccount->account->currency_code directly, find the bank account and get the currency code
45
+     */
46
+    private function getCurrencyCodeFromBankAccountId(?int $bankAccountId): string
47
+    {
48
+        if ($bankAccountId === null) {
49
+            return CurrencyAccessor::getDefaultCurrency();
50
+        }
51
+
52
+        $bankAccount = BankAccount::find($bankAccountId);
53
+
54
+        return $bankAccount?->account?->currency_code ?? CurrencyAccessor::getDefaultCurrency();
55
+    }
40
 }
56
 }

+ 47
- 0
app/Collections/Accounting/JournalEntryCollection.php 查看文件

1
+<?php
2
+
3
+namespace App\Collections\Accounting;
4
+
5
+use App\Models\Accounting\JournalEntry;
6
+use App\Utilities\Currency\CurrencyAccessor;
7
+use App\Utilities\Currency\CurrencyConverter;
8
+use App\ValueObjects\Money;
9
+use Illuminate\Database\Eloquent\Collection;
10
+
11
+class JournalEntryCollection extends Collection
12
+{
13
+    public function sumDebits(): Money
14
+    {
15
+        $total = $this->reduce(static function ($carry, JournalEntry $item) {
16
+            if ($item->type->isDebit()) {
17
+                $amountAsInteger = CurrencyConverter::prepareForAccessor($item->amount, CurrencyAccessor::getDefaultCurrency());
18
+
19
+                return bcadd($carry, $amountAsInteger, 0);
20
+            }
21
+
22
+            return $carry;
23
+        }, 0);
24
+
25
+        return new Money($total, CurrencyAccessor::getDefaultCurrency());
26
+    }
27
+
28
+    public function sumCredits(): Money
29
+    {
30
+        $total = $this->reduce(static function ($carry, JournalEntry $item) {
31
+            if ($item->type->isCredit()) {
32
+                $amountAsInteger = CurrencyConverter::prepareForAccessor($item->amount, CurrencyAccessor::getDefaultCurrency());
33
+
34
+                return bcadd($carry, $amountAsInteger, 0);
35
+            }
36
+
37
+            return $carry;
38
+        }, 0);
39
+
40
+        return new Money($total, CurrencyAccessor::getDefaultCurrency());
41
+    }
42
+
43
+    public function areBalanced(): bool
44
+    {
45
+        return $this->sumDebits()->getAmount() === $this->sumCredits()->getAmount();
46
+    }
47
+}

+ 114
- 42
app/Concerns/HasJournalEntryActions.php 查看文件

3
 namespace App\Concerns;
3
 namespace App\Concerns;
4
 
4
 
5
 use App\Enums\Accounting\JournalEntryType;
5
 use App\Enums\Accounting\JournalEntryType;
6
+use App\Utilities\Currency\CurrencyAccessor;
6
 
7
 
7
 trait HasJournalEntryActions
8
 trait HasJournalEntryActions
8
 {
9
 {
9
-    public string $debitAmount = '0.00';
10
+    public int $debitAmount = 0;
10
 
11
 
11
-    public string $creditAmount = '0.00';
12
+    public int $creditAmount = 0;
12
 
13
 
13
-    public function setDebitAmount(string $amount): void
14
+    private function formatMoney(int $amount): string
14
     {
15
     {
15
-        $this->debitAmount = $amount;
16
+        return money($amount, CurrencyAccessor::getDefaultCurrency())->format();
16
     }
17
     }
17
 
18
 
18
-    public function getDebitAmount(): string
19
+    /**
20
+     * Expects formatted simple amount: e.g. 1,000.00 or 1.000,00
21
+     *
22
+     * Sets debit amount in cents as integer: e.g. 100000
23
+     */
24
+    public function setDebitAmount(int $amount): void
19
     {
25
     {
20
-        return $this->debitAmount;
26
+        $this->debitAmount = $amount;
21
     }
27
     }
22
 
28
 
23
-    public function getFormattedDebitAmount(): string
29
+    /**
30
+     * Expects formatted simple amount: e.g. 1,000.00 or 1.000,00
31
+     *
32
+     * Sets credit amount in cents as integer: e.g. 100000
33
+     */
34
+    public function setCreditAmount(int $amount): void
24
     {
35
     {
25
-        return money($this->getDebitAmount(), 'USD', true)->format();
36
+        $this->creditAmount = $amount;
26
     }
37
     }
27
 
38
 
28
-    public function setCreditAmount(string $amount): void
39
+    /**
40
+     * Returns debit amount in cents as integer: e.g. 100000
41
+     */
42
+    public function getDebitAmount(): int
29
     {
43
     {
30
-        $this->creditAmount = $amount;
44
+        return $this->debitAmount;
31
     }
45
     }
32
 
46
 
33
-    public function getCreditAmount(): string
47
+    /**
48
+     * Returns credit amount in cents as integer: e.g. 100000
49
+     */
50
+    public function getCreditAmount(): int
34
     {
51
     {
35
         return $this->creditAmount;
52
         return $this->creditAmount;
36
     }
53
     }
37
 
54
 
55
+    /**
56
+     * Expects debit amount in cents as string integer: e.g. 100000
57
+     *
58
+     * Returns formatted amount: e.g. $1,000.00 or €1.000,00
59
+     */
60
+    public function getFormattedDebitAmount(): string
61
+    {
62
+        return $this->formatMoney($this->getDebitAmount());
63
+    }
64
+
65
+    /**
66
+     * Expects credit amount in cents as integer: e.g. 100000
67
+     *
68
+     * Returns formatted amount: e.g. $1,000.00 or €1.000,00
69
+     */
38
     public function getFormattedCreditAmount(): string
70
     public function getFormattedCreditAmount(): string
39
     {
71
     {
40
-        return money($this->getCreditAmount(), 'USD', true)->format();
72
+        return $this->formatMoney($this->getCreditAmount());
41
     }
73
     }
42
 
74
 
43
-    public function getBalanceDifference(): string
75
+    /**
76
+     * Returns balance difference in cents as integer: e.g. 100000
77
+     */
78
+    public function getBalanceDifference(): int
44
     {
79
     {
45
-        return bcsub($this->getDebitAmount(), $this->getCreditAmount(), 2);
80
+        return $this->getDebitAmount() - $this->getCreditAmount();
46
     }
81
     }
47
 
82
 
83
+    /**
84
+     * Returns formatted balance difference: e.g. $1,000.00 or €1.000,00
85
+     */
48
     public function getFormattedBalanceDifference(): string
86
     public function getFormattedBalanceDifference(): string
49
     {
87
     {
50
-        $difference = $this->getBalanceDifference();
51
-        $absoluteDifference = abs((float) $difference);
88
+        $absoluteDifference = abs($this->getBalanceDifference());
52
 
89
 
53
-        return money($absoluteDifference, 'USD', true)->format();
90
+        return $this->formatMoney($absoluteDifference);
54
     }
91
     }
55
 
92
 
93
+    /**
94
+     * Returns boolean indicating whether the journal entry is balanced
95
+     * using the debit and credit integer amounts
96
+     */
56
     public function isJournalEntryBalanced(): bool
97
     public function isJournalEntryBalanced(): bool
57
     {
98
     {
58
-        return bccomp($this->getDebitAmount(), $this->getCreditAmount(), 2) === 0;
99
+        return $this->getDebitAmount() === $this->getCreditAmount();
59
     }
100
     }
60
 
101
 
102
+    /**
103
+     * Resets debit and credit amounts to '0.00'
104
+     */
61
     public function resetJournalEntryAmounts(): void
105
     public function resetJournalEntryAmounts(): void
62
     {
106
     {
63
         $this->reset(['debitAmount', 'creditAmount']);
107
         $this->reset(['debitAmount', 'creditAmount']);
65
 
109
 
66
     public function adjustJournalEntryAmountsForTypeChange(JournalEntryType $newType, JournalEntryType $oldType, ?string $amount): void
110
     public function adjustJournalEntryAmountsForTypeChange(JournalEntryType $newType, JournalEntryType $oldType, ?string $amount): void
67
     {
111
     {
68
-        if ($newType !== $oldType) {
69
-            $normalizedAmount = $amount === null ? '0.00' : rtrim($amount, '.');
70
-            $normalizedAmount = $this->sanitizeAndFormatAmount($normalizedAmount);
71
-
72
-            if (bccomp($normalizedAmount, '0.00', 2) === 0) {
73
-                return;
74
-            }
75
-
76
-            if ($oldType->isDebit() && $newType->isCredit()) {
77
-                $this->setDebitAmount(bcsub($this->getDebitAmount(), $normalizedAmount, 2));
78
-                $this->setCreditAmount(bcadd($this->getCreditAmount(), $normalizedAmount, 2));
79
-            } elseif ($oldType->isCredit() && $newType->isDebit()) {
80
-                $this->setDebitAmount(bcadd($this->getDebitAmount(), $normalizedAmount, 2));
81
-                $this->setCreditAmount(bcsub($this->getCreditAmount(), $normalizedAmount, 2));
82
-            }
112
+        if ($newType === $oldType) {
113
+            return;
114
+        }
115
+
116
+        $amountComplete = $this->ensureCompleteDecimal($amount);
117
+        $normalizedAmount = $this->convertAmountToCents($amountComplete);
118
+
119
+        if ($normalizedAmount === 0) {
120
+            return;
121
+        }
122
+
123
+        if ($oldType->isDebit() && $newType->isCredit()) {
124
+            $this->debitAmount -= $normalizedAmount;
125
+            $this->creditAmount += $normalizedAmount;
126
+        } elseif ($oldType->isCredit() && $newType->isDebit()) {
127
+            $this->debitAmount += $normalizedAmount;
128
+            $this->creditAmount -= $normalizedAmount;
83
         }
129
         }
84
     }
130
     }
85
 
131
 
132
+    /**
133
+     * Expects the journal entry type,
134
+     * the new amount and the old amount as formatted simple amounts: e.g. 1,000.00 or 1.000,00
135
+     * It can expect the amounts as partial amounts: e.g. 1,000. or 1.000, (this needs to be handled by this method)
136
+     */
86
     public function updateJournalEntryAmount(JournalEntryType $journalEntryType, ?string $newAmount, ?string $oldAmount): void
137
     public function updateJournalEntryAmount(JournalEntryType $journalEntryType, ?string $newAmount, ?string $oldAmount): void
87
     {
138
     {
88
         if ($newAmount === $oldAmount) {
139
         if ($newAmount === $oldAmount) {
89
             return;
140
             return;
90
         }
141
         }
91
 
142
 
92
-        $normalizedNewAmount = $newAmount === null ? '0.00' : rtrim($newAmount, '.');
93
-        $normalizedOldAmount = $oldAmount === null ? '0.00' : rtrim($oldAmount, '.');
143
+        $newAmountComplete = $this->ensureCompleteDecimal($newAmount);
144
+        $oldAmountComplete = $this->ensureCompleteDecimal($oldAmount);
94
 
145
 
95
-        $formattedNewAmount = $this->sanitizeAndFormatAmount($normalizedNewAmount);
96
-        $formattedOldAmount = $this->sanitizeAndFormatAmount($normalizedOldAmount);
146
+        $formattedNewAmount = $this->convertAmountToCents($newAmountComplete);
147
+        $formattedOldAmount = $this->convertAmountToCents($oldAmountComplete);
97
 
148
 
98
-        $difference = bcsub($formattedNewAmount, $formattedOldAmount, 2);
149
+        $difference = $formattedNewAmount - $formattedOldAmount;
99
 
150
 
100
         if ($journalEntryType->isDebit()) {
151
         if ($journalEntryType->isDebit()) {
101
-            $this->setDebitAmount(bcadd($this->getDebitAmount(), $difference, 2));
152
+            $this->debitAmount += $difference;
102
         } else {
153
         } else {
103
-            $this->setCreditAmount(bcadd($this->getCreditAmount(), $difference, 2));
154
+            $this->creditAmount += $difference;
104
         }
155
         }
105
     }
156
     }
106
 
157
 
107
-    protected function sanitizeAndFormatAmount(string $amount): string
158
+    private function ensureCompleteDecimal(?string $amount): string
159
+    {
160
+        if ($amount === null) {
161
+            return '0';
162
+        }
163
+
164
+        $currency = currency(CurrencyAccessor::getDefaultCurrency());
165
+        $decimal = $currency->getDecimalMark();
166
+
167
+        if (substr($amount, -1) === $decimal) {
168
+            return '0';
169
+        }
170
+
171
+        return $amount;
172
+    }
173
+
174
+    /**
175
+     * Expects formatted simple amount: e.g. 1,000.00 or 1.000,00
176
+     *
177
+     * Returns sanitized amount in cents as integer: e.g. 100000
178
+     */
179
+    protected function convertAmountToCents(string $amount): int
108
     {
180
     {
109
-        return (string) money($amount, 'USD')->getAmount();
181
+        return money($amount, CurrencyAccessor::getDefaultCurrency(), true)->getAmount();
110
     }
182
     }
111
 }
183
 }

+ 7
- 7
app/Contracts/AccountHandler.php 查看文件

6
 use App\DTO\AccountBalanceReportDTO;
6
 use App\DTO\AccountBalanceReportDTO;
7
 use App\Enums\Accounting\AccountCategory;
7
 use App\Enums\Accounting\AccountCategory;
8
 use App\Models\Accounting\Account;
8
 use App\Models\Accounting\Account;
9
-use App\ValueObjects\BalanceValue;
9
+use App\ValueObjects\Money;
10
 
10
 
11
 interface AccountHandler
11
 interface AccountHandler
12
 {
12
 {
13
-    public function getDebitBalance(Account $account, string $startDate, string $endDate): BalanceValue;
13
+    public function getDebitBalance(Account $account, string $startDate, string $endDate): Money;
14
 
14
 
15
-    public function getCreditBalance(Account $account, string $startDate, string $endDate): BalanceValue;
15
+    public function getCreditBalance(Account $account, string $startDate, string $endDate): Money;
16
 
16
 
17
-    public function getNetMovement(Account $account, string $startDate, string $endDate): BalanceValue;
17
+    public function getNetMovement(Account $account, string $startDate, string $endDate): Money;
18
 
18
 
19
-    public function getStartingBalance(Account $account, string $startDate): ?BalanceValue;
19
+    public function getStartingBalance(Account $account, string $startDate): ?Money;
20
 
20
 
21
-    public function getEndingBalance(Account $account, string $startDate, string $endDate): ?BalanceValue;
21
+    public function getEndingBalance(Account $account, string $startDate, string $endDate): ?Money;
22
 
22
 
23
     public function calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance): int;
23
     public function calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance): int;
24
 
24
 
28
 
28
 
29
     public function buildAccountBalanceReport(string $startDate, string $endDate): AccountBalanceReportDTO;
29
     public function buildAccountBalanceReport(string $startDate, string $endDate): AccountBalanceReportDTO;
30
 
30
 
31
-    public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): BalanceValue;
31
+    public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): Money;
32
 
32
 
33
     public function getAccountCategoryOrder(): array;
33
     public function getAccountCategoryOrder(): array;
34
 
34
 

+ 3
- 0
app/Enums/Banking/BankAccountType.php 查看文件

2
 
2
 
3
 namespace App\Enums\Banking;
3
 namespace App\Enums\Banking;
4
 
4
 
5
+use App\Enums\Concerns\ParsesEnum;
5
 use Filament\Support\Contracts\HasLabel;
6
 use Filament\Support\Contracts\HasLabel;
6
 
7
 
7
 enum BankAccountType: string implements HasLabel
8
 enum BankAccountType: string implements HasLabel
8
 {
9
 {
10
+    use ParsesEnum;
11
+
9
     case Investment = 'investment';
12
     case Investment = 'investment';
10
     case Credit = 'credit';
13
     case Credit = 'credit';
11
     case Depository = 'depository';
14
     case Depository = 'depository';

+ 7
- 7
app/Facades/Accounting.php 查看文件

7
 use App\DTO\AccountBalanceReportDTO;
7
 use App\DTO\AccountBalanceReportDTO;
8
 use App\Enums\Accounting\AccountCategory;
8
 use App\Enums\Accounting\AccountCategory;
9
 use App\Models\Accounting\Account;
9
 use App\Models\Accounting\Account;
10
-use App\ValueObjects\BalanceValue;
10
+use App\ValueObjects\Money;
11
 use Illuminate\Support\Facades\Facade;
11
 use Illuminate\Support\Facades\Facade;
12
 
12
 
13
 /**
13
 /**
14
- * @method static BalanceValue getDebitBalance(Account $account, string $startDate, string $endDate)
15
- * @method static BalanceValue getCreditBalance(Account $account, string $startDate, string $endDate)
16
- * @method static BalanceValue getNetMovement(Account $account, string $startDate, string $endDate)
17
- * @method static BalanceValue|null getStartingBalance(Account $account, string $startDate)
18
- * @method static BalanceValue|null getEndingBalance(Account $account, string $startDate, string $endDate)
14
+ * @method static Money getDebitBalance(Account $account, string $startDate, string $endDate)
15
+ * @method static Money getCreditBalance(Account $account, string $startDate, string $endDate)
16
+ * @method static Money getNetMovement(Account $account, string $startDate, string $endDate)
17
+ * @method static Money|null getStartingBalance(Account $account, string $startDate)
18
+ * @method static Money|null getEndingBalance(Account $account, string $startDate, string $endDate)
19
  * @method static int calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance)
19
  * @method static int calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance)
20
  * @method static array getBalances(Account $account, string $startDate, string $endDate)
20
  * @method static array getBalances(Account $account, string $startDate, string $endDate)
21
  * @method static AccountBalanceDTO formatBalances(array $balances)
21
  * @method static AccountBalanceDTO formatBalances(array $balances)
22
  * @method static AccountBalanceReportDTO buildAccountBalanceReport(string $startDate, string $endDate)
22
  * @method static AccountBalanceReportDTO buildAccountBalanceReport(string $startDate, string $endDate)
23
- * @method static BalanceValue getTotalBalanceForAllBankAccounts(string $startDate, string $endDate)
23
+ * @method static Money getTotalBalanceForAllBankAccounts(string $startDate, string $endDate)
24
  * @method static array getAccountCategoryOrder()
24
  * @method static array getAccountCategoryOrder()
25
  * @method static string getEarliestTransactionDate()
25
  * @method static string getEarliestTransactionDate()
26
  *
26
  *

+ 0
- 10
app/Filament/Company/Clusters/Settings/Resources/CurrencyResource.php 查看文件

12
 use App\Models\Setting\Currency as CurrencyModel;
12
 use App\Models\Setting\Currency as CurrencyModel;
13
 use App\Utilities\Currency\CurrencyAccessor;
13
 use App\Utilities\Currency\CurrencyAccessor;
14
 use Closure;
14
 use Closure;
15
-use Filament\Facades\Filament;
16
 use Filament\Forms;
15
 use Filament\Forms;
17
 use Filament\Forms\Form;
16
 use Filament\Forms\Form;
18
 use Filament\Resources\Resource;
17
 use Filament\Resources\Resource;
40
         return translate($modelLabel);
39
         return translate($modelLabel);
41
     }
40
     }
42
 
41
 
43
-    public static function getNavigationParentItem(): ?string
44
-    {
45
-        if (Filament::hasTopNavigation()) {
46
-            return translate('Finance');
47
-        }
48
-
49
-        return null;
50
-    }
51
-
52
     public static function form(Form $form): Form
42
     public static function form(Form $form): Form
53
     {
43
     {
54
         return $form
44
         return $form

+ 47
- 36
app/Filament/Company/Pages/Accounting/Transactions.php 查看文件

6
 use App\Enums\Accounting\AccountCategory;
6
 use App\Enums\Accounting\AccountCategory;
7
 use App\Enums\Accounting\JournalEntryType;
7
 use App\Enums\Accounting\JournalEntryType;
8
 use App\Enums\Accounting\TransactionType;
8
 use App\Enums\Accounting\TransactionType;
9
-use App\Enums\Setting\DateFormat;
10
 use App\Facades\Accounting;
9
 use App\Facades\Accounting;
11
 use App\Filament\Company\Pages\Service\ConnectedAccount;
10
 use App\Filament\Company\Pages\Service\ConnectedAccount;
12
-use App\Forms\Components\DateRangeSelect;
13
-use App\Forms\Components\JournalEntryRepeater;
11
+use App\Filament\Forms\Components\DateRangeSelect;
12
+use App\Filament\Forms\Components\JournalEntryRepeater;
14
 use App\Models\Accounting\Account;
13
 use App\Models\Accounting\Account;
15
 use App\Models\Accounting\Transaction;
14
 use App\Models\Accounting\Transaction;
16
 use App\Models\Banking\BankAccount;
15
 use App\Models\Banking\BankAccount;
17
 use App\Models\Company;
16
 use App\Models\Company;
18
-use App\Models\Setting\Localization;
19
 use App\Utilities\Currency\CurrencyAccessor;
17
 use App\Utilities\Currency\CurrencyAccessor;
20
 use App\Utilities\Currency\CurrencyConverter;
18
 use App\Utilities\Currency\CurrencyConverter;
21
 use Awcodes\TableRepeater\Header;
19
 use Awcodes\TableRepeater\Header;
41
 use Filament\Support\Enums\IconPosition;
39
 use Filament\Support\Enums\IconPosition;
42
 use Filament\Support\Enums\IconSize;
40
 use Filament\Support\Enums\IconSize;
43
 use Filament\Support\Enums\MaxWidth;
41
 use Filament\Support\Enums\MaxWidth;
44
-use Filament\Support\RawJs;
45
 use Filament\Tables;
42
 use Filament\Tables;
46
 use Filament\Tables\Concerns\InteractsWithTable;
43
 use Filament\Tables\Concerns\InteractsWithTable;
47
 use Filament\Tables\Contracts\HasTable;
44
 use Filament\Tables\Contracts\HasTable;
108
                     ->groupedIcon(null)
105
                     ->groupedIcon(null)
109
                     ->modalHeading('Journal Entry')
106
                     ->modalHeading('Journal Entry')
110
                     ->mutateFormDataUsing(static fn (array $data) => array_merge($data, ['type' => TransactionType::Journal]))
107
                     ->mutateFormDataUsing(static fn (array $data) => array_merge($data, ['type' => TransactionType::Journal]))
111
-                    ->afterFormFilled(fn () => $this->resetJournalEntryAmounts()),
108
+                    ->afterFormFilled(fn () => $this->resetJournalEntryAmounts())
109
+                    ->after(fn (Transaction $transaction) => $transaction->updateAmountIfBalanced()),
112
                 Actions\Action::make('connectBank')
110
                 Actions\Action::make('connectBank')
113
                     ->label('Connect Your Bank')
111
                     ->label('Connect Your Bank')
114
                     ->url(ConnectedAccount::getUrl()),
112
                     ->url(ConnectedAccount::getUrl()),
146
             ->schema([
144
             ->schema([
147
                 Forms\Components\DatePicker::make('posted_at')
145
                 Forms\Components\DatePicker::make('posted_at')
148
                     ->label('Date')
146
                     ->label('Date')
149
-                    ->required()
150
-                    ->displayFormat('Y-m-d'),
147
+                    ->required(),
151
                 Forms\Components\TextInput::make('description')
148
                 Forms\Components\TextInput::make('description')
152
                     ->label('Description'),
149
                     ->label('Description'),
153
                 Forms\Components\Select::make('bank_account_id')
150
                 Forms\Components\Select::make('bank_account_id')
225
                 Tables\Columns\TextColumn::make('posted_at')
222
                 Tables\Columns\TextColumn::make('posted_at')
226
                     ->label('Date')
223
                     ->label('Date')
227
                     ->sortable()
224
                     ->sortable()
228
-                    ->formatStateUsing(static function ($state) {
229
-                        $dateFormat = Localization::firstOrFail()->date_format->value ?? DateFormat::DEFAULT;
230
-
231
-                        return Carbon::parse($state)->translatedFormat($dateFormat);
232
-                    }),
225
+                    ->localizeDate(),
233
                 Tables\Columns\TextColumn::make('description')
226
                 Tables\Columns\TextColumn::make('description')
234
                     ->limit(30)
227
                     ->limit(30)
235
                     ->label('Description'),
228
                     ->label('Description'),
237
                     ->label('Account'),
230
                     ->label('Account'),
238
                 Tables\Columns\TextColumn::make('account.name')
231
                 Tables\Columns\TextColumn::make('account.name')
239
                     ->label('Category')
232
                     ->label('Category')
240
-                    ->state(static fn (Transaction $record) => $record->account->name ?? 'Journal Entry'),
233
+                    ->state(static fn (Transaction $transaction) => $transaction->account->name ?? 'Journal Entry'),
241
                 Tables\Columns\TextColumn::make('amount')
234
                 Tables\Columns\TextColumn::make('amount')
242
                     ->label('Amount')
235
                     ->label('Amount')
243
-                    ->weight(static fn (Transaction $record) => $record->reviewed ? null : FontWeight::SemiBold)
236
+                    ->weight(static fn (Transaction $transaction) => $transaction->reviewed ? null : FontWeight::SemiBold)
244
                     ->color(
237
                     ->color(
245
-                        static fn (Transaction $record) => match ($record->type) {
238
+                        static fn (Transaction $transaction) => match ($transaction->type) {
246
                             TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
239
                             TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
247
                             TransactionType::Journal => 'primary',
240
                             TransactionType::Journal => 'primary',
248
                             default => null,
241
                             default => null,
249
                         }
242
                         }
250
                     )
243
                     )
251
-                    ->currency(static fn (Transaction $record) => $record->bankAccount->account->currency_code ?? 'USD', true)
252
-                    ->state(fn (Transaction $record) => $record->type->isJournal() ? $record->journalEntries->first()->amount : $record->amount),
244
+                    ->currency(static fn (Transaction $transaction) => $transaction->bankAccount->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(), true),
253
             ])
245
             ])
254
-            ->recordClasses(static fn (Transaction $record) => $record->reviewed ? 'bg-primary-300/10' : null)
246
+            ->recordClasses(static fn (Transaction $transaction) => $transaction->reviewed ? 'bg-primary-300/10' : null)
255
             ->defaultSort('posted_at', 'desc')
247
             ->defaultSort('posted_at', 'desc')
256
             ->filters([
248
             ->filters([
257
                 Tables\Filters\Filter::make('filters')
249
                 Tables\Filters\Filter::make('filters')
336
                 Tables\Actions\Action::make('markAsReviewed')
328
                 Tables\Actions\Action::make('markAsReviewed')
337
                     ->label('Mark as Reviewed')
329
                     ->label('Mark as Reviewed')
338
                     ->view('filament.company.components.tables.actions.mark-as-reviewed')
330
                     ->view('filament.company.components.tables.actions.mark-as-reviewed')
339
-                    ->icon(static fn (Transaction $record) => $record->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
340
-                    ->color(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
331
+                    ->icon(static fn (Transaction $transaction) => $transaction->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
332
+                    ->color(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
341
                         'reviewed' => 'primary',
333
                         'reviewed' => 'primary',
342
                         'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
334
                         'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
343
                         'uncategorized' => 'gray',
335
                         'uncategorized' => 'gray',
344
                     })
336
                     })
345
-                    ->tooltip(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
337
+                    ->tooltip(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
346
                         'reviewed' => 'Reviewed',
338
                         'reviewed' => 'Reviewed',
347
                         'unreviewed' => 'Mark as Reviewed',
339
                         'unreviewed' => 'Mark as Reviewed',
348
                         'uncategorized' => 'Categorize first to mark as reviewed',
340
                         'uncategorized' => 'Categorize first to mark as reviewed',
349
                     })
341
                     })
350
-                    ->disabled(fn (Transaction $record): bool => $record->isUncategorized())
351
-                    ->action(fn (Transaction $record) => $record->update(['reviewed' => ! $record->reviewed])),
342
+                    ->disabled(fn (Transaction $transaction): bool => $transaction->isUncategorized())
343
+                    ->action(fn (Transaction $transaction) => $transaction->update(['reviewed' => ! $transaction->reviewed])),
352
                 Tables\Actions\ActionGroup::make([
344
                 Tables\Actions\ActionGroup::make([
353
                     Tables\Actions\EditAction::make('updateTransaction')
345
                     Tables\Actions\EditAction::make('updateTransaction')
354
                         ->label('Edit Transaction')
346
                         ->label('Edit Transaction')
355
                         ->modalHeading('Edit Transaction')
347
                         ->modalHeading('Edit Transaction')
356
                         ->modalWidth(MaxWidth::ThreeExtraLarge)
348
                         ->modalWidth(MaxWidth::ThreeExtraLarge)
357
                         ->form(fn (Form $form) => $this->transactionForm($form))
349
                         ->form(fn (Form $form) => $this->transactionForm($form))
358
-                        ->hidden(static fn (Transaction $record) => $record->type->isJournal()),
350
+                        ->hidden(static fn (Transaction $transaction) => $transaction->type->isJournal()),
359
                     Tables\Actions\EditAction::make('updateJournalTransaction')
351
                     Tables\Actions\EditAction::make('updateJournalTransaction')
360
                         ->label('Edit Journal Transaction')
352
                         ->label('Edit Journal Transaction')
361
                         ->modalHeading('Journal Entry')
353
                         ->modalHeading('Journal Entry')
362
                         ->modalWidth(MaxWidth::Screen)
354
                         ->modalWidth(MaxWidth::Screen)
363
                         ->form(fn (Form $form) => $this->journalTransactionForm($form))
355
                         ->form(fn (Form $form) => $this->journalTransactionForm($form))
364
-                        ->afterFormFilled(function (Transaction $record) {
365
-                            $debitAmounts = $record->journalEntries->where('type', JournalEntryType::Debit)->sum('amount');
366
-                            $creditAmounts = $record->journalEntries->where('type', JournalEntryType::Credit)->sum('amount');
356
+                        ->afterFormFilled(function (Transaction $transaction) {
357
+                            $debitAmounts = $transaction->journalEntries->sumDebits()->getAmount();
358
+                            $creditAmounts = $transaction->journalEntries->sumCredits()->getAmount();
367
 
359
 
368
                             $this->setDebitAmount($debitAmounts);
360
                             $this->setDebitAmount($debitAmounts);
369
                             $this->setCreditAmount($creditAmounts);
361
                             $this->setCreditAmount($creditAmounts);
370
                         })
362
                         })
371
-                        ->visible(static fn (Transaction $record) => $record->type->isJournal()),
363
+                        ->modalSubmitAction(fn (Actions\StaticAction $action) => $action->disabled(! $this->isJournalEntryBalanced()))
364
+                        ->after(fn (Transaction $transaction) => $transaction->updateAmountIfBalanced())
365
+                        ->visible(static fn (Transaction $transaction) => $transaction->type->isJournal()),
372
                     Tables\Actions\DeleteAction::make(),
366
                     Tables\Actions\DeleteAction::make(),
373
                     Tables\Actions\ReplicateAction::make()
367
                     Tables\Actions\ReplicateAction::make()
374
                         ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
368
                         ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
375
                         ->modal(false)
369
                         ->modal(false)
376
-                        ->beforeReplicaSaved(static function (Transaction $replica) {
377
-                            $replica->description = '(Copy of) ' . $replica->description;
370
+                        ->beforeReplicaSaved(static function (Transaction $transaction) {
371
+                            $transaction->description = '(Copy of) ' . $transaction->description;
378
                         }),
372
                         }),
379
                 ])
373
                 ])
380
                     ->dropdownPlacement('bottom-start')
374
                     ->dropdownPlacement('bottom-start')
503
             ->schema($this->getJournalEntriesTableRepeaterSchema())
497
             ->schema($this->getJournalEntriesTableRepeaterSchema())
504
             ->streamlined()
498
             ->streamlined()
505
             ->deletable(fn (JournalEntryRepeater $repeater) => $repeater->getItemsCount() > 2)
499
             ->deletable(fn (JournalEntryRepeater $repeater) => $repeater->getItemsCount() > 2)
500
+            ->deleteAction(function (Forms\Components\Actions\Action $action) {
501
+                return $action
502
+                    ->action(function (array $arguments, JournalEntryRepeater $component): void {
503
+                        $items = $component->getState();
504
+
505
+                        $amount = $items[$arguments['item']]['amount'];
506
+                        $type = $items[$arguments['item']]['type'];
507
+
508
+                        $this->updateJournalEntryAmount(JournalEntryType::parse($type), '0.00', $amount);
509
+
510
+                        unset($items[$arguments['item']]);
511
+
512
+                        $component->state($items);
513
+
514
+                        $component->callAfterStateUpdated();
515
+                    });
516
+            })
506
             ->minItems(2)
517
             ->minItems(2)
507
             ->defaultItems(2)
518
             ->defaultItems(2)
508
             ->addable(false)
519
             ->addable(false)
553
             TextInput::make('amount')
564
             TextInput::make('amount')
554
                 ->label('Amount')
565
                 ->label('Amount')
555
                 ->live()
566
                 ->live()
556
-                ->mask(RawJs::make('$money($input)'))
567
+                ->mask(moneyMask(CurrencyAccessor::getDefaultCurrency()))
557
                 ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?string $old) {
568
                 ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?string $old) {
558
                     $this->updateJournalEntryAmount(JournalEntryType::parse($get('type')), $state, $old);
569
                     $this->updateJournalEntryAmount(JournalEntryType::parse($get('type')), $state, $old);
559
                 })
570
                 })
683
         }
694
         }
684
     }
695
     }
685
 
696
 
686
-    protected static function determineTransactionState(Transaction $record, Tables\Actions\Action $action): string
697
+    protected static function determineTransactionState(Transaction $transaction, Tables\Actions\Action $action): string
687
     {
698
     {
688
-        if ($record->reviewed) {
699
+        if ($transaction->reviewed) {
689
             return 'reviewed';
700
             return 'reviewed';
690
         }
701
         }
691
 
702
 
692
-        if ($record->reviewed === false && $action->isEnabled()) {
703
+        if ($transaction->reviewed === false && $action->isEnabled()) {
693
             return 'unreviewed';
704
             return 'unreviewed';
694
         }
705
         }
695
 
706
 
743
 
754
 
744
     protected function getBalanceForAllAccounts(): string
755
     protected function getBalanceForAllAccounts(): string
745
     {
756
     {
746
-        return Accounting::getTotalBalanceForAllBankAccounts($this->fiscalYearStartDate, $this->fiscalYearEndDate)->formatted();
757
+        return Accounting::getTotalBalanceForAllBankAccounts($this->fiscalYearStartDate, $this->fiscalYearEndDate)->format();
747
     }
758
     }
748
 }
759
 }

+ 1
- 1
app/Filament/Company/Pages/Reports/AccountBalances.php 查看文件

3
 namespace App\Filament\Company\Pages\Reports;
3
 namespace App\Filament\Company\Pages\Reports;
4
 
4
 
5
 use App\DTO\AccountBalanceReportDTO;
5
 use App\DTO\AccountBalanceReportDTO;
6
-use App\Forms\Components\DateRangeSelect;
6
+use App\Filament\Forms\Components\DateRangeSelect;
7
 use App\Models\Company;
7
 use App\Models\Company;
8
 use App\Services\AccountBalancesExportService;
8
 use App\Services\AccountBalancesExportService;
9
 use App\Services\AccountService;
9
 use App\Services\AccountService;

+ 13
- 74
app/Filament/Company/Resources/Banking/AccountResource.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Banking;
3
 namespace App\Filament\Company\Resources\Banking;
4
 
4
 
5
-use App\Actions\OptionAction\CreateCurrency;
6
 use App\Enums\Accounting\AccountCategory;
5
 use App\Enums\Accounting\AccountCategory;
7
 use App\Enums\Banking\BankAccountType;
6
 use App\Enums\Banking\BankAccountType;
8
-use App\Facades\Forex;
9
 use App\Filament\Company\Resources\Banking\AccountResource\Pages;
7
 use App\Filament\Company\Resources\Banking\AccountResource\Pages;
8
+use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Models\Accounting\AccountSubtype;
9
 use App\Models\Accounting\AccountSubtype;
11
 use App\Models\Banking\BankAccount;
10
 use App\Models\Banking\BankAccount;
12
-use App\Utilities\Currency\CurrencyAccessor;
13
-use BackedEnum;
14
 use Filament\Forms;
11
 use Filament\Forms;
15
 use Filament\Forms\Form;
12
 use Filament\Forms\Form;
16
 use Filament\Resources\Resource;
13
 use Filament\Resources\Resource;
19
 use Filament\Tables\Table;
16
 use Filament\Tables\Table;
20
 use Illuminate\Support\Collection;
17
 use Illuminate\Support\Collection;
21
 use Illuminate\Support\Facades\Auth;
18
 use Illuminate\Support\Facades\Auth;
22
-use Illuminate\Support\Facades\DB;
23
 use Illuminate\Validation\Rules\Unique;
19
 use Illuminate\Validation\Rules\Unique;
24
 use Wallo\FilamentSelectify\Components\ToggleButton;
20
 use Wallo\FilamentSelectify\Components\ToggleButton;
25
 
21
 
49
                             ->columnSpan(1)
45
                             ->columnSpan(1)
50
                             ->default(BankAccountType::DEFAULT)
46
                             ->default(BankAccountType::DEFAULT)
51
                             ->live()
47
                             ->live()
52
-                            ->afterStateUpdated(static function (Forms\Set $set, $state, ?BankAccount $record, string $operation) {
48
+                            ->afterStateUpdated(static function (Forms\Set $set, $state, ?BankAccount $bankAccount, string $operation) {
53
                                 if ($operation === 'create') {
49
                                 if ($operation === 'create') {
54
                                     $set('account.subtype_id', null);
50
                                     $set('account.subtype_id', null);
55
-                                } elseif ($operation === 'edit' && $record !== null) {
56
-                                    if ($state !== $record->type->value) {
51
+                                } elseif ($operation === 'edit' && $bankAccount !== null) {
52
+                                    if ($state !== $bankAccount->type->value) {
57
                                         $set('account.subtype_id', null);
53
                                         $set('account.subtype_id', null);
58
                                     } else {
54
                                     } else {
59
-                                        $set('account.subtype_id', $record->account->subtype_id);
55
+                                        $set('account.subtype_id', $bankAccount->account->subtype_id);
60
                                     }
56
                                     }
61
                                 }
57
                                 }
62
                             })
58
                             })
66
                             ->relationship('account')
62
                             ->relationship('account')
67
                             ->schema([
63
                             ->schema([
68
                                 Forms\Components\Select::make('subtype_id')
64
                                 Forms\Components\Select::make('subtype_id')
69
-                                    ->options(static function (Forms\Get $get) {
70
-                                        $typeValue = $get('data.type', true); // Bug: $get('type') returns string on edit, but returns Enum type on create
71
-                                        $typeString = $typeValue instanceof BackedEnum ? $typeValue->value : $typeValue;
72
-
73
-                                        return static::groupSubtypesBySubtypeType($typeString);
74
-                                    })
65
+                                    ->options(static fn (Forms\Get $get) => static::groupSubtypesBySubtypeType(BankAccountType::parse($get('data.type', true))))
75
                                     ->localizeLabel()
66
                                     ->localizeLabel()
76
                                     ->searchable()
67
                                     ->searchable()
77
                                     ->live()
68
                                     ->live()
79
                             ]),
70
                             ]),
80
                         Forms\Components\Group::make()
71
                         Forms\Components\Group::make()
81
                             ->relationship('account')
72
                             ->relationship('account')
82
-                            ->columns(2)
73
+                            ->columns()
83
                             ->columnSpanFull()
74
                             ->columnSpanFull()
84
                             ->schema([
75
                             ->schema([
85
                                 Forms\Components\TextInput::make('name')
76
                                 Forms\Components\TextInput::make('name')
86
                                     ->maxLength(100)
77
                                     ->maxLength(100)
87
                                     ->localizeLabel()
78
                                     ->localizeLabel()
88
                                     ->required(),
79
                                     ->required(),
89
-                                Forms\Components\Select::make('currency_code')
90
-                                    ->localizeLabel('Currency')
91
-                                    ->relationship('currency', 'name')
92
-                                    ->default(CurrencyAccessor::getDefaultCurrency())
93
-                                    ->preload()
94
-                                    ->searchable()
95
-                                    ->live()
96
-                                    ->required()
97
-                                    ->createOptionForm([
98
-                                        Forms\Components\Select::make('code')
99
-                                            ->localizeLabel()
100
-                                            ->searchable()
101
-                                            ->options(CurrencyAccessor::getAvailableCurrencies())
102
-                                            ->live()
103
-                                            ->afterStateUpdated(static function (callable $set, $state) {
104
-                                                if ($state === null) {
105
-                                                    return;
106
-                                                }
107
-
108
-                                                $currency_code = currency($state);
109
-                                                $defaultCurrencyCode = currency()->getCurrency();
110
-                                                $forexEnabled = Forex::isEnabled();
111
-                                                $exchangeRate = $forexEnabled ? Forex::getCachedExchangeRate($defaultCurrencyCode, $state) : null;
112
-
113
-                                                $set('name', $currency_code->getName() ?? '');
114
-
115
-                                                if ($forexEnabled && $exchangeRate !== null) {
116
-                                                    $set('rate', $exchangeRate);
117
-                                                }
118
-                                            })
119
-                                            ->required(),
120
-                                        Forms\Components\TextInput::make('name')
121
-                                            ->localizeLabel()
122
-                                            ->maxLength(100)
123
-                                            ->required(),
124
-                                        Forms\Components\TextInput::make('rate')
125
-                                            ->localizeLabel()
126
-                                            ->numeric()
127
-                                            ->required(),
128
-                                    ])->createOptionAction(static function (Forms\Components\Actions\Action $action) {
129
-                                        return $action
130
-                                            ->label('Add Currency')
131
-                                            ->slideOver()
132
-                                            ->modalWidth('md')
133
-                                            ->action(static function (array $data) {
134
-                                                return DB::transaction(static function () use ($data) {
135
-                                                    $code = $data['code'];
136
-                                                    $name = $data['name'];
137
-                                                    $rate = $data['rate'];
138
-
139
-                                                    return (new CreateCurrency())->create($code, $name, $rate);
140
-                                                });
141
-                                            });
142
-                                    }),
80
+                                CreateCurrencySelect::make('currency_code')
81
+                                    ->relationship('currency', 'name'),
143
                             ]),
82
                             ]),
144
                         Forms\Components\Group::make()
83
                         Forms\Components\Group::make()
145
                             ->columns()
84
                             ->columns()
209
         ];
148
         ];
210
     }
149
     }
211
 
150
 
212
-    public static function groupSubtypesBySubtypeType($typeString): array
151
+    public static function groupSubtypesBySubtypeType(BankAccountType $bankAccountType): array
213
     {
152
     {
214
-        $category = match ($typeString) {
215
-            BankAccountType::Depository->value, BankAccountType::Investment->value => AccountCategory::Asset,
216
-            BankAccountType::Credit->value, BankAccountType::Loan->value => AccountCategory::Liability,
153
+        $category = match ($bankAccountType) {
154
+            BankAccountType::Depository, BankAccountType::Investment => AccountCategory::Asset,
155
+            BankAccountType::Credit, BankAccountType::Loan => AccountCategory::Liability,
217
             default => null,
156
             default => null,
218
         };
157
         };
219
 
158
 

+ 0
- 11
app/Filament/Company/Resources/Banking/AccountResource/Pages/CreateAccount.php 查看文件

4
 
4
 
5
 use App\Filament\Company\Resources\Banking\AccountResource;
5
 use App\Filament\Company\Resources\Banking\AccountResource;
6
 use Filament\Resources\Pages\CreateRecord;
6
 use Filament\Resources\Pages\CreateRecord;
7
-use Illuminate\Database\Eloquent\Model;
8
-use Illuminate\Support\Facades\Log;
9
 
7
 
10
 class CreateAccount extends CreateRecord
8
 class CreateAccount extends CreateRecord
11
 {
9
 {
20
     {
18
     {
21
         $data['enabled'] = (bool) ($data['enabled'] ?? false);
19
         $data['enabled'] = (bool) ($data['enabled'] ?? false);
22
 
20
 
23
-        Log::info('CreateAccount::mutateFormDataBeforeCreate', $data);
24
-
25
         return $data;
21
         return $data;
26
     }
22
     }
27
-
28
-    protected function handleRecordCreation(array $data): Model
29
-    {
30
-        Log::info('CreateAccount::handleRecordCreation', $data);
31
-
32
-        return parent::handleRecordCreation($data);
33
-    }
34
 }
23
 }

+ 70
- 0
app/Filament/Forms/Components/CreateCurrencySelect.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Actions\OptionAction\CreateCurrency;
6
+use App\Utilities\Currency\CurrencyAccessor;
7
+use App\Utilities\Currency\CurrencyConverter;
8
+use Filament\Forms\Components\Actions\Action;
9
+use Filament\Forms\Components\Select;
10
+use Filament\Forms\Components\TextInput;
11
+use Filament\Forms\Set;
12
+use Filament\Support\Enums\MaxWidth;
13
+use Illuminate\Support\Facades\DB;
14
+
15
+class CreateCurrencySelect extends Select
16
+{
17
+    protected function setUp(): void
18
+    {
19
+        parent::setUp();
20
+
21
+        $this->localizeLabel('Currency')
22
+            ->default(CurrencyAccessor::getDefaultCurrency())
23
+            ->preload()
24
+            ->searchable()
25
+            ->live()
26
+            ->required()
27
+            ->createOptionForm($this->createCurrencyForm())
28
+            ->createOptionAction(fn (Action $action) => $this->createCurrencyAction($action));
29
+    }
30
+
31
+    protected function createCurrencyForm(): array
32
+    {
33
+        return [
34
+            Select::make('code')
35
+                ->localizeLabel()
36
+                ->searchable()
37
+                ->options(CurrencyAccessor::getAvailableCurrencies())
38
+                ->live()
39
+                ->afterStateUpdated(static function (Set $set, $state) {
40
+                    CurrencyConverter::handleCurrencyChange($set, $state);
41
+                })
42
+                ->required(),
43
+            TextInput::make('name')
44
+                ->localizeLabel()
45
+                ->maxLength(100)
46
+                ->required(),
47
+            TextInput::make('rate')
48
+                ->localizeLabel()
49
+                ->numeric()
50
+                ->required(),
51
+        ];
52
+    }
53
+
54
+    protected function createCurrencyAction(Action $action): Action
55
+    {
56
+        return $action
57
+            ->label('Add Currency')
58
+            ->slideOver()
59
+            ->modalWidth(MaxWidth::Medium)
60
+            ->action(static function (array $data) {
61
+                return DB::transaction(static function () use ($data) {
62
+                    $code = $data['code'];
63
+                    $name = $data['name'];
64
+                    $rate = $data['rate'];
65
+
66
+                    return CreateCurrency::create($code, $name, $rate);
67
+                });
68
+            });
69
+    }
70
+}

app/Forms/Components/DateRangeSelect.php → app/Filament/Forms/Components/DateRangeSelect.php 查看文件

1
 <?php
1
 <?php
2
 
2
 
3
-namespace App\Forms\Components;
3
+namespace App\Filament\Forms\Components;
4
 
4
 
5
 use App\Facades\Accounting;
5
 use App\Facades\Accounting;
6
 use App\Models\Company;
6
 use App\Models\Company;

app/Forms/Components/JournalEntryRepeater.php → app/Filament/Forms/Components/JournalEntryRepeater.php 查看文件

1
 <?php
1
 <?php
2
 
2
 
3
-namespace App\Forms\Components;
3
+namespace App\Filament\Forms\Components;
4
 
4
 
5
 use Awcodes\TableRepeater\Components\TableRepeater;
5
 use Awcodes\TableRepeater\Components\TableRepeater;
6
 use Closure;
6
 use Closure;
30
 
30
 
31
     public function getView(): string
31
     public function getView(): string
32
     {
32
     {
33
-        return 'forms.components.journal-entry-repeater';
33
+        return 'filament.forms.components.journal-entry-repeater';
34
     }
34
     }
35
 }
35
 }

+ 1
- 1
app/Listeners/UpdateAccountBalances.php 查看文件

36
                 /** @var BankAccount $bankAccount */
36
                 /** @var BankAccount $bankAccount */
37
                 $account = $bankAccount->account;
37
                 $account = $bankAccount->account;
38
 
38
 
39
-                $oldConvertedBalanceInCents = $account->ending_balance->convert()->getConvertedValue();
39
+                $oldConvertedBalanceInCents = $account->ending_balance->convert()->getConvertedAmount();
40
                 $ratio = $event->newRate / $event->oldRate;
40
                 $ratio = $event->newRate / $event->oldRate;
41
                 $newConvertedBalance = bcmul($oldConvertedBalanceInCents, $ratio, 2);
41
                 $newConvertedBalance = bcmul($oldConvertedBalanceInCents, $ratio, 2);
42
                 $newConvertedBalanceInCents = (int) round($newConvertedBalance);
42
                 $newConvertedBalanceInCents = (int) round($newConvertedBalance);

+ 7
- 12
app/Models/Accounting/JournalEntry.php 查看文件

2
 
2
 
3
 namespace App\Models\Accounting;
3
 namespace App\Models\Accounting;
4
 
4
 
5
-use App\Casts\MoneyCast;
5
+use App\Casts\JournalEntryCast;
6
+use App\Collections\Accounting\JournalEntryCollection;
6
 use App\Concerns\Blamable;
7
 use App\Concerns\Blamable;
7
 use App\Concerns\CompanyOwned;
8
 use App\Concerns\CompanyOwned;
8
 use App\Enums\Accounting\JournalEntryType;
9
 use App\Enums\Accounting\JournalEntryType;
10
 use App\Observers\JournalEntryObserver;
11
 use App\Observers\JournalEntryObserver;
11
 use Database\Factories\Accounting\JournalEntryFactory;
12
 use Database\Factories\Accounting\JournalEntryFactory;
12
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
13
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
13
-use Illuminate\Database\Eloquent\Builder;
14
 use Illuminate\Database\Eloquent\Factories\Factory;
14
 use Illuminate\Database\Eloquent\Factories\Factory;
15
 use Illuminate\Database\Eloquent\Factories\HasFactory;
15
 use Illuminate\Database\Eloquent\Factories\HasFactory;
16
 use Illuminate\Database\Eloquent\Model;
16
 use Illuminate\Database\Eloquent\Model;
36
 
36
 
37
     protected $casts = [
37
     protected $casts = [
38
         'type' => JournalEntryType::class,
38
         'type' => JournalEntryType::class,
39
-        'amount' => MoneyCast::class,
39
+        'amount' => JournalEntryCast::class,
40
     ];
40
     ];
41
 
41
 
42
     public function account(): BelongsTo
42
     public function account(): BelongsTo
59
         return $this->account->isUncategorized();
59
         return $this->account->isUncategorized();
60
     }
60
     }
61
 
61
 
62
-    public function scopeDebit(Builder $query): Builder
63
-    {
64
-        return $query->where('type', JournalEntryType::Debit);
65
-    }
66
-
67
-    public function scopeCredit(Builder $query): Builder
62
+    protected static function newFactory(): Factory
68
     {
63
     {
69
-        return $query->where('type', JournalEntryType::Credit);
64
+        return JournalEntryFactory::new();
70
     }
65
     }
71
 
66
 
72
-    protected static function newFactory(): Factory
67
+    public function newCollection(array $models = []): JournalEntryCollection
73
     {
68
     {
74
-        return JournalEntryFactory::new();
69
+        return new JournalEntryCollection($models);
75
     }
70
     }
76
 }
71
 }

+ 8
- 0
app/Models/Accounting/Transaction.php 查看文件

76
         return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
76
         return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
77
     }
77
     }
78
 
78
 
79
+    public function updateAmountIfBalanced(): void
80
+    {
81
+        if ($this->journalEntries->areBalanced() && $this->journalEntries->sumDebits()->formatSimple() !== $this->getAttributeValue('amount')) {
82
+            $this->setAttribute('amount', $this->journalEntries->sumDebits()->formatSimple());
83
+            $this->save();
84
+        }
85
+    }
86
+
79
     protected static function newFactory(): Factory
87
     protected static function newFactory(): Factory
80
     {
88
     {
81
         return TransactionFactory::new();
89
         return TransactionFactory::new();

+ 6
- 14
app/Observers/JournalEntryObserver.php 查看文件

15
     }
15
     }
16
 
16
 
17
     /**
17
     /**
18
-     * Handle the JournalEntry "deleting" event.
19
-     */
20
-    public function deleting(JournalEntry $journalEntry): void
21
-    {
22
-        //
23
-    }
24
-
25
-    /**
26
-     * Handle the JournalEntry "deleted" event.
18
+     * Handle the JournalEntry "updated" event.
27
      */
19
      */
28
-    public function deleted(JournalEntry $journalEntry): void
20
+    public function updated(JournalEntry $journalEntry): void
29
     {
21
     {
30
         //
22
         //
31
     }
23
     }
32
 
24
 
33
     /**
25
     /**
34
-     * Handle the JournalEntry "restored" event.
26
+     * Handle the JournalEntry "deleting" event.
35
      */
27
      */
36
-    public function restored(JournalEntry $journalEntry): void
28
+    public function deleting(JournalEntry $journalEntry): void
37
     {
29
     {
38
         //
30
         //
39
     }
31
     }
40
 
32
 
41
     /**
33
     /**
42
-     * Handle the JournalEntry "force deleted" event.
34
+     * Handle the JournalEntry "deleted" event.
43
      */
35
      */
44
-    public function forceDeleted(JournalEntry $journalEntry): void
36
+    public function deleted(JournalEntry $journalEntry): void
45
     {
37
     {
46
         //
38
         //
47
     }
39
     }

+ 8
- 3
app/Observers/TransactionObserver.php 查看文件

35
      */
35
      */
36
     public function updated(Transaction $transaction): void
36
     public function updated(Transaction $transaction): void
37
     {
37
     {
38
+        $transaction->refresh(); // DO NOT REMOVE
39
+
38
         if ($transaction->type->isJournal() || $this->hasRelevantChanges($transaction) === false) {
40
         if ($transaction->type->isJournal() || $this->hasRelevantChanges($transaction) === false) {
39
             return;
41
             return;
40
         }
42
         }
105
     private function getConvertedTransactionAmount(Transaction $transaction): string
107
     private function getConvertedTransactionAmount(Transaction $transaction): string
106
     {
108
     {
107
         $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
109
         $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
108
-        $transactionCurrency = $transaction->bankAccount->account->currency_code; // only account which would have a different currency compared to the default currency
110
+        $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
111
+        $chartAccountCurrency = $transaction->account->currency_code;
109
 
112
 
110
-        if ($transactionCurrency !== $defaultCurrency) {
111
-            return $this->convertToDefaultCurrency($transaction->amount, $transactionCurrency, $defaultCurrency);
113
+        if ($bankAccountCurrency !== $defaultCurrency) {
114
+            return $this->convertToDefaultCurrency($transaction->amount, $bankAccountCurrency, $defaultCurrency);
115
+        } elseif ($chartAccountCurrency !== $defaultCurrency) {
116
+            return $this->convertToDefaultCurrency($transaction->amount, $chartAccountCurrency, $defaultCurrency);
112
         }
117
         }
113
 
118
 
114
         return $transaction->amount;
119
         return $transaction->amount;

+ 14
- 0
app/Providers/MacroServiceProvider.php 查看文件

4
 
4
 
5
 use Akaunting\Money\Currency;
5
 use Akaunting\Money\Currency;
6
 use Akaunting\Money\Money;
6
 use Akaunting\Money\Money;
7
+use App\Enums\Setting\DateFormat;
7
 use App\Models\Accounting\AccountSubtype;
8
 use App\Models\Accounting\AccountSubtype;
9
+use App\Models\Setting\Localization;
8
 use App\Utilities\Accounting\AccountCode;
10
 use App\Utilities\Accounting\AccountCode;
9
 use App\Utilities\Currency\CurrencyAccessor;
11
 use App\Utilities\Currency\CurrencyAccessor;
10
 use BackedEnum;
12
 use BackedEnum;
12
 use Filament\Forms\Components\Field;
14
 use Filament\Forms\Components\Field;
13
 use Filament\Forms\Components\TextInput;
15
 use Filament\Forms\Components\TextInput;
14
 use Filament\Tables\Columns\TextColumn;
16
 use Filament\Tables\Columns\TextColumn;
17
+use Illuminate\Support\Carbon;
15
 use Illuminate\Support\ServiceProvider;
18
 use Illuminate\Support\ServiceProvider;
16
 use Illuminate\Support\Str;
19
 use Illuminate\Support\Str;
17
 
20
 
51
             return $this;
54
             return $this;
52
         });
55
         });
53
 
56
 
57
+        TextColumn::macro('localizeDate', function (): static {
58
+            $localization = Localization::firstOrFail();
59
+
60
+            $dateFormat = $localization->date_format->value ?? DateFormat::DEFAULT;
61
+            $timezone = $localization->timezone ?? Carbon::now()->timezoneName;
62
+
63
+            $this->date($dateFormat, $timezone);
64
+
65
+            return $this;
66
+        });
67
+
54
         TextColumn::macro('currency', function (string | Closure | null $currency = null, ?bool $convert = null): static {
68
         TextColumn::macro('currency', function (string | Closure | null $currency = null, ?bool $convert = null): static {
55
             $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $convert): ?string {
69
             $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $convert): ?string {
56
                 if (blank($state)) {
70
                 if (blank($state)) {

+ 21
- 21
app/Services/AccountService.php 查看文件

13
 use App\Models\Banking\BankAccount;
13
 use App\Models\Banking\BankAccount;
14
 use App\Repositories\Accounting\JournalEntryRepository;
14
 use App\Repositories\Accounting\JournalEntryRepository;
15
 use App\Utilities\Currency\CurrencyAccessor;
15
 use App\Utilities\Currency\CurrencyAccessor;
16
-use App\ValueObjects\BalanceValue;
16
+use App\ValueObjects\Money;
17
 use Illuminate\Database\Eloquent\Collection;
17
 use Illuminate\Database\Eloquent\Collection;
18
 
18
 
19
 class AccountService implements AccountHandler
19
 class AccountService implements AccountHandler
25
         $this->journalEntryRepository = $journalEntryRepository;
25
         $this->journalEntryRepository = $journalEntryRepository;
26
     }
26
     }
27
 
27
 
28
-    public function getDebitBalance(Account $account, string $startDate, string $endDate): BalanceValue
28
+    public function getDebitBalance(Account $account, string $startDate, string $endDate): Money
29
     {
29
     {
30
         $amount = $this->journalEntryRepository->sumDebitAmounts($account, $startDate, $endDate);
30
         $amount = $this->journalEntryRepository->sumDebitAmounts($account, $startDate, $endDate);
31
 
31
 
32
-        return new BalanceValue($amount, $account->currency_code);
32
+        return new Money($amount, $account->currency_code);
33
     }
33
     }
34
 
34
 
35
-    public function getCreditBalance(Account $account, string $startDate, string $endDate): BalanceValue
35
+    public function getCreditBalance(Account $account, string $startDate, string $endDate): Money
36
     {
36
     {
37
         $amount = $this->journalEntryRepository->sumCreditAmounts($account, $startDate, $endDate);
37
         $amount = $this->journalEntryRepository->sumCreditAmounts($account, $startDate, $endDate);
38
 
38
 
39
-        return new BalanceValue($amount, $account->currency_code);
39
+        return new Money($amount, $account->currency_code);
40
     }
40
     }
41
 
41
 
42
-    public function getNetMovement(Account $account, string $startDate, string $endDate): BalanceValue
42
+    public function getNetMovement(Account $account, string $startDate, string $endDate): Money
43
     {
43
     {
44
         $debitBalance = $this->journalEntryRepository->sumDebitAmounts($account, $startDate, $endDate);
44
         $debitBalance = $this->journalEntryRepository->sumDebitAmounts($account, $startDate, $endDate);
45
         $creditBalance = $this->journalEntryRepository->sumCreditAmounts($account, $startDate, $endDate);
45
         $creditBalance = $this->journalEntryRepository->sumCreditAmounts($account, $startDate, $endDate);
46
         $netMovement = $this->calculateNetMovementByCategory($account->category, $debitBalance, $creditBalance);
46
         $netMovement = $this->calculateNetMovementByCategory($account->category, $debitBalance, $creditBalance);
47
 
47
 
48
-        return new BalanceValue($netMovement, $account->currency_code);
48
+        return new Money($netMovement, $account->currency_code);
49
     }
49
     }
50
 
50
 
51
-    public function getStartingBalance(Account $account, string $startDate): ?BalanceValue
51
+    public function getStartingBalance(Account $account, string $startDate): ?Money
52
     {
52
     {
53
         if (in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
53
         if (in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
54
             return null;
54
             return null;
58
         $creditBalanceBefore = $this->journalEntryRepository->sumCreditAmounts($account, $startDate);
58
         $creditBalanceBefore = $this->journalEntryRepository->sumCreditAmounts($account, $startDate);
59
         $startingBalance = $this->calculateNetMovementByCategory($account->category, $debitBalanceBefore, $creditBalanceBefore);
59
         $startingBalance = $this->calculateNetMovementByCategory($account->category, $debitBalanceBefore, $creditBalanceBefore);
60
 
60
 
61
-        return new BalanceValue($startingBalance, $account->currency_code);
61
+        return new Money($startingBalance, $account->currency_code);
62
     }
62
     }
63
 
63
 
64
-    public function getEndingBalance(Account $account, string $startDate, string $endDate): ?BalanceValue
64
+    public function getEndingBalance(Account $account, string $startDate, string $endDate): ?Money
65
     {
65
     {
66
         if (in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
66
         if (in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
67
             return null;
67
             return null;
68
         }
68
         }
69
 
69
 
70
-        $startingBalance = $this->getStartingBalance($account, $startDate)?->getValue();
71
-        $netMovement = $this->getNetMovement($account, $startDate, $endDate)->getValue();
70
+        $startingBalance = $this->getStartingBalance($account, $startDate)?->getAmount();
71
+        $netMovement = $this->getNetMovement($account, $startDate, $endDate)->getAmount();
72
         $endingBalance = $startingBalance + $netMovement;
72
         $endingBalance = $startingBalance + $netMovement;
73
 
73
 
74
-        return new BalanceValue($endingBalance, $account->currency_code);
74
+        return new Money($endingBalance, $account->currency_code);
75
     }
75
     }
76
 
76
 
77
     public function calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance): int
77
     public function calculateNetMovementByCategory(AccountCategory $category, int $debitBalance, int $creditBalance): int
84
 
84
 
85
     public function getBalances(Account $account, string $startDate, string $endDate): array
85
     public function getBalances(Account $account, string $startDate, string $endDate): array
86
     {
86
     {
87
-        $debitBalance = $this->getDebitBalance($account, $startDate, $endDate)->getValue();
88
-        $creditBalance = $this->getCreditBalance($account, $startDate, $endDate)->getValue();
89
-        $netMovement = $this->getNetMovement($account, $startDate, $endDate)->getValue();
87
+        $debitBalance = $this->getDebitBalance($account, $startDate, $endDate)->getAmount();
88
+        $creditBalance = $this->getCreditBalance($account, $startDate, $endDate)->getAmount();
89
+        $netMovement = $this->getNetMovement($account, $startDate, $endDate)->getAmount();
90
 
90
 
91
         $balances = [
91
         $balances = [
92
             'debit_balance' => $debitBalance,
92
             'debit_balance' => $debitBalance,
95
         ];
95
         ];
96
 
96
 
97
         if (! in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
97
         if (! in_array($account->category, [AccountCategory::Expense, AccountCategory::Revenue], true)) {
98
-            $balances['starting_balance'] = $this->getStartingBalance($account, $startDate)?->getValue();
99
-            $balances['ending_balance'] = $this->getEndingBalance($account, $startDate, $endDate)?->getValue();
98
+            $balances['starting_balance'] = $this->getStartingBalance($account, $startDate)?->getAmount();
99
+            $balances['ending_balance'] = $this->getEndingBalance($account, $startDate, $endDate)?->getAmount();
100
         }
100
         }
101
 
101
 
102
         return $balances;
102
         return $balances;
187
         return new AccountBalanceReportDTO($accountCategories, $formattedReportTotalBalances);
187
         return new AccountBalanceReportDTO($accountCategories, $formattedReportTotalBalances);
188
     }
188
     }
189
 
189
 
190
-    public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): BalanceValue
190
+    public function getTotalBalanceForAllBankAccounts(string $startDate, string $endDate): Money
191
     {
191
     {
192
         $bankAccountsAccounts = Account::where('accountable_type', BankAccount::class)
192
         $bankAccountsAccounts = Account::where('accountable_type', BankAccount::class)
193
             ->get();
193
             ->get();
196
 
196
 
197
         // Get ending balance for each bank account
197
         // Get ending balance for each bank account
198
         foreach ($bankAccountsAccounts as $account) {
198
         foreach ($bankAccountsAccounts as $account) {
199
-            $endingBalance = $this->getEndingBalance($account, $startDate, $endDate)?->getValue() ?? 0;
199
+            $endingBalance = $this->getEndingBalance($account, $startDate, $endDate)?->getAmount() ?? 0;
200
             $totalBalance += $endingBalance;
200
             $totalBalance += $endingBalance;
201
         }
201
         }
202
 
202
 
203
-        return new BalanceValue($totalBalance, CurrencyAccessor::getDefaultCurrency());
203
+        return new Money($totalBalance, CurrencyAccessor::getDefaultCurrency());
204
     }
204
     }
205
 
205
 
206
     public function getAccountCategoryOrder(): array
206
     public function getAccountCategoryOrder(): array

+ 2
- 1
app/Utilities/Currency/ConfigureCurrencies.php 查看文件

38
         $customCurrencies = [];
38
         $customCurrencies = [];
39
 
39
 
40
         foreach ($currencies as $currency) {
40
         foreach ($currencies as $currency) {
41
+            /** @var CurrencyModel $currency */
41
             $customCurrencies[$currency->code] = [
42
             $customCurrencies[$currency->code] = [
42
                 'name' => $currency->name,
43
                 'name' => $currency->name,
43
                 'rate' => $currency->rate,
44
                 'rate' => $currency->rate,
60
             try {
61
             try {
61
                 $name = Currencies::getName($code, app()->getLocale());
62
                 $name = Currencies::getName($code, app()->getLocale());
62
                 $existingCurrencies[$code]['name'] = ucwords($name);
63
                 $existingCurrencies[$code]['name'] = ucwords($name);
63
-            } catch (MissingResourceException $e) {
64
+            } catch (MissingResourceException) {
64
                 $existingCurrencies[$code]['name'] = $currency['name'];
65
                 $existingCurrencies[$code]['name'] = $currency['name'];
65
             }
66
             }
66
         }
67
         }

+ 17
- 0
app/Utilities/Currency/CurrencyConverter.php 查看文件

2
 
2
 
3
 namespace App\Utilities\Currency;
3
 namespace App\Utilities\Currency;
4
 
4
 
5
+use App\Facades\Forex;
6
+use Filament\Forms\Set;
7
+
5
 class CurrencyConverter
8
 class CurrencyConverter
6
 {
9
 {
7
     public static function convertAndSet($newCurrency, $oldCurrency, $amount): ?string
10
     public static function convertAndSet($newCurrency, $oldCurrency, $amount): ?string
31
     {
34
     {
32
         return money($balance, $currency, true)->getAmount();
35
         return money($balance, $currency, true)->getAmount();
33
     }
36
     }
37
+
38
+    public static function handleCurrencyChange(Set $set, $state): void
39
+    {
40
+        $currency = currency($state);
41
+        $defaultCurrencyCode = CurrencyAccessor::getDefaultCurrency();
42
+        $forexEnabled = Forex::isEnabled();
43
+        $exchangeRate = $forexEnabled ? Forex::getCachedExchangeRate($defaultCurrencyCode, $state) : null;
44
+
45
+        $set('name', $currency->getName() ?? '');
46
+
47
+        if ($forexEnabled && $exchangeRate !== null) {
48
+            $set('rate', $exchangeRate);
49
+        }
50
+    }
34
 }
51
 }

+ 0
- 69
app/ValueObjects/BalanceValue.php 查看文件

1
-<?php
2
-
3
-namespace App\ValueObjects;
4
-
5
-use App\Utilities\Currency\CurrencyAccessor;
6
-use App\Utilities\Currency\CurrencyConverter;
7
-
8
-class BalanceValue
9
-{
10
-    private int $value;
11
-
12
-    private string $currency;
13
-
14
-    private ?int $convertedValue = null;
15
-
16
-    public function __construct(int $value, string $currency)
17
-    {
18
-        $this->value = $value;
19
-        $this->currency = $currency;
20
-    }
21
-
22
-    public function getValue(): int
23
-    {
24
-        return $this->value;
25
-    }
26
-
27
-    public function getEffectiveValue(): int
28
-    {
29
-        return $this->convertedValue ?? $this->value;
30
-    }
31
-
32
-    public function getConvertedValue(): ?int
33
-    {
34
-        return $this->convertedValue;
35
-    }
36
-
37
-    public function getCurrency(): string
38
-    {
39
-        return $this->currency;
40
-    }
41
-
42
-    public function formatted(): string
43
-    {
44
-        return money($this->getEffectiveValue(), $this->getCurrency())->format();
45
-    }
46
-
47
-    public function formattedSimple(): string
48
-    {
49
-        return money($this->getEffectiveValue(), $this->getCurrency())->formatSimple();
50
-    }
51
-
52
-    public function formatWithCode(bool $codeBefore = false): string
53
-    {
54
-        return money($this->getEffectiveValue(), $this->getCurrency())->formatWithCode($codeBefore);
55
-    }
56
-
57
-    public function convert(): self
58
-    {
59
-        // The journal entry sums are stored in the default currency not the account currency (transaction amounts are stored in the account currency)
60
-        $fromCurrency = CurrencyAccessor::getDefaultCurrency();
61
-        $toCurrency = $this->currency;
62
-
63
-        if ($fromCurrency !== $toCurrency) {
64
-            $this->convertedValue = CurrencyConverter::convertBalance($this->value, $fromCurrency, $toCurrency);
65
-        }
66
-
67
-        return $this;
68
-    }
69
-}

+ 79
- 0
app/ValueObjects/Money.php 查看文件

1
+<?php
2
+
3
+namespace App\ValueObjects;
4
+
5
+use App\Utilities\Currency\CurrencyAccessor;
6
+use App\Utilities\Currency\CurrencyConverter;
7
+
8
+class Money
9
+{
10
+    private int $amount;
11
+
12
+    private string $currencyCode;
13
+
14
+    private ?int $convertedAmount = null;
15
+
16
+    public function __construct(int $amount, string $currencyCode)
17
+    {
18
+        $this->amount = $amount;
19
+        $this->currencyCode = $currencyCode;
20
+    }
21
+
22
+    public function getAmount(): int
23
+    {
24
+        return $this->amount;
25
+    }
26
+
27
+    public function getCurrencyCode(): string
28
+    {
29
+        return $this->currencyCode;
30
+    }
31
+
32
+    public function getEffectiveAmount(): int
33
+    {
34
+        return $this->convertedAmount ?? $this->amount;
35
+    }
36
+
37
+    public function getConvertedAmount(): ?int
38
+    {
39
+        return $this->convertedAmount;
40
+    }
41
+
42
+    public function getValue(): float
43
+    {
44
+        return money($this->amount, $this->currencyCode)->getValue();
45
+    }
46
+
47
+    public function format(): string
48
+    {
49
+        return money($this->getEffectiveAmount(), $this->getCurrencyCode())->format();
50
+    }
51
+
52
+    public function formatSimple(): string
53
+    {
54
+        return money($this->getEffectiveAmount(), $this->getCurrencyCode())->formatSimple();
55
+    }
56
+
57
+    public function formatWithCode(bool $codeBefore = false): string
58
+    {
59
+        return money($this->getEffectiveAmount(), $this->getCurrencyCode())->formatWithCode($codeBefore);
60
+    }
61
+
62
+    public function convert(): self
63
+    {
64
+        // The journal entry sums are stored in the default currency not the account currency (transaction amounts are stored in the account currency)
65
+        $fromCurrency = CurrencyAccessor::getDefaultCurrency();
66
+        $toCurrency = $this->currencyCode;
67
+
68
+        if ($fromCurrency !== $toCurrency) {
69
+            $this->convertedAmount = CurrencyConverter::convertBalance($this->amount, $fromCurrency, $toCurrency);
70
+        }
71
+
72
+        return $this;
73
+    }
74
+
75
+    public function __toString(): string
76
+    {
77
+        return $this->formatSimple();
78
+    }
79
+}

+ 54
- 49
composer.lock 查看文件

974
         },
974
         },
975
         {
975
         {
976
             "name": "brick/math",
976
             "name": "brick/math",
977
-            "version": "0.11.0",
977
+            "version": "0.12.1",
978
             "source": {
978
             "source": {
979
                 "type": "git",
979
                 "type": "git",
980
                 "url": "https://github.com/brick/math.git",
980
                 "url": "https://github.com/brick/math.git",
981
-                "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478"
981
+                "reference": "f510c0a40911935b77b86859eb5223d58d660df1"
982
             },
982
             },
983
             "dist": {
983
             "dist": {
984
                 "type": "zip",
984
                 "type": "zip",
985
-                "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478",
986
-                "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478",
985
+                "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
986
+                "reference": "f510c0a40911935b77b86859eb5223d58d660df1",
987
                 "shasum": ""
987
                 "shasum": ""
988
             },
988
             },
989
             "require": {
989
             "require": {
990
-                "php": "^8.0"
990
+                "php": "^8.1"
991
             },
991
             },
992
             "require-dev": {
992
             "require-dev": {
993
                 "php-coveralls/php-coveralls": "^2.2",
993
                 "php-coveralls/php-coveralls": "^2.2",
994
-                "phpunit/phpunit": "^9.0",
995
-                "vimeo/psalm": "5.0.0"
994
+                "phpunit/phpunit": "^10.1",
995
+                "vimeo/psalm": "5.16.0"
996
             },
996
             },
997
             "type": "library",
997
             "type": "library",
998
             "autoload": {
998
             "autoload": {
1012
                 "arithmetic",
1012
                 "arithmetic",
1013
                 "bigdecimal",
1013
                 "bigdecimal",
1014
                 "bignum",
1014
                 "bignum",
1015
+                "bignumber",
1015
                 "brick",
1016
                 "brick",
1016
-                "math"
1017
+                "decimal",
1018
+                "integer",
1019
+                "math",
1020
+                "mathematics",
1021
+                "rational"
1017
             ],
1022
             ],
1018
             "support": {
1023
             "support": {
1019
                 "issues": "https://github.com/brick/math/issues",
1024
                 "issues": "https://github.com/brick/math/issues",
1020
-                "source": "https://github.com/brick/math/tree/0.11.0"
1025
+                "source": "https://github.com/brick/math/tree/0.12.1"
1021
             },
1026
             },
1022
             "funding": [
1027
             "funding": [
1023
                 {
1028
                 {
1025
                     "type": "github"
1030
                     "type": "github"
1026
                 }
1031
                 }
1027
             ],
1032
             ],
1028
-            "time": "2023-01-15T23:15:59+00:00"
1033
+            "time": "2023-11-29T23:19:16+00:00"
1029
         },
1034
         },
1030
         {
1035
         {
1031
             "name": "carbonphp/carbon-doctrine-types",
1036
             "name": "carbonphp/carbon-doctrine-types",
1980
         },
1985
         },
1981
         {
1986
         {
1982
             "name": "filament/actions",
1987
             "name": "filament/actions",
1983
-            "version": "v3.2.71",
1988
+            "version": "v3.2.72",
1984
             "source": {
1989
             "source": {
1985
                 "type": "git",
1990
                 "type": "git",
1986
                 "url": "https://github.com/filamentphp/actions.git",
1991
                 "url": "https://github.com/filamentphp/actions.git",
2033
         },
2038
         },
2034
         {
2039
         {
2035
             "name": "filament/filament",
2040
             "name": "filament/filament",
2036
-            "version": "v3.2.71",
2041
+            "version": "v3.2.72",
2037
             "source": {
2042
             "source": {
2038
                 "type": "git",
2043
                 "type": "git",
2039
                 "url": "https://github.com/filamentphp/panels.git",
2044
                 "url": "https://github.com/filamentphp/panels.git",
2040
-                "reference": "e00510fb4edc73b027f784317381f791b47c22de"
2045
+                "reference": "28dc99ec5865982a1859a34e48ee88fb59f58313"
2041
             },
2046
             },
2042
             "dist": {
2047
             "dist": {
2043
                 "type": "zip",
2048
                 "type": "zip",
2044
-                "url": "https://api.github.com/repos/filamentphp/panels/zipball/e00510fb4edc73b027f784317381f791b47c22de",
2045
-                "reference": "e00510fb4edc73b027f784317381f791b47c22de",
2049
+                "url": "https://api.github.com/repos/filamentphp/panels/zipball/28dc99ec5865982a1859a34e48ee88fb59f58313",
2050
+                "reference": "28dc99ec5865982a1859a34e48ee88fb59f58313",
2046
                 "shasum": ""
2051
                 "shasum": ""
2047
             },
2052
             },
2048
             "require": {
2053
             "require": {
2094
                 "issues": "https://github.com/filamentphp/filament/issues",
2099
                 "issues": "https://github.com/filamentphp/filament/issues",
2095
                 "source": "https://github.com/filamentphp/filament"
2100
                 "source": "https://github.com/filamentphp/filament"
2096
             },
2101
             },
2097
-            "time": "2024-04-21T22:24:02+00:00"
2102
+            "time": "2024-04-28T08:39:16+00:00"
2098
         },
2103
         },
2099
         {
2104
         {
2100
             "name": "filament/forms",
2105
             "name": "filament/forms",
2101
-            "version": "v3.2.71",
2106
+            "version": "v3.2.72",
2102
             "source": {
2107
             "source": {
2103
                 "type": "git",
2108
                 "type": "git",
2104
                 "url": "https://github.com/filamentphp/forms.git",
2109
                 "url": "https://github.com/filamentphp/forms.git",
2105
-                "reference": "eea1a3ec87bbe62d470dd0a26d9f4b8ef9cb5d39"
2110
+                "reference": "40988aa2cb540b17b86dc6684b9c3f57b9be3109"
2106
             },
2111
             },
2107
             "dist": {
2112
             "dist": {
2108
                 "type": "zip",
2113
                 "type": "zip",
2109
-                "url": "https://api.github.com/repos/filamentphp/forms/zipball/eea1a3ec87bbe62d470dd0a26d9f4b8ef9cb5d39",
2110
-                "reference": "eea1a3ec87bbe62d470dd0a26d9f4b8ef9cb5d39",
2114
+                "url": "https://api.github.com/repos/filamentphp/forms/zipball/40988aa2cb540b17b86dc6684b9c3f57b9be3109",
2115
+                "reference": "40988aa2cb540b17b86dc6684b9c3f57b9be3109",
2111
                 "shasum": ""
2116
                 "shasum": ""
2112
             },
2117
             },
2113
             "require": {
2118
             "require": {
2150
                 "issues": "https://github.com/filamentphp/filament/issues",
2155
                 "issues": "https://github.com/filamentphp/filament/issues",
2151
                 "source": "https://github.com/filamentphp/filament"
2156
                 "source": "https://github.com/filamentphp/filament"
2152
             },
2157
             },
2153
-            "time": "2024-04-21T22:23:42+00:00"
2158
+            "time": "2024-04-28T08:39:12+00:00"
2154
         },
2159
         },
2155
         {
2160
         {
2156
             "name": "filament/infolists",
2161
             "name": "filament/infolists",
2157
-            "version": "v3.2.71",
2162
+            "version": "v3.2.72",
2158
             "source": {
2163
             "source": {
2159
                 "type": "git",
2164
                 "type": "git",
2160
                 "url": "https://github.com/filamentphp/infolists.git",
2165
                 "url": "https://github.com/filamentphp/infolists.git",
2161
-                "reference": "d205fbeacc7faf4430abb05c49334a64fb4d84ae"
2166
+                "reference": "612497be1c0e5b8b1e0ef9eeefe4754baab54271"
2162
             },
2167
             },
2163
             "dist": {
2168
             "dist": {
2164
                 "type": "zip",
2169
                 "type": "zip",
2165
-                "url": "https://api.github.com/repos/filamentphp/infolists/zipball/d205fbeacc7faf4430abb05c49334a64fb4d84ae",
2166
-                "reference": "d205fbeacc7faf4430abb05c49334a64fb4d84ae",
2170
+                "url": "https://api.github.com/repos/filamentphp/infolists/zipball/612497be1c0e5b8b1e0ef9eeefe4754baab54271",
2171
+                "reference": "612497be1c0e5b8b1e0ef9eeefe4754baab54271",
2167
                 "shasum": ""
2172
                 "shasum": ""
2168
             },
2173
             },
2169
             "require": {
2174
             "require": {
2201
                 "issues": "https://github.com/filamentphp/filament/issues",
2206
                 "issues": "https://github.com/filamentphp/filament/issues",
2202
                 "source": "https://github.com/filamentphp/filament"
2207
                 "source": "https://github.com/filamentphp/filament"
2203
             },
2208
             },
2204
-            "time": "2024-04-18T11:26:13+00:00"
2209
+            "time": "2024-04-28T08:39:09+00:00"
2205
         },
2210
         },
2206
         {
2211
         {
2207
             "name": "filament/notifications",
2212
             "name": "filament/notifications",
2208
-            "version": "v3.2.71",
2213
+            "version": "v3.2.72",
2209
             "source": {
2214
             "source": {
2210
                 "type": "git",
2215
                 "type": "git",
2211
                 "url": "https://github.com/filamentphp/notifications.git",
2216
                 "url": "https://github.com/filamentphp/notifications.git",
2212
-                "reference": "dcc47b498c2a5a89296c2f46da651a8aa5e0bf55"
2217
+                "reference": "a37c926d2edbcb07e6fd8a354ce2f5daa59ad92e"
2213
             },
2218
             },
2214
             "dist": {
2219
             "dist": {
2215
                 "type": "zip",
2220
                 "type": "zip",
2216
-                "url": "https://api.github.com/repos/filamentphp/notifications/zipball/dcc47b498c2a5a89296c2f46da651a8aa5e0bf55",
2217
-                "reference": "dcc47b498c2a5a89296c2f46da651a8aa5e0bf55",
2221
+                "url": "https://api.github.com/repos/filamentphp/notifications/zipball/a37c926d2edbcb07e6fd8a354ce2f5daa59ad92e",
2222
+                "reference": "a37c926d2edbcb07e6fd8a354ce2f5daa59ad92e",
2218
                 "shasum": ""
2223
                 "shasum": ""
2219
             },
2224
             },
2220
             "require": {
2225
             "require": {
2253
                 "issues": "https://github.com/filamentphp/filament/issues",
2258
                 "issues": "https://github.com/filamentphp/filament/issues",
2254
                 "source": "https://github.com/filamentphp/filament"
2259
                 "source": "https://github.com/filamentphp/filament"
2255
             },
2260
             },
2256
-            "time": "2024-04-18T11:26:15+00:00"
2261
+            "time": "2024-04-28T08:39:09+00:00"
2257
         },
2262
         },
2258
         {
2263
         {
2259
             "name": "filament/support",
2264
             "name": "filament/support",
2260
-            "version": "v3.2.71",
2265
+            "version": "v3.2.72",
2261
             "source": {
2266
             "source": {
2262
                 "type": "git",
2267
                 "type": "git",
2263
                 "url": "https://github.com/filamentphp/support.git",
2268
                 "url": "https://github.com/filamentphp/support.git",
2264
-                "reference": "594f8d38e365578b6d91455c5beade6c54def5a4"
2269
+                "reference": "169fa6303337ad1f5d0203b9d34baa76bc938384"
2265
             },
2270
             },
2266
             "dist": {
2271
             "dist": {
2267
                 "type": "zip",
2272
                 "type": "zip",
2268
-                "url": "https://api.github.com/repos/filamentphp/support/zipball/594f8d38e365578b6d91455c5beade6c54def5a4",
2269
-                "reference": "594f8d38e365578b6d91455c5beade6c54def5a4",
2273
+                "url": "https://api.github.com/repos/filamentphp/support/zipball/169fa6303337ad1f5d0203b9d34baa76bc938384",
2274
+                "reference": "169fa6303337ad1f5d0203b9d34baa76bc938384",
2270
                 "shasum": ""
2275
                 "shasum": ""
2271
             },
2276
             },
2272
             "require": {
2277
             "require": {
2311
                 "issues": "https://github.com/filamentphp/filament/issues",
2316
                 "issues": "https://github.com/filamentphp/filament/issues",
2312
                 "source": "https://github.com/filamentphp/filament"
2317
                 "source": "https://github.com/filamentphp/filament"
2313
             },
2318
             },
2314
-            "time": "2024-04-21T22:24:20+00:00"
2319
+            "time": "2024-04-28T08:39:28+00:00"
2315
         },
2320
         },
2316
         {
2321
         {
2317
             "name": "filament/tables",
2322
             "name": "filament/tables",
2318
-            "version": "v3.2.71",
2323
+            "version": "v3.2.72",
2319
             "source": {
2324
             "source": {
2320
                 "type": "git",
2325
                 "type": "git",
2321
                 "url": "https://github.com/filamentphp/tables.git",
2326
                 "url": "https://github.com/filamentphp/tables.git",
2322
-                "reference": "b3bc1d2b5947562eb64748e6d3f59ed1073effe7"
2327
+                "reference": "c3fce6d9e3b9a41d48cbccdacef3508f0cf73f56"
2323
             },
2328
             },
2324
             "dist": {
2329
             "dist": {
2325
                 "type": "zip",
2330
                 "type": "zip",
2326
-                "url": "https://api.github.com/repos/filamentphp/tables/zipball/b3bc1d2b5947562eb64748e6d3f59ed1073effe7",
2327
-                "reference": "b3bc1d2b5947562eb64748e6d3f59ed1073effe7",
2331
+                "url": "https://api.github.com/repos/filamentphp/tables/zipball/c3fce6d9e3b9a41d48cbccdacef3508f0cf73f56",
2332
+                "reference": "c3fce6d9e3b9a41d48cbccdacef3508f0cf73f56",
2328
                 "shasum": ""
2333
                 "shasum": ""
2329
             },
2334
             },
2330
             "require": {
2335
             "require": {
2364
                 "issues": "https://github.com/filamentphp/filament/issues",
2369
                 "issues": "https://github.com/filamentphp/filament/issues",
2365
                 "source": "https://github.com/filamentphp/filament"
2370
                 "source": "https://github.com/filamentphp/filament"
2366
             },
2371
             },
2367
-            "time": "2024-04-21T22:24:20+00:00"
2372
+            "time": "2024-04-28T08:39:27+00:00"
2368
         },
2373
         },
2369
         {
2374
         {
2370
             "name": "filament/widgets",
2375
             "name": "filament/widgets",
2371
-            "version": "v3.2.71",
2376
+            "version": "v3.2.72",
2372
             "source": {
2377
             "source": {
2373
                 "type": "git",
2378
                 "type": "git",
2374
                 "url": "https://github.com/filamentphp/widgets.git",
2379
                 "url": "https://github.com/filamentphp/widgets.git",
6403
         },
6408
         },
6404
         {
6409
         {
6405
             "name": "ramsey/uuid",
6410
             "name": "ramsey/uuid",
6406
-            "version": "4.7.5",
6411
+            "version": "4.7.6",
6407
             "source": {
6412
             "source": {
6408
                 "type": "git",
6413
                 "type": "git",
6409
                 "url": "https://github.com/ramsey/uuid.git",
6414
                 "url": "https://github.com/ramsey/uuid.git",
6410
-                "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e"
6415
+                "reference": "91039bc1faa45ba123c4328958e620d382ec7088"
6411
             },
6416
             },
6412
             "dist": {
6417
             "dist": {
6413
                 "type": "zip",
6418
                 "type": "zip",
6414
-                "url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
6415
-                "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
6419
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
6420
+                "reference": "91039bc1faa45ba123c4328958e620d382ec7088",
6416
                 "shasum": ""
6421
                 "shasum": ""
6417
             },
6422
             },
6418
             "require": {
6423
             "require": {
6419
-                "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11",
6424
+                "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
6420
                 "ext-json": "*",
6425
                 "ext-json": "*",
6421
                 "php": "^8.0",
6426
                 "php": "^8.0",
6422
                 "ramsey/collection": "^1.2 || ^2.0"
6427
                 "ramsey/collection": "^1.2 || ^2.0"
6479
             ],
6484
             ],
6480
             "support": {
6485
             "support": {
6481
                 "issues": "https://github.com/ramsey/uuid/issues",
6486
                 "issues": "https://github.com/ramsey/uuid/issues",
6482
-                "source": "https://github.com/ramsey/uuid/tree/4.7.5"
6487
+                "source": "https://github.com/ramsey/uuid/tree/4.7.6"
6483
             },
6488
             },
6484
             "funding": [
6489
             "funding": [
6485
                 {
6490
                 {
6491
                     "type": "tidelift"
6496
                     "type": "tidelift"
6492
                 }
6497
                 }
6493
             ],
6498
             ],
6494
-            "time": "2023-11-08T05:53:05+00:00"
6499
+            "time": "2024-04-27T21:32:50+00:00"
6495
         },
6500
         },
6496
         {
6501
         {
6497
             "name": "ryangjchandler/blade-capture-directive",
6502
             "name": "ryangjchandler/blade-capture-directive",

+ 7
- 12
database/migrations/2023_09_08_040159_create_company_defaults_table.php 查看文件

14
         Schema::create('company_defaults', function (Blueprint $table) {
14
         Schema::create('company_defaults', function (Blueprint $table) {
15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
-            $table->foreignId('bank_account_id')->nullable()->constrained('bank_accounts')->restrictOnDelete();
17
+            $table->foreignId('bank_account_id')->nullable()->constrained('bank_accounts')->nullOnDelete();
18
             $table->string('currency_code')->nullable();
18
             $table->string('currency_code')->nullable();
19
-            $table->foreignId('sales_tax_id')->nullable()->constrained('taxes')->cascadeOnDelete();
20
-            $table->foreignId('purchase_tax_id')->nullable()->constrained('taxes')->cascadeOnDelete();
21
-            $table->foreignId('sales_discount_id')->nullable()->constrained('discounts')->cascadeOnDelete();
22
-            $table->foreignId('purchase_discount_id')->nullable()->constrained('discounts')->cascadeOnDelete();
23
-            $table->foreignId('created_by')->nullable()->constrained('users')->restrictOnDelete();
24
-            $table->foreignId('updated_by')->nullable()->constrained('users')->restrictOnDelete();
19
+            $table->foreignId('sales_tax_id')->nullable()->constrained('taxes')->nullOnDelete();
20
+            $table->foreignId('purchase_tax_id')->nullable()->constrained('taxes')->nullOnDelete();
21
+            $table->foreignId('sales_discount_id')->nullable()->constrained('discounts')->nullOnDelete();
22
+            $table->foreignId('purchase_discount_id')->nullable()->constrained('discounts')->nullOnDelete();
23
+            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
24
+            $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
25
             $table->timestamps();
25
             $table->timestamps();
26
-
27
-            $table->foreign(['company_id', 'currency_code'])
28
-                ->references(['company_id', 'code'])
29
-                ->on('currencies')
30
-                ->restrictOnDelete();
31
         });
26
         });
32
     }
27
     }
33
 
28
 

+ 73
- 73
package-lock.json 查看文件

507
             }
507
             }
508
         },
508
         },
509
         "node_modules/@rollup/rollup-android-arm-eabi": {
509
         "node_modules/@rollup/rollup-android-arm-eabi": {
510
-            "version": "4.16.4",
511
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
512
-            "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==",
510
+            "version": "4.17.0",
511
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.0.tgz",
512
+            "integrity": "sha512-nNvLvC2fjC+3+bHYN9uaGF3gcyy7RHGZhtl8TB/kINj9hiOQza8kWJGZh47GRPMrqeseO8U+Z8ElDMCZlWBdHA==",
513
             "cpu": [
513
             "cpu": [
514
                 "arm"
514
                 "arm"
515
             ],
515
             ],
520
             ]
520
             ]
521
         },
521
         },
522
         "node_modules/@rollup/rollup-android-arm64": {
522
         "node_modules/@rollup/rollup-android-arm64": {
523
-            "version": "4.16.4",
524
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz",
525
-            "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==",
523
+            "version": "4.17.0",
524
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.0.tgz",
525
+            "integrity": "sha512-+kjt6dvxnyTIAo7oHeYseYhDyZ7xRKTNl/FoQI96PHkJVxoChldJnne/LzYqpqidoK1/0kX0/q+5rrYqjpth6w==",
526
             "cpu": [
526
             "cpu": [
527
                 "arm64"
527
                 "arm64"
528
             ],
528
             ],
533
             ]
533
             ]
534
         },
534
         },
535
         "node_modules/@rollup/rollup-darwin-arm64": {
535
         "node_modules/@rollup/rollup-darwin-arm64": {
536
-            "version": "4.16.4",
537
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz",
538
-            "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==",
536
+            "version": "4.17.0",
537
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.0.tgz",
538
+            "integrity": "sha512-Oj6Tp0unMpGTBjvNwbSRv3DopMNLu+mjBzhKTt2zLbDJ/45fB1pltr/rqrO4bE95LzuYwhYn127pop+x/pzf5w==",
539
             "cpu": [
539
             "cpu": [
540
                 "arm64"
540
                 "arm64"
541
             ],
541
             ],
546
             ]
546
             ]
547
         },
547
         },
548
         "node_modules/@rollup/rollup-darwin-x64": {
548
         "node_modules/@rollup/rollup-darwin-x64": {
549
-            "version": "4.16.4",
550
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz",
551
-            "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==",
549
+            "version": "4.17.0",
550
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.0.tgz",
551
+            "integrity": "sha512-3nJx0T+yptxMd+v93rBRxSPTAVCv8szu/fGZDJiKX7kvRe9sENj2ggXjCH/KK1xZEmJOhaNo0c9sGMgGdfkvEw==",
552
             "cpu": [
552
             "cpu": [
553
                 "x64"
553
                 "x64"
554
             ],
554
             ],
559
             ]
559
             ]
560
         },
560
         },
561
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
561
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
562
-            "version": "4.16.4",
563
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz",
564
-            "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==",
562
+            "version": "4.17.0",
563
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.0.tgz",
564
+            "integrity": "sha512-Vb2e8p9b2lxxgqyOlBHmp6hJMu/HSU6g//6Tbr7x5V1DlPCHWLOm37nSIVK314f+IHzORyAQSqL7+9tELxX3zQ==",
565
             "cpu": [
565
             "cpu": [
566
                 "arm"
566
                 "arm"
567
             ],
567
             ],
572
             ]
572
             ]
573
         },
573
         },
574
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
574
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
575
-            "version": "4.16.4",
576
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz",
577
-            "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==",
575
+            "version": "4.17.0",
576
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.0.tgz",
577
+            "integrity": "sha512-Md60KsmC5ZIaRq/bYYDloklgU+XLEZwS2EXXVcSpiUw+13/ZASvSWQ/P92rQ9YDCL6EIoXxuQ829JkReqdYbGg==",
578
             "cpu": [
578
             "cpu": [
579
                 "arm"
579
                 "arm"
580
             ],
580
             ],
585
             ]
585
             ]
586
         },
586
         },
587
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
587
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
588
-            "version": "4.16.4",
589
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz",
590
-            "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==",
588
+            "version": "4.17.0",
589
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.0.tgz",
590
+            "integrity": "sha512-zL5rBFtJ+2EGnMRm2TqKjdjgFqlotSU+ZJEN37nV+fiD3I6Gy0dUh3jBWN0wSlcXVDEJYW7YBe+/2j0N9unb2w==",
591
             "cpu": [
591
             "cpu": [
592
                 "arm64"
592
                 "arm64"
593
             ],
593
             ],
598
             ]
598
             ]
599
         },
599
         },
600
         "node_modules/@rollup/rollup-linux-arm64-musl": {
600
         "node_modules/@rollup/rollup-linux-arm64-musl": {
601
-            "version": "4.16.4",
602
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz",
603
-            "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==",
601
+            "version": "4.17.0",
602
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.0.tgz",
603
+            "integrity": "sha512-s2xAyNkJqUdtRVgNK4NK4P9QttS538JuX/kfVQOdZDI5FIKVAUVdLW7qhGfmaySJ1EvN/Bnj9oPm5go9u8navg==",
604
             "cpu": [
604
             "cpu": [
605
                 "arm64"
605
                 "arm64"
606
             ],
606
             ],
611
             ]
611
             ]
612
         },
612
         },
613
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
613
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
614
-            "version": "4.16.4",
615
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz",
616
-            "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==",
614
+            "version": "4.17.0",
615
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.0.tgz",
616
+            "integrity": "sha512-7F99yzVT67B7IUNMjLD9QCFDCyHkyCJMS1dywZrGgVFJao4VJ9szrIEgH67cR+bXQgEaY01ur/WSL6B0jtcLyA==",
617
             "cpu": [
617
             "cpu": [
618
                 "ppc64"
618
                 "ppc64"
619
             ],
619
             ],
624
             ]
624
             ]
625
         },
625
         },
626
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
626
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
627
-            "version": "4.16.4",
628
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz",
629
-            "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==",
627
+            "version": "4.17.0",
628
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.0.tgz",
629
+            "integrity": "sha512-leFtyiXisfa3Sg9pgZJwRKITWnrQfhtqDjCamnZhkZuIsk1FXmYwKoTkp6lsCgimIcneFFkHKp/yGLxDesga4g==",
630
             "cpu": [
630
             "cpu": [
631
                 "riscv64"
631
                 "riscv64"
632
             ],
632
             ],
637
             ]
637
             ]
638
         },
638
         },
639
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
639
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
640
-            "version": "4.16.4",
641
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz",
642
-            "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==",
640
+            "version": "4.17.0",
641
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.0.tgz",
642
+            "integrity": "sha512-FtOgui6qMJ4jbSXTxElsy/60LEe/3U0rXkkz2G5CJ9rbHPAvjMvI+3qF0A0fwLQ5hW+/ZC6PbnS2KfRW9JkgDQ==",
643
             "cpu": [
643
             "cpu": [
644
                 "s390x"
644
                 "s390x"
645
             ],
645
             ],
650
             ]
650
             ]
651
         },
651
         },
652
         "node_modules/@rollup/rollup-linux-x64-gnu": {
652
         "node_modules/@rollup/rollup-linux-x64-gnu": {
653
-            "version": "4.16.4",
654
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz",
655
-            "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==",
653
+            "version": "4.17.0",
654
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.0.tgz",
655
+            "integrity": "sha512-v6eiam/1w3HUfU/ZjzIDodencqgrSqzlNuNtiwH7PFJHYSo1ezL0/UIzmS2lpSJF1ORNaplXeKHYmmdt81vV2g==",
656
             "cpu": [
656
             "cpu": [
657
                 "x64"
657
                 "x64"
658
             ],
658
             ],
663
             ]
663
             ]
664
         },
664
         },
665
         "node_modules/@rollup/rollup-linux-x64-musl": {
665
         "node_modules/@rollup/rollup-linux-x64-musl": {
666
-            "version": "4.16.4",
667
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz",
668
-            "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==",
666
+            "version": "4.17.0",
667
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.0.tgz",
668
+            "integrity": "sha512-OUhkSdpM5ofVlVU2k4CwVubYwiwu1a4jYWPpubzN7Vzao73GoPBowHcCfaRSFRz1SszJ3HIsk3dZYk4kzbqjgw==",
669
             "cpu": [
669
             "cpu": [
670
                 "x64"
670
                 "x64"
671
             ],
671
             ],
676
             ]
676
             ]
677
         },
677
         },
678
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
678
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
679
-            "version": "4.16.4",
680
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz",
681
-            "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==",
679
+            "version": "4.17.0",
680
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.0.tgz",
681
+            "integrity": "sha512-uL7UYO/MNJPGL/yflybI+HI+n6+4vlfZmQZOCb4I+z/zy1wisHT3exh7oNQsnL6Eso0EUTEfgQ/PaGzzXf6XyQ==",
682
             "cpu": [
682
             "cpu": [
683
                 "arm64"
683
                 "arm64"
684
             ],
684
             ],
689
             ]
689
             ]
690
         },
690
         },
691
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
691
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
692
-            "version": "4.16.4",
693
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz",
694
-            "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==",
692
+            "version": "4.17.0",
693
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.0.tgz",
694
+            "integrity": "sha512-4WnSgaUiUmXILwFqREdOcqvSj6GD/7FrvSjhaDjmwakX9w4Z2F8JwiSP1AZZbuRkPqzi444UI5FPv33VKOWYFQ==",
695
             "cpu": [
695
             "cpu": [
696
                 "ia32"
696
                 "ia32"
697
             ],
697
             ],
702
             ]
702
             ]
703
         },
703
         },
704
         "node_modules/@rollup/rollup-win32-x64-msvc": {
704
         "node_modules/@rollup/rollup-win32-x64-msvc": {
705
-            "version": "4.16.4",
706
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz",
707
-            "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==",
705
+            "version": "4.17.0",
706
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.0.tgz",
707
+            "integrity": "sha512-ve+D8t1prRSRnF2S3pyDtTXDlvW1Pngbz76tjgYFQW1jxVSysmQCZfPoDAo4WP+Ano8zeYp85LsArZBI12HfwQ==",
708
             "cpu": [
708
             "cpu": [
709
                 "x64"
709
                 "x64"
710
             ],
710
             ],
931
             }
931
             }
932
         },
932
         },
933
         "node_modules/caniuse-lite": {
933
         "node_modules/caniuse-lite": {
934
-            "version": "1.0.30001612",
935
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz",
936
-            "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==",
934
+            "version": "1.0.30001613",
935
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001613.tgz",
936
+            "integrity": "sha512-BNjJULJfOONQERivfxte7alLfeLW4QnwHvNW4wEcLEbXfV6VSCYvr+REbf2Sojv8tC1THpjPXBxWgDbq4NtLWg==",
937
             "dev": true,
937
             "dev": true,
938
             "funding": [
938
             "funding": [
939
                 {
939
                 {
2010
             }
2010
             }
2011
         },
2011
         },
2012
         "node_modules/rollup": {
2012
         "node_modules/rollup": {
2013
-            "version": "4.16.4",
2014
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz",
2015
-            "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==",
2013
+            "version": "4.17.0",
2014
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.0.tgz",
2015
+            "integrity": "sha512-wZJSn0WMtWrxhYKQRt5Z6GIXlziOoMDFmbHmRfL3v+sBTAshx2DBq1AfMArB7eIjF63r4ocn2ZTAyUptg/7kmQ==",
2016
             "dev": true,
2016
             "dev": true,
2017
             "dependencies": {
2017
             "dependencies": {
2018
                 "@types/estree": "1.0.5"
2018
                 "@types/estree": "1.0.5"
2025
                 "npm": ">=8.0.0"
2025
                 "npm": ">=8.0.0"
2026
             },
2026
             },
2027
             "optionalDependencies": {
2027
             "optionalDependencies": {
2028
-                "@rollup/rollup-android-arm-eabi": "4.16.4",
2029
-                "@rollup/rollup-android-arm64": "4.16.4",
2030
-                "@rollup/rollup-darwin-arm64": "4.16.4",
2031
-                "@rollup/rollup-darwin-x64": "4.16.4",
2032
-                "@rollup/rollup-linux-arm-gnueabihf": "4.16.4",
2033
-                "@rollup/rollup-linux-arm-musleabihf": "4.16.4",
2034
-                "@rollup/rollup-linux-arm64-gnu": "4.16.4",
2035
-                "@rollup/rollup-linux-arm64-musl": "4.16.4",
2036
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.16.4",
2037
-                "@rollup/rollup-linux-riscv64-gnu": "4.16.4",
2038
-                "@rollup/rollup-linux-s390x-gnu": "4.16.4",
2039
-                "@rollup/rollup-linux-x64-gnu": "4.16.4",
2040
-                "@rollup/rollup-linux-x64-musl": "4.16.4",
2041
-                "@rollup/rollup-win32-arm64-msvc": "4.16.4",
2042
-                "@rollup/rollup-win32-ia32-msvc": "4.16.4",
2043
-                "@rollup/rollup-win32-x64-msvc": "4.16.4",
2028
+                "@rollup/rollup-android-arm-eabi": "4.17.0",
2029
+                "@rollup/rollup-android-arm64": "4.17.0",
2030
+                "@rollup/rollup-darwin-arm64": "4.17.0",
2031
+                "@rollup/rollup-darwin-x64": "4.17.0",
2032
+                "@rollup/rollup-linux-arm-gnueabihf": "4.17.0",
2033
+                "@rollup/rollup-linux-arm-musleabihf": "4.17.0",
2034
+                "@rollup/rollup-linux-arm64-gnu": "4.17.0",
2035
+                "@rollup/rollup-linux-arm64-musl": "4.17.0",
2036
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.17.0",
2037
+                "@rollup/rollup-linux-riscv64-gnu": "4.17.0",
2038
+                "@rollup/rollup-linux-s390x-gnu": "4.17.0",
2039
+                "@rollup/rollup-linux-x64-gnu": "4.17.0",
2040
+                "@rollup/rollup-linux-x64-musl": "4.17.0",
2041
+                "@rollup/rollup-win32-arm64-msvc": "4.17.0",
2042
+                "@rollup/rollup-win32-ia32-msvc": "4.17.0",
2043
+                "@rollup/rollup-win32-x64-msvc": "4.17.0",
2044
                 "fsevents": "~2.3.2"
2044
                 "fsevents": "~2.3.2"
2045
             }
2045
             }
2046
         },
2046
         },
2536
             }
2536
             }
2537
         },
2537
         },
2538
         "node_modules/yaml": {
2538
         "node_modules/yaml": {
2539
-            "version": "2.4.1",
2540
-            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
2541
-            "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
2539
+            "version": "2.4.2",
2540
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
2541
+            "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
2542
             "dev": true,
2542
             "dev": true,
2543
             "bin": {
2543
             "bin": {
2544
                 "yaml": "bin.mjs"
2544
                 "yaml": "bin.mjs"

resources/views/forms/components/journal-entry-repeater.blade.php → resources/views/filament/forms/components/journal-entry-repeater.blade.php 查看文件


正在加载...
取消
保存