Files
firefly-iii/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php

433 lines
15 KiB
PHP
Raw Normal View History

<?php
/**
* TransactionUpdateRequest.php
* Copyright (c) 2019 james@firefly-iii.org
*
* 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/>.
*/
declare(strict_types=1);
2021-03-06 20:52:42 +01:00
namespace FireflyIII\Api\V1\Requests\Models\Transaction;
2023-04-26 05:55:31 +02:00
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Rules\BelongsUser;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsDateOrTime;
2020-11-08 13:36:13 +01:00
use FireflyIII\Support\Request\ChecksLogin;
2020-07-18 08:34:00 +02:00
use FireflyIII\Support\Request\ConvertsDataTypes;
2020-03-21 05:56:27 +01:00
use FireflyIII\Validation\GroupValidation;
use FireflyIII\Validation\TransactionValidation;
use Illuminate\Foundation\Http\FormRequest;
2023-01-11 17:29:19 +01:00
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
/**
2021-03-06 20:52:42 +01:00
* Class UpdateRequest
*/
2021-03-06 20:52:42 +01:00
class UpdateRequest extends FormRequest
{
2022-10-30 14:23:00 +01:00
use TransactionValidation;
use GroupValidation;
use ConvertsDataTypes;
use ChecksLogin;
2020-08-22 19:46:08 +02:00
private array $arrayFields;
private array $booleanFields;
private array $dateFields;
2022-12-29 19:41:57 +01:00
private array $floatFields;
2020-08-22 19:46:08 +02:00
private array $integerFields;
private array $stringFields;
private array $textareaFields;
/**
* Get all data. Is pretty complex because of all the ??-statements.
*
* @return array
2023-07-04 13:29:19 +02:00
* @throws FireflyException
*/
public function getAll(): array
{
2023-01-11 17:29:19 +01:00
Log::debug(sprintf('Now in %s', __METHOD__));
$this->integerFields = [
'order',
'currency_id',
'foreign_currency_id',
2019-06-02 16:33:25 +02:00
'transaction_journal_id',
'source_id',
'destination_id',
'budget_id',
'category_id',
'bill_id',
'recurrence_id',
];
$this->dateFields = [
'date',
'interest_date',
'book_date',
'process_date',
'due_date',
'payment_date',
'invoice_date',
];
2019-09-20 06:14:08 +02:00
$this->textareaFields = [
'notes',
];
2022-12-27 21:13:18 +01:00
$this->floatFields = [ // not really floats, for validation.
2022-12-29 19:41:57 +01:00
'amount',
'foreign_amount',
];
$this->stringFields = [
'type',
'currency_code',
'foreign_currency_code',
'description',
'source_name',
'source_iban',
'source_number',
'source_bic',
'destination_name',
'destination_iban',
'destination_number',
'destination_bic',
'budget_name',
'category_name',
'bill_name',
'internal_reference',
'external_id',
'bunq_payment_id',
'sepa_cc',
'sepa_ct_op',
'sepa_ct_id',
'sepa_db',
'sepa_country',
'sepa_ep',
'sepa_ci',
'sepa_batch_id',
2022-01-24 07:50:33 +01:00
'external_url',
];
$this->booleanFields = [
'reconciled',
];
$this->arrayFields = [
'tags',
];
$data = [];
if ($this->has('transactions')) {
$data['transactions'] = $this->getTransactionData();
}
if ($this->has('apply_rules')) {
$data['apply_rules'] = $this->boolean('apply_rules', true);
}
2021-05-02 14:52:25 +02:00
if ($this->has('fire_webhooks')) {
$data['fire_webhooks'] = $this->boolean('fire_webhooks', true);
}
if ($this->has('group_title')) {
2022-05-02 19:35:35 +02:00
$data['group_title'] = $this->convertString('group_title');
}
return $data;
}
2020-10-18 08:00:49 +02:00
/**
* Get transaction data.
*
* @return array
2023-07-04 13:29:19 +02:00
* @throws FireflyException
2020-10-18 08:00:49 +02:00
*/
private function getTransactionData(): array
{
2023-01-11 17:29:19 +01:00
Log::debug(sprintf('Now in %s', __METHOD__));
2020-10-18 08:00:49 +02:00
$return = [];
if (!is_countable($this->get('transactions'))) {
return $return;
}
2021-04-06 08:51:27 +02:00
/** @var array $transaction */
2020-10-18 08:00:49 +02:00
foreach ($this->get('transactions') as $transaction) {
2023-05-29 13:56:55 +02:00
if (!is_array($transaction)) {
2023-04-26 05:55:31 +02:00
throw new FireflyException('Invalid data submitted: transaction is not array.');
}
2020-10-18 08:00:49 +02:00
// default response is to update nothing in the transaction:
2021-03-21 09:15:40 +01:00
$current = [];
$current = $this->getIntegerData($current, $transaction);
$current = $this->getStringData($current, $transaction);
$current = $this->getNlStringData($current, $transaction);
$current = $this->getDateData($current, $transaction);
$current = $this->getBooleanData($current, $transaction);
$current = $this->getArrayData($current, $transaction);
$current = $this->getFloatData($current, $transaction);
2020-10-18 08:00:49 +02:00
$return[] = $current;
}
return $return;
}
/**
* For each field, add it to the array if a reference is present in the request:
*
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getIntegerData(array $current, array $transaction): array
{
foreach ($this->integerFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
2022-12-29 19:41:57 +01:00
$current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]);
2020-10-18 08:00:49 +02:00
}
}
return $current;
}
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getStringData(array $current, array $transaction): array
{
foreach ($this->stringFields as $fieldName) {
2020-10-18 08:00:49 +02:00
if (array_key_exists($fieldName, $transaction)) {
2022-12-29 19:41:57 +01:00
$current[$fieldName] = $this->clearString((string)$transaction[$fieldName], false);
2020-10-18 08:00:49 +02:00
}
}
return $current;
}
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getNlStringData(array $current, array $transaction): array
{
foreach ($this->textareaFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
2022-12-29 19:41:57 +01:00
$current[$fieldName] = $this->clearString((string)$transaction[$fieldName]);
2020-10-18 08:00:49 +02:00
}
}
return $current;
}
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getDateData(array $current, array $transaction): array
{
foreach ($this->dateFields as $fieldName) {
Log::debug(sprintf('Now at date field %s', $fieldName));
if (array_key_exists($fieldName, $transaction)) {
2022-12-29 19:41:57 +01:00
Log::debug(sprintf('New value: "%s"', (string)$transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]);
2020-10-18 08:00:49 +02:00
}
}
return $current;
}
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getBooleanData(array $current, array $transaction): array
{
foreach ($this->booleanFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
2022-12-29 19:41:57 +01:00
$current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]);
2020-10-18 08:00:49 +02:00
}
}
return $current;
}
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2020-10-18 08:00:49 +02:00
*
* @return array
*/
private function getArrayData(array $current, array $transaction): array
{
foreach ($this->arrayFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]);
}
}
return $current;
}
2022-12-29 19:41:57 +01:00
/**
2023-06-21 12:34:58 +02:00
* @param array $current
* @param array $transaction
2023-05-29 13:56:55 +02:00
*
2022-12-29 19:41:57 +01:00
* @return array
*/
private function getFloatData(array $current, array $transaction): array
{
foreach ($this->floatFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$value = $transaction[$fieldName];
if (is_float($value)) {
2023-05-15 06:33:30 +02:00
$current[$fieldName] = sprintf('%.12f', $value);
2022-12-29 19:41:57 +01:00
}
if (!is_float($value)) {
$current[$fieldName] = (string)$value;
}
}
}
return $current;
}
/**
* The rules that the incoming request must be matched against.
*
* @return array
*/
public function rules(): array
{
2023-01-11 17:29:19 +01:00
Log::debug(sprintf('Now in %s', __METHOD__));
2023-05-29 13:56:55 +02:00
2020-03-15 08:16:16 +01:00
return [
// basic fields for group:
2023-07-11 08:19:47 +02:00
'group_title' => 'between:1,1000|nullable',
2022-10-30 14:23:00 +01:00
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
2022-10-30 14:23:00 +01:00
'transactions.*.date' => [new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// group id:
2022-10-30 14:23:00 +01:00
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
// currency info
2023-07-11 09:14:16 +02:00
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
2023-04-26 06:17:04 +02:00
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
// amount
'transactions.*.amount' => 'numeric|gt:0|max:100000000000',
'transactions.*.foreign_amount' => 'nullable|numeric|gte:0',
// description
'transactions.*.description' => 'nullable|between:1,1000',
// source of transaction
2022-10-30 14:23:00 +01:00
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
'transactions.*.source_name' => 'between:1,255|nullable',
// destination of transaction
2022-10-30 14:23:00 +01:00
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
'transactions.*.destination_name' => 'between:1,255|nullable',
// budget, category, bill and piggy
2023-07-11 09:14:16 +02:00
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
2022-10-30 14:23:00 +01:00
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser()],
2023-07-11 08:19:47 +02:00
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
'transactions.*.category_name' => 'between:1,255|nullable',
2022-10-30 14:23:00 +01:00
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser()],
// other interesting fields
2022-10-30 14:23:00 +01:00
'transactions.*.reconciled' => [new IsBoolean()],
2023-04-26 05:55:31 +02:00
'transactions.*.notes' => 'min:1|max:50000|nullable',
2023-07-11 09:14:16 +02:00
'transactions.*.tags' => 'between:0,255|nullable',
// meta info fields
2023-04-26 05:55:31 +02:00
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
'transactions.*.external_id' => 'min:1|max:255|nullable',
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
'transactions.*.external_url' => 'min:1|max:255|nullable|url',
// SEPA fields:
2023-04-26 05:55:31 +02:00
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// dates
'transactions.*.interest_date' => 'date|nullable',
'transactions.*.book_date' => 'date|nullable',
'transactions.*.process_date' => 'date|nullable',
'transactions.*.due_date' => 'date|nullable',
'transactions.*.payment_date' => 'date|nullable',
'transactions.*.invoice_date' => 'date|nullable',
];
}
/**
* Configure the validator instance.
*
2023-06-21 12:34:58 +02:00
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
2023-04-26 05:55:31 +02:00
Log::debug('Now in withValidator');
/** @var TransactionGroup $transactionGroup */
$transactionGroup = $this->route()->parameter('transactionGroup');
$validator->after(
function (Validator $validator) use ($transactionGroup) {
// if more than one, verify that there are journal ID's present.
$this->validateJournalIds($validator, $transactionGroup);
// all transaction types must be equal:
2021-05-24 08:06:56 +02:00
$this->validateTransactionTypesForUpdate($validator);
2019-04-06 11:08:46 +02:00
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
// a catch when users submit splits with no source or destination info at all.
2022-10-30 14:23:00 +01:00
$this->preventNoAccountInfo($validator, );
2019-06-04 20:42:11 +02:00
// validate that the currency fits the source and/or destination account.
// validate all account info
$this->validateAccountInformationUpdate($validator, $transactionGroup);
}
);
}
}