mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-13 16:00:13 +00:00
Firefly III can automatically stop duplicate transactions from being created.
This commit is contained in:
@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Requests\TransactionStoreRequest;
|
|||||||
use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
|
use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
|
||||||
use FireflyIII\Events\StoredTransactionGroup;
|
use FireflyIII\Events\StoredTransactionGroup;
|
||||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||||
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||||
use FireflyIII\Models\TransactionGroup;
|
use FireflyIII\Models\TransactionGroup;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
@@ -279,7 +280,20 @@ class TransactionController extends Controller
|
|||||||
Log::channel('audit')
|
Log::channel('audit')
|
||||||
->info('Store new transaction over API.', $data);
|
->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));
|
event(new StoredTransactionGroup($transactionGroup));
|
||||||
|
|
||||||
|
@@ -58,8 +58,9 @@ class TransactionStoreRequest extends Request
|
|||||||
public function getAll(): array
|
public function getAll(): array
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'group_title' => $this->string('group_title'),
|
'group_title' => $this->string('group_title'),
|
||||||
'transactions' => $this->getTransactionData(),
|
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
|
||||||
|
'transactions' => $this->getTransactionData(),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
@@ -75,6 +76,7 @@ class TransactionStoreRequest extends Request
|
|||||||
$rules = [
|
$rules = [
|
||||||
// basic fields for group:
|
// basic fields for group:
|
||||||
'group_title' => 'between:1,1000|nullable',
|
'group_title' => 'between:1,1000|nullable',
|
||||||
|
'error_if_duplicate_hash' => [new IsBoolean],
|
||||||
|
|
||||||
// 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',
|
||||||
|
31
app/Exceptions/DuplicateTransactionException.php
Normal file
31
app/Exceptions/DuplicateTransactionException.php
Normal 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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Factory;
|
namespace FireflyIII\Factory;
|
||||||
|
|
||||||
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Models\TransactionGroup;
|
use FireflyIII\Models\TransactionGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
|
|
||||||
@@ -52,10 +53,13 @@ class TransactionGroupFactory
|
|||||||
* @param array $data
|
* @param array $data
|
||||||
*
|
*
|
||||||
* @return TransactionGroup
|
* @return TransactionGroup
|
||||||
|
* @throws DuplicateTransactionException
|
||||||
*/
|
*/
|
||||||
public function create(array $data): TransactionGroup
|
public function create(array $data): TransactionGroup
|
||||||
{
|
{
|
||||||
$this->journalFactory->setUser($this->user);
|
$this->journalFactory->setUser($this->user);
|
||||||
|
$this->journalFactory->setErrorOnHash($data['error_if_duplicate_hash']);
|
||||||
|
|
||||||
$collection = $this->journalFactory->create($data);
|
$collection = $this->journalFactory->create($data);
|
||||||
$title = $data['group_title'] ?? null;
|
$title = $data['group_title'] ?? null;
|
||||||
$title = '' === $title ? null : $title;
|
$title = '' === $title ? null : $title;
|
||||||
|
@@ -26,10 +26,12 @@ namespace FireflyIII\Factory;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\TransactionCurrency;
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use FireflyIII\Models\TransactionJournalMeta;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
@@ -72,6 +74,8 @@ class TransactionJournalFactory
|
|||||||
private $typeRepository;
|
private $typeRepository;
|
||||||
/** @var User The user */
|
/** @var User The user */
|
||||||
private $user;
|
private $user;
|
||||||
|
/** @var bool */
|
||||||
|
private $errorOnHash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -81,7 +85,8 @@ class TransactionJournalFactory
|
|||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->fields = [
|
$this->errorOnHash = false;
|
||||||
|
$this->fields = [
|
||||||
// sepa
|
// sepa
|
||||||
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id',
|
'sepa_cc', 'sepa_ct_op', 'sepa_ct_id',
|
||||||
'sepa_db', 'sepa_country', 'sepa_ep',
|
'sepa_db', 'sepa_country', 'sepa_ep',
|
||||||
@@ -119,6 +124,7 @@ class TransactionJournalFactory
|
|||||||
* @param array $data
|
* @param array $data
|
||||||
*
|
*
|
||||||
* @return Collection
|
* @return Collection
|
||||||
|
* @throws DuplicateTransactionException
|
||||||
*/
|
*/
|
||||||
public function create(array $data): Collection
|
public function create(array $data): Collection
|
||||||
{
|
{
|
||||||
@@ -193,11 +199,15 @@ class TransactionJournalFactory
|
|||||||
* @param NullArrayObject $row
|
* @param NullArrayObject $row
|
||||||
*
|
*
|
||||||
* @return TransactionJournal|null
|
* @return TransactionJournal|null
|
||||||
|
* @throws Exception
|
||||||
|
* @throws DuplicateTransactionException
|
||||||
*/
|
*/
|
||||||
private function createJournal(NullArrayObject $row): ?TransactionJournal
|
private function createJournal(NullArrayObject $row): ?TransactionJournal
|
||||||
{
|
{
|
||||||
$row['import_hash_v2'] = $this->hashArray($row);
|
$row['import_hash_v2'] = $this->hashArray($row);
|
||||||
|
|
||||||
|
$this->errorIfDuplicate($row['import_hash_v2']);
|
||||||
|
|
||||||
/** Some basic fields */
|
/** Some basic fields */
|
||||||
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
||||||
$carbon = $row['date'] ?? new Carbon;
|
$carbon = $row['date'] ?? new Carbon;
|
||||||
@@ -376,6 +386,30 @@ class TransactionJournalFactory
|
|||||||
return $journal;
|
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 TransactionCurrency|null $currency
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
@@ -485,4 +519,14 @@ class TransactionJournalFactory
|
|||||||
throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); // @codeCoverageIgnore
|
throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $errorOnHash
|
||||||
|
*/
|
||||||
|
public function setErrorOnHash(bool $errorOnHash): void
|
||||||
|
{
|
||||||
|
$this->errorOnHash = $errorOnHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\TransactionGroup;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use DB;
|
use DB;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Factory\TransactionGroupFactory;
|
use FireflyIII\Factory\TransactionGroupFactory;
|
||||||
use FireflyIII\Models\AccountMeta;
|
use FireflyIII\Models\AccountMeta;
|
||||||
@@ -314,6 +315,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
|||||||
* @param array $data
|
* @param array $data
|
||||||
*
|
*
|
||||||
* @return TransactionGroup
|
* @return TransactionGroup
|
||||||
|
* @throws DuplicateTransactionException
|
||||||
*/
|
*/
|
||||||
public function store(array $data): TransactionGroup
|
public function store(array $data): TransactionGroup
|
||||||
{
|
{
|
||||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Repositories\TransactionGroup;
|
namespace FireflyIII\Repositories\TransactionGroup;
|
||||||
|
|
||||||
|
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||||
use FireflyIII\Models\TransactionGroup;
|
use FireflyIII\Models\TransactionGroup;
|
||||||
use FireflyIII\Support\NullArrayObject;
|
use FireflyIII\Support\NullArrayObject;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
@@ -125,6 +126,7 @@ interface TransactionGroupRepositoryInterface
|
|||||||
* @param array $data
|
* @param array $data
|
||||||
*
|
*
|
||||||
* @return TransactionGroup
|
* @return TransactionGroup
|
||||||
|
* @throws DuplicateTransactionException
|
||||||
*/
|
*/
|
||||||
public function store(array $data): TransactionGroup;
|
public function store(array $data): TransactionGroup;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user