Better handling of errors.

This commit is contained in:
James Cole 2020-03-12 05:06:42 +01:00
parent d92b741088
commit 5e8d94d16a
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
5 changed files with 141 additions and 54 deletions

View File

@ -29,6 +29,7 @@ use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
@ -294,6 +295,19 @@ class TransactionController extends Controller
],
];
return response()->json($response, 422);
} catch(FireflyException $e) {
Log::warning('Caught an exception. Return error message.');
Log::error($e->getMessage());
// return bad validation message.
// TODO use Laravel's internal validation thing to do this.
$response = [
'message' => 'The given data was invalid.',
'errors' => [
'transactions.0.description' => [sprintf('Internal exception: %s',$e->getMessage())],
],
];
return response()->json($response, 422);
}
app('preferences')->mark();

View File

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
@ -102,11 +103,13 @@ class TransactionFactory
/**
* Create transaction with negative amount (for source accounts).
*
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
public function createNegative(string $amount, ?string $foreignAmount): ?Transaction
public function createNegative(string $amount, ?string $foreignAmount): Transaction
{
if ('' === $foreignAmount) {
$foreignAmount = null;
@ -121,11 +124,13 @@ class TransactionFactory
/**
* Create transaction with positive amount (for destination accounts).
*
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
public function createPositive(string $amount, ?string $foreignAmount): ?Transaction
public function createPositive(string $amount, ?string $foreignAmount): Transaction
{
if ('' === $foreignAmount) {
$foreignAmount = null;
@ -133,7 +138,6 @@ class TransactionFactory
if (null !== $foreignAmount) {
$foreignAmount = app('steam')->positive($foreignAmount);
}
return $this->create(app('steam')->positive($amount), $foreignAmount);
}
@ -157,11 +161,13 @@ class TransactionFactory
}
/**
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
private function create(string $amount, ?string $foreignAmount): ?Transaction
private function create(string $amount, ?string $foreignAmount): Transaction
{
$result = null;
if ('' === $foreignAmount) {
@ -183,6 +189,12 @@ class TransactionFactory
// @codeCoverageIgnoreStart
} catch (QueryException $e) {
Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data);
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException('Query exception when creating transaction.');
}
if (null === $result) {
throw new FireflyException('Transaction is NULL.');
}
// @codeCoverageIgnoreEnd
if (null !== $result) {

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\User;
use Log;
@ -60,7 +61,6 @@ class TransactionGroupFactory
{
$this->journalFactory->setUser($this->user);
$this->journalFactory->setErrorOnHash($data['error_if_duplicate_hash'] ?? false);
try {
$collection = $this->journalFactory->create($data);
} catch(DuplicateTransactionException $e) {

View File

@ -29,6 +29,7 @@ use Exception;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
@ -40,6 +41,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
@ -125,6 +127,7 @@ class TransactionJournalFactory
*
* @return Collection
* @throws DuplicateTransactionException
* @throws FireflyException
*/
public function create(array $data): Collection
{
@ -139,24 +142,30 @@ class TransactionJournalFactory
return new Collection;
}
/** @var array $row */
foreach ($transactions as $index => $row) {
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
Log::debug('Going to call createJournal', $row);
try {
try {
/** @var array $row */
foreach ($transactions as $index => $row) {
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal(new NullArrayObject($row));
} catch (DuplicateTransactionException|Exception $e) {
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
throw new DuplicateTransactionException($e->getMessage());
}
if (null !== $journal) {
$collection->push($journal);
}
if (null === $journal) {
Log::error('The createJournal() method returned NULL. This may indicate an error.');
if (null !== $journal) {
$collection->push($journal);
}
if (null === $journal) {
Log::error('The createJournal() method returned NULL. This may indicate an error.');
}
}
} catch (DuplicateTransactionException $e) {
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new DuplicateTransactionException($e->getMessage());
} catch (FireflyException $e) {
Log::warning('TransactionJournalFactory::create() caught an exception.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new FireflyException($e->getMessage());
}
return $collection;
@ -204,7 +213,7 @@ class TransactionJournalFactory
* @param NullArrayObject $row
*
* @return TransactionJournal|null
* @throws Exception
* @throws FireflyException
* @throws DuplicateTransactionException
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
@ -232,36 +241,33 @@ class TransactionJournalFactory
try {
// validate source and destination using a new Validator.
$this->validateAccounts($row);
/** create or get source and destination accounts */
$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
} catch (FireflyException $e) {
Log::error('Could not validate source or destination.');
Log::error($e->getMessage());
return null;
}
// @codeCoverageIgnoreEnd
/** create or get source and destination accounts */
$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);
// TODO AFTER 4.8,0 better handling below:
@ -337,7 +343,15 @@ class TransactionJournalFactory
$transactionFactory->setCurrency($sourceCurrency);
$transactionFactory->setForeignCurrency($sourceForeignCurrency);
$transactionFactory->setReconciled($row['reconciled'] ?? false);
$transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
try {
$negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
Log::error('Exception creating negative transaction.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError(new Collection([$journal]));
throw new FireflyException($e->getMessage());
}
// and the destination one:
/** @var TransactionFactory $transactionFactory */
@ -348,7 +362,18 @@ class TransactionJournalFactory
$transactionFactory->setCurrency($destCurrency);
$transactionFactory->setForeignCurrency($destForeignCurrency);
$transactionFactory->setReconciled($row['reconciled'] ?? false);
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
try {
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
Log::error('Exception creating positive transaction.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Log::warning('Delete negative transaction.');
$this->forceTrDelete($negative);
$this->forceDeleteOnError(new Collection([$journal]));
throw new FireflyException($e->getMessage());
}
// verify that journal has two transactions. Otherwise, delete and cancel.
// TODO this can't be faked so it can't be tested.
@ -418,6 +443,37 @@ class TransactionJournalFactory
}
}
/**
* Force the deletion of an entire set of transaction journals and their meta object in case of
* an error creating a group.
*
* @param Collection $collection
*/
private function forceDeleteOnError(Collection $collection): void
{
Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count()));
$service = app(JournalDestroyService::class);
/** @var TransactionJournal $journal */
foreach ($collection as $journal) {
Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id));
$service->destroy($journal);
}
}
/**
* @param Transaction $transaction
*/
private function forceTrDelete(Transaction $transaction): void
{
try {
$transaction->delete();
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Log::error('Could not delete negative transaction.');
}
}
/**
* @param TransactionCurrency|null $currency
* @param Account $account

View File

@ -332,6 +332,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
*
* @return TransactionGroup
* @throws DuplicateTransactionException
* @throws FireflyException
*/
public function store(array $data): TransactionGroup
{
@ -343,6 +344,10 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
} catch (DuplicateTransactionException $e) {
Log::warning('Group repository caught group factory with a duplicate exception!');
throw new DuplicateTransactionException($e->getMessage());
} catch(FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());
}