Implemented migration procedure.

This commit is contained in:
James Cole 2014-06-30 10:47:14 +02:00
parent ecadf005a8
commit 39dd382e87
9 changed files with 531 additions and 34 deletions

View File

@ -9,7 +9,7 @@ return [
'table' => 'password_reminders',
'expire' => 60,
],
'verify_mail' => true,
'verify_mail' => false,
'verify_reset' => true,
'allow_register' => true

View File

@ -1,17 +1,22 @@
<?php
class MigrationController extends BaseController {
public function index() {
class MigrationController extends BaseController
{
public function index()
{
// check if database connection is present.
$configValue = Config::get('database.connections.old-firefly');
if(is_null($configValue)) {
if (is_null($configValue)) {
return View::make('migrate.index');
}
// try to connect to it:
$error = '';
try {
DB::connection('old-firefly')->select('SELECT * from `users`;');
} catch(PDOException $e) {
return View::make('migrate.index');
DB::connection('old-firefly')->select('SELECT * from `users`;');
} catch (PDOException $e) {
$error = $e->getMessage();
return View::make('migrate.index')->with('error', $error);
}
return Redirect::route('migrate.select-user');
@ -19,11 +24,404 @@ class MigrationController extends BaseController {
}
public function selectUser() {
// select a user to import data from.
public function selectUser()
{
$oldUsers = [];
try {
$oldUsers = DB::connection('old-firefly')->select('SELECT * from `users`;');
} catch (PDOException $e) {
$error = $e->getMessage();
return View::make('migrate.index')->with('error', $error);
}
return View::make('migrate.select-user')->with('oldUsers', $oldUsers);
}
public function migrate($userID) {
public function postSelectUser()
{
$userID = Input::get('user');
$count = DB::connection('old-firefly')->table('users')->where('id', $userID)->count();
if ($count == 1) {
return Redirect::route('migrate.migrate', $userID);
} else {
return View::make('error')->with('message', 'No such user!');
}
}
public function migrate($userID)
{
// import the data.
$user = Auth::user();
$previousUser = DB::connection('old-firefly')->table('users')->where('id', $userID)->first();
// current user already migrated?
if ($user->migrated) {
return View::make('error')->with('message', 'This user has already been migrated.');
}
// a map to keep the connections intact:
$map = [
'accounts' => [],
'beneficiaries' => [],
'categories' => [],
'budgets' => [],
];
// messages to show in result screen:
$messages = [];
// grab account types:
$defaultAT = AccountType::where('description', 'Default account')->first();
$initialBalanceAT = AccountType::where('description', 'Initial balance account')->first();
$beneficiaryAT = AccountType::where('description', 'Beneficiary account')->first();
// grab transaction types:
$initialBalanceTT = TransactionType::where('type', 'Opening balance')->first();
$depositTT = TransactionType::where('type', 'Deposit')->first();
$withdrawalTT = TransactionType::where('type', 'Withdrawal')->first();
$transferTT = TransactionType::where('type', 'Transfer')->first();
// grab currency:
$currency = TransactionCurrency::where('code', 'EUR')->first();
// grab component types:
$categoryType = ComponentType::where('type', 'category')->first();
$budgetType = ComponentType::where('type', 'budget')->first();
// create a special cash account for this user:
$cashAccount = new Account;
$cashAccount->name = 'Cash account';
$cashAccount->active = true;
$cashAccount->accountType()->associate($beneficiaryAT);
$cashAccount->user()->associate($user);
$cashAccount->save();
$messages[] = 'Created "cash" account.';
// get the old accounts:
$accounts = DB::connection('old-firefly')->table('accounts')->where('user_id', $previousUser->id)->get();
foreach ($accounts as $account) {
// already had one?
$existing = Account::where('name', $account->name)->where('account_type_id', $defaultAT->id)->where(
'user_id', $previousUser->id
)->first();
if (!is_null($existing)) {
$map['accounts'][$account->id] = $existing;
$messages[] = 'Skipped creating account ' . $account->name . ' because it already exists.';
continue;
}
unset($existing);
// create account:
$current = new Account;
$current->name = $account->name;
$current->active = true;
$current->accountType()->associate($defaultAT);
$current->user()->associate($user);
$current->save();
// map and information
$map['accounts'][$account->id] = $current;
$messages[] = 'Account "' . $current->name . '" recreated.';
// recreate opening balance, if relevant:
if (floatval($account->openingbalance) != 0) {
// now create another account, and create the first transfer indicating
$initial = new Account;
$initial->name = $account->name . ' initial balance';
$initial->accountType()->associate($initialBalanceAT);
$initial->active = 1;
$initial->user()->associate($user);
$initial->save();
// create a journal, and two transfers:
$journal = new TransactionJournal;
$journal->transactionType()->associate($initialBalanceTT);
$journal->transactionCurrency()->associate($currency);
$journal->description = $account->name . ' opening balance';
$journal->date = $account->openingbalancedate;
$journal->save();
// coming from a virtual account is:
$credit = new Transaction;
$credit->account()->associate($current);
$credit->transactionJournal()->associate($journal);
$credit->description = null;
$credit->amount = $account->openingbalance;
$credit->save();
// transfer into this new account
$debet = new Transaction;
$debet->account()->associate($initial);
$debet->transactionJournal()->associate($journal);
$debet->description = null;
$debet->amount = $account->openingbalance * -1;
$debet->save();
$messages[]
= 'Saved initial balance for ' . $current->name . ' (' . mf($account->openingbalance, true) . ')';
unset($initial, $journal, $credit, $debet);
}
unset($current);
}
// save all old components
$components = DB::connection('old-firefly')->table('components')->leftJoin(
'types', 'types.id', '=', 'components.type_id'
)->where('user_id', $previousUser->id)->get(['components.*', 'types.type']);
foreach ($components as $component) {
$id = $component->id;
switch ($component->type) {
case 'beneficiary':
if (!isset($map['beneficiaries'][$id])) {
// if it exists, skip:
$existing = Account::where('name', $component->name)->where('user_id', $user->id)->where(
'account_type_id', $beneficiaryAT->id
)->first();
if (!is_null($existing)) {
$map['beneficiaries'][$id] = $existing;
$messages[]
= 'Skipped creating beneficiary "' . $component->name . '" because it already exists.';
unset($existing);
continue;
}
// new account for this beneficiary
$beneficiary = new Account;
$beneficiary->name = $component->name;
$beneficiary->accountType()->associate($beneficiaryAT);
$beneficiary->user()->associate($user);
$beneficiary->active = 1;
$beneficiary->save();
$map['beneficiaries'][$id] = $beneficiary;
$messages[] = 'Recreated beneficiary "' . $beneficiary->name . '".';
unset($beneficiary);
}
break;
case 'category':
// if it exists, skip:
$existing = Component::where('name', $component->name)->where('user_id', $user->id)->where(
'component_type_id', $categoryType->id
)->first();
if (!is_null($existing)) {
$map['categories'][$id] = $existing;
$messages[] = 'Skipped creating category "' . $component->name . '" because it already exists.';
unset($existing);
continue;
}
// new component for this category:
$category = new Component;
$category->componentType()->associate($categoryType);
$category->name = $component->name;
$category->user()->associate($user);
$category->save();
$map['categories'][$id] = $category;
$messages[] = 'Recreated category "' . $category->name . '".';
unset($category);
break;
case 'budget':
// if it exists, skip:
$existing = Component::where('name', $component->name)->where('user_id', $user->id)->where(
'component_type_id', $budgetType->id
)->first();
if (!is_null($existing)) {
$map['budgets'][$id] = $existing;
$messages[] = 'Skipped creating budget "' . $component->name . '" because it already exists.';
unset($existing);
continue;
}
// new component for this budget:
$budget = new Component;
$budget->componentType()->associate($budgetType);
$budget->user()->associate($user);
$budget->name = $component->name;
$budget->save();
$map['budgets'][$id] = $budget;
$messages[] = 'Recreated budget "' . $budget->name . '".';
unset($budget);
break;
}
}
// grab all old transactions:
$transactions = DB::connection('old-firefly')->table('transactions')->where('user_id', $user->id)->orderBy(
'date'
)
->get();
foreach ($transactions as $transaction) {
$accountID = $transaction->account_id;
// grab the components for this transaction
$components = DB::connection('old-firefly')->table('component_transaction')
->leftJoin('components', 'components.id', '=', 'component_transaction.component_id')
->leftJoin('types', 'types.id', '=', 'components.type_id')
->where('transaction_id', $transaction->id)
->get(['components.id', 'types.type']);
$beneficiary = null;
$budget = null;
$category = null;
// loop components, get the right id's:
foreach ($components as $component) {
$id = $component->id;
switch ($component->type) {
case 'beneficiary':
$beneficiary = $map['beneficiaries'][$id];
break;
case 'budget':
$budget = $map['budgets'][$id];
break;
case 'category':
$category = $map['categories'][$id];
break;
}
}
// get a fall back for empty beneficiaries:
if (is_null($beneficiary)) {
$beneficiary = $cashAccount;
}
// create the transaction journal:
$journal = new TransactionJournal;
if ($transaction->amount < 0) {
$journal->transactionType()->associate($withdrawalTT);
} else {
$journal->transactionType()->associate($depositTT);
}
$journal->transactionCurrency()->associate($currency);
$journal->description = $transaction->description;
$journal->date = $transaction->date;
$journal->save();
// create two transactions:
if ($transaction->amount < 0) {
// credit the beneficiary
$credit = new Transaction;
$credit->account()->associate($beneficiary);
$credit->transactionJournal()->associate($journal);
$credit->description = null;
$credit->amount = floatval($transaction->amount) * -1;
$credit->save();
// add budget / category:
if (!is_null($budget)) {
$credit->components()->attach($budget);
}
if (!is_null($category)) {
$credit->components()->attach($category);
}
// debet ourselves:
$debet = new Transaction;
$debet->account()->associate($map['accounts'][$accountID]);
$debet->transactionJournal()->associate($journal);
$debet->description = null;
$debet->amount = floatval($transaction->amount);
$debet->save();
if (!is_null($budget)) {
$debet->components()->attach($budget);
}
if (!is_null($category)) {
$debet->components()->attach($category);
}
$messages[]
= 'Recreated expense "' . $transaction->description . '" (' . mf($transaction->amount, true) . ')';
} else {
$credit = new Transaction;
$credit->account()->associate($map['accounts'][$accountID]);
$credit->transactionJournal()->associate($journal);
$credit->description = null;
$credit->amount = floatval($transaction->amount);
$credit->save();
if (!is_null($budget)) {
$credit->components()->attach($budget);
}
if (!is_null($category)) {
$credit->components()->attach($category);
}
// from whoever!
$debet = new Transaction;
$debet->account()->associate($beneficiary);
$debet->transactionJournal()->associate($journal);
$debet->description = null;
$debet->amount = floatval($transaction->amount) * -1;
$debet->save();
if (!is_null($budget)) {
$debet->components()->attach($budget);
}
if (!is_null($category)) {
$debet->components()->attach($category);
}
$messages[]
= 'Recreated income "' . $transaction->description . '" (' . mf($transaction->amount, true) . ')';
}
}
unset($transaction);
// recreate the transfers:
$transfers = DB::connection('old-firefly')->table('transfers')->where('user_id', $user->id)->get();
foreach ($transfers as $transfer) {
// if it exists already, we don't need to recreate it:
$existingJournal = TransactionJournal::where('description', $transfer->description)->where(
'date', $transfer->date
)->where('transaction_type_id', $transferTT->id)->first();
if (!is_null($existingJournal)) {
// grab transaction from journal to make sure:
$firstTransaction = $existingJournal->transactions()->first();
if ($firstTransaction->amount == $transfer->amount
|| $firstTransaction->amount == $transfer->amount * -1
) {
// probably the same:
$messages[] = 'Skipped transfer "' . $transfer->description . '" because it already exists.';
unset($existingJournal, $firstTransaction);
continue;
}
}
$fromID = $transfer->accountfrom_id;
$toID = $transfer->accountto_id;
// create a journak:
$journal = new TransactionJournal;
$journal->transactionType()->associate($transferTT);
$journal->transactionCurrency()->associate($currency);
$journal->description = $transfer->description;
$journal->date = $transfer->date;
$journal->save();
// from account (debet) to another account (credit)
$debet = new Transaction;
$debet->account()->associate($map['accounts'][$fromID]);
$debet->transactionJournal()->associate($journal);
$debet->description = null;
$debet->amount = floatval($transfer->amount * -1);
$debet->save();
// to another account!
$credit = new Transaction;
$credit->account()->associate($map['accounts'][$toID]);
$credit->transactionJournal()->associate($journal);
$credit->description = null;
$credit->amount = floatval($transfer->amount);
$credit->save();
$messages[] = 'Recreated transfer "' . $transfer->description . '" (' . mf($transfer->amount) . ')';
}
$user->migrated = true;
$user->save();
return View::make('migrate.result')->with('messages', $messages);
}
}

View File

@ -6,6 +6,9 @@ Route::get('/start', ['uses' => 'HomeController@start','as' => 'start','before'
// migration controller:
Route::get('/migrate/index', ['uses' => 'MigrationController@index','as' => 'migrate.index', 'before' => 'auth']);
Route::get('/migrate/select-user', ['uses' => 'MigrationController@selectUser','as' => 'migrate.select-user', 'before' => 'auth']);
Route::post('/migrate/select-user', ['uses' => 'MigrationController@postSelectUser','before' => 'csrf|auth']);
Route::get('/migrate/migrate/{userID}', ['uses' => 'MigrationController@migrate','as' => 'migrate.migrate', 'before' => 'auth']);
// login, register, logout:
Route::get('/login',['uses' => 'UserController@login','as' => 'login','before' => 'guest']);

View File

@ -1,16 +1,29 @@
<html>
<head>
<title>Bla bla</title>
</head>
<body>
{{App::environment()}}
@if(Auth::check())
logged in!
@endif
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly<br/>
<small>What's playing?</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h2>Accounts</h2>
<div id="accounts">
</div>
<p><small>[settings]</small></p>
</div>
</div>
<br />
<a href="{{route('logout')}}">logout!</a>
</body>
</html>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<h3>Expenses</h3>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<h3>Budgets</h3>
</div>
</div>
@stop
@section('scripts')
@stop

View File

@ -42,7 +42,10 @@ return [
],
</pre>
<p>
Refresh this page; when the connection is valid this page will refresh.
This page will disappear when the connection is valid.
</p>
<p>
Current error: <code>{{$error or ''}}</code>
</p>
</div>
</div>

View File

@ -0,0 +1,21 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12">
<h1>Firefly<br/>
<small>Migration results</small>
</h1>
<p class="lead">
The migration was successful! You can now return to <a href="{{route('index')}}">the home page</a>
and start using Firefly.
</p>
<ul>
@foreach($messages as $m)
<li>{{$m}}</li>
@endforeach
</ul>
</div>
</div>
@stop

View File

@ -0,0 +1,35 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12">
<h1>Firefly<br/>
<small>Select a user for migration.</small>
</h1>
<p>
Select a user from the list below. Then press import.
</p>
{{Form::open(['class' => 'form-horizontal'])}}
<div class="form-group">
<label for="inputUser" class="col-sm-2 control-label">User</label>
<div class="col-sm-10">
<select class="form-control" name="user">
@foreach($oldUsers as $old)
<option value="{{$old->id}}" label="# {{$old->id}}: {{$old->username}} ({{$old->email}})">
# {{$old->id}}: {{$old->username}} ({{$old->email}})</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-info">Import</button><br />
<small>Please be patient; importing data may take some time.</small>
</div>
</div>
{{Form::close()}}
</div>
</div>
@stop

View File

@ -18,7 +18,7 @@
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<h2>Start anew</h2>
<h2>Start fresh</h2>
<p>
Click the link below to create your first account, and get started with Firefly.
</p>

View File

@ -11,6 +11,28 @@
|
*/
if (!function_exists('mf')) {
function mf($n, $coloured = false)
{
$n = floatval($n);
$n = round($n, 2);
$string = number_format($n, 2, ',', '.');
if ($coloured === true && $n === 0.0) {
return '<span style="color:#999">&#128; ' . $string . '</span>';
}
if ($coloured === true && $n > 0) {
return '<span class="text-success">&#128; ' . $string . '</span>';
}
if ($coloured === true && $n < 0) {
return '<span class="text-danger">&#128; ' . $string . '</span>';
}
return '&#128; ' . $string;
}
}
$app = new Illuminate\Foundation\Application;
/*
@ -24,10 +46,12 @@ $app = new Illuminate\Foundation\Application;
|
*/
$env = $app->detectEnvironment(function() {
$env = $app->detectEnvironment(
function () {
// Default to production if LARAVEL_ENV is not set
return getenv('LARAVEL_ENV') ?: 'production';
});
return getenv('LARAVEL_ENV') ? : 'production';
}
);
/*
@ -41,7 +65,7 @@ $env = $app->detectEnvironment(function() {
|
*/
$app->bindInstallPaths(require __DIR__.'/paths.php');
$app->bindInstallPaths(require __DIR__ . '/paths.php');
/*
|--------------------------------------------------------------------------
@ -54,10 +78,10 @@ $app->bindInstallPaths(require __DIR__.'/paths.php');
|
*/
$framework = $app['path.base'].
'/vendor/laravel/framework/src';
$framework = $app['path.base'] .
'/vendor/laravel/framework/src';
require $framework.'/Illuminate/Foundation/start.php';
require $framework . '/Illuminate/Foundation/start.php';
/*
|--------------------------------------------------------------------------