diff --git a/app/Api/V1/Controllers/Data/Bulk/AccountController.php b/app/Api/V1/Controllers/Data/Bulk/AccountController.php new file mode 100644 index 0000000000..ec64b3b1bd --- /dev/null +++ b/app/Api/V1/Controllers/Data/Bulk/AccountController.php @@ -0,0 +1,70 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Data\Bulk; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Data\Bulk\MoveTransactionsRequest; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Services\Internal\Destroy\AccountDestroyService; +use Illuminate\Http\JsonResponse; + +/** + * Class AccountController + */ +class AccountController extends Controller +{ + private AccountRepositoryInterface $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(AccountRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + * @param MoveTransactionsRequest $request + * + * @return JsonResponse + */ + public function moveTransactions(MoveTransactionsRequest $request): JsonResponse + { + $accountIds = $request->getAll(); + $original = $this->repository->findNull($accountIds['original_account']); + $destination = $this->repository->findNull($accountIds['destination_account']); + + /** @var AccountDestroyService $service */ + $service = app(AccountDestroyService::class); + $service->moveTransactions($original, $destination); + + return response()->json([], 204); + + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php new file mode 100644 index 0000000000..60da62d1b2 --- /dev/null +++ b/app/Api/V1/Requests/Data/Bulk/MoveTransactionsRequest.php @@ -0,0 +1,104 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Data\Bulk; + +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Request\ChecksLogin; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Validator; + +/** + * Class MoveTransactionsRequest + */ +class MoveTransactionsRequest extends FormRequest +{ + use ChecksLogin, ConvertsDataTypes; + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'original_account' => $this->integer('original_account'), + 'destination_account' => $this->integer('destination_account'), + ]; + } + + /** + * @return string[] + */ + public function rules(): array + { + return [ + 'original_account' => 'required|different:destination_account|belongsToUser:accounts,id', + 'destination_account' => 'required|different:original_account|belongsToUser:accounts,id', + ]; + } + + /** + * Configure the validator instance with special rules for after the basic validation rules. + * + * @param Validator $validator + * TODO duplicate code. + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + // validate start before end only if both are there. + $data = $validator->getData(); + if (array_key_exists('original_account', $data) && array_key_exists('destination_account', $data)) { + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $original = $repository->findNull((int)$data['original_account']); + $destination = $repository->findNull((int)$data['destination_account']); + if ($original->accountType->type !== $destination->accountType->type) { + $validator->errors()->add('title', (string)trans('validation.same_account_type')); + + return; + } + // get currency pref: + $originalCurrency = $repository->getAccountCurrency($original); + $destinationCurrency = $repository->getAccountCurrency($destination); + if (null === $originalCurrency xor null === $destinationCurrency) { + $validator->errors()->add('title', (string)trans('validation.same_account_currency')); + + return; + } + if (null === $originalCurrency && null === $destinationCurrency) { + // this is OK + return; + } + if ($originalCurrency->code !== $destinationCurrency->code) { + $validator->errors()->add('title', (string)trans('validation.same_account_currency')); + + return; + } + } + } + ); + } +} \ No newline at end of file diff --git a/app/Services/Internal/Destroy/AccountDestroyService.php b/app/Services/Internal/Destroy/AccountDestroyService.php index a23d4f39f3..27b68eb942 100644 --- a/app/Services/Internal/Destroy/AccountDestroyService.php +++ b/app/Services/Internal/Destroy/AccountDestroyService.php @@ -115,7 +115,7 @@ class AccountDestroyService * @param Account $account * @param Account $moveTo */ - private function moveTransactions(Account $account, Account $moveTo): void + public function moveTransactions(Account $account, Account $moveTo): void { DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); } diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 2c1743da08..48540ee6fe 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -134,6 +134,8 @@ return [ 'starts_with' => 'The value must start with :values.', 'unique_webhook' => 'You already have a webhook with these values.', 'unique_existing_webhook' => 'You already have another webhook with these values.', + 'same_account_type' => 'Both accounts must be of the same account type', + 'same_account_currency' => 'Both accounts must have the same currency setting', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://bit.ly/FF3-password-security', 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', diff --git a/routes/api.php b/routes/api.php index ebea292f15..0de13d2954 100644 --- a/routes/api.php +++ b/routes/api.php @@ -90,6 +90,15 @@ Route::group( } ); +// Bulk update Account routes +Route::group( + ['namespace' => 'FireflyIII\Api\V1\Controllers\Data\Bulk', 'prefix' => 'data/bulk/accounts', + 'as' => 'api.v1.data.bulk.',], + static function () { + Route::post('transactions', ['uses' => 'AccountController@moveTransactions', 'as' => 'accounts.move-transactions']); + } +); + /** * INSIGHTS ROUTES */