Files
firefly-iii/app/Console/Commands/VerifyDatabase.php

336 lines
12 KiB
PHP
Raw Normal View History

2016-04-24 18:25:52 +02:00
<?php
2016-05-20 11:59:54 +02:00
/**
* VerifyDatabase.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
2016-05-20 11:59:54 +02:00
*/
2016-05-20 08:57:45 +02:00
declare(strict_types = 1);
2016-04-24 18:25:52 +02:00
namespace FireflyIII\Console\Commands;
use Crypt;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
2016-04-24 18:35:45 +02:00
use FireflyIII\Models\Transaction;
2016-04-24 18:25:52 +02:00
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
2016-04-24 18:25:52 +02:00
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command;
2016-04-24 18:35:45 +02:00
use Illuminate\Database\Eloquent\Builder;
2016-04-24 18:25:52 +02:00
use stdClass;
/**
* Class VerifyDatabase
*
* @package FireflyIII\Console\Commands
*/
class VerifyDatabase extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Will verify your database.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly:verify';
/**
* Create a new command instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*/
public function handle()
{
// accounts with no transactions.
$this->reportAccounts();
// budgets with no limits
$this->reportBudgetLimits();
// budgets with no transactions
$this->reportBudgets();
// categories with no transactions
$this->reportCategories();
// tags with no transactions
$this->reportTags();
// sum of transactions is not zero.
$this->reportSum();
// any deleted transaction journals that have transactions that are NOT deleted:
$this->reportJournals();
// deleted transactions that are connected to a not deleted journal.
$this->reportTransactions();
// deleted accounts that still have not deleted transactions or journals attached to them.
$this->reportDeletedAccounts();
2016-04-29 17:26:38 +02:00
// report on journals with no transactions at all.
$this->reportNoTransactions();
// transfers with budgets.
$this->reportTransfersBudgets();
2016-04-24 18:25:52 +02:00
}
/**
* Reports on accounts with no transactions.
*/
private function reportAccounts()
{
$set = Account
::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
->whereNull('transactions.account_id')
2016-08-13 23:31:42 +02:00
->get(
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
2016-08-13 23:31:42 +02:00
);
2016-04-24 18:25:52 +02:00
/** @var stdClass $entry */
foreach ($set as $entry) {
$name = $entry->name;
2016-08-13 23:31:42 +02:00
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name);
2016-04-24 18:25:52 +02:00
$this->line($line);
}
}
/**
* Reports on budgets with no budget limits (which makes them pointless).
*/
private function reportBudgetLimits()
{
$set = Budget
::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
->groupBy(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email'])
->whereNull('budget_limits.id')
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
2016-04-24 18:25:52 +02:00
/** @var stdClass $entry */
foreach ($set as $entry) {
2016-04-24 18:36:44 +02:00
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
2016-04-24 18:25:52 +02:00
. '") which has no budget limits.';
$this->line($line);
}
}
/**
* Reports on budgets without any transactions.
*/
private function reportBudgets()
{
$set = Budget
::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
->distinct()
2016-05-11 08:40:22 +02:00
->whereNull('budget_transaction_journal.budget_id')
->whereNull('budgets.deleted_at')
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']);
2016-04-24 18:25:52 +02:00
/** @var stdClass $entry */
foreach ($set as $entry) {
2016-04-24 18:36:44 +02:00
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has budget #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
2016-04-24 18:25:52 +02:00
. '") which has no transactions.';
$this->line($line);
}
}
/**
* Reports on categories without any transactions.
*/
private function reportCategories()
{
$set = Category
::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id')
->leftJoin('users', 'categories.user_id', '=', 'users.id')
->distinct()
2016-05-11 08:40:22 +02:00
->whereNull('category_transaction_journal.category_id')
->whereNull('categories.deleted_at')
->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']);
2016-04-24 18:25:52 +02:00
/** @var stdClass $entry */
foreach ($set as $entry) {
2016-04-24 18:36:44 +02:00
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has category #' . $entry->id . ' ("' . Crypt::decrypt($entry->name)
2016-04-24 18:25:52 +02:00
. '") which has no transactions.';
$this->line($line);
}
}
2016-04-24 18:35:45 +02:00
/**
* Reports on deleted accounts that still have not deleted transactions or journals attached to them.
*/
2016-04-24 18:25:52 +02:00
private function reportDeletedAccounts()
{
2016-04-24 18:35:45 +02:00
$set = Account
::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->whereNotNull('accounts.deleted_at')
->whereNotNull('transactions.id')
->where(
function (Builder $q) {
$q->whereNull('transactions.deleted_at');
$q->orWhereNull('transaction_journals.deleted_at');
}
)
->get(
['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id',
'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id',
'transaction_journals.deleted_at as journal_deleted_at']
);
/** @var stdClass $entry */
foreach ($set as $entry) {
$date = is_null($entry->transaction_deleted_at) ? $entry->journal_deleted_at : $entry->transaction_deleted_at;
$this->error(
2016-04-24 18:36:44 +02:00
'Error: Account #' . $entry->account_id . ' should have been deleted, but has not.' .
2016-10-06 05:26:38 +02:00
' Find it in the table called "accounts" and change the "deleted_at" field to: "' . $date . '"'
2016-04-24 18:35:45 +02:00
);
}
2016-04-24 18:25:52 +02:00
}
/**
* Any deleted transaction journals that have transactions that are NOT deleted:
*/
private function reportJournals()
{
$set = TransactionJournal
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNotNull('transaction_journals.deleted_at')// USE THIS
->whereNull('transactions.deleted_at')
->whereNotNull('transactions.id')
->get(
[
'transaction_journals.id as journal_id',
'transaction_journals.description',
'transaction_journals.deleted_at as journal_deleted',
'transactions.id as transaction_id',
'transactions.deleted_at as transaction_deleted_at']
);
/** @var stdClass $entry */
foreach ($set as $entry) {
$this->error(
2016-04-24 18:36:44 +02:00
'Error: Transaction #' . $entry->transaction_id . ' should have been deleted, but has not.' .
2016-10-06 05:26:38 +02:00
' Find it in the table called "transactions" and change the "deleted_at" field to: "' . $entry->journal_deleted . '"'
2016-04-24 18:25:52 +02:00
);
}
}
/**
*
*/
2016-04-29 17:26:38 +02:00
private function reportNoTransactions()
{
$set = TransactionJournal
::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->groupBy('transaction_journals.id')
->whereNull('transactions.transaction_journal_id')
->get(['transaction_journals.id']);
2016-04-29 17:26:38 +02:00
foreach ($set as $entry) {
$this->error(
2016-10-06 05:26:38 +02:00
'Error: Journal #' . $entry->id . ' has zero transactions. Open table "transaction_journals" and delete the entry with id #' . $entry->id
2016-04-29 17:26:38 +02:00
);
}
}
2016-04-24 18:25:52 +02:00
/**
* Reports for each user when the sum of their transactions is not zero.
*/
private function reportSum()
{
/** @var UserRepositoryInterface $userRepository */
2016-05-01 15:05:29 +02:00
$userRepository = app(UserRepositoryInterface::class);
2016-04-24 18:25:52 +02:00
/** @var User $user */
foreach ($userRepository->all() as $user) {
$sum = strval($user->transactions()->sum('amount'));
2016-04-24 18:25:52 +02:00
if (bccomp($sum, '0') !== 0) {
2016-04-24 18:36:44 +02:00
$this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!');
2016-04-24 18:25:52 +02:00
}
}
}
/**
* Reports on tags without any transactions.
*/
private function reportTags()
{
$set = Tag
::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
->leftJoin('users', 'tags.user_id', '=', 'users.id')
->distinct()
2016-05-11 08:40:22 +02:00
->whereNull('tag_transaction_journal.tag_id')
->whereNull('tags.deleted_at')
->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']);
2016-04-24 18:25:52 +02:00
/** @var stdClass $entry */
foreach ($set as $entry) {
2016-04-24 18:36:44 +02:00
$line = 'Notice: User #' . $entry->user_id . ' (' . $entry->email . ') has tag #' . $entry->id . ' ("' . $entry->tag
2016-04-24 18:25:52 +02:00
. '") which has no transactions.';
$this->line($line);
}
}
2016-04-24 18:35:45 +02:00
/**
* Reports on deleted transactions that are connected to a not deleted journal.
*/
2016-04-24 18:25:52 +02:00
private function reportTransactions()
{
2016-04-24 18:35:45 +02:00
$set = Transaction
::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNotNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->get(
['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id',
'transaction_journals.deleted_at']
);
/** @var stdClass $entry */
foreach ($set as $entry) {
$this->error(
2016-04-24 18:36:44 +02:00
'Error: Transaction journal #' . $entry->journal_id . ' should have been deleted, but has not.' .
2016-10-06 05:26:38 +02:00
' Find it in the table called "transaction_journals" and change the "deleted_at" field to: "' . $entry->transaction_deleted . '"'
2016-04-24 18:35:45 +02:00
);
}
2016-04-24 18:25:52 +02:00
}
/**
*
*/
private function reportTransfersBudgets()
{
$set = TransactionJournal
::distinct()
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id')
->where('transaction_types.type', TransactionType::TRANSFER)
->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.id']);
/** @var TransactionJournal $entry */
foreach ($set as $entry) {
$this->error(
sprintf(
'Error: Transaction journal #%d is a transfer, but has a budget. Edit it without changing anything, so the budget will be removed.',
$entry->id
)
);
}
}
2016-04-24 18:25:52 +02:00
}