{"record":{"id":"ae34c06feb45cb45","repo":"firefly-iii/firefly-iii","slug":"source-s","errorCode":null,"errorMessage":"Source: %s","messagePattern":"Source: (.+?)","errorType":"exception","errorClass":"FireflyException","httpStatus":null,"severity":"error","filePath":"app/Factory/TransactionJournalFactory.php","lineNumber":643,"sourceCode":"    private function validateAccounts(NullArrayObject $data): void\n    {\n        Log::debug(sprintf('Now in %s', __METHOD__));\n        $transactionType  = $data['type'] ?? 'invalid';\n        $this->accountValidator->setUser($this->user);\n        $this->accountValidator->setTransactionType($transactionType);\n\n        // validate source account.\n        $array            = [\n            'id'     => null !== $data['source_id'] ? (int) $data['source_id'] : null,\n            'name'   => null !== $data['source_name'] ? (string) $data['source_name'] : null,\n            'iban'   => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,\n            'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,\n        ];\n        $validSource      = $this->accountValidator->validateSource($array);\n\n        // do something with result:\n        if (false === $validSource) {\n            throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError));\n        }\n        Log::debug('Source seems valid.');\n\n        // validate destination account\n        $array            = [\n            'id'     => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,\n            'name'   => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,\n            'iban'   => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,\n            'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null,\n        ];\n\n        $validDestination = $this->accountValidator->validateDestination($array);\n        // do something with result:\n        if (false === $validDestination) {\n            throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError));\n        }\n    }\n}","sourceCodeStart":625,"sourceCodeEnd":661,"githubUrl":"https://github.com/firefly-iii/firefly-iii/blob/fd8791d08d4d9e6467519a78048cd038e26b8878/app/Factory/TransactionJournalFactory.php#L625-L661","documentation":"As part of journal creation, TransactionJournalFactory builds {id, name, iban, number} from the payload's source_* fields and runs AccountValidator::validateSource(). A false result throws FireflyException 'Source: <validator reason>' — the appended reason (accountValidator->sourceError) states why the account cannot be the source of this transaction type.","triggerScenarios":"source_id pointing at a deleted account or an account of another user; using an account type that may not fund the flow (an expense/revenue account as a withdrawal source — the source of a withdrawal must be an asset/loan/debt account); a source name+iban+number combination that resolves to no allowed account and cannot be auto-created.","commonSituations":"Hardcoded account IDs that drift between environments; CSV/OFX imports with foreign IBANs; forgetting that for transfers both sides must be personal accounts.","solutions":["Use a source account you own of an allowed type (asset, loan or debt) — fetch IDs from the accounts endpoint of the same user.","When supplying only a name, ensure it uniquely matches an existing allowed account or leave it empty so the validator can resolve it.","Verify the account still exists and is not deleted before submitting."],"exampleFix":"// before\n['type' => 'withdrawal', 'source_id' => $revenueAccount->id, ...] // revenue account cannot be a withdrawal source\n\n// after\n['type' => 'withdrawal', 'source_id' => $assetAccount->id, 'destination_name' => 'Grocery store', ...]","handlingStrategy":"validation","validationCode":"$source = Account::where('user_id', $userId)->find($data['source_id'] ?? 0);\nif (null === $source || !in_array($source->accountType->type, ['Asset account', 'Loan', 'Debt'], true)) {\n    throw new InvalidArgumentException('Source must be an owned asset/loan/debt account');\n}","typeGuard":"function isAllowedSource(?\\FireflyIII\\Models\\Account $a): bool\n{\n    return null !== $a\n        && in_array($a->accountType->type, ['Asset account', 'Loan', 'Debt'], true);\n}","tryCatchPattern":"try {\n    $journal = $factory->create($data);\n} catch (FireflyException $e) {\n    if (str_starts_with($e->getMessage(), 'Source: ')) {\n        // re-map source_id / source_name using the validator's reason, then re-submit\n    }\n    throw $e;\n}","preventionTips":["Fetch source IDs from the authenticated user's accounts at request time, never hardcode them.","Check source/destination direction before submitting — swapped sides are the top cause.","Keep account type rules per transaction type in one shared validator in your client."],"tags":["firefly-iii","account","validation","transaction","factory"],"backgroundTag":"account-validation-failed","analyzedSha":"fd8791d08d4d9e6467519a78048cd038e26b8878","analyzedAt":"2026-08-17T02:14:53.848Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}