Merge pull request #7273 from firefly-iii/ac-accounts

Ac accounts
This commit is contained in:
James Cole 2023-03-25 13:18:29 +01:00 committed by GitHub
commit 2500cc55e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1062 additions and 161 deletions

View File

@ -25,10 +25,105 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Autocomplete;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use JsonException;
/**
* Class AccountController
*/
class AccountController extends Controller
{
use AccountFilter;
private array $balanceTypes;
private AdminAccountRepositoryInterface $adminRepository;
private AccountRepositoryInterface $repository;
/**
* AccountController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->repository = app(AccountRepositoryInterface::class);
$this->adminRepository = app(AdminAccountRepositoryInterface::class);
return $next($request);
}
);
$this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,];
}
/**
* Documentation for this endpoint:
* TODO endpoint is not documented.
*
* @param AutocompleteRequest $request
*
* @return JsonResponse
* @throws JsonException
* @throws FireflyException
* @throws FireflyException
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone'));
$this->adminRepository->setAdministrationId($data['administration_id']);
$return = [];
$result = $this->adminRepository->searchAccount((string)$query, $types, $data['limit']);
$defaultCurrency = app('amount')->getDefaultCurrency();
/** @var Account $account */
foreach ($result as $account) {
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = app('steam')->balance($account, $date);
$nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false));
}
$return[] = [
'id' => (string)$account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'type' => $account->accountType->type,
'currency_id' => $currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
}
// custom order.
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
usort(
$return,
function ($a, $b) use ($order) {
$pos_a = array_search($a['type'], $order, true);
$pos_b = array_search($b['type'], $order, true);
return $pos_a - $pos_b;
}
);
return response()->json($return);
}
}

View File

@ -79,7 +79,7 @@ class Controller extends BaseController
$page = 1;
}
$integers = ['limit'];
$integers = ['limit', 'administration'];
$dates = ['start', 'end', 'date'];
if ($page < 1) {

View File

@ -0,0 +1,98 @@
<?php
/*
* AutocompleteRequest.php
* Copyright (c) 2023 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);
namespace FireflyIII\Api\V2\Request\Autocomplete;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\UserRole;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\User;
use FireflyIII\Validation\Administration\ValidatesAdministrationAccess;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
/**
* Class AutocompleteRequest
*/
class AutocompleteRequest extends FormRequest
{
use ConvertsDataTypes;
use ChecksLogin;
use ValidatesAdministrationAccess;
/**
* @return array
* @throws FireflyException
*/
public function getData(): array
{
$types = $this->convertString('types');
$array = [];
if ('' !== $types) {
$array = explode(',', $types);
}
$limit = $this->convertInteger('limit');
$limit = 0 === $limit ? 10 : $limit;
// remove 'initial balance' and another from allowed types. its internal
$array = array_diff($array, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION]);
/** @var User $user */
$user = auth()->user();
return [
'types' => $array,
'query' => $this->convertString('query'),
'date' => $this->getCarbonDate('date'),
'limit' => $limit,
'administration_id' => (int)($this->get('administration_id', null) ?? $user->getAdministrationId()),
];
}
/**
* @return array
*/
public function rules(): array
{
return [
'limit' => 'min:0|max:1337',
];
}
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
// validate if the account can access this administration
$this->validateAdministration($validator, [UserRole::CHANGE_TRANSACTIONS]);
}
);
}
}

View File

@ -63,4 +63,14 @@ class UserGroup extends Model
{
return $this->hasMany(GroupMembership::class);
}
/**
* Link to accounts.
*
* @return HasMany
*/
public function accounts(): HasMany
{
return $this->hasMany(Account::class);
}
}

View File

@ -25,6 +25,8 @@ namespace FireflyIII\Providers;
use FireflyIII\Repositories\Account\AccountRepository;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepository as AdminAccountRepository;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTasker;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Account\OperationsRepository;
@ -73,6 +75,22 @@ class AccountServiceProvider extends ServiceProvider
}
);
$this->app->bind(
AdminAccountRepositoryInterface::class,
function (Application $app) {
/** @var AdminAccountRepositoryInterface $repository */
$repository = app(AdminAccountRepository::class);
// phpstan thinks auth does not exist.
if ($app->auth->check()) { // @phpstan-ignore-line
$repository->setUser(auth()->user());
$repository->setAdministrationId((int) auth()->user()->user_group_id);
}
return $repository;
}
);
$this->app->bind(
OperationsRepositoryInterface::class,
static function (Application $app) {

View File

@ -0,0 +1,61 @@
<?php
/*
* AccountRepository.php
* Copyright (c) 2023 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/>.
*/
namespace FireflyIII\Repositories\Administration\Account;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use Illuminate\Support\Collection;
/**
* Class AccountRepository
*/
class AccountRepository implements AccountRepositoryInterface
{
use AdministrationTrait;
/**
* @inheritDoc
*/
public function searchAccount(string $query, array $types, int $limit): Collection
{
// search by group, not by user
$dbQuery = $this->userGroup->accounts()
->where('active', true)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType']);
if ('' !== $query) {
// split query on spaces just in case:
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$dbQuery->where('name', 'LIKE', $search);
}
}
if (0 !== count($types)) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$dbQuery->whereIn('account_types.type', $types);
}
return $dbQuery->take($limit)->get(['accounts.*']);
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* AccountRepositoryInterface.php
* Copyright (c) 2023 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/>.
*/
namespace FireflyIII\Repositories\Administration\Account;
use Illuminate\Support\Collection;
/**
* Interface AccountRepositoryInterface
*/
interface AccountRepositoryInterface
{
/**
* @param string $query
* @param array $types
* @param int $limit
*
* @return Collection
*/
public function searchAccount(string $query, array $types, int $limit): Collection;
}

View File

@ -0,0 +1,82 @@
<?php
/*
* AdministrationTrait.php
* Copyright (c) 2023 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/>.
*/
namespace FireflyIII\Support\Repositories\Administration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
/**
* Trait AdministrationTrait
*/
trait AdministrationTrait
{
protected User $user;
protected ?int $administrationId = null;
protected ?UserGroup $userGroup = null;
/**
* @return int
*/
public function getAdministrationId(): int
{
return $this->administrationId;
}
/**
* @param int $administrationId
* @throws FireflyException
*/
public function setAdministrationId(int $administrationId): void
{
$this->administrationId = $administrationId;
$this->refreshAdministration();
}
/**
* @return void
*/
private function refreshAdministration(): void
{
if (null !== $this->administrationId) {
$memberships = GroupMembership::where('user_id', $this->user->id)
->where('user_group_id', $this->administrationId)
->count();
if (0 === $memberships) {
throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $this->administrationId));
}
$this->userGroup = UserGroup::find($this->administrationId);
return;
}
throw new FireflyException(sprintf('Cannot validate administration for user #%d', $this->user->id));
}
public function setUser(Authenticatable|User|null $user): void
{
if(null !== $user) {
$this->user = $user;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* get.js
* Copyright (c) 2022 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/>.
*/
import {api} from "boot/axios";
export default class Accounts {
/**
*
* @param types
* @returns {Promise<AxiosResponse<any>>}
*/
get(types, query) {
let url = 'api/v2/autocomplete/accounts';
return api.get(url, {params: {types: types, query: query, limit: 25}})
}
}

View File

@ -34,7 +34,7 @@ const cache = setupCache({
// "export default () => {}" function below (which runs individually
// for each client)
const url = process.env.DEBUGGING ? 'https://firefly.sd.home' : '/';
const url = process.env.DEBUGGING ? 'https://firefly.sd.local' : '/';
const api = axios.create({baseURL: url, withCredentials: true, adapter: cache.adapter});
export default boot(({app}) => {

View File

@ -0,0 +1,316 @@
<!--
- Split.vue
- Copyright (c) 2023 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/>.
-->
<template>
<div>
<div class="row">
<div class="col">
<div class="text-h6">Info for {{ $route.params.type }} {{ index }}</div>
</div>
</div>
<div class="row">
<div class="col q-mb-xs q-pr-sm">
<q-card bordered flat>
<q-item>
<q-item-section>
<q-item-label><strong>Main info</strong></q-item-label>
</q-item-section>
</q-item>
<q-separator/>
<q-card-section>
<div class="row">
<div class="col q-mb-md">
<TransactionDescription
:submission-error="submissionErrors.description"
:has-submission-error="hasSubmissionErrors.description"
:disabled-input="disabledInput"
:description="transaction.description"
@update:description="updateDescription"/>
</div>
</div>
<div class="row">
<div class="col-4 q-mb-xs q-pr-xs">
<SourceAccount name="Test" :disabled-input="false" submission-error="" :has-submission-error="false" />
</div>
<div class="col-4 q-px-xs">
<q-input
v-model="transaction.amount"
:disable="disabledInput" dense
:error="hasSubmissionErrors.amount" :error-message="submissionErrors.amount"
:label="$t('firefly.amount')" bottom-slots clearable fill-mask="0"
hint="Expects #.##"
mask="#.##"
outlined reverse-fill-mask/>
</div>
<div class="col-4 q-pl-xs">
<q-input dense
v-model="transaction.destination"
:disable="disabledInput"
:error="hasSubmissionErrors.destination"
:error-message="submissionErrors.destination" :label="$t('firefly.destination_account')"
bottom-slots
clearable
outlined/>
</div>
</div>
<div class="row">
<div class="col-4 q-pl-xs">
Optional
</div>
<div class="col-4 q-pl-xs">
Foreign amount
</div>
</div>
</q-card-section>
</q-card>
</div>
<div class="col q-mb-xs q-pl-sm">
<q-card bordered flat>
<q-item>
<q-item-section>
<q-item-label><strong>More meta info</strong></q-item-label>
</q-item-section>
</q-item>
<q-separator/>
<q-card-section>
<div class="row">
<div class="col q-mb-md">
<q-input
v-model="transaction.date" dense
:disable="disabledInput"
:error="hasSubmissionErrors.date" :error-message="submissionErrors.date"
:hint="$t('firefly.date')" bottom-slots outlined
type="date"/>
</div>
<div class="col">
<q-input v-model="transaction.time" :disable="disabledInput" :hint="$t('firefly.time')" bottom-slots
outlined dense
type="time"/>
</div>
</div>
<div class="row">
<div class="col q-mb-md">
<q-input
v-model="transaction.category"
:disable="disabledInput" dense
:error="hasSubmissionErrors.category" :error-message="submissionErrors.category"
:label="$t('firefly.category')" bottom-slots
hint="category"
clearable outlined/>
</div>
<div class="col">
<q-select dense v-model="transaction.budget" clearable outlined hint="Budget"/>
</div>
</div>
<div class="row">
<div class="col q-mb-md">
<q-select
v-model="transaction.bill"
:disable="disabledInput" dense
:error="hasSubmissionErrors.category" :error-message="submissionErrors.category"
:label="$t('firefly.bill')" bottom-slots
hint="bill"
clearable outlined/>
</div>
<div class="col">
<q-select
v-model="transaction.piggy"
:disable="disabledInput" dense
:error="hasSubmissionErrors.category" :error-message="submissionErrors.category"
:label="$t('firefly.piggy')" bottom-slots
hint="bill"
clearable outlined/>
</div>
</div>
<div class="row">
<div class="col">
<q-input
v-model="transaction.tags"
:disable="disabledInput" dense
:error="hasSubmissionErrors.tags" :error-message="submissionErrors.category"
:label="$t('firefly.tags')" bottom-slots
hint="Tags"
clearable outlined/>
</div>
</div>
</q-card-section>
</q-card>
</div>
</div>
<div class="row">
<div class="col q-mb-xs q-pl-sm">
<q-card bordered flat>
<q-item>
<q-item-section>
<q-item-label><strong>More meta info</strong></q-item-label>
</q-item-section>
</q-item>
<q-separator/>
<q-card-section>
Extra opts
</q-card-section>
</q-card>
</div>
<div class="col q-mb-xs q-pl-sm">
<q-card bordered flat>
<q-item>
<q-item-section>
<q-item-label><strong>Date info</strong></q-item-label>
</q-item-section>
</q-item>
<q-separator/>
<q-card-section>
Date fields
</q-card-section>
</q-card>
</div>
</div>
<!--
</div>
<div class="col-4 offset-4">
<q-input v-model="transaction.interest_date" filled type="date" hint="Interest date"/>
<q-input v-model="transaction.book_date" filled type="date" hint="Book date"/>
<q-input v-model="transaction.process_date" filled type="date" hint="Processing date"/>
<q-input v-model="transaction.due_date" filled type="date" hint="Due date"/>
<q-input v-model="transaction.payment_date" filled type="date" hint="Payment date"/>
<q-input v-model="transaction.invoice_date" filled type="date" hint="Invoice date"/>
</div>
</div>
</q-card-section>
</q-card>
-->
<!--
<q-card bordered class="q-mt-md">
<q-card-section>
<div class="text-h6">Meta for {{ $route.params.type }}</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
<q-select filled v-model="transaction.budget" :options="tempBudgets" label="Budget"/>
</div>
<div class="col-6">
<q-input filled clearable v-model="transaction.category" :label="$t('firefly.category')" outlined/>
</div>
</div>
<div class="row">
<div class="col-6">
<q-select filled v-model="transaction.subscription" :options="tempSubscriptions" label="Subscription"/>
</div>
<div class="col-6">
Tags
</div>
</div>
<div class="row">
<div class="col-6">
Bill
</div>
<div class="col-6">
???
</div>
</div>
</q-card-section>
</q-card>
-->
<!--
<q-card bordered class="q-mt-md">
<q-card-section>
<div class="text-h6">Extr for {{ $route.params.type }}</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
Notes
</div>
<div class="col-6">
attachments
</div>
</div>
<div class="row">
<div class="col-6">
Links
</div>
<div class="col-6">
reference
</div>
</div>
<div class="row">
<div class="col-6">
url
</div>
<div class="col-6">
location
</div>
</div>
</q-card-section>
</q-card>
-->
</div>
</template>
<script>
import TransactionDescription from "components/transactions/form/TransactionDescription.vue";
import SourceAccount from "components/transactions/form/SourceAccount.vue";
export default {
name: "Split",
components: {SourceAccount, TransactionDescription},
props: {
index: {
type: Number,
required: true
},
disabledInput: {
type: Boolean,
required: true
},
hasSubmissionErrors: {
type: Object,
required: true
},
submissionErrors: {
type: Object,
required: true
},
transaction: {
type: Object,
required: true
},
},
methods: {
updateDescription(newVal) {
this.transaction.description = newVal;
}
},
watch: {
transaction: {
handler: function (val) {
const obj = {index: this.index, transaction: val}
this.$emit('update:transaction', obj);
},
deep: true
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,167 @@
<!--
- SourceAccount.vue
- Copyright (c) 2023 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/>.
-->
<template>
<q-select
v-model="model"
use-input
:options="options"
@filter="filterFn"
dense
:loading="loading"
outlined
:disable="disabledInput"
:error="hasSubmissionError"
:label="$t('firefly.source_account')"
:error-message="submissionError"
bottom-slots
clearable
>
<!--
input-debounce="0"
label="Lazy filter"
-->
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
<q-item-label caption>{{ scope.opt.description }}</q-item-label>
</q-item-section>
</q-item>
</template>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</template>
<!--
source account is basic dropdown from API
with optional filters on account type. This depends
on transaction type which is null or invalid or withdrawal or whatever
if the index is not null the field shall be disabled and empty.
-->
<script>
import Accounts from '../../../api/v2/autocomplete/accounts'
export default {
name: "SourceAccount",
data() {
return {
model: null,
transactionTypeString: '',
options: [],
loading: true,
}
},
props: {
name: {
type: String,
required: true
},
transactionType: {
type: String,
required: false
},
disabledInput: {
type: Boolean,
default: false,
required: true
},
hasSubmissionError: {
type: Boolean,
default: false,
required: true
},
submissionError: {
type: String,
required: true
}
},
mounted() {
console.log('Mounted');
//this.options.value = this.stringOptions
this.getAccounts('');
},
methods: {
getAccounts: function (query) {
this.loading = true;
console.log('getAccounts("'+query+'")');
// default set of account types, will later be set by the transaction type.
let types = 'Asset account,Revenue account,Loan,Debt,Mortgage';
(new Accounts).get(types, query).then(response => {
this.stringOptions = [];
for (let i in response.data) {
let entry = response.data[i];
let current = {
label: entry.name,
value: entry.id,
description: entry.type
}
this.stringOptions.push(current);
}
//this.stringOptions = response.data.data;
this.options = this.stringOptions;
this.loading = false;
console.log('getAccounts done!');
});
},
filterFn(val, update, abort) {
console.log('filterFn(' + val + ')');
if (val === '') {
update(() => {
this.getAccounts('');
//this.options = stringOptions
// here you have access to "ref" which
// is the Vue reference of the QSelect
})
return
}
update(() => {
this.getAccounts(val);
//const needle = val.toLowerCase()
//this.options = this.options.filter(v => v.label.toLowerCase().indexOf(needle) > -1)
})
// console.log('filterFn(' + val + ')');
// if (this.loading) {
// console.log('return');
// return
// }
// const needle = val.toLowerCase()
// this.options = this.stringOptions.filter(v => v.label.toLowerCase().indexOf(needle) > -1);
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,70 @@
<!--
- TransactionDescription.vue
- Copyright (c) 2023 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/>.
-->
<template>
<q-input
v-model="description"
hint=" " dense
:disable="disabledInput"
:error="hasSubmissionError" :error-message="submissionError"
:label="$t('firefly.description')" bottom-slots clearable
outlined
type="text"/>
</template>
<script>
export default {
name: "TransactionDescription",
props: {
description: {
type: String,
required: true
},
disabledInput: {
type: Boolean,
default: false,
required: true
},
hasSubmissionError: {
type: Boolean,
default: false,
required: true
},
submissionError: {
type: String,
required: true
}
},
watch: {
description: {
handler: function (newVal) {
this.$emit('update:description', newVal)
},
deep: true
}
}
}
</script>
<style scoped>
</style>

View File

@ -30,7 +30,6 @@
</q-banner>
</div>
</div>
<!--
<div class="row q-ma-md">
<div class="col-12">
<q-card>
@ -43,163 +42,28 @@
align="left"
class="text-teal col"
>
<q-tab v-for="(transaction,index) in transactions" :name="'split-' + index" :label="getSplitLabel(index)"/>
<q-btn @click="addTransaction" flat label="Add split" icon="fas fa-plus-circle" class="text-orange"></q-btn>
<q-tab v-for="(transaction,index) in transactions" :name="'split-' + index"
:label="getSplitLabel(index)"/>
<q-btn @click="addTransaction" flat label="Add split" icon="fas fa-plus-circle"
class="text-orange"></q-btn>
</q-tabs>
</div>
</q-card-section>
</q-card>
</div>
</div>
-->
<div class="row">
<div class="col-12">
<q-tab-panels v-model="tab" animated>
<q-tab-panel v-for="(transaction,index) in transactions" :key="index" :name="'split-' + index">
<q-card bordered>
<q-card-section>
<div class="text-h6">Info for {{ $route.params.type }} {{ index }}</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-12 q-mb-xs">
<q-input
v-model="transaction.description"
:disable="disabledInput"
:error="hasSubmissionErrors[index].description" :error-message="submissionErrors[index].description" :label="$t('firefly.description')" bottom-slots clearable
outlined
type="text"/>
</div>
</div>
<div class="row">
<div class="col-4 q-mb-xs q-pr-xs">
<q-input
v-model="transaction.source"
:disable="disabledInput"
:error="hasSubmissionErrors[index].source" :error-message="submissionErrors[index].source" :label="$t('firefly.source_account')" bottom-slots
clearable outlined/>
</div>
<div class="col-4 q-px-xs">
<q-input
v-model="transaction.amount"
:disable="disabledInput"
:error="hasSubmissionErrors[index].amount" :error-message="submissionErrors[index].amount" :label="$t('firefly.amount')" bottom-slots clearable fill-mask="0"
hint="Expects #.##"
mask="#.##"
outlined reverse-fill-mask/>
</div>
<div class="col-4 q-pl-xs">
<q-input
v-model="transaction.destination"
:disable="disabledInput"
:error="hasSubmissionErrors[index].destination" :error-message="submissionErrors[index].destination" :label="$t('firefly.destination_account')" bottom-slots
clearable
outlined/>
</div>
</div>
<!--
<div class="row">
<div class="col-4 offset-4">
Foreign
</div>
</div>
-->
<div class="row">
<div class="col-4">
<div class="row">
<div class="col">
<q-input
v-model="transaction.date"
:disable="disabledInput"
:error="hasSubmissionErrors[index].date" :error-message="submissionErrors[index].date" :hint="$t('firefly.date')" bottom-slots outlined
type="date"/>
</div>
<div class="col">
<q-input v-model="transaction.time" :disable="disabledInput" :hint="$t('firefly.time')" bottom-slots outlined
type="time"/>
</div>
</div>
</div>
<!--
<div class="col-4 offset-4">
<q-input v-model="transaction.interest_date" filled type="date" hint="Interest date"/>
<q-input v-model="transaction.book_date" filled type="date" hint="Book date"/>
<q-input v-model="transaction.process_date" filled type="date" hint="Processing date"/>
<q-input v-model="transaction.due_date" filled type="date" hint="Due date"/>
<q-input v-model="transaction.payment_date" filled type="date" hint="Payment date"/>
<q-input v-model="transaction.invoice_date" filled type="date" hint="Invoice date"/>
</div>
-->
</div>
</q-card-section>
</q-card>
<!--
<q-card bordered class="q-mt-md">
<q-card-section>
<div class="text-h6">Meta for {{ $route.params.type }}</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
<q-select filled v-model="transaction.budget" :options="tempBudgets" label="Budget"/>
</div>
<div class="col-6">
<q-input filled clearable v-model="transaction.category" :label="$t('firefly.category')" outlined/>
</div>
</div>
<div class="row">
<div class="col-6">
<q-select filled v-model="transaction.subscription" :options="tempSubscriptions" label="Subscription"/>
</div>
<div class="col-6">
Tags
</div>
</div>
<div class="row">
<div class="col-6">
Bill
</div>
<div class="col-6">
???
</div>
</div>
</q-card-section>
</q-card>
-->
<!--
<q-card bordered class="q-mt-md">
<q-card-section>
<div class="text-h6">Extr for {{ $route.params.type }}</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
Notes
</div>
<div class="col-6">
attachments
</div>
</div>
<div class="row">
<div class="col-6">
Links
</div>
<div class="col-6">
reference
</div>
</div>
<div class="row">
<div class="col-6">
url
</div>
<div class="col-6">
location
</div>
</div>
</q-card-section>
</q-card>
-->
<Split
:transaction="transaction"
:index="index"
:disabled-input="disabledInput"
:has-submission-errors="hasSubmissionErrors[index]"
:submission-errors="submissionErrors[index]"
@update:transaction="updateTransaction"
/>
</q-tab-panel>
<!--
@ -219,7 +83,7 @@
<div class="row q-mx-md">
<div class="col-12">
<q-card class="q-mt-xs">
<q-card class="q-mt-xs" bordered flat>
<q-card-section>
<div class="row">
<div class="col-12 text-right">
@ -231,7 +95,8 @@
<q-checkbox v-model="doReturnHere" :disable="disabledInput" label="Return here to create another one"
left-label/>
<br/>
<q-checkbox v-model="doResetForm" :disable="!doReturnHere || disabledInput" label="Reset form after submission"
<q-checkbox v-model="doResetForm" :disable="!doReturnHere || disabledInput"
label="Reset form after submission"
left-label/>
</div>
</div>
@ -246,9 +111,11 @@
import format from 'date-fns/format';
import formatISO from 'date-fns/formatISO';
import Post from "../../api/transactions/post";
import Split from "components/transactions/Split.vue";
export default {
name: 'Create',
components: {Split},
data() {
return {
tab: 'split-0',
@ -268,35 +135,50 @@ export default {
},
computed: {
disabledInput: function () {
return this.submitting;
return this.submitting ?? false;
}
},
created() {
console.log('Created');
this.resetForm();
},
methods: {
resetForm: function () {
console.log('ResetForm');
this.transactions = [];
const info = this.getDefaultTransaction();
this.transactions.push(info.transaction);
this.submissionErrors.push(info.submissionError);
this.hasSubmissionErrors.push(info.hasSubmissionError);
this.addTransaction();
// const info = this.getDefaultTransaction();
// this.transactions.push(info.transaction);
// this.submissionErrors.push(info.submissionError);
// this.hasSubmissionErrors.push(info.hasSubmissionError);
},
addTransaction: function () {
const transaction = this.getDefaultTransaction();
this.transactions.push(transaction);
this.tab = 'split-' + (parseInt(this.transactions.length) - 1);
// push all three
this.transactions.push(transaction.transaction);
this.submissionErrors.push(transaction.submissionError);
this.hasSubmissionErrors.push(transaction.hasSubmissionError);
const index = String(this.transactions.length - 1);
console.log('AddTransaction ' + index);
this.tab = 'split-' + index;
},
getSplitLabel: function (index) {
if (this.transactions.hasOwnProperty(index) && null !== this.transactions[index].description && this.transactions[index].description.length > 0) {
console.log('Get split label (' + index + ')');
if (this.transactions.hasOwnProperty(index) &&
null !== this.transactions[index].description &&
this.transactions[index].description.length > 0) {
return this.transactions[index].description
}
return this.$t('firefly.single_split') + ' ' + (index + 1);
},
dismissBanner: function () {
console.log('Dismiss banner');
this.errorMessage = '';
},
submitTransaction: function () {
console.log('submit transaction');
this.submitting = true;
this.errorMessage = '';
@ -312,7 +194,15 @@ export default {
.catch(this.processErrors)
.then(this.processSuccess);
},
updateTransaction: function (obj) {
const index = obj.index;
const transaction = obj.transaction;
console.log('Update transaction ' + index);
console.log(transaction);
this.transactions[index] = transaction;
},
processSuccess: function (response) {
console.log('process success');
this.submitting = false;
let message = {
level: 'success',
@ -340,6 +230,7 @@ export default {
},
resetErrors: function () {
console.log('reset errors');
let length = this.transactions.length;
let transaction = this.getDefaultTransaction();
for (let i = 0; i < length; i++) {
@ -348,6 +239,7 @@ export default {
}
},
processErrors: function (error) {
console.log('process errors');
if (error.response) {
let errors = error.response.data; // => the response payload
this.errorMessage = errors.message;
@ -360,7 +252,8 @@ export default {
this.submitting = false;
},
processSingleError: function (key, errors) {
// lol dumbest way to explode "transactions.0.something" ever.
console.log('process single error');
// lol the dumbest way to explode "transactions.0.something" ever.
let index = parseInt(key.split('.')[1]);
let fieldName = key.split('.')[2];
switch (fieldName) {
@ -383,6 +276,7 @@ export default {
}
},
buildTransaction: function () {
console.log('build transaction');
const obj = {
transactions: []
};
@ -401,6 +295,7 @@ export default {
return obj;
},
getDefaultTransaction: function () {
console.log('get default transaction');
let date = '';
let time = '00:00';

View File

@ -48,6 +48,21 @@ Route::group(
}
);
/**
* V2 API routes for auto complete
*/
Route::group(
[
'namespace' => 'FireflyIII\Api\V2\Controllers\Autocomplete',
'prefix' => 'v2/autocomplete',
'as' => 'api.v2.autocomplete.',
],
static function () {
// Auto complete routes
Route::get('accounts', ['uses' => 'AccountController@accounts', 'as' => 'accounts']);
}
);
/**
* V2 API route for net worth endpoint(s);
*/