Starting to work on reports

This commit is contained in:
Hillel Coren 2013-12-15 14:55:50 +02:00
parent 19abec6f09
commit 374e34e733
29 changed files with 1827 additions and 129 deletions

View File

@ -57,3 +57,4 @@ Configure config/database.php and then initialize the database
* [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) - A simple API extension for DateTime with PHP 5.3+ * [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) - A simple API extension for DateTime with PHP 5.3+
* [thomaspark/bootswatch](https://github.com/thomaspark/bootswatch) - Themes for Bootstrap * [thomaspark/bootswatch](https://github.com/thomaspark/bootswatch) - Themes for Bootstrap
* [mozilla/pdf.js)](https://github.com/mozilla/pdf.js) - PDF Reader in JavaScript * [mozilla/pdf.js)](https://github.com/mozilla/pdf.js) - PDF Reader in JavaScript
* [nnnick/Chart.js](https://github.com/nnnick/Chart.js) - Simple HTML5 Charts using the <canvas> tag

View File

@ -40,7 +40,7 @@ class AccountController extends \BaseController {
} }
Auth::login($user, true); Auth::login($user, true);
Session::put('tz', 'US/Eastern'); Event::fire('user.login');
return Redirect::to('invoices/create'); return Redirect::to('invoices/create');
} }
@ -51,9 +51,8 @@ class AccountController extends \BaseController {
{ {
$account = Account::with('users')->findOrFail(Auth::user()->account_id); $account = Account::with('users')->findOrFail(Auth::user()->account_id);
$countries = Country::orderBy('name')->get(); $countries = Country::orderBy('name')->get();
$timezones = Timezone::orderBy('location')->get();
return View::make('accounts.details', array('account'=>$account, 'countries'=>$countries, 'timezones'=>$timezones)); return View::make('accounts.details', array('account'=>$account, 'countries'=>$countries));
} }
else if ($section == ACCOUNT_SETTINGS) else if ($section == ACCOUNT_SETTINGS)
{ {
@ -71,7 +70,10 @@ class AccountController extends \BaseController {
'account' => $account, 'account' => $account,
'accountGateway' => $accountGateway, 'accountGateway' => $accountGateway,
'config' => json_decode($config), 'config' => json_decode($config),
'gateways' => Gateway::all() 'gateways' => Gateway::all(),
'timezones' => Timezone::orderBy('location')->get(),
'dateFormats' => DateFormat::all(),
'datetimeFormats' => DatetimeFormat::all(),
]; ];
foreach ($data['gateways'] as $gateway) foreach ($data['gateways'] as $gateway)
@ -383,6 +385,12 @@ class AccountController extends \BaseController {
$account = Account::findOrFail(Auth::user()->account_id); $account = Account::findOrFail(Auth::user()->account_id);
$account->account_gateways()->forceDelete(); $account->account_gateways()->forceDelete();
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
Event::fire('user.refresh');
$account->invoice_terms = Input::get('invoice_terms'); $account->invoice_terms = Input::get('invoice_terms');
$account->save(); $account->save();
@ -431,7 +439,6 @@ class AccountController extends \BaseController {
$account->state = trim(Input::get('state')); $account->state = trim(Input::get('state'));
$account->postal_code = trim(Input::get('postal_code')); $account->postal_code = trim(Input::get('postal_code'));
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null; $account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->save(); $account->save();
$user = $account->users()->first(); $user = $account->users()->first();

View File

@ -179,11 +179,12 @@ class ClientController extends \BaseController {
$client->address2 = trim(Input::get('address2')); $client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city')); $client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state')); $client->state = trim(Input::get('state'));
$client->notes = trim(Input::get('notes'));
$client->postal_code = trim(Input::get('postal_code')); $client->postal_code = trim(Input::get('postal_code'));
$client->country_id = Input::get('country_id') ? Input::get('country_id') : null; $client->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$client->notes = trim(Input::get('notes'));
$client->client_size_id = Input::get('client_size_id') ? Input::get('client_size_id') : null; $client->client_size_id = Input::get('client_size_id') ? Input::get('client_size_id') : null;
$client->client_industry_id = Input::get('client_industry_id') ? Input::get('client_industry_id') : null; $client->client_industry_id = Input::get('client_industry_id') ? Input::get('client_industry_id') : null;
$client->website = trim(Input::get('website'));
$client->save(); $client->save();
@ -239,14 +240,14 @@ class ClientController extends \BaseController {
$clients = Client::scope($ids)->get(); $clients = Client::scope($ids)->get();
foreach ($clients as $client) { foreach ($clients as $client) {
if ($action == 'archive') { if ($action == 'delete') {
$client->delete(); $client->is_deleted = true;
} else if ($action == 'delete') { $client->save();
$client->forceDelete();
} }
$client->delete();
} }
$message = Utils::pluralize('Successfully '.$action.'d ? client', count($ids)); $message = Utils::pluralize('Successfully '.$action.'d ? client', count($clients));
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to('clients'); return Redirect::to('clients');

View File

@ -22,6 +22,7 @@ class CreditController extends \BaseController {
->join('clients', 'clients.id', '=','credits.client_id') ->join('clients', 'clients.id', '=','credits.client_id')
->where('clients.account_id', '=', Auth::user()->account_id) ->where('clients.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->where('credits.deleted_at', '=', null)
->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.credit_date'); ->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.credit_date');
if ($clientPublicId) { if ($clientPublicId) {
@ -55,7 +56,7 @@ class CreditController extends \BaseController {
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('credits/'.$model->public_id.'/edit') . '">Edit Credit</a></li> <li><a href="' . URL::to('credits/'.$model->public_id.'/edit') . '">Edit Credit</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="' . URL::to('credits/'.$model->public_id.'/archive') . '">Archive Credit</a></li> <li><a href="javascript:archiveEntity(' . $model->public_id. ')">Archive Credit</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Credit</a></li> <li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Credit</a></li>
</ul> </ul>
</div>'; </div>';
@ -65,7 +66,7 @@ class CreditController extends \BaseController {
} }
public function create($clientPublicId) public function create($clientPublicId = null)
{ {
$client = null; $client = null;
if ($clientPublicId) { if ($clientPublicId) {
@ -144,14 +145,14 @@ class CreditController extends \BaseController {
$credits = Credit::scope($ids)->get(); $credits = Credit::scope($ids)->get();
foreach ($credits as $credit) { foreach ($credits as $credit) {
if ($action == 'archive') { if ($action == 'delete') {
$credit->delete(); $credit->is_deleted = true;
} else if ($action == 'delete') { $credit->save();
$credit->forceDelete();
} }
$credit->delete();
} }
$message = Utils::pluralize('Successfully '.$action.'d ? credit', count($ids)); $message = Utils::pluralize('Successfully '.$action.'d ? credit', count($credits));
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to('credits'); return Redirect::to('credits');

View File

@ -37,6 +37,7 @@ class InvoiceController extends \BaseController {
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id') ->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->where('invoices.account_id', '=', Auth::user()->account_id) ->where('invoices.account_id', '=', Auth::user()->account_id)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'total', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name'); ->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'total', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name');
@ -81,7 +82,7 @@ class InvoiceController extends \BaseController {
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li> <li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="' . URL::to('invoices/'.$model->public_id.'/archive') . '">Archive Invoice</a></li> <li><a href="javascript:archiveEntity(' . $model->public_id . ')">Archive Invoice</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li> <li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li>
</ul> </ul>
</div>'; </div>';
@ -138,7 +139,7 @@ class InvoiceController extends \BaseController {
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li> <li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="' . URL::to('invoices/'.$model->public_id.'/archive') . '">Archive Invoice</a></li> <li><a href="javascript:archiveEntity(' . $model->public_id . ')">Archive Invoice</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li> <li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li>
</ul> </ul>
</div>'; </div>';
@ -150,12 +151,22 @@ class InvoiceController extends \BaseController {
public function view($invitationKey) public function view($invitationKey)
{ {
$invitation = Invitation::with('user', 'invoice.account', 'invoice.invoice_items', 'invoice.client.account.account_gateways') $invitation = Invitation::with('user', 'invoice.account', 'invoice.client', 'invoice.invoice_items', 'invoice.client.account.account_gateways')
->where('invitation_key', '=', $invitationKey)->firstOrFail(); ->where('invitation_key', '=', $invitationKey)->firstOrFail();
$user = $invitation->user; $user = $invitation->user;
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return View::make('invoices.deleted');
}
$client = $invoice->client;
if (!$client || $client->is_deleted) {
return View::make('invoices.deleted');
}
if ($invoice->invoice_status_id < INVOICE_STATUS_VIEWED) { if ($invoice->invoice_status_id < INVOICE_STATUS_VIEWED) {
$invoice->invoice_status_id = INVOICE_STATUS_VIEWED; $invoice->invoice_status_id = INVOICE_STATUS_VIEWED;
$invoice->save(); $invoice->save();
@ -438,6 +449,10 @@ class InvoiceController extends \BaseController {
$client->state = trim($inputClient->state); $client->state = trim($inputClient->state);
$client->postal_code = trim($inputClient->postal_code); $client->postal_code = trim($inputClient->postal_code);
$client->country_id = $inputClient->country_id ? $inputClient->country_id : null; $client->country_id = $inputClient->country_id ? $inputClient->country_id : null;
$client->notes = trim($inputClient->notes);
$client->client_size_id = $inputClient->client_size_id ? $inputClient->client_size_id : null;
$client->client_industry_id = $inputClient->client_industry_id ? $inputClient->client_industry_id : null;
$client->website = trim($inputClient->website);
$client->save(); $client->save();
$isPrimary = true; $isPrimary = true;
@ -498,7 +513,7 @@ class InvoiceController extends \BaseController {
$invoice->frequency_id = $input->frequency_id ? $input->frequency_id : 0; $invoice->frequency_id = $input->frequency_id ? $input->frequency_id : 0;
$invoice->start_date = Utils::toSqlDate($input->start_date); $invoice->start_date = Utils::toSqlDate($input->start_date);
$invoice->end_date = Utils::toSqlDate($input->end_date); $invoice->end_date = Utils::toSqlDate($input->end_date);
$invoice->notes = $input->notes; $invoice->terms = $input->terms;
$invoice->po_number = $input->po_number; $invoice->po_number = $input->po_number;
@ -587,6 +602,7 @@ class InvoiceController extends \BaseController {
/* /*
*/ */
$message = $clientPublicId == "-1" ? ' and created client' : '';
if ($action == 'clone') if ($action == 'clone')
{ {
return InvoiceController::cloneInvoice($publicId); return InvoiceController::cloneInvoice($publicId);
@ -595,9 +611,9 @@ class InvoiceController extends \BaseController {
{ {
$this->mailer->sendInvoice($invoice); $this->mailer->sendInvoice($invoice);
Session::flash('message', 'Successfully emailed invoice'); Session::flash('message', 'Successfully emailed invoice'.$message);
} else { } else {
Session::flash('message', 'Successfully saved invoice'); Session::flash('message', 'Successfully saved invoice'.$message);
} }
$url = 'invoices/' . $invoice->public_id . '/edit'; $url = 'invoices/' . $invoice->public_id . '/edit';
@ -640,14 +656,14 @@ class InvoiceController extends \BaseController {
$invoices = Invoice::scope($ids)->get(); $invoices = Invoice::scope($ids)->get();
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if ($action == 'archive') { if ($action == 'delete') {
$invoice->delete(); $invoice->is_deleted = true;
} else if ($action == 'delete') { $invoice->save();
$invoice->forceDelete();
} }
$invoice->delete();
} }
$message = Utils::pluralize('Successfully '.$action.'d ? invoice', count($ids)); $message = Utils::pluralize('Successfully '.$action.'d ? invoice', count($invoices));
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to('invoices'); return Redirect::to('invoices');

View File

@ -18,6 +18,7 @@ class PaymentController extends \BaseController
->leftJoin('invoices', 'invoices.id', '=','payments.invoice_id') ->leftJoin('invoices', 'invoices.id', '=','payments.invoice_id')
->where('payments.account_id', '=', Auth::user()->account_id) ->where('payments.account_id', '=', Auth::user()->account_id)
->where('payments.deleted_at', '=', null) ->where('payments.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number'); ->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number');
if ($clientPublicId) { if ($clientPublicId) {
@ -57,7 +58,7 @@ class PaymentController extends \BaseController
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('payments/'.$model->public_id.'/edit') . '">Edit Payment</a></li> <li><a href="' . URL::to('payments/'.$model->public_id.'/edit') . '">Edit Payment</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="' . URL::to('payments/'.$model->public_id.'/archive') . '">Archive Payment</a></li> <li><a href="javascript:archiveEntity(' . $model->public_id. ')">Archive Payment</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Payment</a></li> <li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Payment</a></li>
</ul> </ul>
</div>'; </div>';
@ -153,14 +154,14 @@ class PaymentController extends \BaseController
$payments = Payment::scope($ids)->get(); $payments = Payment::scope($ids)->get();
foreach ($payments as $payment) { foreach ($payments as $payment) {
if ($action == 'archive') { if ($action == 'delete') {
$payment->delete(); $payment->is_deleted = true;
} else if ($action == 'delete') { $payment->save();
$payment->forceDelete();
} }
$payment->delete();
} }
$message = Utils::pluralize('Successfully '.$action.'d ? payment', count($ids)); $message = Utils::pluralize('Successfully '.$action.'d ? payment', count($payments));
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to('payments'); return Redirect::to('payments');

View File

@ -0,0 +1,46 @@
<?php
class ReportController extends \BaseController {
public function monthly()
{
$records = DB::table('invoices')
->select(DB::raw('sum(total) as total, month(invoice_date) as month'))
->where('invoices.deleted_at', '=', null)
->where('invoices.invoice_date', '>', 0)
->groupBy('month');
$totals = $records->lists('total');
$dates = $records->lists('month');
$data = array_combine($dates, $totals);
$startDate = date_create('2013-06-30');
$endDate = date_create('2013-12-30');
$endDate = $endDate->modify('+1 month');
$interval = new DateInterval('P1M');
$period = new DatePeriod($startDate, $interval, $endDate);
$totals = [];
$dates = [];
foreach ($period as $d)
{
$date = $d->format('Y-m-d');
$month = $d->format('n');
$dates[] = $date;
$totals[] = isset($data[$month]) ? $data[$month] : 0;
}
$width = (ceil( max($totals) / 100 ) * 100) / 10;
$params = [
'dates' => $dates,
'totals' => $totals,
'scaleStepWidth' => $width,
];
return View::make('reports.monthly', $params);
}
}

View File

@ -74,6 +74,7 @@ class UserController extends BaseController {
{ {
if( Confide::user() ) if( Confide::user() )
{ {
Event::fire('user.login');
return Redirect::to('/clients'); return Redirect::to('/clients');
} }
else else
@ -101,10 +102,7 @@ class UserController extends BaseController {
// Get the value from the config file instead of changing the controller // Get the value from the config file instead of changing the controller
if ( Confide::logAttempt( $input, Config::get('confide::signup_confirm') ) ) if ( Confide::logAttempt( $input, Config::get('confide::signup_confirm') ) )
{ {
$account = Account::findOrFail(Auth::user()->account_id); Event::fire('user.login');
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();
// Redirect the user to the URL they were trying to access before // Redirect the user to the URL they were trying to access before
// caught by the authentication filter IE Redirect::guest('user/login'). // caught by the authentication filter IE Redirect::guest('user/login').
// Otherwise fallback to '/' // Otherwise fallback to '/'

View File

@ -31,6 +31,8 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');
Schema::dropIfExists('timezones'); Schema::dropIfExists('timezones');
Schema::dropIfExists('frequencies'); Schema::dropIfExists('frequencies');
Schema::dropIfExists('date_formats');
Schema::dropIfExists('datetime_formats');
Schema::create('countries', function($table) Schema::create('countries', function($table)
{ {
@ -63,10 +65,26 @@ class ConfideSetupUsersTable extends Migration {
$t->string('location'); $t->string('location');
}); });
Schema::create('date_formats', function($t)
{
$t->increments('id');
$t->string('format');
$t->string('label');
});
Schema::create('datetime_formats', function($t)
{
$t->increments('id');
$t->string('format');
$t->string('label');
});
Schema::create('accounts', function($t) Schema::create('accounts', function($t)
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('timezone_id')->nullable(); $t->unsignedInteger('timezone_id')->nullable();
$t->unsignedInteger('date_format_id')->nullable();
$t->unsignedInteger('datetime_format_id')->nullable();
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -85,6 +103,8 @@ class ConfideSetupUsersTable extends Migration {
$t->text('invoice_terms'); $t->text('invoice_terms');
$t->foreign('timezone_id')->references('id')->on('timezones'); $t->foreign('timezone_id')->references('id')->on('timezones');
$t->foreign('date_format_id')->references('id')->on('date_formats');
$t->foreign('datetime_format_id')->references('id')->on('datetime_formats');
$t->foreign('country_id')->references('id')->on('countries'); $t->foreign('country_id')->references('id')->on('countries');
}); });
@ -92,7 +112,6 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->timestamps(); $t->timestamps();
$t->softDeletes();
$t->string('name'); $t->string('name');
$t->string('provider'); $t->string('provider');
@ -105,7 +124,6 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('gateway_id'); $t->unsignedInteger('gateway_id');
$t->timestamps(); $t->timestamps();
$t->softDeletes();
$t->text('config'); $t->text('config');
@ -175,10 +193,12 @@ class ConfideSetupUsersTable extends Migration {
$t->string('work_phone'); $t->string('work_phone');
$t->text('notes'); $t->text('notes');
$t->decimal('balance', 10, 2); $t->decimal('balance', 10, 2);
$t->decimal('paid_to_date', 10, 2);
$t->timestamp('last_login'); $t->timestamp('last_login');
$t->string('website'); $t->string('website');
$t->unsignedInteger('client_industry_id')->nullable(); $t->unsignedInteger('client_industry_id')->nullable();
$t->unsignedInteger('client_size_id')->nullable(); $t->unsignedInteger('client_size_id')->nullable();
$t->boolean('is_deleted');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users'); $t->foreign('user_id')->references('id')->on('users');
@ -240,8 +260,8 @@ class ConfideSetupUsersTable extends Migration {
$t->string('po_number'); $t->string('po_number');
$t->date('invoice_date')->nullable(); $t->date('invoice_date')->nullable();
$t->date('due_date')->nullable(); $t->date('due_date')->nullable();
$t->text('notes'); $t->text('terms');
$t->boolean('is_deleted');
$t->boolean('is_recurring'); $t->boolean('is_recurring');
$t->unsignedInteger('frequency_id'); $t->unsignedInteger('frequency_id');
$t->date('start_date')->nullable(); $t->date('start_date')->nullable();
@ -342,6 +362,7 @@ class ConfideSetupUsersTable extends Migration {
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted');
$t->decimal('amount', 10, 2); $t->decimal('amount', 10, 2);
$t->date('payment_date'); $t->date('payment_date');
$t->string('transaction_reference'); $t->string('transaction_reference');
@ -367,8 +388,9 @@ class ConfideSetupUsersTable extends Migration {
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_deleted');
$t->decimal('amount', 10, 2); $t->decimal('amount', 10, 2);
$t->date('credit_date'); $t->date('credit_date')->nullable();
$t->string('credit_number'); $t->string('credit_number');
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
@ -432,5 +454,7 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');
Schema::dropIfExists('timezones'); Schema::dropIfExists('timezones');
Schema::dropIfExists('frequencies'); Schema::dropIfExists('frequencies');
Schema::dropIfExists('date_formats');
Schema::dropIfExists('datetime_formats');
} }
} }

View File

@ -69,6 +69,13 @@ class ConstantsSeeder extends Seeder
ClientSize::create(array('name' => '101 - 500')); ClientSize::create(array('name' => '101 - 500'));
ClientSize::create(array('name' => '500+')); ClientSize::create(array('name' => '500+'));
DatetimeFormat::create(array('format' => 'F j, Y, g:i a', 'label' => 'March 10, 2013, 6:15 pm'));
DatetimeFormat::create(array('format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013, 6:15 pm'));
DateFormat::create(array('format' => 'F j, Y', 'label' => 'March 10, 2013'));
DateFormat::create(array('format' => 'D M jS', 'label' => 'Mon March 10th, 2013'));
$gateways = [ $gateways = [
array('name'=>'Authorize.Net AIM', 'provider'=>'AuthorizeNet_AIM'), array('name'=>'Authorize.Net AIM', 'provider'=>'AuthorizeNet_AIM'),
array('name'=>'Authorize.Net SIM', 'provider'=>'AuthorizeNet_SIM'), array('name'=>'Authorize.Net SIM', 'provider'=>'AuthorizeNet_SIM'),

View File

@ -0,0 +1,36 @@
<?php
class UserEventHandler
{
public function subscribe($events)
{
$events->listen('user.signup', 'UserEventHandler@onSignup');
$events->listen('user.login', 'UserEventHandler@onLogin');
$events->listen('user.refresh', 'UserEventHandler@onRefresh');
}
public function onSignup()
{
dd('user signed up');
}
public function onLogin()
{
$account = Account::findOrFail(Auth::user()->account_id);
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();
Event::fire('user.refresh');
}
public function onRefresh()
{
$user = User::whereId(Auth::user()->id)->with('account', 'account.date_format', 'account.datetime_format', 'account.timezone')->firstOrFail();
$account = $user->account;
Session::put(SESSION_TIMEZONE, $account->timezone ? $account->timezone->name : DEFAULT_TIMEZONE);
Session::put(SESSION_DATE_FORMAT, $account->date_format ? $account->date_format->format : DEFAULT_DATE_FORMAT);
Session::put(SESSION_DATETIME_FORMAT, $account->datetime_format ? $account->datetime_format->format : DEFAULT_DATETIME_FORMAT);
}
}

View File

@ -52,30 +52,25 @@ class Utils
} }
public static function timestampToDateTimeString($timestamp) { public static function timestampToDateTimeString($timestamp) {
$tz = Session::get('tz'); $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
if (!$tz) { $format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
$tz = 'US/Eastern'; return Utils::timestampToString($timestamp, $timezone, $format);
}
$date = new Carbon($timestamp);
$date->tz = $tz;
if ($date->year < 1900) {
return '';
}
return $date->format('D M jS, Y g:ia');
} }
public static function timestampToDateString($timestamp) { public static function timestampToDateString($timestamp) {
$tz = Session::get('tz'); $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
if (!$tz) { $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
$tz = 'US/Eastern'; return Utils::timestampToString($timestamp, $timezone, $format);
} }
public static function timestampToString($timestamp, $timzone, $format)
{
$date = new Carbon($timestamp); $date = new Carbon($timestamp);
$date->tz = $tz; $date->tz = $timzone;
if ($date->year < 1900) { if ($date->year < 1900) {
return ''; return '';
} }
return $date->toFormattedDateString(); return $date->format($format);
} }
public static function toSqlDate($date) public static function toSqlDate($date)

View File

@ -35,6 +35,17 @@ class Account extends Eloquent
return $this->belongsTo('Timezone'); return $this->belongsTo('Timezone');
} }
public function date_format()
{
return $this->belongsTo('DateFormat');
}
public function datetime_format()
{
return $this->belongsTo('DatetimeFormat');
}
public function isGatewayConfigured($gatewayId = 0) public function isGatewayConfigured($gatewayId = 0)
{ {
if ($gatewayId) if ($gatewayId)

View File

@ -131,6 +131,16 @@ class Client extends EntityModel
return $str; return $str;
} }
public function getWebsite()
{
if (!$this->website)
{
return '';
}
return link_to($this->website, $this->website);
}
public function getDateCreated() public function getDateCreated()
{ {
if ($this->created_at == '0000-00-00 00:00:00') if ($this->created_at == '0000-00-00 00:00:00')

7
app/models/DateFormat.php Executable file
View File

@ -0,0 +1,7 @@
<?php
class DateFormat extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

7
app/models/DatetimeFormat.php Executable file
View File

@ -0,0 +1,7 @@
<?php
class DatetimeFormat extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -2,5 +2,5 @@
class Gateway extends Eloquent class Gateway extends Eloquent
{ {
protected $softDelete = true;
} }

View File

@ -14,7 +14,7 @@
//dd(DB::getQueryLog()); //dd(DB::getQueryLog());
//dd(Client::getPrivateId(1)); //dd(Client::getPrivateId(1));
//dd(new DateTime()); //dd(new DateTime());
//Event::fire('user.signup');
Route::get('/send_emails', function() { Route::get('/send_emails', function() {
Artisan::call('ninja:send-invoices'); Artisan::call('ninja:send-invoices');
@ -80,9 +80,9 @@ Route::group(array('before' => 'auth'), function()
Route::resource('credits', 'CreditController'); Route::resource('credits', 'CreditController');
Route::get('credits/create/{client_id?}', 'CreditController@create'); Route::get('credits/create/{client_id?}', 'CreditController@create');
Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable'));
Route::post('credits/bulk', 'PaymentController@bulk'); Route::post('credits/bulk', 'CreditController@bulk');
Route::get('reports', function() { return View::make('header'); }); Route::get('reports', 'ReportController@monthly');
}); });
@ -161,3 +161,11 @@ define('FREQUENCY_MONTHLY', 4);
define('FREQUENCY_THREE_MONTHS', 5); define('FREQUENCY_THREE_MONTHS', 5);
define('FREQUENCY_SIX_MONTHS', 6); define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7); define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone');
define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_DATE_FORMAT', 'F j, Y');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');

View File

@ -18,6 +18,7 @@ ClassLoader::addDirectories(array(
app_path().'/models', app_path().'/models',
app_path().'/database/seeds', app_path().'/database/seeds',
app_path().'/libraries', app_path().'/libraries',
app_path().'/handlers',
)); ));
@ -70,6 +71,11 @@ App::down(function()
return Response::make("Be right back!", 503); return Response::make("Be right back!", 503);
}); });
Event::subscribe('UserEventHandler');
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Require The Filters File | Require The Filters File

View File

@ -27,8 +27,6 @@
{{ Former::legend('Account') }} {{ Former::legend('Account') }}
{{ Former::text('name') }} {{ Former::text('name') }}
{{ Former::select('timezone_id')->addOption('','')->label('Timezone')
->fromQuery($timezones, 'location', 'id')->select($account->timezone_id) }}
{{ Former::file('logo')->max(2, 'MB')->accept('image')->wrap('test') }} {{ Former::file('logo')->max(2, 'MB')->accept('image')->wrap('test') }}
@if (file_exists($account->getLogoPath())) @if (file_exists($account->getLogoPath()))

View File

@ -3,7 +3,7 @@
@section('content') @section('content')
@parent @parent
{{ Former::open()->addClass('col-md-10 col-md-offset-1') }} {{ Former::open()->addClass('col-md-8 col-md-offset-2') }}
{{ Former::populate($account) }} {{ Former::populate($account) }}
{{ Former::legend('Payment Gateway') }} {{ Former::legend('Payment Gateway') }}
@ -36,6 +36,15 @@
@endforeach @endforeach
{{ Former::legend('Date and Time') }}
{{ Former::select('timezone_id')->addOption('','')->label('Timezone')
->fromQuery($timezones, 'location', 'id')->select($account->timezone_id) }}
{{ Former::select('date_format_id')->addOption('','')->label('Date Format')
->fromQuery($dateFormats, 'label', 'id')->select($account->date_format_id) }}
{{ Former::select('datetime_format_id')->addOption('','')->label('Date/Time Format')
->fromQuery($datetimeFormats, 'label', 'id')->select($account->datetime_format_id) }}
{{ Former::legend('Invoices') }} {{ Former::legend('Invoices') }}
{{ Former::textarea('invoice_terms') }} {{ Former::textarea('invoice_terms') }}

View File

@ -24,6 +24,7 @@
{{ Former::legend('Organization') }} {{ Former::legend('Organization') }}
{{ Former::text('name') }} {{ Former::text('name') }}
{{ Former::text('website') }}
{{ Former::text('work_phone')->label('Phone') }} {{ Former::text('work_phone')->label('Phone') }}

View File

@ -50,6 +50,7 @@
<p>{{ $client->getPhone() }}</p> <p>{{ $client->getPhone() }}</p>
<p>{{ $client->getNotes() }}</p> <p>{{ $client->getNotes() }}</p>
<p>{{ $client->getIndustry() }}</p> <p>{{ $client->getIndustry() }}</p>
<p>{{ $client->getWebsite() }}
</div> </div>
<div class="col-md-3"> <div class="col-md-3">

View File

@ -273,7 +273,7 @@
{{ HTML::menu_link('invoice') }} {{ HTML::menu_link('invoice') }}
{{ HTML::menu_link('payment') }} {{ HTML::menu_link('payment') }}
{{ HTML::menu_link('credit') }} {{ HTML::menu_link('credit') }}
{{-- HTML::nav_link('reports', 'Reports') --}} {{ HTML::nav_link('reports', 'Reports') }}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown"> <li class="dropdown">

View File

@ -0,0 +1,7 @@
@extends('header')
@section('content')
The requested invoice is no longer available.
@stop

View File

@ -22,7 +22,7 @@
->addGroupClass('client_select closer-row') }} ->addGroupClass('client_select closer-row') }}
<div class="form-group" style="margin-bottom: 8px"> <div class="form-group" style="margin-bottom: 8px">
<div class="col-lg-8 col-lg-offset-4"> <div class="col-lg-8 col-sm-8 col-lg-offset-4 col-sm-offset-4">
<a href="#" data-bind="click: showClientForm, text: showClientText"></a> <a href="#" data-bind="click: showClientForm, text: showClientText"></a>
</div> </div>
</div> </div>
@ -38,7 +38,7 @@
</div> </div>
</div> </div>
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }} {{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }}
{{ Former::textarea('notes')->data_bind("value: notes, valueUpdate: 'afterkeydown'") }} {{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'") }}
</div> </div>
<div class="col-md-4" id="col_2"> <div class="col-md-4" id="col_2">
@ -94,7 +94,7 @@
->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }} ->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }}
</td> </td>
<td style="width:300px"> <td style="width:300px">
<textarea onkeyup="checkWordWrap(event)" data-bind="value: notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" style="resize: none;" class="form-control" onchange="refreshPDF()"></textarea> <textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown'" rows="1" cols="60" style="resize: none;" class="form-control word-wrap" onchange="refreshPDF()"></textarea>
</td> </td>
<td style="width:100px"> <td style="width:100px">
<input onkeyup="onChange()" data-bind="value: cost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//> <input onkeyup="onChange()" data-bind="value: cost, valueUpdate: 'afterkeydown'" style="text-align: right" class="form-control" onchange="refreshPDF()"//>
@ -195,6 +195,7 @@
{{ Former::legend('Organization') }} {{ Former::legend('Organization') }}
{{ Former::text('name')->data_bind("value: name, valueUpdate: 'afterkeydown'") }} {{ Former::text('name')->data_bind("value: name, valueUpdate: 'afterkeydown'") }}
{{ Former::text('website')->data_bind("value: website, valueUpdate: 'afterkeydown'") }}
{{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'")->label('Phone') }} {{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'")->label('Phone') }}
@ -437,9 +438,10 @@
this.client = new ClientModel(); this.client = new ClientModel();
self.discount = ko.observable(''); self.discount = ko.observable('');
self.frequency_id = ko.observable(''); self.frequency_id = ko.observable('');
self.notes = ko.observable(''); self.terms = ko.observable('');
self.po_number = ko.observable(''); self.po_number = ko.observable('');
self.invoice_date = ko.observable(''); self.invoice_date = ko.observable('');
self.invoice_number = ko.observable('');
self.due_date = ko.observable(''); self.due_date = ko.observable('');
self.start_date = ko.observable(''); self.start_date = ko.observable('');
self.end_date = ko.observable(''); self.end_date = ko.observable('');
@ -455,11 +457,22 @@
} }
} }
self.wrapped_terms = ko.computed({
read: function() {
return this.terms();
},
write: function(value) {
value = wordWrapText(value, 250);
self.terms(value);
$('#terms').height(value.split('\n').length * 22);
},
owner: this
});
self.showClientText = ko.computed(function() { self.showClientText = ko.computed(function() {
return self.client.public_id() ? 'Edit client details' : 'Create new client'; return self.client.public_id() ? 'Edit client details' : 'Create new client';
}); });
self.showClientForm = function() { self.showClientForm = function() {
if (self.client.public_id() == 0) { if (self.client.public_id() == 0) {
$('#myModal input').val(''); $('#myModal input').val('');
@ -552,6 +565,7 @@
self.country_id = ko.observable(''); self.country_id = ko.observable('');
self.client_size_id = ko.observable(''); self.client_size_id = ko.observable('');
self.client_industry_id = ko.observable(''); self.client_industry_id = ko.observable('');
self.website = ko.observable('');
self.contacts = ko.observableArray(); self.contacts = ko.observableArray();
self.mapping = { self.mapping = {
@ -617,6 +631,18 @@
ko.mapping.fromJS(data, {}, this); ko.mapping.fromJS(data, {}, this);
} }
self.wrapped_notes = ko.computed({
read: function() {
return this.notes();
},
write: function(value) {
value = wordWrapText(value);
self.notes(value);
onChange();
},
owner: this
});
this.rawTotal = ko.computed(function() { this.rawTotal = ko.computed(function() {
var cost = parseFloat(self.cost()); var cost = parseFloat(self.cost());
var qty = parseFloat(self.qty()); var qty = parseFloat(self.qty());
@ -646,36 +672,6 @@
} }
} }
function checkWordWrap(event)
{
var doc = new jsPDF('p', 'pt');
doc.setFont('Helvetica','');
doc.setFontSize(10);
var $textarea = $(event.target || event.srcElement);
var lines = $textarea.val().split("\n");
for (var i = 0; i < lines.length; i++) {
var numLines = doc.splitTextToSize(lines[i], 200).length;
if (numLines <= 1) continue;
var j = 0; space = lines[i].length;
while (j++ < lines[i].length) {
if (lines[i].charAt(j) === " ") space = j;
}
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space);
}
var val = lines.slice(0, 6).join("\n");
if (val != $textarea.val())
{
var model = ko.dataFor($textarea[0]);
model.notes(val);
refreshPDF();
}
$textarea.height(val.split('\n').length * 22);
onChange();
}
function onChange() function onChange()
{ {
var hasEmpty = false; var hasEmpty = false;
@ -688,6 +684,10 @@
if (!hasEmpty) { if (!hasEmpty) {
model.addItem(); model.addItem();
} }
$('.word-wrap').each(function(index, input) {
$(input).height($(input).val().split('\n').length * 22);
});
} }
var products = {{ $products }}; var products = {{ $products }};
@ -696,8 +696,8 @@
for (var i=0; i<clients.length; i++) { for (var i=0; i<clients.length; i++) {
var client = clients[i]; var client = clients[i];
for (var i=0; i<client.contacts.length; i++) { for (var j=0; j<client.contacts.length; j++) {
var contact = client.contacts[i]; var contact = client.contacts[j];
contact.send_invoice = contact.is_primary; contact.send_invoice = contact.is_primary;
} }
clientMap[client.public_id] = client; clientMap[client.public_id] = client;
@ -716,7 +716,8 @@
contact.send_invoice = invitationContactIds.indexOf(contact.public_id) >= 0; contact.send_invoice = invitationContactIds.indexOf(contact.public_id) >= 0;
} }
@else @else
model.invoice_number = '{{ $invoiceNumber }}'; model.invoice_number('{{ $invoiceNumber }}');
model.terms(wordWrapText('{{ $account->invoice_terms }}', 250));
@endif @endif
model.invoice_items.push(new ItemModel()); model.invoice_items.push(new ItemModel());
ko.applyBindings(model); ko.applyBindings(model);

View File

@ -0,0 +1,39 @@
@extends('header')
@section('head')
@parent
<script src="{{ asset('js/chart.js') }}" type="text/javascript"></script>
@stop
@section('content')
<center style="padding-top: 40px">
<canvas id="monthly-reports" width="800" height="300"></canvas>
</center>
<script type="text/javascript">
var ctx = document.getElementById('monthly-reports').getContext('2d');
var chart = {
labels: {{ json_encode($dates)}},
datasets: [{
data: {{ json_encode($totals) }},
fillColor : "rgba(151,187,205,0.5)",
strokeColor : "rgba(151,187,205,1)",
}]
}
var options = {
scaleOverride: true,
scaleSteps: 10,
scaleStepWidth: {{ $scaleStepWidth }},
scaleStartValue: 0,
scaleLabel : "<%=formatMoney(value)%>",
};
new Chart(ctx).Bar(chart, options);
</script>
@stop

1426
public/js/Chart.js vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -139,12 +139,16 @@ function generatePDF(invoice) {
var x = tableTop + (line * rowHeight); var x = tableTop + (line * rowHeight);
doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, x); doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, x);
doc.text(tableLeft, x+16, invoice.terms);
x += 16;
doc.text(footerLeft, x, 'Subtotal');
var total = formatNumber(total);
var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, x, total);
if (invoice.discount > 0) { if (invoice.discount > 0) {
x += 16;
doc.text(footerLeft, x, 'Subtotal');
var total = formatNumber(total);
var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, x, total);
x += 16; x += 16;
doc.text(footerLeft, x, 'Discount'); doc.text(footerLeft, x, 'Discount');
@ -154,6 +158,13 @@ function generatePDF(invoice) {
doc.text(discountX, x, discount); doc.text(discountX, x, discount);
} }
x += 16;
doc.text(footerLeft, x, 'Paid to Date');
var paid = formatNumber(0);
var paidX = headerRight - (doc.getStringUnitWidth(paid) * doc.internal.getFontSize());
doc.text(paidX, x, paid);
x += 16; x += 16;
doc.setFontType("bold"); doc.setFontType("bold");
doc.text(footerLeft, x, 'Total'); doc.text(footerLeft, x, 'Total');
@ -229,15 +240,6 @@ function generatePDF(invoice) {
return doc; return doc;
} }
function formatNumber(num) {
num = parseFloat(num);
if (!num) return '';
var p = num.toFixed(2).split(".");
return p[0].split("").reverse().reduce(function(acc, num, i, orig) {
return num + (i && !(i % 3) ? "," : "") + acc;
}, "") + "." + p[1];
}
/* Handle converting variables in the invoices (ie, MONTH+1) */ /* Handle converting variables in the invoices (ie, MONTH+1) */
function processVariables(str) { function processVariables(str) {
@ -316,6 +318,16 @@ function formatMoney(num) {
} }
function formatNumber(num) {
num = parseFloat(num);
if (!num) num = 0;
var p = num.toFixed(2).split(".");
return p[0].split("").reverse().reduce(function(acc, num, i, orig) {
return num + (i && !(i % 3) ? "," : "") + acc;
}, "") + "." + p[1];
}
/* Set the defaults for DataTables initialisation */ /* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, { $.extend( true, $.fn.dataTable.defaults, {
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>", "sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
@ -590,6 +602,28 @@ ko.bindingHandlers.dropdown = {
} }
}; };
function wordWrapText(value, width)
{
if (!width) width = 200;
var doc = new jsPDF('p', 'pt');
doc.setFont('Helvetica','');
doc.setFontSize(10);
var lines = value.split("\n");
for (var i = 0; i < lines.length; i++) {
var numLines = doc.splitTextToSize(lines[i], width).length;
if (numLines <= 1) continue;
var j = 0; space = lines[i].length;
while (j++ < lines[i].length) {
if (lines[i].charAt(j) === " ") space = j;
}
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space);
}
return lines.slice(0, 6).join("\n");
}
var CONSTS = {}; var CONSTS = {};
CONSTS.INVOICE_STATUS_DRAFT = 1; CONSTS.INVOICE_STATUS_DRAFT = 1;