1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-17 16:42:48 +01:00

Bug fixes

This commit is contained in:
Hillel Coren 2013-12-11 22:33:44 +02:00
parent d97c805d46
commit f946791780
21 changed files with 448 additions and 134 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
/app/config/staging
/app/config/development
/app/storage/
/public/logo
/bootstrap/compiled.php
/vendor

View File

@ -7,7 +7,7 @@ class ActivityController extends \BaseController {
$clientId = Client::getPrivateId($clientPublicId);
return Datatable::collection(Activity::scope()->where('client_id','=',$clientId)->get())
->addColumn('date', function($model) { return Utils::timestampToDateString($model->created_at); })
->addColumn('date', function($model) { return Utils::timestampToDateTimeString($model->created_at); })
->addColumn('message', function($model) { return $model->message; })
->addColumn('balance', function($model) { return '$' . $model->balance; })
->orderColumns('date')

View File

@ -15,12 +15,12 @@ class ClientController extends \BaseController {
return View::make('list', array(
'entityType'=>ENTITY_CLIENT,
'title' => '- Clients',
'columns'=>['checkbox', 'Client', 'Contact', 'Balance', 'Last Login', 'Date Created', 'Email', 'Phone', 'Action']
'columns'=>['checkbox', 'Client', 'Contact', 'Date Created', 'Email', 'Phone', 'Last Login', 'Balance', 'Action']
));
}
public function getDatatable()
{
{
$query = DB::table('clients')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.account_id', '=', Auth::user()->account_id)
@ -28,15 +28,30 @@ class ClientController extends \BaseController {
->where('contacts.is_primary', '=', true)
->select('clients.public_id','clients.name','contacts.first_name','contacts.last_name','clients.balance','clients.last_login','clients.created_at','clients.work_phone','contacts.email');
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
});
}
//$query->get();
//dd(DB::getQueryLog());
return Datatable::query($query)
->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->public_id . '">'; })
->addColumn('name', function($model) { return link_to('clients/' . $model->public_id, $model->name); })
->addColumn('first_name', function($model) { return $model->first_name . ' ' . $model->last_name; })
->addColumn('balance', function($model) { return '$' . $model->balance; })
->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); })
->addColumn('created_at', function($model) { return Utils::timestampToDateString($model->created_at); })
->addColumn('email', function($model) { return $model->email ? HTML::mailto($model->email, $model->email) : ''; })
->addColumn('work_phone', function($model) { return Utils::formatPhoneNumber($model->work_phone); })
->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); })
->addColumn('balance', function($model) { return '$' . $model->balance; })
->addColumn('dropdown', function($model)
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -71,6 +86,8 @@ class ClientController extends \BaseController {
'method' => 'POST',
'url' => 'clients',
'title' => '- New Client',
'clientSizes' => ClientSize::orderBy('id')->get(),
'clientIndustries' => ClientIndustry::orderBy('name')->get(),
'countries' => Country::orderBy('name')->get());
return View::make('clients.edit', $data);
@ -94,13 +111,13 @@ class ClientController extends \BaseController {
*/
public function show($publicId)
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
$client = Client::scope($publicId)->with('contacts', 'client_size', 'client_industry')->firstOrFail();
Utils::trackViewed($client->name, ENTITY_CLIENT);
$data = array(
'client' => $client,
'title' => '- ' . $client->name,
'hasRecurringInvoices' => Invoice::scope()->where('frequency_id', '>', '0')->count() > 0
'hasRecurringInvoices' => Invoice::scope()->where('frequency_id', '>', '0')->whereClientId($client->id)->count() > 0
);
return View::make('clients.show', $data);
@ -120,6 +137,8 @@ class ClientController extends \BaseController {
'method' => 'PUT',
'url' => 'clients/' . $publicId,
'title' => '- ' . $client->name,
'clientSizes' => ClientSize::orderBy('id')->get(),
'clientIndustries' => ClientIndustry::orderBy('name')->get(),
'countries' => Country::orderBy('name')->get());
return View::make('clients.edit', $data);
}
@ -161,10 +180,11 @@ class ClientController extends \BaseController {
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->notes = trim(Input::get('notes'));
$client->postal_code = trim(Input::get('postal_code'));
if (Input::get('country_id')) {
$client->country_id = Input::get('country_id');
}
$client->postal_code = trim(Input::get('postal_code'));
$client->country_id = Input::get('country_id') ? Input::get('country_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->save();
$data = json_decode(Input::get('data'));
@ -201,7 +221,12 @@ class ClientController extends \BaseController {
}
}
Session::flash('message', 'Successfully updated client');
if ($publicId) {
Session::flash('message', 'Successfully updated client');
} else {
Session::flash('message', 'Successfully created client');
}
return Redirect::to('clients/' . $client->public_id);
}

View File

@ -12,7 +12,7 @@ class CreditController extends \BaseController {
return View::make('list', array(
'entityType'=>ENTITY_CREDIT,
'title' => '- Credits',
'columns'=>['checkbox', 'Client', 'Amount', 'Credit Date', 'Action']
'columns'=>['checkbox', 'Client', 'Credit Amount', 'Credit Date', 'Action']
));
}
@ -28,6 +28,15 @@ class CreditController extends \BaseController {
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query);
if (!$clientPublicId) {

View File

@ -18,13 +18,13 @@ class InvoiceController extends \BaseController {
$data = [
'title' => '- Invoices',
'entityType'=>ENTITY_INVOICE,
'columns'=>['checkbox', 'Invoice Number', 'Client', 'Total', 'Amount Due', 'Invoice Date', 'Due Date', 'Status', 'Action']
'columns'=>['checkbox', 'Invoice Number', 'Client', 'Invoice Date', 'Invoice Total', 'Balance Due', 'Due Date', 'Status', 'Action']
];
if (Invoice::scope()->where('frequency_id', '>', '0')->count() > 0)
{
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
$data['secColumns'] = ['checkbox', 'Client', 'Total', 'Frequency', 'Start Date', 'End Date', 'Action'];
$data['secColumns'] = ['checkbox', 'Frequency', 'Client', 'Start Date', 'End Date', 'Invoice Total', 'Action'];
}
return View::make('list', $data);
@ -44,6 +44,17 @@ class InvoiceController extends \BaseController {
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query);
if (!$clientPublicId) {
@ -56,9 +67,9 @@ class InvoiceController extends \BaseController {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); });
}
return $table->addColumn('total', function($model) { return '$' . money_format('%i', $model->total); })
return $table->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('total', function($model) { return '$' . money_format('%i', $model->total); })
->addColumn('balance', function($model) { return '$' . money_format('%i', $model->balance); })
->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('due_date', function($model) { return Utils::fromSqlDate($model->due_date); })
->addColumn('invoice_status_name', function($model) { return $model->invoice_status_name; })
->addColumn('dropdown', function($model)
@ -93,20 +104,31 @@ class InvoiceController extends \BaseController {
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query);
if (!$clientPublicId) {
$table->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->public_id . '">'; });
}
$table->addColumn('frequency', function($model) { return link_to('invoices/' . $model->public_id, $model->frequency); });
if (!$clientPublicId) {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); });
}
return $table->addColumn('total', function($model) { return '$' . money_format('%i', $model->total); })
->addColumn('frequency', function($model) { return $model->frequency; })
->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); })
return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); })
->addColumn('end_date', function($model) { return Utils::fromSqlDate($model->end_date); })
->addColumn('total', function($model) { return '$' . money_format('%i', $model->total); })
->addColumn('dropdown', function($model)
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -340,7 +362,7 @@ class InvoiceController extends \BaseController {
'account' => Auth::user()->account,
'products' => Product::scope()->get(array('product_key','notes','cost','qty')),
'countries' => Country::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',
@ -371,7 +393,7 @@ class InvoiceController extends \BaseController {
{
return InvoiceController::bulk();
}
$rules = array(
'client' => 'required',
);
@ -388,33 +410,33 @@ class InvoiceController extends \BaseController {
if ($clientPublicId == "-1")
{
$client = Client::createNew();
$client->name = trim(Input::get('name'));
$client->work_phone = trim(Input::get('work_phone'));
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
if (Input::get('country_id')) {
$client->country_id = Input::get('country_id');
}
$client->save();
$clientId = $client->id;
$contact = Contact::createNew();
$contact->is_primary = true;
$contact->first_name = trim(Input::get('first_name'));
$contact->last_name = trim(Input::get('last_name'));
$contact->phone = trim(Input::get('phone'));
$contact->email = trim(Input::get('email'));
$client->contacts()->save($contact);
$contact->is_primary = true;
}
else
{
$client = Client::scope($clientPublicId)->with('contacts')->firstOrFail();
$contact = $client->contacts()->first();
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
}
$client->name = trim(Input::get('name'));
$client->work_phone = trim(Input::get('work_phone'));
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
if (Input::get('country_id')) {
$client->country_id = Input::get('country_id');
}
$client->save();
$contact->first_name = trim(Input::get('first_name'));
$contact->last_name = trim(Input::get('last_name'));
$contact->phone = trim(Input::get('phone'));
$contact->email = trim(Input::get('email'));
$client->contacts()->save($contact);
if ($publicId) {
$invoice = Invoice::scope($publicId)->firstOrFail();
$invoice->invoice_items()->forceDelete();
@ -423,7 +445,7 @@ class InvoiceController extends \BaseController {
}
$invoice->client_id = $client->id;
$invoice->discount = 0;
$invoice->discount = Input::get('discount');
$invoice->invoice_number = trim(Input::get('invoice_number'));
$invoice->invoice_date = Utils::toSqlDate(Input::get('invoice_date'));
$invoice->due_date = Utils::toSqlDate(Input::get('due_date'));
@ -432,7 +454,8 @@ class InvoiceController extends \BaseController {
$invoice->start_date = Utils::toSqlDate(Input::get('start_date'));
$invoice->end_date = Utils::toSqlDate(Input::get('end_date'));
$invoice->notes = Input::get('notes');
$invoice->po_number = Input::get('po_number');
$client->invoices()->save($invoice);
$items = json_decode(Input::get('items'));
@ -500,7 +523,11 @@ class InvoiceController extends \BaseController {
/*
*/
if ($action == 'email')
if ($action == 'clone')
{
return InvoiceController::cloneInvoice($publicId);
}
else if ($action == 'email')
{
$this->mailer->sendInvoice($invoice, $contact);
@ -561,4 +588,37 @@ class InvoiceController extends \BaseController {
return Redirect::to('invoices');
}
public static function cloneInvoice($publicId)
{
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
$clone = Invoice::createNew();
foreach (['client_id', 'discount', 'invoice_date', 'due_date', 'frequency_id', 'start_date', 'end_date', 'notes'] as $field)
{
$clone->$field = $invoice->$field;
}
if (!$clone->isRecurring())
{
$clone->invoice_number = Auth::user()->account->getNextInvoiceNumber();
}
$clone->save();
foreach ($invoice->invoice_items as $item)
{
$cloneItem = InvoiceItem::createNew();
foreach (['product_id', 'product_key', 'notes', 'cost', 'qty'] as $field)
{
$cloneItem->$field = $item->$field;
}
$clone->invoice_items()->save($cloneItem);
}
Session::flash('message', 'Successfully cloned invoice');
return Redirect::to('invoices/' . $clone->public_id);
}
}

View File

@ -7,7 +7,7 @@ class PaymentController extends \BaseController
return View::make('list', array(
'entityType'=>ENTITY_PAYMENT,
'title' => '- Payments',
'columns'=>['checkbox', 'Transaction Reference', 'Client', 'Invoice', 'Amount', 'Payment Date', 'Action']
'columns'=>['checkbox', 'Transaction Reference', 'Client', 'Invoice', 'Payment Amount', 'Payment Date', 'Action']
));
}
@ -24,6 +24,15 @@ class PaymentController extends \BaseController
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query);
if (!$clientPublicId) {

View File

@ -23,6 +23,8 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('invoices');
Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients');
Schema::dropIfExists('client_sizes');
Schema::dropIfExists('client_industries');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts');
Schema::dropIfExists('invoice_statuses');
@ -85,7 +87,6 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('timezone_id')->references('id')->on('timezones');
$t->foreign('country_id')->references('id')->on('countries');
});
Schema::create('gateways', function($t)
{
@ -115,7 +116,7 @@ class ConfideSetupUsersTable extends Migration {
Schema::create('users', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('account_id')->index();
$t->timestamps();
$t->softDeletes();
@ -144,11 +145,23 @@ class ConfideSetupUsersTable extends Migration {
$t->string('token');
});
Schema::create('client_sizes', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('client_industries', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('clients', function($t)
{
$t->increments('id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('account_id')->index();
$t->timestamps();
$t->softDeletes();
@ -163,12 +176,17 @@ class ConfideSetupUsersTable extends Migration {
$t->text('notes');
$t->decimal('balance', 10, 2);
$t->timestamp('last_login');
$t->string('website');
$t->unsignedInteger('client_industry_id')->nullable();
$t->unsignedInteger('client_size_id')->nullable();
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users');
$t->foreign('country_id')->references('id')->on('countries');
$t->foreign('client_industry_id')->references('id')->on('client_industries');
$t->foreign('client_size_id')->references('id')->on('client_sizes');
$t->unsignedInteger('public_id');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
@ -177,7 +195,7 @@ class ConfideSetupUsersTable extends Migration {
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('client_id');
$t->unsignedInteger('client_id')->index();
$t->timestamps();
$t->softDeletes();
@ -210,15 +228,16 @@ class ConfideSetupUsersTable extends Migration {
Schema::create('invoices', function($t)
{
$t->increments('id');
$t->unsignedInteger('client_id');
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('user_id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('invoice_status_id')->default(1);
$t->timestamps();
$t->softDeletes();
$t->string('invoice_number');
$t->float('discount');
$t->string('po_number');
$t->date('invoice_date');
$t->date('due_date')->nullable();
$t->text('notes');
@ -227,7 +246,7 @@ class ConfideSetupUsersTable extends Migration {
$t->date('start_date')->nullable();
$t->date('end_date')->nullable();
$t->date('last_sent_date')->nullable();
$t->unsignedInteger('recurring_invoice_id')->nullable();
$t->unsignedInteger('recurring_invoice_id')->index()->nullable();
$t->decimal('total', 10, 2);
@ -239,7 +258,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('invoice_status_id')->references('id')->on('invoice_statuses');
$t->foreign('recurring_invoice_id')->references('id')->on('invoices');
$t->unsignedInteger('public_id');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
@ -250,8 +269,8 @@ class ConfideSetupUsersTable extends Migration {
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('contact_id');
$t->unsignedInteger('invoice_id');
$t->string('invitation_key')->unique();
$t->unsignedInteger('invoice_id')->index();
$t->string('invitation_key')->index()->unique();
$t->timestamps();
$t->softDeletes();
@ -261,14 +280,14 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->unsignedInteger('public_id');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('products', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('user_id');
$t->timestamps();
$t->softDeletes();
@ -291,7 +310,7 @@ class ConfideSetupUsersTable extends Migration {
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('invoice_id');
$t->unsignedInteger('invoice_id')->index();
$t->unsignedInteger('product_id')->nullable();
$t->timestamps();
$t->softDeletes();
@ -313,8 +332,8 @@ class ConfideSetupUsersTable extends Migration {
{
$t->increments('id');
$t->unsignedInteger('invoice_id')->nullable();
$t->unsignedInteger('account_id');
$t->unsignedInteger('client_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('contact_id')->nullable();
$t->unsignedInteger('invitation_id')->nullable();
$t->unsignedInteger('user_id')->nullable();
@ -332,16 +351,16 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('contact_id')->references('id')->on('contacts');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('credits', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('user_id');
$t->unsignedInteger('client_id')->nullable();
$t->unsignedInteger('client_id')->index()->nullable();
$t->unsignedInteger('contact_id')->nullable();
$t->timestamps();
$t->softDeletes();
@ -355,7 +374,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('contact_id')->references('id')->on('contacts');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
@ -403,6 +422,8 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('invoices');
Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients');
Schema::dropIfExists('client_sizes');
Schema::dropIfExists('client_industries');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts');
Schema::dropIfExists('invoice_statuses');

View File

@ -58,6 +58,17 @@ class ConstantsSeeder extends Seeder
Frequency::create(array('name' => 'Six months'));
Frequency::create(array('name' => 'Annually'));
ClientIndustry::create(array('name' => 'Accounting'));
ClientIndustry::create(array('name' => 'Travel'));
ClientIndustry::create(array('name' => 'Engineering'));
ClientIndustry::create(array('name' => 'Marketing'));
ClientSize::create(array('name' => '1 - 10'));
ClientSize::create(array('name' => '11 - 50'));
ClientSize::create(array('name' => '51 - 100'));
ClientSize::create(array('name' => '101 - 500'));
ClientSize::create(array('name' => '500+'));
$gateways = [
array('name'=>'Authorize.Net AIM', 'provider'=>'AuthorizeNet_AIM'),
array('name'=>'Authorize.Net SIM', 'provider'=>'AuthorizeNet_SIM'),

View File

@ -62,7 +62,7 @@ class Utils
return '';
}
return $date->format('l M jS, Y g:ia');
return $date->format('D M jS, Y g:ia');
}
public static function timestampToDateString($timestamp) {
@ -212,4 +212,5 @@ class Utils
{
return ucwords(str_replace('_', ' ', $entityType));
}
}

View File

@ -39,6 +39,16 @@ class Client extends EntityModel
return $this->belongsTo('Country');
}
public function client_size()
{
return $this->belongsTo('ClientSize');
}
public function client_industry()
{
return $this->belongsTo('ClientIndustry');
}
public function getName()
{
return $this->name;
@ -104,6 +114,23 @@ class Client extends EntityModel
return $str;
}
public function getIndustry()
{
$str = '';
if ($this->client_industry)
{
$str .= $this->client_industry->name . ' ';
}
if ($this->client_size)
{
$str .= $this->client_size->name;
}
return $str;
}
public function getDateCreated()
{
if ($this->created_at == '0000-00-00 00:00:00')

6
app/models/ClientIndustry.php Executable file
View File

@ -0,0 +1,6 @@
<?php
class ClientIndustry extends Eloquent
{
public $timestamps = false;
}

6
app/models/ClientSize.php Executable file
View File

@ -0,0 +1,6 @@
<?php
class ClientSize extends Eloquent
{
public $timestamps = false;
}

View File

@ -14,6 +14,8 @@
//dd(DB::getQueryLog());
//dd(Client::getPrivateId(1));
//dd(new DateTime());
Route::get('/send_emails', function() {
Artisan::call('ninja:send-invoices');
});

View File

@ -25,8 +25,7 @@
{{ Former::legend('Organization') }}
{{ Former::text('name') }}
{{ Former::text('work_phone')->label('Phone') }}
{{ Former::textarea('notes') }}
{{ Former::legend('Address') }}
{{ Former::text('address1')->label('Street') }}
@ -61,9 +60,15 @@
</span>
</div>
</div>
</div>
{{ Former::legend('Additional Info') }}
{{ Former::select('client_size_id')->addOption('','')->label('Size')
->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }}
{{ Former::select('client_industry_id')->addOption('','')->label('Industry')
->fromQuery($clientIndustries, 'name', 'id')->select($client ? $client->client_industry_id : '') }}
{{ Former::textarea('notes') }}
</div>
</div>

View File

@ -49,6 +49,7 @@
<p>{{ $client->getAddress() }}</p>
<p>{{ $client->getPhone() }}</p>
<p>{{ $client->getNotes() }}</p>
<p>{{ $client->getIndustry() }}</p>
</div>
<div class="col-md-3">
@ -91,7 +92,7 @@
@if ($hasRecurringInvoices)
{{ Datatable::table()
->addColumn('Total', 'How Often', 'Start Date', 'End Date')
->addColumn('How Often', 'Start Date', 'End Date', 'Invoice Total')
->setUrl(url('api/recurring_invoices/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
@ -99,7 +100,7 @@
@endif
{{ Datatable::table()
->addColumn('Invoice Number', 'Total', 'Amount Due', 'Invoice Date', 'Due Date', 'Status')
->addColumn('Invoice Number', 'Invoice Date', 'Invoice Total', 'Balance Due', 'Due Date', 'Status')
->setUrl(url('api/invoices/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
@ -109,7 +110,7 @@
<div class="tab-pane" id="payments">
{{ Datatable::table()
->addColumn('Transaction Reference', 'Invoice', 'Amount', 'Payment Date')
->addColumn('Transaction Reference', 'Invoice', 'Payment Amount', 'Payment Date')
->setUrl(url('api/payments/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
@ -119,7 +120,7 @@
<div class="tab-pane" id="credits">
{{ Datatable::table()
->addColumn('Amount', 'Credit Date')
->addColumn('Credit Amount', 'Credit Date')
->setUrl(url('api/credits/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)

View File

@ -7,7 +7,11 @@
<thead>
<tr>
@foreach($columns as $i => $c)
<th align="center" valign="middle" class="head{{ $i }}">
<th align="center" valign="middle" class="head{{ $i }}"
@if ($c == 'checkbox')
style="width:20px"
@endif
>
@if ($c == 'checkbox' && $hasCheckboxes = true)
<input type="checkbox" class="selectAll"/>
@else
@ -45,7 +49,6 @@
{{ json_encode($k) }}: {{ $o }},
@endforeach
"fnDrawCallback": function(oSettings) {
//jQuery.uniform.update();
if (window.onDatatableReady) {
window.onDatatableReady();
}

View File

@ -33,22 +33,25 @@
@endif
<div class="row" style="min-height:195px">
<div class="col-md-6" id="col_1">
<div class="col-md-7" id="col_1">
{{ Former::select('client')->addOption('', '')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addGroupClass('client_select')
->help('<a style="cursor:pointer" data-toggle="modal" id="modalLink" onclick="showCreateNew()">Create new client</a>') }}
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") }}
{{ Former::textarea('notes') }}
</div>
<div class="col-md-5" id="col_2">
<div class="col-md-4" id="col_2">
<div id="recurring_checkbox">
{{ Former::checkbox('recurring')->text('Enable automatic invoicing | <a href="#">Learn more</a>')->onchange('toggleRecurring()')
{{ Former::checkbox('recurring')->text('Enable | <a href="#">Learn more</a>')->onchange('toggleRecurring()')
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::timestampToDateString($invoice->last_sent_date) : '') }}
</div>
<div id="recurring_off">
{{ Former::text('invoice_number')->label('Invoice #') }}
{{ Former::text('po_number')->label('PO number') }}
{{ Former::text('invoice_date') }}
{{ Former::text('due_date') }}
{{-- Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'") --}}
{{-- Former::text('invoice_date')->label('Invoice Date')->data_date_format('yyyy-mm-dd') --}}
</div>
<div id="recurring_on" style="display:none">
@ -56,8 +59,11 @@
{{ Former::text('start_date')->onchange('updateRecurringStats()') }}
{{ Former::text('end_date')->onchange('updateRecurringStats()') }}
</div>
</div>
<div class="col-md-3" id="col_3" style="display:none">
</div>
</div>
@ -71,8 +77,8 @@
<th class="hide-border"></th>
<th>Item</th>
<th>Description</th>
<th>Unit&nbsp;Cost</th>
<th>Quantity</th>
<th>Rate</th>
<th>Units</th>
<th>Line&nbsp;Total</th>
<th class="hide-border"></th>
</tr>
@ -130,7 +136,7 @@
<tr>
<td class="hide-border"></td>
<td colspan="2" class="hide-border"/>
<td colspan="2"><b>Invoice Total</b></td>
<td colspan="2"><b>Balance Due</b></td>
<td style="text-align: right"><span data-bind="text: total"/></td>
</tr>
</tfoot>
@ -146,11 +152,15 @@
@endif
</div>
{{ Button::normal('Download PDF', array('onclick' => 'onDownloadClick()')) }}
@if ($invoice)
{{ DropdownButton::normal('Download PDF',
{{ DropdownButton::primary('Save Invoice',
Navigation::links(
array(
array('Download PDF', "javascript:onDownloadClick()"),
array('Save Invoice', "javascript:onSaveClick()"),
array('Clone Invoice', "javascript:onCloneClick()"),
array(Navigation::DIVIDER),
array('Archive Invoice', "javascript:onArchiveClick()"),
array('Delete Invoice', "javascript:onDeleteClick()"),
@ -158,18 +168,17 @@
)
, array('id'=>'actionDropDown','style'=>'text-align:left'))->split(); }}
@else
{{ Button::normal('Download PDF', array('onclick' => 'onDownloadClick()')) }}
{{ Button::primary_submit('Save Invoice') }}
@endif
{{ Button::primary_submit('Save Invoice') }}
{{ Button::primary('Send Email', array('id' => 'email_button', 'onclick' => 'onEmailClick()')) }}
</div>
<p>&nbsp;</p>
<!-- <textarea rows="20" cols="120" id="pdfText" onkeyup="runCode()"></textarea> -->
<!-- <iframe frameborder="1" width="100%" height="600" style="display:block;margin: 0 auto"></iframe> -->
<!-- <iframe frameborder="1" width="100%" height="500"></iframe> -->
<canvas id="the-canvas" style="width:100%;border:solid 1px #CCCCCC;"></canvas>
<iframe id="theIFrame" frameborder="1" width="100%" height="500"></iframe>
<canvas id="theCanvas" style="display:none;width:100%;border:solid 1px #CCCCCC;"></canvas>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
@ -253,13 +262,13 @@
$(function() {
$('form').change(refreshPDF);
$('#country_id').combobox();
$('#invoice_date').datepicker({
autoclose: true,
todayHighlight: true
}).on('changeDate', function(e) {
refreshPDF();
});
$('#due_date, #start_date, #end_date').datepicker({
@ -270,11 +279,13 @@
var $input = $('select#client');
$input.combobox();
$('.client_select input.form-control').on('change', function(e) {
refreshPDF();
if ($('input[name=client]').val() != '-1') {
$('#modalLink').text('Create new client');
var clientId = parseInt($('input[name=client]').val(), 10);
$('#modalLink').text(clientId ? 'Edit client details' : 'Create new client');
if (clientId > 0) {
loadClientDetails(clientId);
}
});
}).trigger('change');
//enableHoverClick($('.combobox-container input.form-control'), $('.combobox-container input[name=client]'), '{{ URL::to('clients') }}');
@if ($client)
@ -296,7 +307,7 @@
$('#invoice_number').change(refreshPDF);
$('#actionDropDown > button:first').click(function() {
onDownloadClick();
onSaveClick();
});
@ -305,24 +316,46 @@
@if ($invoice && $invoice->isRecurring())
$('#recurring').prop('checked', true);
@elseif ($invoice && $invoice->isSent())
$('#recurring_checkbox').hide();
@elseif (isset($invoice->recurring_invoice_id) && $invoice->recurring_invoice_id)
$('#recurring_checkbox > div > div').html('Created by a {{ link_to('/invoices/'.$invoice->recurring_invoice_id, 'recurring invoice') }}').css('padding-top','6px');
@elseif ($invoice && $invoice->isSent())
$('#recurring_checkbox').hide();
@endif
toggleRecurring();
toggleRecurring();
applyComboboxListeners();
refreshPDF();
});
function showCreateNew() {
if ($('input[name=client]').val() != '-1') {
function loadClientDetails(clientId) {
var client = clientMap[clientId];
$('#name').val(client.name);
$('#work_phone').val(client.work_phone);
$('#address1').val(client.address1);
$('#address2').val(client.address2);
$('#city').val(client.city);
$('#state').val(client.state);
$('#postal_code').val(client.postal_code);
$('#country_id').val(client.country_id).combobox('refresh');
for (var i=0; i<client.contacts.length; i++) {
var contact = client.contacts[i];
if (contact.is_primary) {
$('#first_name').val(contact.first_name);
$('#last_name').val(contact.last_name);
$('#email').val(contact.email);
$('#phone').val(contact.phone);
}
}
}
function showCreateNew() {
if (!$('input[name=client]').val()) {
$('#myModal input').val('');
$('#myModal #country_id').val('');
$('#nameError').css( "display", "none" );
}
}
$('#myModal').modal('show');
}
@ -357,6 +390,8 @@
var invoice = {
invoice_number: $('#invoice_number').val(),
invoice_date: $('#invoice_date').val(),
discount: parseFloat($('#discount').val()),
po_number: $('#po_number').val(),
account: {
name: "{{ $account->name }}",
address1: "{{ $account->address1 }}",
@ -376,8 +411,20 @@
invoice_items: []
};
var clientId = $('input[name=client]').val();
var client = {
name: $('#name').val(),
address1: $('#address1').val(),
address2: $('#address2').val(),
city: $('#city').val(),
state: $('#state').val(),
postal_code: $('#postal_code').val(),
country: {
name: $('.country_select input[type=text]').val()
}
};
/*
var clientId = $('input[name=client]').val();
if (clientId == '-1') {
var client = {
name: $('#name').val(),
@ -393,6 +440,7 @@
} else if (clientMap.hasOwnProperty(clientId)) {
var client = clientMap[clientId];
}
*/
invoice.client = client;
for(var i=0; i<model.items().length; i++) {
@ -417,10 +465,10 @@
function _refreshPDF() {
var invoice = createInvoiceModel();
var doc = generatePDF(invoice);
var doc = generatePDF(invoice);
/*
var string = doc.output('dataurlstring');
//console.log(string);
var pdfAsArray = convertDataURIToBinary(string);
PDFJS.getDocument(pdfAsArray).then(function getPdfHelloWorld(pdf) {
@ -428,7 +476,7 @@
var scale = 1.5;
var viewport = page.getViewport(scale);
var canvas = document.getElementById('the-canvas');
var canvas = document.getElementById('theCanvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
@ -436,8 +484,10 @@
page.render({canvasContext: context, viewport: viewport});
});
});
*/
//$('iframe').attr('src', string);
var string = doc.output('datauristring');
$('#theIFrame').attr('src', string);
}
function onDownloadClick() {
@ -453,6 +503,15 @@
}
}
function onSaveClick() {
$('.main_form').submit();
}
function onCloneClick() {
$('#action').val('clone');
$('.main_form').submit();
}
function onArchiveClick() {
$('#action').val('archive');
$('.main_form').submit();
@ -471,7 +530,9 @@
if (!name) $('#nameError').css( "display", "inline" );
} else {
$('select#client').combobox('setSelected');
$('input[name=client]').val('-1');
if (!$('input[name=client]').val()) {
$('input[name=client]').val('-1');
}
$('.client_select input.form-control').val(name);
$('.client_select .combobox-container').addClass('combobox-selected');
@ -556,7 +617,7 @@
this.total = ko.computed(function() {
var total = self.rawSubtotal();
var discount = parseInt(self.discount());
var discount = parseFloat(self.discount());
if (discount > 0) {
total = total * ((100 - discount)/100);
}

View File

@ -18,14 +18,16 @@
, array('id'=>'archive'))->split(); }}
{{ Button::primary_link(URL::to($entityType . 's/create'), 'New ' . Utils::getEntityName($entityType), array('class' => 'pull-right')) }}
<div id="top_right_buttons" class="pull-right">
<input id="tableFilter" type="text" style="width:140px;margin-right:4px" class="form-control pull-left" placeholder="Filter"/>
{{ Button::primary_link(URL::to($entityType . 's/create'), 'New ' . Utils::getEntityName($entityType), array('class' => 'pull-right')) }}
</div>
@if (isset($secEntityType))
{{ Datatable::table()
->addColumn($secColumns)
->setUrl(route('api.' . $secEntityType . 's'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->render('datatable') }}
@endif
@ -33,7 +35,6 @@
->addColumn($columns)
->setUrl(route('api.' . $entityType . 's'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->render('datatable') }}
{{ Former::close() }}
@ -46,6 +47,7 @@
return;
}
}
$('#action').val(action);
$('form.listForm').submit();
}
@ -67,8 +69,36 @@
@stop
@section('onReady')
var tableFilter = '';
var searchTimeout = false;
var oTable0 = $('#DataTables_Table_0').dataTable();
var oTable1 = $('#DataTables_Table_1').dataTable();
function filterTable(val) {
if (val == tableFilter) {
return;
}
tableFilter = val;
oTable0.fnFilter(val);
@if (isset($secEntityType))
oTable1.fnFilter(val);
@endif
}
$('#tableFilter').on('keyup', function(){
if (searchTimeout) {
window.clearTimeout(searchTimeout);
}
searchTimeout = setTimeout(function() {
filterTable($('#tableFilter').val());
}, 1000);
})
window.onDatatableReady = function() {
console.log('data loaded');
$(':checkbox').click(function() {
setArchiveEnabled();
});
@ -90,6 +120,7 @@
$dropdown.css('visibility','hidden');
}
});
}
$('#archive > button').prop('disabled', true);

View File

@ -102,9 +102,12 @@
, select: function () {
var val = this.$menu.find('.active').attr('data-value');
this.$element.val(this.updater(val)).trigger('change');
this.$target.val(this.map[val]).trigger('change');
this.$source.val(this.map[val]).trigger('change');
this.$element.val(this.updater(val));
this.$target.val(this.map[val]);
this.$source.val(this.map[val]);
this.$element.trigger('change');
this.$target.trigger('change');
this.$source.trigger('change');
this.$container.addClass('combobox-selected');
this.selected = true;
return this.hide();

View File

@ -1,5 +1,4 @@
function generatePDF(invoice) {
var invoiceNumber = invoice.invoice_number;
var issuedOn = invoice.invoice_date;
var amount = '$0.00';
@ -9,7 +8,7 @@ function generatePDF(invoice) {
var headerLeft = 360;
var headerRight = 540;
var rowHeight = 15;
var footerLeft = 450;
var footerLeft = 420;
var tableTop = 240;
var tableLeft = 60;
@ -30,10 +29,18 @@ function generatePDF(invoice) {
/* table header */
doc.setDrawColor(200,200,200);
doc.setFillColor(230,230,230);
doc.rect(headerLeft - 6, headerTop + rowHeight + 4, headerRight - headerLeft + 12, rowHeight + 2, 'FD');
var x1 = headerLeft - 6;
var y1 = headerTop + rowHeight + 4;
var x2 = headerRight - headerLeft + 12;
var y2 = rowHeight + 2;
if (invoice.po_number) {
y1 += rowHeight;
}
doc.rect(x1, y1, x2, y2, 'FD');
var invoiceNumberX = headerRight - (doc.getStringUnitWidth(invoiceNumber) * doc.internal.getFontSize());
var issuedOnX = headerRight - (doc.getStringUnitWidth(issuedOn) * doc.internal.getFontSize());
var poNumberX = headerRight - (doc.getStringUnitWidth(invoice.po_number) * doc.internal.getFontSize());
doc.setFontType("normal");
if (invoice.client) {
@ -55,13 +62,23 @@ function generatePDF(invoice) {
}
}
doc.text(headerLeft, headerTop, 'Invoice #');
doc.text(invoiceNumberX, headerTop, invoiceNumber);
doc.text(headerLeft, headerTop + rowHeight, 'Invoice Date');
doc.text(issuedOnX, headerTop + rowHeight, issuedOn);
var headerY = headerTop;
doc.text(headerLeft, headerY, 'Invoice #');
doc.text(invoiceNumberX, headerY, invoiceNumber);
if (invoice.po_number) {
headerY += rowHeight;
doc.text(headerLeft, headerY, 'PO Number');
doc.text(poNumberX, headerY, invoice.po_number);
}
headerY += rowHeight;
doc.text(headerLeft, headerY, 'Invoice Date');
doc.text(issuedOnX, headerY, issuedOn);
headerY += rowHeight;
doc.setFontType("bold");
doc.text(headerLeft, headerTop + (2 * rowHeight), 'Amount Due');
doc.text(headerLeft, headerY, 'Amount Due');
doc.setDrawColor(200,200,200);
doc.setFillColor(230,230,230);
@ -122,6 +139,21 @@ function generatePDF(invoice) {
var x = tableTop + (line * rowHeight);
doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, x);
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;
doc.text(footerLeft, x, 'Discount');
var discount = formatNumber(total * (invoice.discount/100));
total -= discount;
var discountX = headerRight - (doc.getStringUnitWidth(discount) * doc.internal.getFontSize());
doc.text(discountX, x, discount);
}
x += 16;
doc.setFontType("bold");
doc.text(footerLeft, x, 'Total');
@ -131,9 +163,10 @@ function generatePDF(invoice) {
doc.text(totalX, x, total);
totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, headerTop + (2 * rowHeight), total);
doc.text(totalX, headerY, total);
/* payment stub */
/*
var y = 680;
doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, y - 30);
doc.setFontSize(20);
@ -191,7 +224,8 @@ function generatePDF(invoice) {
y += 16;
doc.setFontType("bold");
doc.text(headerLeft, y, 'Amount Enclosed');
*/
return doc;
}
@ -282,20 +316,18 @@ function formatMoney(num) {
}
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
//"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"bProcessing": false,
//"bProcessing": true,
//"iDisplayLength": 50,
"bInfo": true,
"oLanguage": {
//"sLengthMenu": "_MENU_ records per page"
"sLengthMenu": "_MENU_"
"sLengthMenu": "_MENU_",
"sSearch": ""
},
//"sScrollY": "500px",
} );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB