mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-30 18:41:08 +00:00
Expand API so you can also submit IBAN, BIC or number for new accounts
This commit is contained in:
@@ -74,7 +74,7 @@ class TransactionStoreRequest extends Request
|
|||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
// basic fields for group:
|
// basic fields for group:
|
||||||
'group_title' => 'between:1,1000',
|
'group_title' => 'between:1,1000|nullable',
|
||||||
|
|
||||||
// transaction rules (in array for splits):
|
// transaction rules (in array for splits):
|
||||||
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||||
@@ -82,10 +82,10 @@ class TransactionStoreRequest extends Request
|
|||||||
'transactions.*.order' => 'numeric|min:0',
|
'transactions.*.order' => 'numeric|min:0',
|
||||||
|
|
||||||
// currency info
|
// currency info
|
||||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
|
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||||
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|nullable',
|
||||||
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id',
|
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||||
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code|nullable',
|
||||||
|
|
||||||
// amount
|
// amount
|
||||||
'transactions.*.amount' => 'required|numeric|more:0',
|
'transactions.*.amount' => 'required|numeric|more:0',
|
||||||
@@ -97,10 +97,16 @@ class TransactionStoreRequest extends Request
|
|||||||
// source of transaction
|
// source of transaction
|
||||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser],
|
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser],
|
||||||
'transactions.*.source_name' => 'between:1,255|nullable',
|
'transactions.*.source_name' => 'between:1,255|nullable',
|
||||||
|
'transactions.*.source_iban' => 'between:1,255|nullable|iban',
|
||||||
|
'transactions.*.source_number' => 'between:1,255|nullable',
|
||||||
|
'transactions.*.source_bic' => 'between:1,255|nullable|bic',
|
||||||
|
|
||||||
// destination of transaction
|
// destination of transaction
|
||||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser],
|
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser],
|
||||||
'transactions.*.destination_name' => 'between:1,255|nullable',
|
'transactions.*.destination_name' => 'between:1,255|nullable',
|
||||||
|
'transactions.*.destination_iban' => 'between:1,255|nullable|iban',
|
||||||
|
'transactions.*.destination_number' => 'between:1,255|nullable',
|
||||||
|
'transactions.*.destination_bic' => 'between:1,255|nullable|bic',
|
||||||
|
|
||||||
// budget, category, bill and piggy
|
// budget, category, bill and piggy
|
||||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
|
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
|
||||||
@@ -202,7 +208,7 @@ class TransactionStoreRequest extends Request
|
|||||||
'date' => $this->dateFromValue($object['date']),
|
'date' => $this->dateFromValue($object['date']),
|
||||||
'order' => $this->integerFromValue((string)$object['order']),
|
'order' => $this->integerFromValue((string)$object['order']),
|
||||||
|
|
||||||
'currency_id' => $this->integerFromValue($object['currency_id']),
|
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
|
||||||
'currency_code' => $this->stringFromValue($object['currency_code']),
|
'currency_code' => $this->stringFromValue($object['currency_code']),
|
||||||
|
|
||||||
// foreign currency info:
|
// foreign currency info:
|
||||||
@@ -219,10 +225,16 @@ class TransactionStoreRequest extends Request
|
|||||||
// source of transaction. If everything is null, assume cash account.
|
// source of transaction. If everything is null, assume cash account.
|
||||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||||
'source_name' => $this->stringFromValue($object['source_name']),
|
'source_name' => $this->stringFromValue($object['source_name']),
|
||||||
|
'source_iban' => $this->stringFromValue($object['source_iban']),
|
||||||
|
'source_number' => $this->stringFromValue($object['source_number']),
|
||||||
|
'source_bic' => $this->stringFromValue($object['source_bic']),
|
||||||
|
|
||||||
// destination of transaction. If everything is null, assume cash account.
|
// destination of transaction. If everything is null, assume cash account.
|
||||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||||
'destination_name' => $this->stringFromValue($object['destination_name']),
|
'destination_name' => $this->stringFromValue($object['destination_name']),
|
||||||
|
'destination_iban' => $this->stringFromValue($object['destination_iban']),
|
||||||
|
'destination_number' => $this->stringFromValue($object['destination_number']),
|
||||||
|
'destination_bic' => $this->stringFromValue($object['destination_bic']),
|
||||||
|
|
||||||
// budget info
|
// budget info
|
||||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||||
|
@@ -105,7 +105,13 @@ class TransactionUpdateRequest extends Request
|
|||||||
'foreign_amount',
|
'foreign_amount',
|
||||||
'description',
|
'description',
|
||||||
'source_name',
|
'source_name',
|
||||||
|
'source_iban',
|
||||||
|
'source_number',
|
||||||
|
'source_bic',
|
||||||
'destination_name',
|
'destination_name',
|
||||||
|
'destination_iban',
|
||||||
|
'destination_number',
|
||||||
|
'destination_bic',
|
||||||
'budget_name',
|
'budget_name',
|
||||||
'category_name',
|
'category_name',
|
||||||
'bill_name',
|
'bill_name',
|
||||||
|
@@ -68,7 +68,6 @@ class AccountMetaFactory
|
|||||||
{
|
{
|
||||||
/** @var AccountMeta $entry */
|
/** @var AccountMeta $entry */
|
||||||
$entry = $account->accountMeta()->where('name', $field)->first();
|
$entry = $account->accountMeta()->where('name', $field)->first();
|
||||||
|
|
||||||
// must not be an empty string:
|
// must not be an empty string:
|
||||||
if ('' !== $value) {
|
if ('' !== $value) {
|
||||||
|
|
||||||
|
@@ -212,16 +212,33 @@ class TransactionJournalFactory
|
|||||||
$carbon->setTimezone(config('app.timezone'));
|
$carbon->setTimezone(config('app.timezone'));
|
||||||
|
|
||||||
/** Get source + destination account */
|
/** Get source + destination account */
|
||||||
Log::debug(sprintf('Source info: ID #%d, name "%s"', $row['source_id'], $row['source_name']));
|
Log::debug(sprintf('Currency is #%d (%s)', $currency->id, $currency->code));
|
||||||
Log::debug(sprintf('Destination info: ID #%d, name "%s"', $row['destination_id'], $row['destination_name']));
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// validate source and destination using a new Validator.
|
// validate source and destination using a new Validator.
|
||||||
$this->validateAccounts($row);
|
$this->validateAccounts($row);
|
||||||
/** create or get source and destination accounts */
|
/** create or get source and destination accounts */
|
||||||
$sourceAccount = $this->getAccount($type->type, 'source', (int)$row['source_id'], $row['source_name']);
|
|
||||||
$destinationAccount = $this->getAccount($type->type, 'destination', (int)$row['destination_id'], $row['destination_name']);
|
$sourceInfo = [
|
||||||
|
'id' => (int)$row['source_id'],
|
||||||
|
'name' => $row['source_name'],
|
||||||
|
'iban' => $row['source_iban'],
|
||||||
|
'number' => $row['source_number'],
|
||||||
|
'bic' => $row['source_bic'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$destInfo = [
|
||||||
|
'id' => (int)$row['destination_id'],
|
||||||
|
'name' => $row['destination_name'],
|
||||||
|
'iban' => $row['destination_iban'],
|
||||||
|
'number' => $row['destination_number'],
|
||||||
|
'bic' => $row['destination_bic'],
|
||||||
|
];
|
||||||
|
Log::debug('Source info:', $sourceInfo);
|
||||||
|
Log::debug('Destination info:', $destInfo);
|
||||||
|
|
||||||
|
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
||||||
|
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
} catch (FireflyException $e) {
|
} catch (FireflyException $e) {
|
||||||
Log::error('Could not validate source or destination.');
|
Log::error('Could not validate source or destination.');
|
||||||
@@ -372,8 +389,10 @@ class TransactionJournalFactory
|
|||||||
// return user's default:
|
// return user's default:
|
||||||
return app('amount')->getDefaultCurrencyByUser($this->user);
|
return app('amount')->getDefaultCurrencyByUser($this->user);
|
||||||
}
|
}
|
||||||
|
$result = $preference ?? $currency;
|
||||||
|
Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
|
||||||
|
|
||||||
return $preference ?? $currency;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -160,6 +160,10 @@ class BelongsUser implements Rule
|
|||||||
*/
|
*/
|
||||||
private function validateAccountId(int $value): bool
|
private function validateAccountId(int $value): bool
|
||||||
{
|
{
|
||||||
|
if (0 === $value) {
|
||||||
|
// its ok to submit 0. other checks will fail.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
$count = Account::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count();
|
$count = Account::where('id', '=', $value)->where('user_id', '=', auth()->user()->id)->count();
|
||||||
|
|
||||||
return 1 === $count;
|
return 1 === $count;
|
||||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Services\Internal\Support;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Factory\AccountMetaFactory;
|
||||||
use FireflyIII\Factory\TagFactory;
|
use FireflyIII\Factory\TagFactory;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
@@ -84,16 +85,15 @@ trait JournalServiceTrait
|
|||||||
/**
|
/**
|
||||||
* @param string $transactionType
|
* @param string $transactionType
|
||||||
* @param string $direction
|
* @param string $direction
|
||||||
* @param int|null $accountId
|
* @param array $data
|
||||||
* @param string|null $accountName
|
|
||||||
*
|
*
|
||||||
* @return Account
|
* @return Account
|
||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
protected function getAccount(string $transactionType, string $direction, ?int $accountId, ?string $accountName): Account
|
protected function getAccount(string $transactionType, string $direction, array $data): Account
|
||||||
{
|
{
|
||||||
// some debug logging:
|
// some debug logging:
|
||||||
Log::debug(sprintf('Now in getAccount(%s, %d, %s)', $direction, $accountId, $accountName));
|
Log::debug(sprintf('Now in getAccount(%s)', $direction), $data);
|
||||||
|
|
||||||
// final result:
|
// final result:
|
||||||
$result = null;
|
$result = null;
|
||||||
@@ -109,8 +109,8 @@ trait JournalServiceTrait
|
|||||||
Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType])));
|
Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType])));
|
||||||
|
|
||||||
// first attempt, find by ID.
|
// first attempt, find by ID.
|
||||||
if (null !== $accountId) {
|
if (null !== $data['id']) {
|
||||||
$search = $this->accountRepository->findNull($accountId);
|
$search = $this->accountRepository->findNull($data['id']);
|
||||||
if (null !== $search && in_array($search->accountType->type, $expectedTypes[$transactionType], true)) {
|
if (null !== $search && in_array($search->accountType->type, $expectedTypes[$transactionType], true)) {
|
||||||
Log::debug(
|
Log::debug(
|
||||||
sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $search->id, $search->name, $search->accountType->type)
|
sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $search->id, $search->name, $search->accountType->type)
|
||||||
@@ -120,12 +120,12 @@ trait JournalServiceTrait
|
|||||||
}
|
}
|
||||||
|
|
||||||
// second attempt, find by name.
|
// second attempt, find by name.
|
||||||
if (null === $result && null !== $accountName) {
|
if (null === $result && null !== $data['name']) {
|
||||||
Log::debug('Found nothing by account ID.');
|
Log::debug('Found nothing by account ID.');
|
||||||
// find by preferred type.
|
// find by preferred type.
|
||||||
$source = $this->accountRepository->findByName($accountName, [$expectedTypes[$transactionType][0]]);
|
$source = $this->accountRepository->findByName($data['name'], [$expectedTypes[$transactionType][0]]);
|
||||||
// or any expected type.
|
// or any expected type.
|
||||||
$source = $source ?? $this->accountRepository->findByName($accountName, $expectedTypes[$transactionType]);
|
$source = $source ?? $this->accountRepository->findByName($data['name'], $expectedTypes[$transactionType]);
|
||||||
|
|
||||||
if (null !== $source) {
|
if (null !== $source) {
|
||||||
Log::debug(sprintf('Found "account_name" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
Log::debug(sprintf('Found "account_name" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
||||||
@@ -134,30 +134,57 @@ trait JournalServiceTrait
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// third attempt, find by IBAN
|
||||||
|
if (null === $result && null !== $data['iban']) {
|
||||||
|
Log::debug('Found nothing by account name.');
|
||||||
|
// find by preferred type.
|
||||||
|
$source = $this->accountRepository->findByIbanNull($data['iban'], [$expectedTypes[$transactionType][0]]);
|
||||||
|
// or any expected type.
|
||||||
|
$source = $source ?? $this->accountRepository->findByIbanNull($data['iban'], $expectedTypes[$transactionType]);
|
||||||
|
|
||||||
|
if (null !== $source) {
|
||||||
|
Log::debug(sprintf('Found "account_iban" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
||||||
|
|
||||||
|
$result = $source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// return cash account.
|
// return cash account.
|
||||||
if (null === $result && null === $accountName
|
if (null === $result && null === $data['name']
|
||||||
&& in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
|
&& in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
|
||||||
$result = $this->accountRepository->getCashAccount();
|
$result = $this->accountRepository->getCashAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
// return new account.
|
// return new account.
|
||||||
if (null === $result) {
|
if (null === $result) {
|
||||||
$accountName = $accountName ?? '(no name)';
|
$data['name'] = $data['name'] ?? '(no name)';
|
||||||
// final attempt, create it.
|
// final attempt, create it.
|
||||||
$preferredType = $expectedTypes[$transactionType][0];
|
$preferredType = $expectedTypes[$transactionType][0];
|
||||||
if (AccountType::ASSET === $preferredType) {
|
if (AccountType::ASSET === $preferredType) {
|
||||||
throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with ID #%d or name "%s".', $accountId, $accountName));
|
throw new FireflyException('TransactionFactory: Cannot create asset account with these values', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->accountRepository->store(
|
$result = $this->accountRepository->store(
|
||||||
[
|
[
|
||||||
'account_type_id' => null,
|
'account_type_id' => null,
|
||||||
'account_type' => $preferredType,
|
'account_type' => $preferredType,
|
||||||
'name' => $accountName,
|
'name' => $data['name'],
|
||||||
'active' => true,
|
'active' => true,
|
||||||
'iban' => null,
|
'iban' => $data['iban'],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
// store BIC
|
||||||
|
if (null !== $data['bic']) {
|
||||||
|
/** @var AccountMetaFactory $metaFactory */
|
||||||
|
$metaFactory = app(AccountMetaFactory::class);
|
||||||
|
$metaFactory->create(['account_id' => $result->id, 'name' => 'BIC', 'data' => $data['bic']]);
|
||||||
|
}
|
||||||
|
// store account number
|
||||||
|
if (null !== $data['number']) {
|
||||||
|
/** @var AccountMetaFactory $metaFactory */
|
||||||
|
$metaFactory = app(AccountMetaFactory::class);
|
||||||
|
$metaFactory->create(['account_id' => $result->id, 'name' => 'account_number', 'data' => $data['bic']]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
@@ -294,14 +294,19 @@ class JournalUpdateService
|
|||||||
return $this->getOriginalDestinationAccount();
|
return $this->getOriginalDestinationAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
$destId = $this->data['destination_id'] ?? null;
|
$destInfo = [
|
||||||
$destName = $this->data['destination_name'] ?? null;
|
'id' => (int)($this->data['destination_id'] ?? null),
|
||||||
|
'name' => $this->data['destination_name'] ?? null,
|
||||||
|
'iban' => $this->data['destination_iban'] ?? null,
|
||||||
|
'number' => $this->data['destination_number'] ?? null,
|
||||||
|
'bic' => $this->data['destination_bic'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
// make new account validator.
|
// make new account validator.
|
||||||
$expectedType = $this->getExpectedType();
|
$expectedType = $this->getExpectedType();
|
||||||
Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
|
Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
|
||||||
try {
|
try {
|
||||||
$result = $this->getAccount($expectedType, 'destination', $destId, $destName);
|
$result = $this->getAccount($expectedType, 'destination', $destInfo);
|
||||||
} catch (FireflyException $e) {
|
} catch (FireflyException $e) {
|
||||||
Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage()));
|
Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage()));
|
||||||
$result = $this->getOriginalDestinationAccount();
|
$result = $this->getOriginalDestinationAccount();
|
||||||
@@ -318,16 +323,22 @@ class JournalUpdateService
|
|||||||
private function getValidSourceAccount(): Account
|
private function getValidSourceAccount(): Account
|
||||||
{
|
{
|
||||||
Log::debug('Now in getValidSourceAccount().');
|
Log::debug('Now in getValidSourceAccount().');
|
||||||
$sourceId = $this->data['source_id'] ?? null;
|
|
||||||
$sourceName = $this->data['source_name'] ?? null;
|
|
||||||
|
|
||||||
if (!$this->hasFields(['source_id', 'source_name'])) {
|
if (!$this->hasFields(['source_id', 'source_name'])) {
|
||||||
return $this->getOriginalSourceAccount();
|
return $this->getOriginalSourceAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sourceInfo = [
|
||||||
|
'id' => (int)($this->data['source_id'] ?? null),
|
||||||
|
'name' => $this->data['source_name'] ?? null,
|
||||||
|
'iban' => $this->data['source_iban'] ?? null,
|
||||||
|
'number' => $this->data['source_number'] ?? null,
|
||||||
|
'bic' => $this->data['source_bic'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
$expectedType = $this->getExpectedType();
|
$expectedType = $this->getExpectedType();
|
||||||
try {
|
try {
|
||||||
$result = $this->getAccount($expectedType, 'source', $sourceId, $sourceName);
|
$result = $this->getAccount($expectedType, 'source', $sourceInfo);
|
||||||
} catch (FireflyException $e) {
|
} catch (FireflyException $e) {
|
||||||
Log::error(sprintf('Cant get the valid source account: %s', $e->getMessage()));
|
Log::error(sprintf('Cant get the valid source account: %s', $e->getMessage()));
|
||||||
|
|
||||||
|
@@ -108,7 +108,7 @@ class AccountSearch implements GenericSearchInterface
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->get(['accounts.*']);
|
return $query->distinct()->get(['accounts.*']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user