Firefly III can automatically stop duplicate transactions from being created.

This commit is contained in:
James Cole
2019-10-26 07:49:12 +02:00
parent 2cf000a9fd
commit 6823adc976
7 changed files with 103 additions and 4 deletions

View File

@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Requests\TransactionStoreRequest;
use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
@@ -279,7 +280,20 @@ class TransactionController extends Controller
Log::channel('audit')
->info('Store new transaction over API.', $data);
$transactionGroup = $this->groupRepository->store($data);
try {
$transactionGroup = $this->groupRepository->store($data);
} catch (DuplicateTransactionException $e) {
// 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' => [$e->getMessage()],
],
];
return response()->json($response, 422);
}
event(new StoredTransactionGroup($transactionGroup));

View File

@@ -58,8 +58,9 @@ class TransactionStoreRequest extends Request
public function getAll(): array
{
$data = [
'group_title' => $this->string('group_title'),
'transactions' => $this->getTransactionData(),
'group_title' => $this->string('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'transactions' => $this->getTransactionData(),
];
return $data;
@@ -75,6 +76,7 @@ class TransactionStoreRequest extends Request
$rules = [
// basic fields for group:
'group_title' => 'between:1,1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',

View File

@@ -0,0 +1,31 @@
<?php
/**
* DuplicateTransactionException.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Exceptions;
use Exception;
/**
* Class DuplicateTransactionException
*/
class DuplicateTransactionException extends Exception
{
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\User;
@@ -52,10 +53,13 @@ class TransactionGroupFactory
* @param array $data
*
* @return TransactionGroup
* @throws DuplicateTransactionException
*/
public function create(array $data): TransactionGroup
{
$this->journalFactory->setUser($this->user);
$this->journalFactory->setErrorOnHash($data['error_if_duplicate_hash']);
$collection = $this->journalFactory->create($data);
$title = $data['group_title'] ?? null;
$title = '' === $title ? null : $title;

View File

@@ -26,10 +26,12 @@ namespace FireflyIII\Factory;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
@@ -72,6 +74,8 @@ class TransactionJournalFactory
private $typeRepository;
/** @var User The user */
private $user;
/** @var bool */
private $errorOnHash;
/**
* Constructor.
@@ -81,7 +85,8 @@ class TransactionJournalFactory
*/
public function __construct()
{
$this->fields = [
$this->errorOnHash = false;
$this->fields = [
// sepa
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id',
'sepa_db', 'sepa_country', 'sepa_ep',
@@ -119,6 +124,7 @@ class TransactionJournalFactory
* @param array $data
*
* @return Collection
* @throws DuplicateTransactionException
*/
public function create(array $data): Collection
{
@@ -193,11 +199,15 @@ class TransactionJournalFactory
* @param NullArrayObject $row
*
* @return TransactionJournal|null
* @throws Exception
* @throws DuplicateTransactionException
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
{
$row['import_hash_v2'] = $this->hashArray($row);
$this->errorIfDuplicate($row['import_hash_v2']);
/** Some basic fields */
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? new Carbon;
@@ -376,6 +386,30 @@ class TransactionJournalFactory
return $journal;
}
/**
* If this transaction already exists, throw an error.
*
* @param string $hash
*
* @throws DuplicateTransactionException
*/
private function errorIfDuplicate(string $hash): void
{
if (false === $this->errorOnHash) {
return;
}
$result = null;
if ($this->errorOnHash) {
/** @var TransactionJournalMeta $result */
$result = TransactionJournalMeta::where('data', json_encode($hash, JSON_THROW_ON_ERROR))
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
->first();
}
if (null !== $result) {
throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $result->transactionJournal->transaction_group_id));
}
}
/**
* @param TransactionCurrency|null $currency
* @param Account $account
@@ -485,4 +519,14 @@ class TransactionJournalFactory
throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); // @codeCoverageIgnore
}
}
/**
* @param bool $errorOnHash
*/
public function setErrorOnHash(bool $errorOnHash): void
{
$this->errorOnHash = $errorOnHash;
}
}

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\TransactionGroup;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Models\AccountMeta;
@@ -314,6 +315,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
* @param array $data
*
* @return TransactionGroup
* @throws DuplicateTransactionException
*/
public function store(array $data): TransactionGroup
{

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\TransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
@@ -125,6 +126,7 @@ interface TransactionGroupRepositoryInterface
* @param array $data
*
* @return TransactionGroup
* @throws DuplicateTransactionException
*/
public function store(array $data): TransactionGroup;