mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Code cleanup.
This commit is contained in:
		| @@ -29,6 +29,4 @@ namespace FireflyIII\Validation\Account; | ||||
|  * | ||||
|  * Trait AccountValidatorProperties | ||||
|  */ | ||||
| trait AccountValidatorProperties | ||||
| { | ||||
| } | ||||
| trait AccountValidatorProperties {} | ||||
|   | ||||
| @@ -31,11 +31,6 @@ use FireflyIII\Models\AccountType; | ||||
|  */ | ||||
| trait DepositValidation | ||||
| { | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateDepositDestination(array $array): bool | ||||
|     { | ||||
|         $result      = null; | ||||
| @@ -79,26 +74,10 @@ trait DepositValidation | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $accountTypes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     abstract protected function canCreateTypes(array $accountTypes): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $validTypes | ||||
|      * @param array $data | ||||
|      * | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateDepositSource(array $array): bool | ||||
|     { | ||||
|         $accountId     = array_key_exists('id', $array) ? $array['id'] : null; | ||||
| @@ -113,11 +92,11 @@ trait DepositValidation | ||||
| 
 | ||||
|         // source can be any of the following types.
 | ||||
|         $validTypes = array_keys($this->combinations[$this->transactionType]); | ||||
|         if (null === $accountId && | ||||
|             null === $accountName && | ||||
|             null === $accountIban && | ||||
|             null === $accountNumber && | ||||
|             false === $this->canCreateTypes($validTypes)) { | ||||
|         if (null === $accountId | ||||
|             && null === $accountName | ||||
|             && null === $accountIban | ||||
|             && null === $accountNumber | ||||
|             && false === $this->canCreateTypes($validTypes)) { | ||||
|             // if both values are NULL return false,
 | ||||
|             // because the source of a deposit can't be created.
 | ||||
|             // (this never happens).
 | ||||
| @@ -131,6 +110,7 @@ trait DepositValidation | ||||
|             $existing = $this->findExistingAccount($validTypes, ['iban' => $accountIban], true); | ||||
|             if (null !== $existing) { | ||||
|                 $this->sourceError = (string)trans('validation.deposit_src_iban_exists'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -32,11 +32,6 @@ use FireflyIII\Models\AccountType; | ||||
|  */ | ||||
| trait LiabilityValidation | ||||
| { | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateLCDestination(array $array): bool | ||||
|     { | ||||
|         app('log')->debug('Now in validateLCDestination', $array); | ||||
| @@ -50,30 +45,31 @@ trait LiabilityValidation | ||||
|         if (null !== $accountId) { | ||||
|             if (AccountType::LIABILITY_CREDIT !== $this->source?->accountType?->type) { | ||||
|                 app('log')->error('Source account is not a liability.'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|             $result = $this->findExistingAccount($validTypes, $array); | ||||
|             if (null === $result) { | ||||
|                 app('log')->error('Destination account is not a liability.'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (null !== $accountName && '' !== $accountName) { | ||||
|             app('log')->debug('Destination ID is null, now we can assume the destination is a (new) liability credit account.'); | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|         app('log')->error('Destination ID is null, but destination name is also NULL.'); | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Source of a liability credit must be a liability or liability credit account. | ||||
|      * | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateLCSource(array $array): bool | ||||
|     { | ||||
| @@ -87,10 +83,12 @@ trait LiabilityValidation | ||||
|             $result = $this->findExistingAccount(config('firefly.valid_liabilities'), $array); | ||||
|             if (null === $result) { | ||||
|                 app('log')->error('Did not find a liability account, return false.'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|             app('log')->debug(sprintf('Return true, found #%d ("%s")', $result->id, $result->name)); | ||||
|             $this->setSource($result); | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -32,11 +32,6 @@ use FireflyIII\Models\AccountType; | ||||
|  */ | ||||
| trait OBValidation | ||||
| { | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateOBDestination(array $array): bool | ||||
|     { | ||||
|         $result      = null; | ||||
| @@ -78,20 +73,11 @@ trait OBValidation | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $accountTypes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     abstract protected function canCreateTypes(array $accountTypes): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * Source of an opening balance can either be an asset account | ||||
|      * or an "initial balance account". The latter can be created. | ||||
|      * | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateOBSource(array $array): bool | ||||
|     { | ||||
| @@ -138,6 +124,7 @@ trait OBValidation | ||||
| 
 | ||||
|             // set the source to be a (dummy) initial balance account.
 | ||||
|             $account = new Account(); | ||||
| 
 | ||||
|             /** @var AccountType $accountType */ | ||||
|             $accountType          = AccountType::whereType(AccountType::INITIAL_BALANCE)->first(); | ||||
|             $account->accountType = $accountType; | ||||
|   | ||||
| @@ -34,11 +34,6 @@ trait ReconciliationValidation | ||||
|     public ?Account $destination; | ||||
|     public ?Account $source; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateReconciliationDestination(array $array): bool | ||||
|     { | ||||
|         $accountId   = array_key_exists('id', $array) ? $array['id'] : null; | ||||
| @@ -71,10 +66,6 @@ trait ReconciliationValidation | ||||
| 
 | ||||
|     /** | ||||
|      * Basically the same check | ||||
|      * | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateReconciliationSource(array $array): bool | ||||
|     { | ||||
| @@ -86,6 +77,7 @@ trait ReconciliationValidation | ||||
|         if (null === $accountId && null === $accountName) { | ||||
|             app('log')->debug('The source is valid because ID and name are NULL.'); | ||||
|             $this->setSource(new Account()); | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -30,11 +30,6 @@ use FireflyIII\Models\Account; | ||||
|  */ | ||||
| trait TransferValidation | ||||
| { | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateTransferDestination(array $array): bool | ||||
|     { | ||||
|         $accountId   = array_key_exists('id', $array) ? $array['id'] : null; | ||||
| @@ -72,26 +67,10 @@ trait TransferValidation | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $accountTypes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     abstract protected function canCreateTypes(array $accountTypes): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $validTypes | ||||
|      * @param array $data | ||||
|      * | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateTransferSource(array $array): bool | ||||
|     { | ||||
|         $accountId     = array_key_exists('id', $array) ? $array['id'] : null; | ||||
|   | ||||
| @@ -31,11 +31,6 @@ use FireflyIII\Models\AccountType; | ||||
|  */ | ||||
| trait WithdrawalValidation | ||||
| { | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateGenericSource(array $array): bool | ||||
|     { | ||||
|         $accountId   = array_key_exists('id', $array) ? $array['id'] : null; | ||||
| @@ -67,26 +62,10 @@ trait WithdrawalValidation | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $accountTypes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     abstract protected function canCreateTypes(array $accountTypes): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $validTypes | ||||
|      * @param array $data | ||||
|      * | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     abstract protected function findExistingAccount(array $validTypes, array $data): ?Account; | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateWithdrawalDestination(array $array): bool | ||||
|     { | ||||
|         $accountId     = array_key_exists('id', $array) ? $array['id'] : null; | ||||
| @@ -112,6 +91,7 @@ trait WithdrawalValidation | ||||
|                 $type = $found->accountType->type; | ||||
|                 if (in_array($type, $validTypes, true)) { | ||||
|                     $this->setDestination($found); | ||||
| 
 | ||||
|                     return true; | ||||
|                 } | ||||
|                 // todo explain error in log message.
 | ||||
| @@ -128,6 +108,7 @@ trait WithdrawalValidation | ||||
|             $existing = $this->findExistingAccount($validTypes, ['iban' => $accountIban], true); | ||||
|             if (null !== $existing) { | ||||
|                 $this->destError = (string)trans('validation.withdrawal_dest_iban_exists'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| @@ -136,11 +117,6 @@ trait WithdrawalValidation | ||||
|         return true === $this->canCreateTypes($validTypes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function validateWithdrawalSource(array $array): bool | ||||
|     { | ||||
|         $accountId     = array_key_exists('id', $array) ? $array['id'] : null; | ||||
|   | ||||
| @@ -75,17 +75,11 @@ class AccountValidator | ||||
|         $this->userGroupAccountRepository = app(UserGroupAccountRepositoryInterface::class); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     public function getSource(): ?Account | ||||
|     { | ||||
|         return $this->source; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Account|null $account | ||||
|      */ | ||||
|     public function setSource(?Account $account): void | ||||
|     { | ||||
|         if (null === $account) { | ||||
| @@ -97,9 +91,6 @@ class AccountValidator | ||||
|         $this->source = $account; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Account|null $account | ||||
|      */ | ||||
|     public function setDestination(?Account $account): void | ||||
|     { | ||||
|         if (null === $account) { | ||||
| @@ -111,40 +102,24 @@ class AccountValidator | ||||
|         $this->destination = $account; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $transactionType | ||||
|      */ | ||||
|     public function setTransactionType(string $transactionType): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Transaction type for validator is now "%s".', ucfirst($transactionType))); | ||||
|         $this->transactionType = ucfirst($transactionType); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param User $user | ||||
|      */ | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->accountRepository->setUser($user); | ||||
|         $this->useUserGroupRepository = false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param UserGroup $userGroup | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function setUserGroup(UserGroup $userGroup): void | ||||
|     { | ||||
|         $this->userGroupAccountRepository->setUserGroup($userGroup); | ||||
|         $this->useUserGroupRepository = true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateDestination(array $array): bool | ||||
|     { | ||||
|         app('log')->debug('Now in AccountValidator::validateDestination()', $array); | ||||
| @@ -154,83 +129,100 @@ class AccountValidator | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         switch ($this->transactionType) { | ||||
|             default: | ||||
|                 $this->destError = sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType); | ||||
|                 app('log')->error(sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType)); | ||||
| 
 | ||||
|                 $result = false; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::WITHDRAWAL: | ||||
|                 $result = $this->validateWithdrawalDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::DEPOSIT: | ||||
|                 $result = $this->validateDepositDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::TRANSFER: | ||||
|                 $result = $this->validateTransferDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::OPENING_BALANCE: | ||||
|                 $result = $this->validateOBDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::LIABILITY_CREDIT: | ||||
|                 $result = $this->validateLCDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::RECONCILIATION: | ||||
|                 $result = $this->validateReconciliationDestination($array); | ||||
| 
 | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateSource(array $array): bool | ||||
|     { | ||||
|         app('log')->debug('Now in AccountValidator::validateSource()', $array); | ||||
| 
 | ||||
|         switch ($this->transactionType) { | ||||
|             default: | ||||
|                 app('log')->error(sprintf('AccountValidator::validateSource cannot handle "%s", so it will do a generic check.', $this->transactionType)); | ||||
|                 $result = $this->validateGenericSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::WITHDRAWAL: | ||||
|                 $result = $this->validateWithdrawalSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::DEPOSIT: | ||||
|                 $result = $this->validateDepositSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::TRANSFER: | ||||
|                 $result = $this->validateTransferSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::OPENING_BALANCE: | ||||
|                 $result = $this->validateOBSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::LIABILITY_CREDIT: | ||||
|                 $result = $this->validateLCSource($array); | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionType::RECONCILIATION: | ||||
|                 app('log')->debug('Calling validateReconciliationSource'); | ||||
|                 $result = $this->validateReconciliationSource($array); | ||||
| 
 | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $accountTypes | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function canCreateTypes(array $accountTypes): bool | ||||
|     { | ||||
|         app('log')->debug('Can we create any of these types?', $accountTypes); | ||||
| 
 | ||||
|         /** @var string $accountType */ | ||||
|         foreach ($accountTypes as $accountType) { | ||||
|             if ($this->canCreateType($accountType)) { | ||||
| @@ -244,11 +236,6 @@ class AccountValidator | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $accountType | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function canCreateType(string $accountType): bool | ||||
|     { | ||||
|         $canCreate = [AccountType::EXPENSE, AccountType::REVENUE, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT]; | ||||
| @@ -260,11 +247,6 @@ class AccountValidator | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $validTypes | ||||
|      * @param array $data | ||||
|      * @param bool  $inverse | ||||
|      * | ||||
|      * @return Account|null | ||||
|      * @SuppressWarnings(PHPMD.BooleanArgumentFlag) | ||||
|      */ | ||||
|     protected function findExistingAccount(array $validTypes, array $data, bool $inverse = false): ?Account | ||||
| @@ -284,6 +266,7 @@ class AccountValidator | ||||
|             $check       = $inverse ? !$check : $check; // reverse the validation check if necessary.
 | ||||
|             if ((null !== $first) && $check) { | ||||
|                 app('log')->debug(sprintf('ID: Found %s account #%d ("%s", IBAN "%s")', $first->accountType->type, $first->id, $first->name, $first->iban ?? 'no iban')); | ||||
| 
 | ||||
|                 return $first; | ||||
|             } | ||||
|         } | ||||
| @@ -296,6 +279,7 @@ class AccountValidator | ||||
|             $check       = $inverse ? !$check : $check; // reverse the validation check if necessary.
 | ||||
|             if ((null !== $first) && $check) { | ||||
|                 app('log')->debug(sprintf('Iban: Found %s account #%d ("%s", IBAN "%s")', $first->accountType->type, $first->id, $first->name, $first->iban ?? 'no iban')); | ||||
| 
 | ||||
|                 return $first; | ||||
|             } | ||||
|         } | ||||
| @@ -308,6 +292,7 @@ class AccountValidator | ||||
|             $check       = $inverse ? !$check : $check; // reverse the validation check if necessary.
 | ||||
|             if ((null !== $first) && $check) { | ||||
|                 app('log')->debug(sprintf('Number: Found %s account #%d ("%s", IBAN "%s")', $first->accountType->type, $first->id, $first->name, $first->iban ?? 'no iban')); | ||||
| 
 | ||||
|                 return $first; | ||||
|             } | ||||
|         } | ||||
| @@ -317,6 +302,7 @@ class AccountValidator | ||||
|             $first = $this->getRepository()->findByName($accountName, $validTypes); | ||||
|             if (null !== $first) { | ||||
|                 app('log')->debug(sprintf('Name: Found %s account #%d ("%s", IBAN "%s")', $first->accountType->type, $first->id, $first->name, $first->iban ?? 'no iban')); | ||||
| 
 | ||||
|                 return $first; | ||||
|             } | ||||
|         } | ||||
| @@ -325,10 +311,7 @@ class AccountValidator | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return AccountRepositoryInterface|UserGroupAccountRepositoryInterface | ||||
|      */ | ||||
|     private function getRepository(): AccountRepositoryInterface | UserGroupAccountRepositoryInterface | ||||
|     private function getRepository(): AccountRepositoryInterface|UserGroupAccountRepositoryInterface | ||||
|     { | ||||
|         if ($this->useUserGroupRepository) { | ||||
|             return $this->userGroupAccountRepository; | ||||
|   | ||||
| @@ -26,17 +26,11 @@ namespace FireflyIII\Validation\Api\Data\Bulk; | ||||
| 
 | ||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||
| use Illuminate\Validation\Validator; | ||||
| use JsonException; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  */ | ||||
| trait ValidatesBulkTransactionQuery | ||||
| { | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * | ||||
|      * @throws JsonException | ||||
|      * @throws \JsonException | ||||
|      */ | ||||
|     protected function validateTransactionQuery(Validator $validator): void | ||||
|     { | ||||
| @@ -73,8 +67,8 @@ trait ValidatesBulkTransactionQuery | ||||
|             $sourceCurrency = $repository->getAccountCurrency($source); | ||||
|             $destCurrency   = $repository->getAccountCurrency($dest); | ||||
|             if ( | ||||
|                 $sourceCurrency !== null | ||||
|                 && $destCurrency !== null | ||||
|                 null !== $sourceCurrency | ||||
|                 && null !== $destCurrency | ||||
|                 && $sourceCurrency->id !== $destCurrency->id | ||||
|             ) { | ||||
|                 $validator->errors()->add('query', (string)trans('validation.invalid_query_currency')); | ||||
|   | ||||
| @@ -30,9 +30,6 @@ use Illuminate\Validation\Validator; | ||||
|  */ | ||||
| trait ValidatesAutoBudgetRequest | ||||
| { | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     protected function validateAutoBudgetAmount(Validator $validator): void | ||||
|     { | ||||
|         $data         = $validator->getData(); | ||||
| @@ -66,6 +63,7 @@ trait ValidatesAutoBudgetRequest | ||||
|         // too big amount
 | ||||
|         if ((int)$amount > 268435456) { | ||||
|             $validator->errors()->add('auto_budget_amount', (string)trans('validation.amount_required_for_auto_budget')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -37,8 +37,6 @@ trait CurrencyValidation | ||||
| 
 | ||||
|     /** | ||||
|      * If the transactions contain foreign amounts, there must also be foreign currency information. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     protected function validateForeignCurrencyInformation(Validator $validator): void | ||||
|     { | ||||
| @@ -62,7 +60,7 @@ trait CurrencyValidation | ||||
|                 && 0 !== bccomp('0', $transaction['foreign_amount']) | ||||
|             ) { | ||||
|                 $validator->errors()->add( | ||||
|                     'transactions.' . $index . '.foreign_amount', | ||||
|                     'transactions.'.$index.'.foreign_amount', | ||||
|                     (string)trans('validation.require_currency_info') | ||||
|                 ); | ||||
|             } | ||||
| @@ -73,17 +71,12 @@ trait CurrencyValidation | ||||
|                     $transaction | ||||
|                 )) { | ||||
|                 $validator->errors()->add( | ||||
|                     'transactions.' . $index . '.foreign_amount', | ||||
|                     'transactions.'.$index.'.foreign_amount', | ||||
|                     (string)trans('validation.require_currency_amount') | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     abstract protected function getTransactionsArray(Validator $validator): array; | ||||
| } | ||||
|   | ||||
| @@ -23,8 +23,6 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Validation; | ||||
| 
 | ||||
| use Config; | ||||
| use DB; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Account; | ||||
| use FireflyIII\Models\AccountMeta; | ||||
| @@ -37,14 +35,11 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; | ||||
| use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; | ||||
| use FireflyIII\Services\Password\Verifier; | ||||
| use FireflyIII\Support\ParseDateString; | ||||
| use FireflyIII\TransactionRules\Triggers\TriggerInterface; | ||||
| use FireflyIII\User; | ||||
| use Google2FA; | ||||
| use Illuminate\Validation\Validator; | ||||
| use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException; | ||||
| use PragmaRX\Google2FA\Exceptions\InvalidCharactersException; | ||||
| use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException; | ||||
| use ValueError; | ||||
| 
 | ||||
| /** | ||||
|  * Class FireflyValidator. | ||||
| @@ -56,10 +51,10 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * | ||||
|      * @return bool | ||||
|      * @throws IncompatibleWithGoogleAuthenticatorException | ||||
|      * @throws InvalidCharactersException | ||||
|      * @throws SecretKeyTooShortException | ||||
|      * | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validate2faCode($attribute, $value): bool | ||||
| @@ -70,6 +65,7 @@ class FireflyValidator extends Validator | ||||
|         $user = auth()->user(); | ||||
|         if (null === $user) { | ||||
|             app('log')->error('No user during validate2faCode'); | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
|         $secretPreference = app('preferences')->get('temp-mfa-secret'); | ||||
| @@ -78,16 +74,15 @@ class FireflyValidator extends Validator | ||||
|             $secret = ''; | ||||
|         } | ||||
| 
 | ||||
|         return (bool)Google2FA::verifyKey((string)$secret, $value); | ||||
|         return (bool)\Google2FA::verifyKey((string)$secret, $value); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateBelongsToUser($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -96,7 +91,7 @@ class FireflyValidator extends Validator | ||||
|         if (0 === (int)$value) { | ||||
|             return true; | ||||
|         } | ||||
|         $count = DB::table($parameters[0])->where('user_id', auth()->user()->id)->where($field, $value)->count(); | ||||
|         $count = \DB::table($parameters[0])->where('user_id', auth()->user()->id)->where($field, $value)->count(); | ||||
| 
 | ||||
|         return 1 === $count; | ||||
|     } | ||||
| @@ -104,9 +99,8 @@ class FireflyValidator extends Validator | ||||
|     /** | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateBic($attribute, $value): bool | ||||
|     { | ||||
| @@ -123,11 +117,7 @@ class FireflyValidator extends Validator | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateIban(mixed $attribute, mixed $value): bool | ||||
|     { | ||||
| @@ -225,14 +215,15 @@ class FireflyValidator extends Validator | ||||
|         // take
 | ||||
|         $first = substr($value, 0, 4); | ||||
|         $last  = substr($value, 4); | ||||
|         $iban  = $last . $first; | ||||
|         $iban  = $last.$first; | ||||
|         $iban  = trim(str_replace($search, $replace, $iban)); | ||||
|         if ('' === $iban) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $checksum = bcmod($iban, '97'); | ||||
|         } catch (ValueError $e) { // @phpstan-ignore-line
 | ||||
|         } catch (\ValueError $e) { // @phpstan-ignore-line
 | ||||
|             $message = sprintf('Could not validate IBAN check value "%s" (IBAN "%s")', $iban, $value); | ||||
|             app('log')->error($message); | ||||
|             app('log')->error($e->getTraceAsString()); | ||||
| @@ -247,9 +238,8 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateLess($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -263,9 +253,8 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateMore($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -279,9 +268,8 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateMustExist($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -290,18 +278,11 @@ class FireflyValidator extends Validator | ||||
|         if (0 === (int)$value) { | ||||
|             return true; | ||||
|         } | ||||
|         $count = DB::table($parameters[0])->where($field, $value)->count(); | ||||
|         $count = \DB::table($parameters[0])->where($field, $value)->count(); | ||||
| 
 | ||||
|         return 1 === $count; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string      $attribute | ||||
|      * | ||||
|      * @param string|null $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateRuleActionValue(string $attribute, string $value = null): bool | ||||
|     { | ||||
|         // first, get the index from this string:
 | ||||
| @@ -367,11 +348,6 @@ class FireflyValidator extends Validator | ||||
| 
 | ||||
|     /** | ||||
|      * $attribute has the format triggers.%d.value. | ||||
|      * | ||||
|      * @param string      $attribute | ||||
|      * @param string|null $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateRuleTriggerValue(string $attribute, string $value = null): bool | ||||
|     { | ||||
| @@ -436,6 +412,7 @@ class FireflyValidator extends Validator | ||||
|         if (in_array($triggerType, ['date_is', 'created_on', 'updated_on', 'date_before', 'date_after'], true)) { | ||||
|             /** @var ParseDateString $parser */ | ||||
|             $parser = app(ParseDateString::class); | ||||
| 
 | ||||
|             try { | ||||
|                 $parser->parseDate($value); | ||||
|             } catch (FireflyException $e) { | ||||
| @@ -451,9 +428,8 @@ class FireflyValidator extends Validator | ||||
|     /** | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateSecurePassword($attribute, $value): bool | ||||
|     { | ||||
| @@ -475,167 +451,56 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueAccountForUser($attribute, $value, $parameters): bool | ||||
|     { | ||||
|         // because a user does not have to be logged in (tests and what-not).
 | ||||
|         if (!auth()->check()) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::anon'); | ||||
| 
 | ||||
|             return $this->validateAccountAnonymously(); | ||||
|         } | ||||
|         if (array_key_exists('objectType', $this->data)) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::typeString'); | ||||
| 
 | ||||
|             return $this->validateByAccountTypeString($value, $parameters, $this->data['objectType']); | ||||
|         } | ||||
|         if (array_key_exists('type', $this->data)) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::typeString'); | ||||
| 
 | ||||
|             return $this->validateByAccountTypeString($value, $parameters, (string)$this->data['type']); | ||||
|         } | ||||
|         if (array_key_exists('account_type_id', $this->data)) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::typeId'); | ||||
| 
 | ||||
|             return $this->validateByAccountTypeId($value, $parameters); | ||||
|         } | ||||
|         $parameterId = $parameters[0] ?? null; | ||||
|         if (null !== $parameterId) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::paramId'); | ||||
| 
 | ||||
|             return $this->validateByParameterId((int)$parameterId, $value); | ||||
|         } | ||||
|         if (array_key_exists('id', $this->data)) { | ||||
|             app('log')->debug('validateUniqueAccountForUser::accountId'); | ||||
| 
 | ||||
|             return $this->validateByAccountId($value); | ||||
|         } | ||||
| 
 | ||||
|         // without type, just try to validate the name.
 | ||||
|         app('log')->debug('validateUniqueAccountForUser::accountName'); | ||||
| 
 | ||||
|         return $this->validateByAccountName($value); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateAccountAnonymously(): bool | ||||
|     { | ||||
|         if (!array_key_exists('user_id', $this->data)) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         $user  = User::find($this->data['user_id']); | ||||
|         $type  = AccountType::find($this->data['account_type_id'])->first(); | ||||
|         $value = $this->data['name']; | ||||
| 
 | ||||
|         /** @var Account|null $result */ | ||||
|         $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); | ||||
| 
 | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $value | ||||
|      * @param array  $parameters | ||||
|      * @param string $type | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateByAccountTypeString(string $value, array $parameters, string $type): bool | ||||
|     { | ||||
|         /** @var array|null $search */ | ||||
|         $search = Config::get('firefly.accountTypeByIdentifier.' . $type); | ||||
| 
 | ||||
|         if (null === $search) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $accountTypes   = AccountType::whereIn('type', $search)->get(); | ||||
|         $ignore         = (int)($parameters[0] ?? 0.0); | ||||
|         $accountTypeIds = $accountTypes->pluck('id')->toArray(); | ||||
|         /** @var Account|null $result */ | ||||
|         $result = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore) | ||||
|                         ->where('name', $value) | ||||
|                         ->first(); | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateByAccountTypeId($value, $parameters): bool | ||||
|     { | ||||
|         $type   = AccountType::find($this->data['account_type_id'])->first(); | ||||
|         $ignore = (int)($parameters[0] ?? 0.0); | ||||
| 
 | ||||
|         /** @var Account|null $result */ | ||||
|         $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|                         ->where('name', $value) | ||||
|                         ->first(); | ||||
| 
 | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param int   $accountId | ||||
|      * @param mixed $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateByParameterId(int $accountId, $value): bool | ||||
|     { | ||||
|         /** @var Account $existingAccount */ | ||||
|         $existingAccount = Account::find($accountId); | ||||
| 
 | ||||
|         $type   = $existingAccount->accountType; | ||||
|         $ignore = $existingAccount->id; | ||||
| 
 | ||||
|         $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|                        ->where('name', $value) | ||||
|                        ->first(); | ||||
| 
 | ||||
|         return null === $entry; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateByAccountId($value): bool | ||||
|     { | ||||
|         /** @var Account $existingAccount */ | ||||
|         $existingAccount = Account::find($this->data['id']); | ||||
| 
 | ||||
|         $type   = $existingAccount->accountType; | ||||
|         $ignore = $existingAccount->id; | ||||
| 
 | ||||
|         $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|                        ->where('name', $value) | ||||
|                        ->first(); | ||||
| 
 | ||||
|         return null === $entry; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function validateByAccountName(string $value): bool | ||||
|     { | ||||
|         return auth()->user()->accounts()->where('name', $value)->count() === 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueAccountNumberForUser($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -645,10 +510,11 @@ class FireflyValidator extends Validator | ||||
|         } | ||||
| 
 | ||||
|         $query = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') | ||||
|                             ->whereNull('accounts.deleted_at') | ||||
|                             ->where('accounts.user_id', auth()->user()->id) | ||||
|                             ->where('account_meta.name', 'account_number') | ||||
|                             ->where('account_meta.data', json_encode($value)); | ||||
|             ->whereNull('accounts.deleted_at') | ||||
|             ->where('accounts.user_id', auth()->user()->id) | ||||
|             ->where('account_meta.name', 'account_number') | ||||
|             ->where('account_meta.data', json_encode($value)) | ||||
|         ; | ||||
| 
 | ||||
|         if ($accountId > 0) { | ||||
|             // exclude current account from check.
 | ||||
| @@ -666,9 +532,11 @@ class FireflyValidator extends Validator | ||||
|         $type = $this->data['objectType'] ?? 'unknown'; | ||||
|         if ('expense' !== $type && 'revenue' !== $type) { | ||||
|             app('log')->warning(sprintf('Account number "%s" is not unique and account type "%s" cannot share its account number.', $value, $type)); | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
|         app('log')->debug(sprintf('Account number "%s" is not unique but account type "%s" may share its account number.', $value, $type)); | ||||
| 
 | ||||
|         // one other account with this account number.
 | ||||
|         /** @var AccountMeta $entry */ | ||||
|         foreach ($set as $entry) { | ||||
| @@ -676,56 +544,37 @@ class FireflyValidator extends Validator | ||||
|             $otherType    = (string)config(sprintf('firefly.shortNamesByFullName.%s', $otherAccount->accountType->type)); | ||||
|             if (('expense' === $otherType || 'revenue' === $otherType) && $otherType !== $type) { | ||||
|                 app('log')->debug(sprintf('The other account with this account number is a "%s" so return true.', $otherType)); | ||||
| 
 | ||||
|                 return true; | ||||
|             } | ||||
|             app('log')->debug(sprintf('The other account with this account number is a "%s" so return false.', $otherType)); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $attribute | ||||
|      * @param string|null $value | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateUniqueCurrencyCode(string | null $attribute, string | null $value): bool | ||||
|     public function validateUniqueCurrencyCode(null|string $attribute, null|string $value): bool | ||||
|     { | ||||
|         return $this->validateUniqueCurrency('code', (string)$attribute, (string)$value); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $field | ||||
|      * @param string $attribute | ||||
|      * @param string $value | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateUniqueCurrency(string $field, string $attribute, string $value): bool | ||||
|     { | ||||
|         return 0 === DB::table('transaction_currencies')->where($field, $value)->whereNull('deleted_at')->count(); | ||||
|         return 0 === \DB::table('transaction_currencies')->where($field, $value)->whereNull('deleted_at')->count(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $attribute | ||||
|      * @param string|null $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateUniqueCurrencyName(string | null $attribute, string | null $value): bool | ||||
|     public function validateUniqueCurrencyName(null|string $attribute, null|string $value): bool | ||||
|     { | ||||
|         return $this->validateUniqueCurrency('name', (string)$attribute, (string)$value); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $attribute | ||||
|      * @param string|null $value | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateUniqueCurrencySymbol(string | null $attribute, string | null $value): bool | ||||
|     public function validateUniqueCurrencySymbol(null|string $attribute, null|string $value): bool | ||||
|     { | ||||
|         return $this->validateUniqueCurrency('symbol', (string)$attribute, (string)$value); | ||||
|     } | ||||
| @@ -734,9 +583,8 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @param mixed $something | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueExistingWebhook($value, $parameters, $something): bool | ||||
|     { | ||||
| @@ -750,7 +598,7 @@ class FireflyValidator extends Validator | ||||
|         if (auth()->check()) { | ||||
|             // get existing webhook value:
 | ||||
|             if (0 !== $existingId) { | ||||
|                 /** @var Webhook|null $webhook */ | ||||
|                 /** @var null|Webhook $webhook */ | ||||
|                 $webhook = auth()->user()->webhooks()->find($existingId); | ||||
|                 if (null === $webhook) { | ||||
|                     return false; | ||||
| @@ -769,18 +617,18 @@ class FireflyValidator extends Validator | ||||
|             $userId = auth()->user()->id; | ||||
| 
 | ||||
|             return 0 === Webhook::whereUserId($userId) | ||||
|                                 ->where('trigger', $trigger) | ||||
|                                 ->where('response', $response) | ||||
|                                 ->where('delivery', $delivery) | ||||
|                                 ->where('id', '!=', $existingId) | ||||
|                                 ->where('url', $url)->count(); | ||||
|                 ->where('trigger', $trigger) | ||||
|                 ->where('response', $response) | ||||
|                 ->where('delivery', $delivery) | ||||
|                 ->where('id', '!=', $existingId) | ||||
|                 ->where('url', $url)->count() | ||||
|             ; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * Validate an object and its uniqueness. Checks for encryption / encrypted values as well. | ||||
|      * | ||||
|      * parameter 0: the table | ||||
| @@ -790,9 +638,8 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueObjectForUser($attribute, $value, $parameters): bool | ||||
|     { | ||||
| @@ -808,13 +655,15 @@ class FireflyValidator extends Validator | ||||
|             $exclude = (int)$data['id']; | ||||
|         } | ||||
|         // get entries from table
 | ||||
|         $result = DB::table($table)->where('user_id', auth()->user()->id)->whereNull('deleted_at') | ||||
|                     ->where('id', '!=', $exclude) | ||||
|                     ->where($field, $value) | ||||
|                     ->first([$field]); | ||||
|         $result = \DB::table($table)->where('user_id', auth()->user()->id)->whereNull('deleted_at') | ||||
|             ->where('id', '!=', $exclude) | ||||
|             ->where($field, $value) | ||||
|             ->first([$field]) | ||||
|         ; | ||||
|         if (null === $result) { | ||||
|             return true; // not found, so true.
 | ||||
|         } | ||||
| 
 | ||||
|         // found, so not unique.
 | ||||
|         return false; | ||||
|     } | ||||
| @@ -823,17 +672,17 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueObjectGroup($attribute, $value, $parameters): bool | ||||
|     { | ||||
|         $exclude = $parameters[0] ?? null; | ||||
|         $query   = DB::table('object_groups') | ||||
|                      ->whereNull('object_groups.deleted_at') | ||||
|                      ->where('object_groups.user_id', auth()->user()->id) | ||||
|                      ->where('object_groups.title', $value); | ||||
|         $query   = \DB::table('object_groups') | ||||
|             ->whereNull('object_groups.deleted_at') | ||||
|             ->where('object_groups.user_id', auth()->user()->id) | ||||
|             ->where('object_groups.title', $value) | ||||
|         ; | ||||
|         if (null !== $exclude) { | ||||
|             $query->where('object_groups.id', '!=', (int)$exclude); | ||||
|         } | ||||
| @@ -845,15 +694,15 @@ class FireflyValidator extends Validator | ||||
|      * @param mixed $attribute | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool | ||||
|     { | ||||
|         $exclude = $parameters[0] ?? null; | ||||
|         $query   = DB::table('piggy_banks')->whereNull('piggy_banks.deleted_at') | ||||
|                      ->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', auth()->user()->id); | ||||
|         $query   = \DB::table('piggy_banks')->whereNull('piggy_banks.deleted_at') | ||||
|             ->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', auth()->user()->id) | ||||
|         ; | ||||
|         if (null !== $exclude) { | ||||
|             $query->where('piggy_banks.id', '!=', (int)$exclude); | ||||
|         } | ||||
| @@ -865,9 +714,8 @@ class FireflyValidator extends Validator | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @return bool | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateUniqueWebhook($value, $parameters): bool | ||||
|     { | ||||
| @@ -884,12 +732,113 @@ class FireflyValidator extends Validator | ||||
|             $userId   = auth()->user()->id; | ||||
| 
 | ||||
|             return 0 === Webhook::whereUserId($userId) | ||||
|                                 ->where('trigger', $trigger) | ||||
|                                 ->where('response', $response) | ||||
|                                 ->where('delivery', $delivery) | ||||
|                                 ->where('url', $url)->count(); | ||||
|                 ->where('trigger', $trigger) | ||||
|                 ->where('response', $response) | ||||
|                 ->where('delivery', $delivery) | ||||
|                 ->where('url', $url)->count() | ||||
|             ; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private function validateAccountAnonymously(): bool | ||||
|     { | ||||
|         if (!array_key_exists('user_id', $this->data)) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         $user  = User::find($this->data['user_id']); | ||||
|         $type  = AccountType::find($this->data['account_type_id'])->first(); | ||||
|         $value = $this->data['name']; | ||||
| 
 | ||||
|         /** @var null|Account $result */ | ||||
|         $result = $user->accounts()->where('account_type_id', $type->id)->where('name', $value)->first(); | ||||
| 
 | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     private function validateByAccountTypeString(string $value, array $parameters, string $type): bool | ||||
|     { | ||||
|         /** @var null|array $search */ | ||||
|         $search = \Config::get('firefly.accountTypeByIdentifier.'.$type); | ||||
| 
 | ||||
|         if (null === $search) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $accountTypes   = AccountType::whereIn('type', $search)->get(); | ||||
|         $ignore         = (int)($parameters[0] ?? 0.0); | ||||
|         $accountTypeIds = $accountTypes->pluck('id')->toArray(); | ||||
| 
 | ||||
|         /** @var null|Account $result */ | ||||
|         $result = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore) | ||||
|             ->where('name', $value) | ||||
|             ->first() | ||||
|         ; | ||||
| 
 | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      * @param mixed $parameters | ||||
|      */ | ||||
|     private function validateByAccountTypeId($value, $parameters): bool | ||||
|     { | ||||
|         $type   = AccountType::find($this->data['account_type_id'])->first(); | ||||
|         $ignore = (int)($parameters[0] ?? 0.0); | ||||
| 
 | ||||
|         /** @var null|Account $result */ | ||||
|         $result = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|             ->where('name', $value) | ||||
|             ->first() | ||||
|         ; | ||||
| 
 | ||||
|         return null === $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      */ | ||||
|     private function validateByParameterId(int $accountId, $value): bool | ||||
|     { | ||||
|         /** @var Account $existingAccount */ | ||||
|         $existingAccount = Account::find($accountId); | ||||
| 
 | ||||
|         $type   = $existingAccount->accountType; | ||||
|         $ignore = $existingAccount->id; | ||||
| 
 | ||||
|         $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|             ->where('name', $value) | ||||
|             ->first() | ||||
|         ; | ||||
| 
 | ||||
|         return null === $entry; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $value | ||||
|      */ | ||||
|     private function validateByAccountId($value): bool | ||||
|     { | ||||
|         /** @var Account $existingAccount */ | ||||
|         $existingAccount = Account::find($this->data['id']); | ||||
| 
 | ||||
|         $type   = $existingAccount->accountType; | ||||
|         $ignore = $existingAccount->id; | ||||
| 
 | ||||
|         $entry = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore) | ||||
|             ->where('name', $value) | ||||
|             ->first() | ||||
|         ; | ||||
| 
 | ||||
|         return null === $entry; | ||||
|     } | ||||
| 
 | ||||
|     private function validateByAccountName(string $value): bool | ||||
|     { | ||||
|         return 0 === auth()->user()->accounts()->where('name', $value)->count(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -42,8 +42,6 @@ trait GroupValidation | ||||
|      * TODO This should prevent errors down the road but I'm not yet sure what I'm validating here | ||||
|      * TODO so I disabled this on 2023-10-22 to see if it causes any issues. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     protected function preventNoAccountInfo(Validator $validator): void | ||||
| @@ -59,7 +57,8 @@ trait GroupValidation | ||||
|             'source_number', | ||||
|             'destination_number', | ||||
|         ]; | ||||
|         /** @var array|null $transaction */ | ||||
| 
 | ||||
|         /** @var null|array $transaction */ | ||||
|         foreach ($transactions as $index => $transaction) { | ||||
|             if (!is_array($transaction)) { | ||||
|                 throw new FireflyException('Invalid data submitted: transaction is not array.'); | ||||
| @@ -86,35 +85,26 @@ trait GroupValidation | ||||
|         // only an issue if there is no transaction_journal_id
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     abstract protected function getTransactionsArray(Validator $validator): array; | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator        $validator | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     protected function preventUpdateReconciled(Validator $validator, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in %s', __METHOD__)); | ||||
| 
 | ||||
|         $count = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id') | ||||
|                             ->leftJoin('transaction_groups', 'transaction_groups.id', 'transaction_journals.transaction_group_id') | ||||
|                             ->where('transaction_journals.transaction_group_id', $transactionGroup->id) | ||||
|                             ->where('transactions.reconciled', 1)->where('transactions.amount', '<', 0)->count('transactions.id'); | ||||
|             ->leftJoin('transaction_groups', 'transaction_groups.id', 'transaction_journals.transaction_group_id') | ||||
|             ->where('transaction_journals.transaction_group_id', $transactionGroup->id) | ||||
|             ->where('transactions.reconciled', 1)->where('transactions.amount', '<', 0)->count('transactions.id') | ||||
|         ; | ||||
|         if (0 === $count) { | ||||
|             app('log')->debug(sprintf('Transaction is not reconciled, done with %s', __METHOD__)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $data      = $validator->getData(); | ||||
|         $forbidden = ['amount', 'foreign_amount', 'currency_code', 'currency_id', 'foreign_currency_code', 'foreign_currency_id', | ||||
|                       'source_id', 'source_name', 'source_number', 'source_iban', | ||||
|                       'destination_id', 'destination_name', 'destination_number', 'destination_iban', | ||||
|             'source_id', 'source_name', 'source_number', 'source_iban', | ||||
|             'destination_id', 'destination_name', 'destination_number', 'destination_iban', | ||||
|         ]; | ||||
|         foreach ($data['transactions'] as $index => $row) { | ||||
|             foreach ($forbidden as $key) { | ||||
| @@ -133,8 +123,6 @@ trait GroupValidation | ||||
|     /** | ||||
|      * Adds an error to the "description" field when the user has submitted no descriptions and no | ||||
|      * journal description. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     protected function validateDescriptions(Validator $validator): void | ||||
|     { | ||||
| @@ -146,7 +134,7 @@ trait GroupValidation | ||||
|         $validDescriptions = 0; | ||||
|         foreach ($transactions as $transaction) { | ||||
|             if ('' !== (string)($transaction['description'] ?? null)) { | ||||
|                 $validDescriptions++; | ||||
|                 ++$validDescriptions; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @@ -159,9 +147,6 @@ trait GroupValidation | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     protected function validateGroupDescription(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
| @@ -181,9 +166,6 @@ trait GroupValidation | ||||
|      * This method validates if the user has submitted transaction journal ID's for each array they submit, if they've | ||||
|      * submitted more than 1 transaction journal. This check is necessary because Firefly III isn't able to distinguish | ||||
|      * between journals without the ID. | ||||
|      * | ||||
|      * @param Validator        $validator | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      */ | ||||
|     protected function validateJournalIds(Validator $validator, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
| @@ -196,6 +178,7 @@ trait GroupValidation | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // check each array:
 | ||||
|         /** | ||||
|          * @var int   $index | ||||
| @@ -208,12 +191,6 @@ trait GroupValidation | ||||
| 
 | ||||
|     /** | ||||
|      * Do the validation required by validateJournalIds. | ||||
|      * | ||||
|      * @param Validator        $validator | ||||
|      * @param int              $index | ||||
|      * @param array            $transaction | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      * | ||||
|      */ | ||||
|     private function validateJournalId(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|   | ||||
| @@ -27,13 +27,11 @@ use Carbon\Carbon; | ||||
| use FireflyIII\Models\Recurrence; | ||||
| use FireflyIII\Models\RecurrenceTransaction; | ||||
| use Illuminate\Validation\Validator; | ||||
| use InvalidArgumentException; | ||||
| 
 | ||||
| /** | ||||
|  * Trait RecurrenceValidation | ||||
|  * | ||||
|  * Contains advanced validation rules used in validation of new and existing recurrences. | ||||
|  * | ||||
|  */ | ||||
| trait RecurrenceValidation | ||||
| { | ||||
| @@ -41,8 +39,6 @@ trait RecurrenceValidation | ||||
|      * Validate account information input for recurrences which are being updated. | ||||
|      * | ||||
|      * TODO Must always trigger when the type of the recurrence changes. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function valUpdateAccountInfo(Validator $validator): void | ||||
|     { | ||||
| @@ -53,12 +49,14 @@ trait RecurrenceValidation | ||||
|         // grab model from parameter and try to set the transaction type from it
 | ||||
|         if ('invalid' === $transactionType) { | ||||
|             app('log')->debug('Type is invalid but we will search for it.'); | ||||
|             /** @var Recurrence|null $recurrence */ | ||||
| 
 | ||||
|             /** @var null|Recurrence $recurrence */ | ||||
|             $recurrence = $this->route()?->parameter('recurrence'); | ||||
|             if (null !== $recurrence) { | ||||
|                 app('log')->debug('There is a recurrence in the route.'); | ||||
| 
 | ||||
|                 // ok so we have a recurrence should be able to extract type somehow.
 | ||||
|                 /** @var RecurrenceTransaction|null $first */ | ||||
|                 /** @var null|RecurrenceTransaction $first */ | ||||
|                 $first = $recurrence->recurrenceTransactions()->first(); | ||||
|                 if (null !== $first) { | ||||
|                     $transactionType = null !== $first->transactionType ? $first->transactionType->type : 'withdrawal'; | ||||
| @@ -104,7 +102,7 @@ trait RecurrenceValidation | ||||
|             // validate destination account
 | ||||
|             $destinationId    = array_key_exists('destination_id', $transaction) ? (int)$transaction['destination_id'] : null; | ||||
|             $destinationName  = $transaction['destination_name'] ?? null; | ||||
|             $validDestination = $accountValidator->validateDestination(['id' => $destinationId, 'name' => $destinationName,]); | ||||
|             $validDestination = $accountValidator->validateDestination(['id' => $destinationId, 'name' => $destinationName]); | ||||
|             // do something with result:
 | ||||
|             if (false === $validDestination) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); | ||||
| @@ -117,8 +115,6 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no repetitions in the array of data. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateOneRepetition(Validator $validator): void | ||||
|     { | ||||
| @@ -132,8 +128,6 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no repetitions in the array of data. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateOneRepetitionUpdate(Validator $validator): void | ||||
|     { | ||||
| @@ -151,8 +145,6 @@ trait RecurrenceValidation | ||||
|     /** | ||||
|      * Validates that the recurrence has valid repetition information. It either doesn't stop, | ||||
|      * or stops after X times or at X date. Not both of them., | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateRecurrenceRepetition(Validator $validator): void | ||||
|     { | ||||
| @@ -166,11 +158,6 @@ trait RecurrenceValidation | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function validateRecurringConfig(Validator $validator) | ||||
|     { | ||||
|         $data        = $validator->getData(); | ||||
| @@ -189,9 +176,6 @@ trait RecurrenceValidation | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateRepetitionMoment(Validator $validator): void | ||||
|     { | ||||
|         $data        = $validator->getData(); | ||||
| @@ -201,6 +185,7 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * @var int   $index | ||||
|          * @var array $repetition | ||||
| @@ -218,20 +203,30 @@ trait RecurrenceValidation | ||||
|                     $validator->errors()->add(sprintf('repetitions.%d.type', $index), (string)trans('validation.valid_recurrence_rep_type')); | ||||
| 
 | ||||
|                     return; | ||||
| 
 | ||||
|                 case 'daily': | ||||
|                     $this->validateDaily($validator, $index, (string)$repetition['moment']); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'monthly': | ||||
|                     $this->validateMonthly($validator, $index, (int)$repetition['moment']); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'ndom': | ||||
|                     $this->validateNdom($validator, $index, (string)$repetition['moment']); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'weekly': | ||||
|                     $this->validateWeekly($validator, $index, (int)$repetition['moment']); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'yearly': | ||||
|                     $this->validateYearly($validator, $index, (string)$repetition['moment']); | ||||
| 
 | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| @@ -239,10 +234,6 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * If the repetition type is daily, the moment should be empty. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param string    $moment | ||||
|      */ | ||||
|     protected function validateDaily(Validator $validator, int $index, string $moment): void | ||||
|     { | ||||
| @@ -253,10 +244,6 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * If the repetition type is monthly, the moment should be a day between 1-31 (inclusive). | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param int       $dayOfMonth | ||||
|      */ | ||||
|     protected function validateMonthly(Validator $validator, int $index, int $dayOfMonth): void | ||||
|     { | ||||
| @@ -268,10 +255,6 @@ trait RecurrenceValidation | ||||
|     /** | ||||
|      * If the repetition type is "ndom", the first part must be between 1-5 (inclusive), for the week in the month, | ||||
|      * and the second one must be between 1-7 (inclusive) for the day of the week. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param string    $moment | ||||
|      */ | ||||
|     protected function validateNdom(Validator $validator, int $index, string $moment): void | ||||
|     { | ||||
| @@ -295,10 +278,6 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * If the repetition type is weekly, the moment should be a day between 1-7 (inclusive). | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param int       $dayOfWeek | ||||
|      */ | ||||
|     protected function validateWeekly(Validator $validator, int $index, int $dayOfWeek): void | ||||
|     { | ||||
| @@ -309,27 +288,17 @@ trait RecurrenceValidation | ||||
| 
 | ||||
|     /** | ||||
|      * If the repetition type is yearly, the moment should be a valid date. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param string    $moment | ||||
|      */ | ||||
|     protected function validateYearly(Validator $validator, int $index, string $moment): void | ||||
|     { | ||||
|         try { | ||||
|             Carbon::createFromFormat('Y-m-d', $moment); | ||||
|         } catch (InvalidArgumentException $e) { // @phpstan-ignore-line
 | ||||
|         } catch (\InvalidArgumentException $e) { // @phpstan-ignore-line
 | ||||
|             app('log')->debug(sprintf('Invalid argument for Carbon: %s', $e->getMessage())); | ||||
|             $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Recurrence $recurrence | ||||
|      * @param Validator  $validator | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     protected function validateTransactionId(Recurrence $recurrence, Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateTransactionId'); | ||||
| @@ -339,6 +308,7 @@ trait RecurrenceValidation | ||||
|         if (0 === $submittedTrCount) { | ||||
|             app('log')->warning('[b] User submitted no transactions.'); | ||||
|             $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $originalTrCount = $recurrence->recurrenceTransactions()->count(); | ||||
| @@ -346,11 +316,13 @@ trait RecurrenceValidation | ||||
|             $first = $transactions[0]; // can safely assume index 0.
 | ||||
|             if (!array_key_exists('id', $first)) { | ||||
|                 app('log')->debug('Single count and no ID, done.'); | ||||
| 
 | ||||
|                 return; // home safe!
 | ||||
|             } | ||||
|             $id = $first['id']; | ||||
|             if ('' === (string)$id) { | ||||
|                 app('log')->debug('Single count and empty ID, done.'); | ||||
| 
 | ||||
|                 return; // home safe!
 | ||||
|             } | ||||
|             $integer     = (int)$id; | ||||
| @@ -360,6 +332,7 @@ trait RecurrenceValidation | ||||
|                 $validator->errors()->add('transactions.0.id', (string)trans('validation.id_does_not_match', ['id' => $integer])); | ||||
|             } | ||||
|             app('log')->debug('Single ID validation done.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @@ -369,6 +342,7 @@ trait RecurrenceValidation | ||||
|             app('log')->debug(sprintf('User submits %d transaction, recurrence has %d transactions. All entries must have ID.', $submittedTrCount, $originalTrCount)); | ||||
|             $idsMandatory = true; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Loop all transactions submitted by the user. | ||||
|          * If the user has submitted fewer transactions than the original recurrence has, all submitted entries must have an ID. | ||||
| @@ -387,11 +361,13 @@ trait RecurrenceValidation | ||||
|             if (!is_array($transaction)) { | ||||
|                 app('log')->warning('Not an array. Give error.'); | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.id', $index), (string)trans('validation.at_least_one_transaction')); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             if (!array_key_exists('id', $transaction) && $idsMandatory) { | ||||
|                 app('log')->warning('ID is mandatory but array has no ID.'); | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.id', $index), (string)trans('validation.need_id_to_match')); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             if (array_key_exists('id', $transaction)) { // don't matter if $idsMandatory
 | ||||
| @@ -399,12 +375,12 @@ trait RecurrenceValidation | ||||
|                 $idCount = $recurrence->recurrenceTransactions()->where('recurrences_transactions.id', (int)$transaction['id'])->count(); | ||||
|                 if (0 === $idCount) { | ||||
|                     app('log')->debug('ID does not exist or no match. Count another unmatched ID.'); | ||||
|                     $unmatchedIds++; | ||||
|                     ++$unmatchedIds; | ||||
|                 } | ||||
|             } | ||||
|             if (!array_key_exists('id', $transaction) && !$idsMandatory) { | ||||
|                 app('log')->debug('Array has no ID but was not mandatory at this point.'); | ||||
|                 $unmatchedIds++; | ||||
|                 ++$unmatchedIds; | ||||
|             } | ||||
|         } | ||||
|         // if too many don't match, but you haven't submitted more than already present:
 | ||||
| @@ -413,6 +389,7 @@ trait RecurrenceValidation | ||||
|         if ($unmatchedIds > $maxUnmatched) { | ||||
|             app('log')->warning(sprintf('Too many unmatched transactions (%d).', $unmatchedIds)); | ||||
|             $validator->errors()->add('transactions.0.id', (string)trans('validation.too_many_unmatched')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Done with ID validation.'); | ||||
|   | ||||
| @@ -44,10 +44,6 @@ trait TransactionValidation | ||||
|      * Validates the given account information. Switches on given transaction type. | ||||
|      * | ||||
|      * Inclusion of user and/or group is optional. | ||||
|      * | ||||
|      * @param Validator      $validator | ||||
|      * @param User|null      $user | ||||
|      * @param UserGroup|null $userGroup | ||||
|      */ | ||||
|     public function validateAccountInformation(Validator $validator, User $user = null, UserGroup $userGroup = null): void | ||||
|     { | ||||
| @@ -60,8 +56,9 @@ trait TransactionValidation | ||||
|         $transactionType = $data['type'] ?? 'invalid'; | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Going to loop %d transaction(s)', count($transactions))); | ||||
| 
 | ||||
|         /** | ||||
|          * @var int|null $index | ||||
|          * @var null|int $index | ||||
|          * @var array    $transaction | ||||
|          */ | ||||
|         foreach ($transactions as $index => $transaction) { | ||||
| @@ -75,10 +72,135 @@ trait TransactionValidation | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * Validates the given account information. Switches on given transaction type. | ||||
|      * | ||||
|      * @return array | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function validateAccountInformationUpdate(Validator $validator, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateAccountInformationUpdate()'); | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             app('log')->debug('Validator already has errors, so return.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         /** | ||||
|          * @var null|int $index | ||||
|          * @var array    $transaction | ||||
|          */ | ||||
|         foreach ($transactions as $index => $transaction) { | ||||
|             if (!is_int($index)) { | ||||
|                 throw new FireflyException('Invalid data submitted: transaction is not array.'); | ||||
|             } | ||||
|             $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no transactions in the array of data. | ||||
|      */ | ||||
|     public function validateOneRecurrenceTransaction(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateOneRecurrenceTransaction()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         // need at least one transaction
 | ||||
|         if (0 === count($transactions)) { | ||||
|             $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no transactions in the array of data. | ||||
|      */ | ||||
|     public function validateOneTransaction(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateOneTransaction'); | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             app('log')->debug('Validator already has errors, so return.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         // need at least one transaction
 | ||||
|         if (0 === count($transactions)) { | ||||
|             $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); | ||||
|             app('log')->debug('Added error: at_least_one_transaction.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Added NO errors.'); | ||||
|     } | ||||
| 
 | ||||
|     public function validateTransactionArray(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             return; | ||||
|         } | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         foreach (array_keys($transactions) as $key) { | ||||
|             if (!is_int($key)) { | ||||
|                 $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); | ||||
|                 app('log')->debug('Added error: at_least_one_transaction.'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * All types of splits must be equal. | ||||
|      */ | ||||
|     public function validateTransactionTypes(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Now in validateTransactionTypes()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         $types = []; | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $types[] = $transaction['type'] ?? 'invalid'; | ||||
|         } | ||||
|         $unique = array_unique($types); | ||||
|         if (count($unique) > 1) { | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $first = $unique[0] ?? 'invalid'; | ||||
|         if ('invalid' === $first) { | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.invalid_transaction_type')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * All types of splits must be equal. | ||||
|      */ | ||||
|     public function validateTransactionTypesForUpdate(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateTransactionTypesForUpdate()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         $types        = []; | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $originalType = $this->getOriginalType((int)($transaction['transaction_journal_id'] ?? 0)); | ||||
|             // if type is not set, fall back to the type of the journal, if one is given.
 | ||||
|             $types[] = $transaction['type'] ?? $originalType; | ||||
|         } | ||||
|         $unique = array_unique($types); | ||||
|         if (count($unique) > 1) { | ||||
|             app('log')->warning('Add error for mismatch transaction types.'); | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('No errors in validateTransactionTypesForUpdate()'); | ||||
|     } | ||||
| 
 | ||||
|     protected function getTransactionsArray(Validator $validator): array | ||||
|     { | ||||
|         app('log')->debug('Now in getTransactionsArray'); | ||||
| @@ -95,15 +217,10 @@ trait TransactionValidation | ||||
|         return $transactions; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * @param int       $index | ||||
|      * @param string    $transactionType | ||||
|      * @param array     $transaction | ||||
|      */ | ||||
|     protected function validateSingleAccount(Validator $validator, int $index, string $transactionType, array $transaction): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in validateSingleAccount(%d)', $index)); | ||||
| 
 | ||||
|         /** @var AccountValidator $accountValidator */ | ||||
|         $accountValidator = app(AccountValidator::class); | ||||
| 
 | ||||
| @@ -162,20 +279,13 @@ trait TransactionValidation | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      * @param string    $transactionType | ||||
|      * @param int       $index | ||||
|      * @param array     $source | ||||
|      * @param array     $destination | ||||
|      * | ||||
|      * @return void | ||||
|      * @SuppressWarnings(PHPMD.ExcessiveParameterList) | ||||
|      */ | ||||
|     protected function sanityCheckReconciliation(Validator $validator, string $transactionType, int $index, array $source, array $destination): void | ||||
|     { | ||||
|         app('log')->debug('Now in sanityCheckReconciliation'); | ||||
|         if (TransactionType::RECONCILIATION === ucfirst($transactionType) && | ||||
|             null === $source['id'] && null === $source['name'] && null === $destination['id'] && null === $destination['name'] | ||||
|         if (TransactionType::RECONCILIATION === ucfirst($transactionType) | ||||
|             && null === $source['id'] && null === $source['name'] && null === $destination['id'] && null === $destination['name'] | ||||
|         ) { | ||||
|             app('log')->debug('Both are NULL, error!'); | ||||
|             $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); | ||||
| @@ -184,9 +294,9 @@ trait TransactionValidation | ||||
|             $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), trans('validation.reconciliation_either_account')); | ||||
|         } | ||||
| 
 | ||||
|         if (TransactionType::RECONCILIATION === $transactionType && | ||||
|             (null !== $source['id'] || null !== $source['name']) && | ||||
|             (null !== $destination['id'] || null !== $destination['name'])) { | ||||
|         if (TransactionType::RECONCILIATION === $transactionType | ||||
|             && (null !== $source['id'] || null !== $source['name']) | ||||
|             && (null !== $destination['id'] || null !== $destination['name'])) { | ||||
|             app('log')->debug('Both are not NULL, error!'); | ||||
|             $validator->errors()->add(sprintf('transactions.%d.source_id', $index), trans('validation.reconciliation_either_account')); | ||||
|             $validator->errors()->add(sprintf('transactions.%d.source_name', $index), trans('validation.reconciliation_either_account')); | ||||
| @@ -195,208 +305,6 @@ trait TransactionValidation | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO describe this method. | ||||
|      * | ||||
|      * @param Validator        $validator | ||||
|      * @param AccountValidator $accountValidator | ||||
|      * @param array            $transaction | ||||
|      * @param string           $transactionType | ||||
|      * @param int              $index | ||||
|      * | ||||
|      * @return void | ||||
|      * @SuppressWarnings(PHPMD.ExcessiveParameterList) | ||||
|      */ | ||||
|     private function sanityCheckForeignCurrency( | ||||
|         Validator        $validator, | ||||
|         AccountValidator $accountValidator, | ||||
|         array            $transaction, | ||||
|         string           $transactionType, | ||||
|         int              $index | ||||
|     ): void | ||||
|     { | ||||
|         app('log')->debug('Now in sanityCheckForeignCurrency()'); | ||||
|         if (0 !== $validator->errors()->count()) { | ||||
|             app('log')->debug('Already have errors, return'); | ||||
|             return; | ||||
|         } | ||||
|         if (null === $accountValidator->source) { | ||||
|             app('log')->debug('No source, return'); | ||||
|             return; | ||||
|         } | ||||
|         if (null === $accountValidator->destination) { | ||||
|             app('log')->debug('No destination, return'); | ||||
|             return; | ||||
|         } | ||||
|         $source      = $accountValidator->source; | ||||
|         $destination = $accountValidator->destination; | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Source: #%d "%s (%s)"', $source->id, $source->name, $source->accountType->type)); | ||||
|         app('log')->debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $source->accountType->type)); | ||||
| 
 | ||||
|         if (!$this->isLiabilityOrAsset($source) || !$this->isLiabilityOrAsset($destination)) { | ||||
|             app('log')->debug('Any account must be liability or asset account to continue.'); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /** @var AccountRepositoryInterface $accountRepository */ | ||||
|         $accountRepository   = app(AccountRepositoryInterface::class); | ||||
|         $defaultCurrency     = app('amount')->getDefaultCurrency(); | ||||
|         $sourceCurrency      = $accountRepository->getAccountCurrency($source) ?? $defaultCurrency; | ||||
|         $destinationCurrency = $accountRepository->getAccountCurrency($destination) ?? $defaultCurrency; | ||||
|         // if both accounts have the same currency, continue.
 | ||||
|         if ($sourceCurrency->code === $destinationCurrency->code) { | ||||
|             app('log')->debug('Both accounts have the same currency, continue.'); | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug(sprintf('Source account expects %s', $sourceCurrency->code)); | ||||
|         app('log')->debug(sprintf('Destination account expects %s', $destinationCurrency->code)); | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Amount is %s', $transaction['amount'])); | ||||
| 
 | ||||
|         if (TransactionType::DEPOSIT === ucfirst($transactionType)) { | ||||
|             app('log')->debug(sprintf('Processing as a "%s"', $transactionType)); | ||||
|             // use case: deposit from liability account to an asset account
 | ||||
|             // the foreign amount must be in the currency of the source
 | ||||
|             // the amount must be in the currency of the destination
 | ||||
| 
 | ||||
|             // no foreign currency information is present:
 | ||||
|             if (!$this->hasForeignCurrencyInfo($transaction)) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // wrong currency information is present
 | ||||
|             $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; | ||||
|             $foreignCurrencyId   = (int)($transaction['foreign_currency_id'] ?? 0); | ||||
|             app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); | ||||
|             if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== $sourceCurrency->id) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string)trans('validation.require_foreign_src')); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         if (TransactionType::TRANSFER === ucfirst($transactionType) || TransactionType::WITHDRAWAL === ucfirst($transactionType)) { | ||||
|             app('log')->debug(sprintf('Processing as a "%s"', $transactionType)); | ||||
|             // use case: withdrawal from asset account to a liability account.
 | ||||
|             // the foreign amount must be in the currency of the destination
 | ||||
|             // the amount must be in the currency of the source
 | ||||
| 
 | ||||
|             // use case: transfer between accounts with different currencies.
 | ||||
|             // the foreign amount must be in the currency of the destination
 | ||||
|             // the amount must be in the currency of the source
 | ||||
| 
 | ||||
|             // no foreign currency information is present:
 | ||||
|             if (!$this->hasForeignCurrencyInfo($transaction)) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // wrong currency information is present
 | ||||
|             $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; | ||||
|             $foreignCurrencyId   = (int)($transaction['foreign_currency_id'] ?? 0); | ||||
|             app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); | ||||
|             if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== $destinationCurrency->id) { | ||||
|                 app('log')->debug(sprintf('No match on code, "%s" vs "%s"', $foreignCurrencyCode, $destinationCurrency->code)); | ||||
|                 app('log')->debug(sprintf('No match on ID, #%d vs #%d', $foreignCurrencyId, $destinationCurrency->id)); | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_dest')); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Account $account | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function isLiabilityOrAsset(Account $account): bool | ||||
|     { | ||||
|         return $this->isLiability($account) || $this->isAsset($account); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Account $account | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function isLiability(Account $account): bool | ||||
|     { | ||||
|         $type = $account->accountType->type; | ||||
|         if (in_array($type, config('firefly.valid_liabilities'), true)) { | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Account $account | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function isAsset(Account $account): bool | ||||
|     { | ||||
|         $type = $account->accountType->type; | ||||
|         return $type === AccountType::ASSET; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $transaction | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function hasForeignCurrencyInfo(array $transaction): bool | ||||
|     { | ||||
|         if (!array_key_exists('foreign_currency_code', $transaction) && !array_key_exists('foreign_currency_id', $transaction)) { | ||||
|             return false; | ||||
|         } | ||||
|         if (!array_key_exists('foreign_amount', $transaction)) { | ||||
|             return false; | ||||
|         } | ||||
|         if ('' === $transaction['foreign_amount']) { | ||||
|             return false; | ||||
|         } | ||||
|         if (bccomp('0', $transaction['foreign_amount']) === 0) { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validates the given account information. Switches on given transaction type. | ||||
|      * | ||||
|      * @param Validator        $validator | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function validateAccountInformationUpdate(Validator $validator, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateAccountInformationUpdate()'); | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             app('log')->debug('Validator already has errors, so return.'); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         /** | ||||
|          * @var int|null $index | ||||
|          * @var array    $transaction | ||||
|          */ | ||||
|         foreach ($transactions as $index => $transaction) { | ||||
|             if (!is_int($index)) { | ||||
|                 throw new FireflyException('Invalid data submitted: transaction is not array.'); | ||||
|             } | ||||
|             $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator        $validator | ||||
|      * @param int              $index | ||||
|      * @param array            $transaction | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      */ | ||||
|     protected function validateSingleUpdate(Validator $validator, int $index, array $transaction, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         app('log')->debug('Now validating single account update in validateSingleUpdate()'); | ||||
| @@ -411,6 +319,7 @@ trait TransactionValidation | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // create validator:
 | ||||
|         /** @var AccountValidator $accountValidator */ | ||||
|         $accountValidator = app(AccountValidator::class); | ||||
| @@ -420,10 +329,10 @@ trait TransactionValidation | ||||
| 
 | ||||
|         // validate if the submitted source ID/name/iban/number are valid
 | ||||
|         if ( | ||||
|             array_key_exists('source_id', $transaction) || | ||||
|             array_key_exists('source_name', $transaction) || | ||||
|             array_key_exists('source_iban', $transaction) || | ||||
|             array_key_exists('source_number', $transaction) | ||||
|             array_key_exists('source_id', $transaction) | ||||
|             || array_key_exists('source_name', $transaction) | ||||
|             || array_key_exists('source_iban', $transaction) | ||||
|             || array_key_exists('source_number', $transaction) | ||||
|         ) { | ||||
|             app('log')->debug('Will try to validate source account information.'); | ||||
|             $sourceId     = (int)($transaction['source_id'] ?? 0); | ||||
| @@ -448,11 +357,10 @@ trait TransactionValidation | ||||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             array_key_exists('destination_id', $transaction) || | ||||
|             array_key_exists('destination_name', $transaction) || | ||||
|             array_key_exists('destination_iban', $transaction) || | ||||
|             array_key_exists('destination_number', $transaction) | ||||
| 
 | ||||
|             array_key_exists('destination_id', $transaction) | ||||
|             || array_key_exists('destination_name', $transaction) | ||||
|             || array_key_exists('destination_iban', $transaction) | ||||
|             || array_key_exists('destination_number', $transaction) | ||||
|         ) { | ||||
|             app('log')->debug('Will try to validate destination account information.'); | ||||
|             // at this point the validator may not have a source account, because it was never submitted for validation.
 | ||||
| @@ -484,22 +392,158 @@ trait TransactionValidation | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param TransactionGroup $group | ||||
|      * @param array            $transactions | ||||
|      * TODO describe this method. | ||||
|      * | ||||
|      * @return string | ||||
|      * @SuppressWarnings(PHPMD.ExcessiveParameterList) | ||||
|      */ | ||||
|     private function sanityCheckForeignCurrency( | ||||
|         Validator        $validator, | ||||
|         AccountValidator $accountValidator, | ||||
|         array            $transaction, | ||||
|         string           $transactionType, | ||||
|         int              $index | ||||
|     ): void { | ||||
|         app('log')->debug('Now in sanityCheckForeignCurrency()'); | ||||
|         if (0 !== $validator->errors()->count()) { | ||||
|             app('log')->debug('Already have errors, return'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if (null === $accountValidator->source) { | ||||
|             app('log')->debug('No source, return'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if (null === $accountValidator->destination) { | ||||
|             app('log')->debug('No destination, return'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $source      = $accountValidator->source; | ||||
|         $destination = $accountValidator->destination; | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Source: #%d "%s (%s)"', $source->id, $source->name, $source->accountType->type)); | ||||
|         app('log')->debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $source->accountType->type)); | ||||
| 
 | ||||
|         if (!$this->isLiabilityOrAsset($source) || !$this->isLiabilityOrAsset($destination)) { | ||||
|             app('log')->debug('Any account must be liability or asset account to continue.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         /** @var AccountRepositoryInterface $accountRepository */ | ||||
|         $accountRepository   = app(AccountRepositoryInterface::class); | ||||
|         $defaultCurrency     = app('amount')->getDefaultCurrency(); | ||||
|         $sourceCurrency      = $accountRepository->getAccountCurrency($source) ?? $defaultCurrency; | ||||
|         $destinationCurrency = $accountRepository->getAccountCurrency($destination) ?? $defaultCurrency; | ||||
|         // if both accounts have the same currency, continue.
 | ||||
|         if ($sourceCurrency->code === $destinationCurrency->code) { | ||||
|             app('log')->debug('Both accounts have the same currency, continue.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug(sprintf('Source account expects %s', $sourceCurrency->code)); | ||||
|         app('log')->debug(sprintf('Destination account expects %s', $destinationCurrency->code)); | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Amount is %s', $transaction['amount'])); | ||||
| 
 | ||||
|         if (TransactionType::DEPOSIT === ucfirst($transactionType)) { | ||||
|             app('log')->debug(sprintf('Processing as a "%s"', $transactionType)); | ||||
|             // use case: deposit from liability account to an asset account
 | ||||
|             // the foreign amount must be in the currency of the source
 | ||||
|             // the amount must be in the currency of the destination
 | ||||
| 
 | ||||
|             // no foreign currency information is present:
 | ||||
|             if (!$this->hasForeignCurrencyInfo($transaction)) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // wrong currency information is present
 | ||||
|             $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; | ||||
|             $foreignCurrencyId   = (int)($transaction['foreign_currency_id'] ?? 0); | ||||
|             app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); | ||||
|             if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== $sourceCurrency->id) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string)trans('validation.require_foreign_src')); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         if (TransactionType::TRANSFER === ucfirst($transactionType) || TransactionType::WITHDRAWAL === ucfirst($transactionType)) { | ||||
|             app('log')->debug(sprintf('Processing as a "%s"', $transactionType)); | ||||
|             // use case: withdrawal from asset account to a liability account.
 | ||||
|             // the foreign amount must be in the currency of the destination
 | ||||
|             // the amount must be in the currency of the source
 | ||||
| 
 | ||||
|             // use case: transfer between accounts with different currencies.
 | ||||
|             // the foreign amount must be in the currency of the destination
 | ||||
|             // the amount must be in the currency of the source
 | ||||
| 
 | ||||
|             // no foreign currency information is present:
 | ||||
|             if (!$this->hasForeignCurrencyInfo($transaction)) { | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_currency')); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // wrong currency information is present
 | ||||
|             $foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false; | ||||
|             $foreignCurrencyId   = (int)($transaction['foreign_currency_id'] ?? 0); | ||||
|             app('log')->debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction); | ||||
|             if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== $destinationCurrency->id) { | ||||
|                 app('log')->debug(sprintf('No match on code, "%s" vs "%s"', $foreignCurrencyCode, $destinationCurrency->code)); | ||||
|                 app('log')->debug(sprintf('No match on ID, #%d vs #%d', $foreignCurrencyId, $destinationCurrency->id)); | ||||
|                 $validator->errors()->add(sprintf('transactions.%d.foreign_amount', $index), (string)trans('validation.require_foreign_dest')); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function isLiabilityOrAsset(Account $account): bool | ||||
|     { | ||||
|         return $this->isLiability($account) || $this->isAsset($account); | ||||
|     } | ||||
| 
 | ||||
|     private function isLiability(Account $account): bool | ||||
|     { | ||||
|         $type = $account->accountType->type; | ||||
|         if (in_array($type, config('firefly.valid_liabilities'), true)) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private function isAsset(Account $account): bool | ||||
|     { | ||||
|         $type = $account->accountType->type; | ||||
| 
 | ||||
|         return AccountType::ASSET === $type; | ||||
|     } | ||||
| 
 | ||||
|     private function hasForeignCurrencyInfo(array $transaction): bool | ||||
|     { | ||||
|         if (!array_key_exists('foreign_currency_code', $transaction) && !array_key_exists('foreign_currency_id', $transaction)) { | ||||
|             return false; | ||||
|         } | ||||
|         if (!array_key_exists('foreign_amount', $transaction)) { | ||||
|             return false; | ||||
|         } | ||||
|         if ('' === $transaction['foreign_amount']) { | ||||
|             return false; | ||||
|         } | ||||
|         if (0 === bccomp('0', $transaction['foreign_amount'])) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private function getTransactionType(TransactionGroup $group, array $transactions): string | ||||
|     { | ||||
|         return $transactions[0]['type'] ?? strtolower((string)$group->transactionJournals()->first()?->transactionType->type); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array            $transaction | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      * | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     private function getOriginalSource(array $transaction, TransactionGroup $transactionGroup): ?Account | ||||
|     { | ||||
|         if (1 === $transactionGroup->transactionJournals->count()) { | ||||
| @@ -507,6 +551,7 @@ trait TransactionValidation | ||||
| 
 | ||||
|             return $journal?->transactions()->where('amount', '<', 0)->first()?->account; | ||||
|         } | ||||
| 
 | ||||
|         /** @var TransactionJournal $journal */ | ||||
|         foreach ($transactionGroup->transactionJournals as $journal) { | ||||
|             $journalId = (int)($transaction['transaction_journal_id'] ?? 0); | ||||
| @@ -518,129 +563,13 @@ trait TransactionValidation | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no transactions in the array of data. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateOneRecurrenceTransaction(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateOneRecurrenceTransaction()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         // need at least one transaction
 | ||||
|         if (0 === count($transactions)) { | ||||
|             $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an error to the validator when there are no transactions in the array of data. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateOneTransaction(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateOneTransaction'); | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             app('log')->debug('Validator already has errors, so return.'); | ||||
|             return; | ||||
|         } | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         // need at least one transaction
 | ||||
|         if (0 === count($transactions)) { | ||||
|             $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); | ||||
|             app('log')->debug('Added error: at_least_one_transaction.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Added NO errors.'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateTransactionArray(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             return; | ||||
|         } | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         foreach (array_keys($transactions) as $key) { | ||||
|             if (!is_int($key)) { | ||||
|                 $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); | ||||
|                 app('log')->debug('Added error: at_least_one_transaction.'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * All types of splits must be equal. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateTransactionTypes(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Now in validateTransactionTypes()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
| 
 | ||||
|         $types = []; | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $types[] = $transaction['type'] ?? 'invalid'; | ||||
|         } | ||||
|         $unique = array_unique($types); | ||||
|         if (count($unique) > 1) { | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $first = $unique[0] ?? 'invalid'; | ||||
|         if ('invalid' === $first) { | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.invalid_transaction_type')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * All types of splits must be equal. | ||||
|      * | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     public function validateTransactionTypesForUpdate(Validator $validator): void | ||||
|     { | ||||
|         app('log')->debug('Now in validateTransactionTypesForUpdate()'); | ||||
|         $transactions = $this->getTransactionsArray($validator); | ||||
|         $types        = []; | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $originalType = $this->getOriginalType((int)($transaction['transaction_journal_id'] ?? 0)); | ||||
|             // if type is not set, fall back to the type of the journal, if one is given.
 | ||||
|             $types[] = $transaction['type'] ?? $originalType; | ||||
|         } | ||||
|         $unique = array_unique($types); | ||||
|         if (count($unique) > 1) { | ||||
|             app('log')->warning('Add error for mismatch transaction types.'); | ||||
|             $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('No errors in validateTransactionTypesForUpdate()'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param int $journalId | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     private function getOriginalType(int $journalId): string | ||||
|     { | ||||
|         if (0 === $journalId) { | ||||
|             return 'invalid'; | ||||
|         } | ||||
|         /** @var TransactionJournal|null $journal */ | ||||
| 
 | ||||
|         /** @var null|TransactionJournal $journal */ | ||||
|         $journal = TransactionJournal::with(['transactionType'])->find($journalId); | ||||
|         if (null !== $journal) { | ||||
|             return strtolower($journal->transactionType->type); | ||||
| @@ -649,9 +578,6 @@ trait TransactionValidation | ||||
|         return 'invalid'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator $validator | ||||
|      */ | ||||
|     private function validateEqualAccounts(Validator $validator): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
| @@ -673,35 +599,38 @@ trait TransactionValidation | ||||
|         } | ||||
|         $sources = array_unique($sources); | ||||
|         $dests   = array_unique($dests); | ||||
| 
 | ||||
|         switch ($type) { | ||||
|             default: | ||||
|             case 'withdrawal': | ||||
|                 if (count($sources) > 1) { | ||||
|                     $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case 'deposit': | ||||
|                 if (count($dests) > 1) { | ||||
|                     $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case'transfer': | ||||
|                 if (count($sources) > 1 || count($dests) > 1) { | ||||
|                     $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); | ||||
|                     $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Validator        $validator | ||||
|      * @param TransactionGroup $transactionGroup | ||||
|      */ | ||||
|     private function validateEqualAccountsForUpdate(Validator $validator, TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         if ($validator->errors()->count() > 0) { | ||||
|             app('log')->debug('Validator already has errors, so return.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @@ -740,17 +669,13 @@ trait TransactionValidation | ||||
|         app('log')->debug('No errors found in validateEqualAccountsForUpdate'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $transactions | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     private function collectComparisonData(array $transactions): array | ||||
|     { | ||||
|         $fields     = ['source_id', 'destination_id', 'source_name', 'destination_name']; | ||||
|         $comparison = []; | ||||
|         foreach ($fields as $field) { | ||||
|             $comparison[$field] = []; | ||||
| 
 | ||||
|             /** @var array $transaction */ | ||||
|             foreach ($transactions as $transaction) { | ||||
|                 // source or destination may be omitted. If this is the case, use the original source / destination name + ID.
 | ||||
| @@ -764,11 +689,6 @@ trait TransactionValidation | ||||
|         return $comparison; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param int $journalId | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     private function getOriginalData(int $journalId): array | ||||
|     { | ||||
|         $return = [ | ||||
| @@ -780,13 +700,15 @@ trait TransactionValidation | ||||
|         if (0 === $journalId) { | ||||
|             return $return; | ||||
|         } | ||||
|         /** @var Transaction|null $source */ | ||||
| 
 | ||||
|         /** @var null|Transaction $source */ | ||||
|         $source = Transaction::where('transaction_journal_id', $journalId)->where('amount', '<', 0)->with(['account'])->first(); | ||||
|         if (null !== $source) { | ||||
|             $return['source_id']   = $source->account_id; | ||||
|             $return['source_name'] = $source->account->name; | ||||
|         } | ||||
|         /** @var Transaction|null $destination */ | ||||
| 
 | ||||
|         /** @var null|Transaction $destination */ | ||||
|         $destination = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->with(['account'])->first(); | ||||
|         if (null !== $destination) { | ||||
|             $return['destination_id']   = $destination->account_id; | ||||
| @@ -796,12 +718,6 @@ trait TransactionValidation | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $type | ||||
|      * @param array  $comparison | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function compareAccountData(string $type, array $comparison): bool | ||||
|     { | ||||
|         return match ($type) { | ||||
| @@ -811,11 +727,6 @@ trait TransactionValidation | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $comparison | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function compareAccountDataWithdrawal(array $comparison): bool | ||||
|     { | ||||
|         if ($this->arrayEqual($comparison['source_id'])) { | ||||
| @@ -830,21 +741,11 @@ trait TransactionValidation | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $array | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function arrayEqual(array $array): bool | ||||
|     { | ||||
|         return 1 === count(array_unique($array)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $comparison | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function compareAccountDataDeposit(array $comparison): bool | ||||
|     { | ||||
|         if ($this->arrayEqual($comparison['destination_id'])) { | ||||
| @@ -859,11 +760,6 @@ trait TransactionValidation | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $comparison | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     private function compareAccountDataTransfer(array $comparison): bool | ||||
|     { | ||||
|         if ($this->arrayEqual($comparison['source_id'])) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user