mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
First attempt at #142. Needs a lot of work still.
This commit is contained in:
203
app/Crud/Split/Journal.php
Normal file
203
app/Crud/Split/Journal.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Journal.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Crud\Split;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class Journal
|
||||
*
|
||||
* @package FireflyIII\Crud\Split
|
||||
*/
|
||||
class Journal implements JournalInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* AttachmentRepository constructor.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function storeJournal(array $data) : TransactionJournal
|
||||
{
|
||||
// find transaction type.
|
||||
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
|
||||
$journal = new TransactionJournal(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'transaction_currency_id' => $data['currency_id'],
|
||||
'description' => $data['description'],
|
||||
'completed' => 0,
|
||||
'date' => $data['date'],
|
||||
'interest_date' => $data['interest_date'],
|
||||
'book_date' => $data['book_date'],
|
||||
'process_date' => $data['process_date'],
|
||||
]
|
||||
);
|
||||
$journal->save();
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $transaction
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function storeTransaction(TransactionJournal $journal, array $transaction): Collection
|
||||
{
|
||||
// store accounts (depends on type)
|
||||
list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction);
|
||||
|
||||
// store transaction one way:
|
||||
/** @var Transaction $one */
|
||||
$one = Transaction::create( // first transaction.
|
||||
[
|
||||
'account_id' => $sourceAccount->id,
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'amount' => $transaction['amount'] * -1,
|
||||
]
|
||||
);
|
||||
|
||||
// store transaction the other way:
|
||||
$two = Transaction::create( // first transaction.
|
||||
[
|
||||
'account_id' => $destinationAccount->id,
|
||||
'transaction_journal_id' => $journal->id,
|
||||
'amount' => $transaction['amount'],
|
||||
]
|
||||
);
|
||||
|
||||
// store or get category and connect:
|
||||
if (strlen($transaction['category']) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]);
|
||||
$one->categories()->save($category);
|
||||
$two->categories()->save($category);
|
||||
}
|
||||
// store or get budget
|
||||
if (intval($transaction['budget_id']) > 0) {
|
||||
$budget = Budget::find($transaction['budget_id']);
|
||||
$one->budgets()->save($budget);
|
||||
$two->budgets()->save($budget);
|
||||
}
|
||||
|
||||
return new Collection([$one, $two]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array $transaction
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function storeAccounts(string $type, array $transaction): array
|
||||
{
|
||||
$sourceAccount = null;
|
||||
$destinationAccount = null;
|
||||
switch ($type) {
|
||||
case TransactionType::WITHDRAWAL:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first();
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Cannot handle ' . e($type));
|
||||
}
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeDepositAccounts(array $data): array
|
||||
{
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']);
|
||||
|
||||
if (strlen($data['source_account_name']) > 0) {
|
||||
$fromType = AccountType::where('type', 'Revenue account')->first();
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
);
|
||||
|
||||
return [$fromAccount, $destinationAccount];
|
||||
} else {
|
||||
$fromType = AccountType::where('type', 'Cash account')->first();
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
}
|
||||
|
||||
return [$fromAccount, $destinationAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeWithdrawalAccounts(array $data): array
|
||||
{
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
|
||||
|
||||
if (strlen($data['destination_account_name']) > 0) {
|
||||
$destinationType = AccountType::where('type', 'Expense account')->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'account_type_id' => $destinationType->id,
|
||||
'name' => $data['destination_account_name'],
|
||||
'active' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
}
|
||||
$destinationType = AccountType::where('type', 'Cash account')->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
return [$sourceAccount, $destinationAccount];
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
39
app/Crud/Split/JournalInterface.php
Normal file
39
app/Crud/Split/JournalInterface.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalInterface.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Crud\Split;
|
||||
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface JournalInterface
|
||||
*
|
||||
* @package FireflyIII\Crud\Split
|
||||
*/
|
||||
interface JournalInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function storeJournal(array $data) : TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $transaction
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function storeTransaction(TransactionJournal $journal, array $transaction): Collection;
|
||||
}
|
||||
@@ -11,11 +11,14 @@ namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Crud\Split\JournalInterface;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SplitJournalFormRequest;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class SplitController
|
||||
@@ -39,26 +42,45 @@ class SplitController extends Controller
|
||||
|
||||
// expect data to be in session or in post?
|
||||
$journalData = session('temporary_split_data');
|
||||
$currency = $currencyRepository->find(intval($journalData['amount_currency_id_amount']));
|
||||
$currencies = ExpandedForm::makeSelectList($currencyRepository->get());
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
|
||||
if (!is_array($journalData)) {
|
||||
throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me.
|
||||
}
|
||||
// echo '<pre>';
|
||||
// var_dump($journalData);
|
||||
// echo '</pre>';
|
||||
// exit;
|
||||
|
||||
return view('split.journals.from-store', compact('currency', 'assetAccounts', 'budgets'))->with('data', $journalData);
|
||||
return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets'))->with('data', $journalData);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function postJournalFromStore()
|
||||
/**
|
||||
* @param SplitJournalFormRequest $request
|
||||
* @param JournalInterface $repository
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function postJournalFromStore(SplitJournalFormRequest $request, JournalInterface $repository)
|
||||
{
|
||||
$data = $request->getSplitData();
|
||||
|
||||
// store an empty journal first. This will be the place holder.
|
||||
$journal = $repository->storeJournal($data);
|
||||
// Then, store each transaction individually.
|
||||
|
||||
foreach ($data['transactions'] as $transaction) {
|
||||
$transactions = $repository->storeTransaction($journal, $transaction);
|
||||
}
|
||||
|
||||
// TODO move to repository.
|
||||
$journal->completed = true;
|
||||
$journal->save();
|
||||
|
||||
// forget temp journal data
|
||||
// Session::forget('temporary_split_data');
|
||||
Session::forget('temporary_split_data');
|
||||
|
||||
// this is where we originally came from.
|
||||
return redirect(session('transactions.create.url'));
|
||||
}
|
||||
|
||||
}
|
||||
85
app/Http/Requests/SplitJournalFormRequest.php
Normal file
85
app/Http/Requests/SplitJournalFormRequest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* SplitJournalFormRequest.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
||||
/**
|
||||
* Class SplitJournalFormRequest
|
||||
*
|
||||
* @package FireflyIII\Http\Requests
|
||||
*/
|
||||
class SplitJournalFormRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Only allow logged in users
|
||||
return Auth::check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSplitData(): array
|
||||
{
|
||||
$data = [
|
||||
'description' => $this->get('journal_description'),
|
||||
'currency_id' => intval($this->get('currency')),
|
||||
'source_account_id' => intval($this->get('source_account_id')),
|
||||
'date' => new Carbon($this->get('date')),
|
||||
'what' => $this->get('what'),
|
||||
'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null,
|
||||
'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null,
|
||||
'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null,
|
||||
'transactions' => [],
|
||||
];
|
||||
// description is leading because it is one of the mandatory fields.
|
||||
foreach ($this->get('description') as $index => $description) {
|
||||
$transaction = [
|
||||
'description' => $description,
|
||||
'amount' => round($this->get('amount')[$index], 2),
|
||||
'budget_id' => $this->get('budget')[$index] ? $this->get('budget')[$index] : 0,
|
||||
'category' => $this->get('category')[$index] ?? '',
|
||||
'source_account_id' => intval($this->get('source_account_id')),
|
||||
'destination_account_name' => $this->get('destination_account_name')[$index] ?? ''
|
||||
];
|
||||
$data['transactions'][] = $transaction;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'journal_description' => 'required|between:1,255',
|
||||
'currency' => 'required|exists:transaction_currencies,id',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
'interest_date' => 'date',
|
||||
'book_date' => 'date',
|
||||
'process_date' => 'date',
|
||||
'description.*' => 'required|between:1,255',
|
||||
'destination_account_name.*' => 'between:1,255',
|
||||
'amount.*' => 'required|numeric',
|
||||
'budget.*' => 'belongsToUser:budgets,id',
|
||||
'category.*' => 'between:1,255',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -346,6 +346,7 @@ Route::group(
|
||||
* Split controller
|
||||
*/
|
||||
Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']);
|
||||
Route::post('/transaction/split', ['uses' => 'Transaction\SplitController@postJournalFromStore', 'as' => 'split.journal.from-store.post']);
|
||||
|
||||
/**
|
||||
* Tag Controller
|
||||
|
||||
@@ -34,6 +34,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereActive($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereEncrypted($value)
|
||||
* @mixin \Eloquent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions
|
||||
*/
|
||||
class Budget extends Model
|
||||
{
|
||||
@@ -136,6 +137,14 @@ class Budget extends Model
|
||||
return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'budget_transaction_journal', 'budget_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function transactions()
|
||||
{
|
||||
return $this->belongsToMany('FireflyIII\Models\Transaction', 'budget_transaction', 'budget_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereUserId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereEncrypted($value)
|
||||
* @mixin \Eloquent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions
|
||||
*/
|
||||
class Category extends Model
|
||||
{
|
||||
@@ -116,6 +117,13 @@ class Category extends Model
|
||||
{
|
||||
return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'category_transaction_journal', 'category_id');
|
||||
}
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function transactions()
|
||||
{
|
||||
return $this->belongsToMany('FireflyIII\Models\Transaction', 'category_transaction', 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
|
||||
@@ -32,6 +32,8 @@ use Watson\Validating\ValidatingTrait;
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereDescription($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereAmount($value)
|
||||
* @mixin \Eloquent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories
|
||||
*/
|
||||
class Transaction extends Model
|
||||
{
|
||||
@@ -104,4 +106,20 @@ class Transaction extends Model
|
||||
{
|
||||
return $this->belongsTo('FireflyIII\Models\TransactionJournal');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function budgets()
|
||||
{
|
||||
return $this->belongsToMany('FireflyIII\Models\Budget');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function categories()
|
||||
{
|
||||
return $this->belongsToMany('FireflyIII\Models\Category');
|
||||
}
|
||||
}
|
||||
|
||||
54
app/Providers/CrudServiceProvider.php
Normal file
54
app/Providers/CrudServiceProvider.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* CrudServiceProvider.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* Class CrudServiceProvider
|
||||
*
|
||||
* @package FireflyIII\Providers
|
||||
*/
|
||||
class CrudServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
'FireflyIII\Crud\Split\JournalInterface',
|
||||
function (Application $app, array $arguments) {
|
||||
if (!isset($arguments[0]) && $app->auth->check()) {
|
||||
return app('FireflyIII\Crud\Split\Journal', [$app->auth->user()]);
|
||||
}
|
||||
if (!isset($arguments[0]) && !$app->auth->check()) {
|
||||
throw new FireflyException('There is no user present.');
|
||||
}
|
||||
|
||||
return app('FireflyIII\Crud\Split\Journal', $arguments);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,6 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
);
|
||||
$journal->save();
|
||||
|
||||
|
||||
// store or get category
|
||||
if (strlen($data['category']) > 0) {
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
|
||||
@@ -403,8 +402,8 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$sourceAccount = Account::find($data['account_from_id']);
|
||||
$destinationAccount = Account::find($data['account_to_id']);
|
||||
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first();
|
||||
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Did not recognise transaction type.');
|
||||
@@ -439,9 +438,10 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
);
|
||||
|
||||
return [$fromAccount, $destinationAccount];
|
||||
} else {
|
||||
$fromType = AccountType::where('type', 'Cash account')->first();
|
||||
$fromType = AccountType::where('type', 'Cash account')->first();
|
||||
$fromAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
);
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace FireflyIII\Support\Models;
|
||||
use FireflyIII\Models\Tag;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* FireflyIII\Support\Models\TagSupport
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class TagSupport extends Model
|
||||
{
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user