bug fixes

This commit is contained in:
Hillel Coren 2013-12-30 22:17:45 +02:00
parent ef24ac2c36
commit 2b5f42e5ac
24 changed files with 364 additions and 171 deletions

View File

@ -40,7 +40,8 @@ class SendRecurringInvoices extends Command {
$invoice->client_id = $recurInvoice->client_id; $invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id; $invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(); $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber();
$invoice->total = $recurInvoice->total; $invoice->amount = $recurInvoice->amount;
$invoice->currency_id = $recurInvoice->currency_id;
$invoice->invoice_date = new DateTime(); $invoice->invoice_date = new DateTime();
$invoice->due_date = new DateTime(); $invoice->due_date = new DateTime();
$invoice->save(); $invoice->save();

View File

@ -4,12 +4,18 @@ class ActivityController extends \BaseController {
public function getDatatable($clientPublicId) public function getDatatable($clientPublicId)
{ {
$clientId = Client::getPrivateId($clientPublicId); $query = DB::table('activities')
->join('clients', 'clients.id', '=', 'activities.client_id')
->where('clients.public_id', '=', $clientPublicId)
->where('activities.account_id', '=', Auth::user()->account_id)
->select('activities.message', 'activities.created_at', 'activities.currency_id', 'activities.balance', 'activities.adjustment');
return Datatable::collection(Activity::scope()->where('client_id','=',$clientId)->get())
return Datatable::query($query)
->addColumn('date', function($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); }) ->addColumn('date', function($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function($model) { return $model->message; }) ->addColumn('message', function($model) { return $model->message; })
->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->account->currency_id); }) ->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function($model) { return $model->adjustment != 0 ? Utils::formatMoney($model->adjustment, $model->currency_id) : ''; })
->orderColumns('date') ->orderColumns('date')
->make(); ->make();
} }

View File

@ -15,7 +15,7 @@ class ClientController extends \BaseController {
return View::make('list', array( return View::make('list', array(
'entityType'=>ENTITY_CLIENT, 'entityType'=>ENTITY_CLIENT,
'title' => '- Clients', 'title' => '- Clients',
'columns'=>['checkbox', 'Client', 'Contact', 'Date Created', 'Email', 'Phone', 'Last Login', 'Balance', 'Action'] 'columns'=>['checkbox', 'Client', 'Contact', 'Email', 'Date Created', 'Phone', 'Last Login', 'Balance', 'Action']
)); ));
} }
@ -46,9 +46,9 @@ class ClientController extends \BaseController {
return Datatable::query($query) return Datatable::query($query)
->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->public_id . '">'; }) ->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('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('first_name', function($model) { return link_to('clients/' . $model->public_id, $model->first_name . ' ' . $model->last_name); })
->addColumn('email', function($model) { return link_to('clients/' . $model->public_id, $model->email); })
->addColumn('created_at', function($model) { return Utils::timestampToDateString(strtotime($model->created_at)); }) ->addColumn('created_at', function($model) { return Utils::timestampToDateString(strtotime($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('work_phone', function($model) { return Utils::formatPhoneNumber($model->work_phone); })
->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); }) ->addColumn('last_login', function($model) { return Utils::timestampToDateString($model->last_login); })
->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->currency_id); }) ->addColumn('balance', function($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
@ -88,6 +88,7 @@ class ClientController extends \BaseController {
'title' => '- New Client', 'title' => '- New Client',
'clientSizes' => ClientSize::orderBy('id')->get(), 'clientSizes' => ClientSize::orderBy('id')->get(),
'clientIndustries' => ClientIndustry::orderBy('name')->get(), 'clientIndustries' => ClientIndustry::orderBy('name')->get(),
'currencies' => Currency::orderBy('name')->get(),
'countries' => Country::orderBy('name')->get()); 'countries' => Country::orderBy('name')->get());
return View::make('clients.edit', $data); return View::make('clients.edit', $data);
@ -117,7 +118,7 @@ class ClientController extends \BaseController {
$data = array( $data = array(
'client' => $client, 'client' => $client,
'title' => '- ' . $client->name, 'title' => '- ' . $client->name,
'hasRecurringInvoices' => Invoice::scope()->where('frequency_id', '>', '0')->whereClientId($client->id)->count() > 0 'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0
); );
return View::make('clients.show', $data); return View::make('clients.show', $data);
@ -158,7 +159,7 @@ class ClientController extends \BaseController {
private function save($publicId = null) private function save($publicId = null)
{ {
$rules = array( $rules = array(
'name' => 'required' 'email' => 'required'
); );
$validator = Validator::make(Input::all(), $rules); $validator = Validator::make(Input::all(), $rules);
@ -182,7 +183,7 @@ class ClientController extends \BaseController {
$client->state = trim(Input::get('state')); $client->state = trim(Input::get('state'));
$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->private_notes = trim(Input::get('private_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->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null; $client->currency_id = Input::get('currency_id') ? Input::get('currency_id') : null;

View File

@ -20,10 +20,12 @@ class CreditController extends \BaseController {
{ {
$query = DB::table('credits') $query = DB::table('credits')
->join('clients', 'clients.id', '=','credits.client_id') ->join('clients', 'clients.id', '=','credits.client_id')
->join('contacts', 'contacts.client_id', '=', 'clients.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) ->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', 'credits.currency_id'); ->where('contacts.is_primary', '=', true)
->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.credit_date', 'credits.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
if ($clientPublicId) { if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId); $query->where('clients.public_id', '=', $clientPublicId);
@ -42,7 +44,7 @@ class CreditController extends \BaseController {
if (!$clientPublicId) { if (!$clientPublicId) {
$table->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->public_id . '">'; }) $table->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->public_id . '">'; })
->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); ->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
} }
return $table->addColumn('amount', function($model){ return Utils::formatMoney($model->amount, $model->currency_id); }) return $table->addColumn('amount', function($model){ return Utils::formatMoney($model->amount, $model->currency_id); })
@ -68,19 +70,14 @@ class CreditController extends \BaseController {
public function create($clientPublicId = null) public function create($clientPublicId = null)
{ {
$client = null;
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
}
$data = array( $data = array(
'client' => $client, 'clientPublicId' => $clientPublicId,
'credit' => null, 'credit' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'credits', 'url' => 'credits',
'title' => '- New Credit', 'title' => '- New Credit',
'currencies' => Currency::orderBy('name')->get(), 'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->with('contacts')->orderBy('name')->get());
return View::make('credits.edit', $data); return View::make('credits.edit', $data);
} }
@ -95,7 +92,7 @@ class CreditController extends \BaseController {
'url' => 'credits/' . $publicId, 'url' => 'credits/' . $publicId,
'title' => '- Edit Credit', 'title' => '- Edit Credit',
'currencies' => Currency::orderBy('name')->get(), 'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->with('contacts')->orderBy('name')->get());
return View::make('credit.edit', $data); return View::make('credit.edit', $data);
} }

View File

@ -51,7 +51,7 @@ class InvoiceController extends \BaseController {
$table->addColumn('invoice_number', function($model) { return link_to('invoices/' . $model->public_id . '/edit', $model->invoice_number); }); $table->addColumn('invoice_number', function($model) { return link_to('invoices/' . $model->public_id . '/edit', $model->invoice_number); });
if (!$clientPublicId) { if (!$clientPublicId) {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); $table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
} }
return $table->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); }) return $table->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); })
@ -89,7 +89,7 @@ class InvoiceController extends \BaseController {
$table->addColumn('frequency', function($model) { return link_to('invoices/' . $model->public_id, $model->frequency); }); $table->addColumn('frequency', function($model) { return link_to('invoices/' . $model->public_id, $model->frequency); });
if (!$clientPublicId) { if (!$clientPublicId) {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); $table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
} }
return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); }) return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); })
@ -385,10 +385,8 @@ class InvoiceController extends \BaseController {
} }
$input = json_decode(Input::get('data')); $input = json_decode(Input::get('data'));
$inputClient = $input->client;
$inputClient->name = trim($inputClient->name);
if (!$inputClient->name) if (!$input->client->contacts[0]->email)
{ {
return Redirect::to('invoices/create') return Redirect::to('invoices/create')
->withInput(); ->withInput();
@ -521,7 +519,7 @@ class InvoiceController extends \BaseController {
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail(); $invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
$clone = Invoice::createNew(); $clone = Invoice::createNew();
foreach (['client_id', 'discount', 'invoice_date', 'due_date', 'is_recurring', 'frequency_id', 'start_date', 'end_date', 'notes'] as $field) foreach (['client_id', 'discount', 'invoice_date', 'due_date', 'is_recurring', 'frequency_id', 'start_date', 'end_date', 'terms', 'currency_id'] as $field)
{ {
$clone->$field = $invoice->$field; $clone->$field = $invoice->$field;
} }
@ -537,7 +535,7 @@ class InvoiceController extends \BaseController {
{ {
$cloneItem = InvoiceItem::createNew(); $cloneItem = InvoiceItem::createNew();
foreach (['product_id', 'product_key', 'notes', 'cost', 'qty'] as $field) foreach (['product_id', 'product_key', 'notes', 'cost', 'qty', 'tax_name', 'tax_rate'] as $field)
{ {
$cloneItem->$field = $item->$field; $cloneItem->$field = $item->$field;
} }

View File

@ -16,10 +16,12 @@ class PaymentController extends \BaseController
$query = DB::table('payments') $query = DB::table('payments')
->join('clients', 'clients.id', '=','payments.client_id') ->join('clients', 'clients.id', '=','payments.client_id')
->leftJoin('invoices', 'invoices.id', '=','payments.invoice_id') ->leftJoin('invoices', 'invoices.id', '=','payments.invoice_id')
->join('contacts', 'contacts.client_id', '=', 'clients.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) ->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', 'payments.currency_id'); ->where('contacts.is_primary', '=', true)
->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', 'payments.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
if ($clientPublicId) { if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId); $query->where('clients.public_id', '=', $clientPublicId);
@ -43,7 +45,7 @@ class PaymentController extends \BaseController
$table->addColumn('transaction_reference', function($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; }); $table->addColumn('transaction_reference', function($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; });
if (!$clientPublicId) { if (!$clientPublicId) {
$table->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); $table->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
} }
return $table->addColumn('invoice_number', function($model) { return $model->invoice_public_id ? link_to('invoices/' . $model->invoice_public_id . '/edit', $model->invoice_number) : ''; }) return $table->addColumn('invoice_number', function($model) { return $model->invoice_public_id ? link_to('invoices/' . $model->invoice_public_id . '/edit', $model->invoice_number) : ''; })
@ -70,21 +72,16 @@ class PaymentController extends \BaseController
public function create($clientPublicId = 0) public function create($clientPublicId = 0)
{ {
$client = null;
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
}
$data = array( $data = array(
'client' => $client, 'clientPublicId' => $clientPublicId,
'invoice' => null, 'invoice' => null,
'invoices' => Invoice::with('client')->scope()->orderBy('invoice_number')->get(), 'invoices' => Invoice::scope()->with('client')->orderBy('invoice_number')->get(),
'payment' => null, 'payment' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'payments', 'url' => 'payments',
'title' => '- New Payment', 'title' => '- New Payment',
'currencies' => Currency::orderBy('name')->get(), 'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->with('contacts')->orderBy('name')->get());
return View::make('payments.edit', $data); return View::make('payments.edit', $data);
} }
@ -95,13 +92,13 @@ class PaymentController extends \BaseController
$data = array( $data = array(
'client' => null, 'client' => null,
'invoice' => null, 'invoice' => null,
'invoices' => Invoice::scope()->orderBy('invoice_number')->get(array('public_id','invoice_number')), 'invoices' => Invoice::scope()->with('client')->orderBy('invoice_number')->get(array('public_id','invoice_number')),
'payment' => $payment, 'payment' => $payment,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'payments/' . $publicId, 'url' => 'payments/' . $publicId,
'title' => '- Edit Payment', 'title' => '- Edit Payment',
'currencies' => Currency::orderBy('name')->get(), 'currencies' => Currency::orderBy('name')->get(),
'clients' => Client::scope()->orderBy('name')->get()); 'clients' => Client::scope()->with('contacts')->orderBy('name')->get());
return View::make('payments.edit', $data); return View::make('payments.edit', $data);
} }

View File

@ -165,7 +165,7 @@ class ConfideSetupUsersTable extends Migration {
$t->boolean('confirmed')->default(false); $t->boolean('confirmed')->default(false);
$t->integer('theme_id'); $t->integer('theme_id');
$t->boolean('notify_sent')->default(false); $t->boolean('notify_sent')->default(true);
$t->boolean('notify_viewed')->default(false); $t->boolean('notify_viewed')->default(false);
$t->boolean('notify_paid')->default(true); $t->boolean('notify_paid')->default(true);
@ -212,7 +212,7 @@ class ConfideSetupUsersTable extends Migration {
$t->string('postal_code'); $t->string('postal_code');
$t->unsignedInteger('country_id')->nullable(); $t->unsignedInteger('country_id')->nullable();
$t->string('work_phone'); $t->string('work_phone');
$t->text('notes'); $t->text('private_notes');
$t->decimal('balance', 13, 4); $t->decimal('balance', 13, 4);
$t->decimal('paid_to_date', 13, 4); $t->decimal('paid_to_date', 13, 4);
$t->timestamp('last_login')->nullable(); $t->timestamp('last_login')->nullable();
@ -285,6 +285,7 @@ class ConfideSetupUsersTable extends Migration {
$t->date('invoice_date')->nullable(); $t->date('invoice_date')->nullable();
$t->date('due_date')->nullable(); $t->date('due_date')->nullable();
$t->text('terms'); $t->text('terms');
$t->text('public_notes');
$t->boolean('is_deleted'); $t->boolean('is_deleted');
$t->boolean('is_recurring'); $t->boolean('is_recurring');
$t->unsignedInteger('frequency_id'); $t->unsignedInteger('frequency_id');

View File

@ -246,4 +246,20 @@ class Utils
return ucwords(str_replace('_', ' ', $entityType)); return ucwords(str_replace('_', ' ', $entityType));
} }
public static function getClientDisplayName($model)
{
if ($model->client_name)
{
return $model->client_name;
}
else if ($model->first_name || $model->last_name)
{
return $model->first_name . ' ' . $model->last_name;
}
else
{
return $model->email;
}
}
} }

View File

@ -5,16 +5,17 @@ define("ACTIVITY_TYPE_CREATE_CLIENT", 1);
define("ACTIVITY_TYPE_ARCHIVE_CLIENT", 2); define("ACTIVITY_TYPE_ARCHIVE_CLIENT", 2);
define("ACTIVITY_TYPE_DELETE_CLIENT", 3); define("ACTIVITY_TYPE_DELETE_CLIENT", 3);
define("ACTIVITY_TYPE_CREATE_INVOICE", 4); define("ACTIVITY_TYPE_CREATE_INVOICE", 4);
define("ACTIVITY_TYPE_EMAIL_INVOICE", 5); define("ACTIVITY_TYPE_UPDATE_INVOICE", 5);
define("ACTIVITY_TYPE_VIEW_INVOICE", 6); define("ACTIVITY_TYPE_EMAIL_INVOICE", 6);
define("ACTIVITY_TYPE_ARCHIVE_INVOICE", 7); define("ACTIVITY_TYPE_VIEW_INVOICE", 7);
define("ACTIVITY_TYPE_DELETE_INVOICE", 8); define("ACTIVITY_TYPE_ARCHIVE_INVOICE", 8);
define("ACTIVITY_TYPE_CREATE_PAYMENT", 9); define("ACTIVITY_TYPE_DELETE_INVOICE", 9);
define("ACTIVITY_TYPE_ARCHIVE_PAYMENT", 10); define("ACTIVITY_TYPE_CREATE_PAYMENT", 10);
define("ACTIVITY_TYPE_DELETE_PAYMENT", 11); define("ACTIVITY_TYPE_ARCHIVE_PAYMENT", 11);
define("ACTIVITY_TYPE_CREATE_CREDIT", 12); define("ACTIVITY_TYPE_DELETE_PAYMENT", 12);
define("ACTIVITY_TYPE_ARCHIVE_CREDIT", 13); define("ACTIVITY_TYPE_CREATE_CREDIT", 13);
define("ACTIVITY_TYPE_DELETE_CREDIT", 14); define("ACTIVITY_TYPE_ARCHIVE_CREDIT", 14);
define("ACTIVITY_TYPE_DELETE_CREDIT", 15);
class Activity extends Eloquent class Activity extends Eloquent
@ -55,7 +56,6 @@ class Activity extends Eloquent
$activity->client_id = $client->id; $activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT;
$activity->message = Auth::user()->getFullName() . ' created client ' . link_to('clients/'.$client->public_id, $client->name); $activity->message = Auth::user()->getFullName() . ' created client ' . link_to('clients/'.$client->public_id, $client->name);
$activity->save(); $activity->save();
} }
@ -65,6 +65,7 @@ class Activity extends Eloquent
$activity->client_id = $client->id; $activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT;
$activity->message = Auth::user()->getFullName() . ' archived client ' . $client->name; $activity->message = Auth::user()->getFullName() . ' archived client ' . $client->name;
$activity->balance = $client->balance;
$activity->save(); $activity->save();
} }
@ -72,9 +73,12 @@ class Activity extends Eloquent
{ {
$userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>'; $userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>';
if ($invoice->is_recurring) { if ($invoice->is_recurring)
{
$message = $userName . ' created ' . link_to('invoices/'.$invoice->public_id, 'recuring invoice'); $message = $userName . ' created ' . link_to('invoices/'.$invoice->public_id, 'recuring invoice');
} else { }
else
{
$message = $userName . ' created invoice ' . link_to('invoices/'.$invoice->public_id, $invoice->invoice_number); $message = $userName . ' created invoice ' . link_to('invoices/'.$invoice->public_id, $invoice->invoice_number);
} }
@ -84,6 +88,7 @@ class Activity extends Eloquent
$activity->currency_id = $invoice->currency_id; $activity->currency_id = $invoice->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE;
$activity->message = $message; $activity->message = $message;
$activity->balance = $invoice->client->balance;
$activity->save(); $activity->save();
} }
@ -94,11 +99,22 @@ class Activity extends Eloquent
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_INVOICE;
$activity->message = Auth::user()->getFullName() . ' archived invoice ' . $invoice->invoice_number; $activity->message = Auth::user()->getFullName() . ' archived invoice ' . $invoice->invoice_number;
$activity->balance = $invoice->client->balance;
$activity->save(); $activity->save();
} }
public static function emailInvoice($invitation) public static function emailInvoice($invitation)
{ {
$adjustment = 0;
if (!$invitation->invoice->isSent())
{
$adjustment = $invitation->invoice->amount;
$client = $invitation->invoice->client;
$client->balance = $client->balance + $adjustment;
$client->save();
}
$userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>'; $userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>';
$activity = Activity::getBlank($invitation); $activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id; $activity->client_id = $invitation->invoice->client_id;
@ -106,11 +122,45 @@ class Activity extends Eloquent
$activity->contact_id = $invitation->contact_id; $activity->contact_id = $invitation->contact_id;
$activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE;
$activity->message = $userName . ' emailed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number) . ' to ' . $invitation->contact->getFullName(); $activity->message = $userName . ' emailed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number) . ' to ' . $invitation->contact->getFullName();
$activity->balance = $invitation->invoice->client->balance;
$activity->adjustment = $adjustment;
$activity->save();
}
public static function updateInvoice($invoice)
{
if ($invoice->invoice_status_id < INVOICE_STATUS_SENT)
{
return;
}
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
if ($diff == 0)
{
return;
}
$client = $invoice->client;
$client->balance = $client->balance + $diff;
$client->save();
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Auth::user()->getFullName() . ' updated invoice ' . link_to('invoices/'.$invoice->public_id, $invoice->invoice_number);
$activity->balance = $client->balance;
$activity->adjustment = $diff;
$activity->save(); $activity->save();
} }
public static function createPayment($payment) public static function createPayment($payment)
{ {
$client = $payment->client;
$client->balance = $client->balance - $payment->amount;
$client->save();
if (Auth::check()) if (Auth::check())
{ {
$activity = Activity::getBlank(); $activity = Activity::getBlank();
@ -130,23 +180,28 @@ class Activity extends Eloquent
$activity->client_id = $payment->client_id; $activity->client_id = $payment->client_id;
$activity->currency_id = $payment->currency_id; $activity->currency_id = $payment->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT;
$activity->balance = $client->balance;
$activity->adjustment = $payment->amount * -1;
$activity->save(); $activity->save();
} }
public static function createCredit($credit) public static function createCredit($credit)
{ {
$client = $credit->client;
$client->balance = $client->balance - $credit->amount;
$client->save();
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->message = Auth::user()->getFullName() . ' created credit'; $activity->message = Auth::user()->getFullName() . ' created credit';
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->currency_id = $credit->currency_id; $activity->currency_id = $credit->currency_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
$activity->balance = $client->balance;
$activity->adjustment = $credit->amount * -1;
$activity->save(); $activity->save();
} }
public static function archivePayment($payment) public static function archivePayment($payment)
{ {
$activity = Activity::getBlank(); $activity = Activity::getBlank();
@ -154,6 +209,7 @@ class Activity extends Eloquent
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT;
$activity->message = Auth::user()->getFullName() . ' archived payment'; $activity->message = Auth::user()->getFullName() . ' archived payment';
$activity->balance = $payment->client->balance;
$activity->save(); $activity->save();
} }
@ -168,6 +224,7 @@ class Activity extends Eloquent
$activity->invoice_id = $invitation->invoice_id; $activity->invoice_id = $invitation->invoice_id;
$activity->activity_type_id = ACTIVITY_TYPE_VIEW_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_VIEW_INVOICE;
$activity->message = $invitation->contact->getFullName() . ' viewed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number); $activity->message = $invitation->contact->getFullName() . ' viewed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number);
$activity->balance = $invitation->invoice->client->balance;
$activity->save(); $activity->save();
} }
} }

View File

@ -2,7 +2,7 @@
class Client extends EntityModel class Client extends EntityModel
{ {
protected $hidden = array('id', 'account_id', 'created_at', 'updated_at', 'deleted_at', 'notes', 'last_login'); protected $hidden = array('id', 'account_id', 'created_at', 'updated_at', 'deleted_at', 'private_notes', 'last_login');
public static $fieldName = 'Client - Name'; public static $fieldName = 'Client - Name';
public static $fieldPhone = 'Client - Phone'; public static $fieldPhone = 'Client - Phone';

View File

@ -35,6 +35,11 @@ class Contact extends EntityModel
public function getFullName() public function getFullName()
{ {
if (!$this->first_name && !$this->last_name)
{
return $this->email;
}
$fullName = $this->first_name . ' ' . $this->last_name; $fullName = $this->first_name . ' ' . $this->last_name;
if ($fullName == ' ') if ($fullName == ' ')

View File

@ -19,8 +19,3 @@ class Invitation extends EntityModel
return $this->belongsTo('User'); return $this->belongsTo('User');
} }
} }
Invitation::created(function($invitation)
{
Activity::emailInvoice($invitation);
});

View File

@ -96,3 +96,8 @@ Invoice::created(function($invoice)
{ {
Activity::createInvoice($invoice); Activity::createInvoice($invoice);
}); });
Invoice::updating(function($invoice)
{
Activity::updateInvoice($invoice);
});

View File

@ -5,6 +5,7 @@ use Contact;
use Invitation; use Invitation;
use URL; use URL;
use Auth; use Auth;
use Activity;
class ContactMailer extends Mailer { class ContactMailer extends Mailer {
@ -20,6 +21,8 @@ class ContactMailer extends Mailer {
$invitation->save(); $invitation->save();
$this->sendTo($invitation->contact->email, $subject, $view, $data); $this->sendTo($invitation->contact->email, $subject, $view, $data);
Activity::emailInvoice($invitation);
} }
if (!$invoice->isSent()) if (!$invoice->isSent())

View File

@ -9,13 +9,14 @@ class AccountRepository
{ {
$clients = \DB::table('clients') $clients = \DB::table('clients')
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->whereRaw("clients.name <> ''")
->select(\DB::raw("'Clients' as type, clients.public_id, clients.name, '' as token")); ->select(\DB::raw("'Clients' as type, clients.public_id, clients.name, '' as token"));
$contacts = \DB::table('clients') $contacts = \DB::table('clients')
->join('contacts', 'contacts.client_id', '=', 'clients.id') ->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->whereRaw("CONCAT(contacts.first_name, contacts.last_name) <> ''") ->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''")
->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ': ', clients.name) as name, '' as token")); ->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token"));
$invoices = \DB::table('clients') $invoices = \DB::table('clients')
->join('invoices', 'invoices.client_id', '=', 'clients.id') ->join('invoices', 'invoices.client_id', '=', 'clients.id')

View File

@ -27,7 +27,7 @@ class ClientRepository
$client->state = trim($data['state']); $client->state = trim($data['state']);
$client->postal_code = trim($data['postal_code']); $client->postal_code = trim($data['postal_code']);
$client->country_id = $data['country_id'] ? $data['country_id'] : null; $client->country_id = $data['country_id'] ? $data['country_id'] : null;
$client->notes = trim($data['notes']); $client->private_notes = trim($data['private_notes']);
$client->client_size_id = $data['client_size_id'] ? $data['client_size_id'] : null; $client->client_size_id = $data['client_size_id'] ? $data['client_size_id'] : null;
$client->client_industry_id = $data['client_industry_id'] ? $data['client_industry_id'] : null; $client->client_industry_id = $data['client_industry_id'] ? $data['client_industry_id'] : null;
$client->currency_id = $data['currency_id'] ? $data['currency_id'] : null; $client->currency_id = $data['currency_id'] ? $data['currency_id'] : null;

View File

@ -13,11 +13,13 @@ class InvoiceRepository
$query = \DB::table('invoices') $query = \DB::table('invoices')
->join('clients', 'clients.id', '=','invoices.client_id') ->join('clients', 'clients.id', '=','invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id') ->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId) ->where('invoices.account_id', '=', $accountId)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('clients.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', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'invoices.currency_id'); ->where('contacts.is_primary', '=', true)
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'invoices.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
if ($clientPublicId) if ($clientPublicId)
{ {
@ -42,10 +44,12 @@ class InvoiceRepository
$query = \DB::table('invoices') $query = \DB::table('invoices')
->join('clients', 'clients.id', '=','invoices.client_id') ->join('clients', 'clients.id', '=','invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id') ->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId) ->where('invoices.account_id', '=', $accountId)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true) ->where('invoices.is_recurring', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'invoices.currency_id'); ->where('contacts.is_primary', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'invoices.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
if ($clientPublicId) if ($clientPublicId)
{ {
@ -87,6 +91,7 @@ class InvoiceRepository
$invoice->start_date = Utils::toSqlDate($data['start_date']); $invoice->start_date = Utils::toSqlDate($data['start_date']);
$invoice->end_date = Utils::toSqlDate($data['end_date']); $invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->terms = trim($data['terms']); $invoice->terms = trim($data['terms']);
$invoice->public_notes = trim($data['public_notes']);
$invoice->po_number = trim($data['po_number']); $invoice->po_number = trim($data['po_number']);
$invoice->currency_id = $data['currency_id']; $invoice->currency_id = $data['currency_id'];
@ -108,6 +113,7 @@ class InvoiceRepository
} }
$invoice->amount = $total; $invoice->amount = $total;
$invoice->balance = $total;
$invoice->save(); $invoice->save();
foreach ($data['invoice_items'] as $item) foreach ($data['invoice_items'] as $item)

View File

@ -2,7 +2,7 @@
@section('onReady') @section('onReady')
$('input#name').focus(); $('input#first_name').focus();
@stop @stop
@section('content') @section('content')
@ -10,8 +10,7 @@
<!--<h3>{{ $title }} Client</h3>--> <!--<h3>{{ $title }} Client</h3>-->
{{ Former::open($url)->addClass('col-md-10 col-md-offset-1 main_form')->method($method)->rules(array( {{ Former::open($url)->addClass('col-md-10 col-md-offset-1 main_form')->method($method)->rules(array(
'name' => 'required', 'email' => 'email|required'
'email' => 'email'
)); }} )); }}
@if ($client) @if ($client)
@ -20,25 +19,6 @@
<div class="row"> <div class="row">
<div class="col-md-6">
{{ Former::legend('Organization') }}
{{ Former::text('name') }}
{{ Former::text('website') }}
{{ Former::text('work_phone')->label('Phone') }}
{{ Former::legend('Address') }}
{{ Former::text('address1')->label('Street') }}
{{ Former::text('address2')->label('Apt/Floor') }}
{{ Former::text('city') }}
{{ Former::text('state') }}
{{ Former::text('postal_code') }}
{{ Former::select('country_id')->addOption('','')->label('Country')
->fromQuery($countries, 'name', 'id')->select($client ? $client->country_id : '') }}
</div>
<div class="col-md-6"> <div class="col-md-6">
{{ Former::legend('Contacts') }} {{ Former::legend('Contacts') }}
@ -70,7 +50,28 @@
->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }} ->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }}
{{ Former::select('client_industry_id')->addOption('','')->label('Industry') {{ Former::select('client_industry_id')->addOption('','')->label('Industry')
->fromQuery($clientIndustries, 'name', 'id')->select($client ? $client->client_industry_id : '') }} ->fromQuery($clientIndustries, 'name', 'id')->select($client ? $client->client_industry_id : '') }}
{{ Former::textarea('notes') }} {{ Former::textarea('private_notes') }}
</div>
<div class="col-md-6">
{{ Former::legend('Organization') }}
{{ Former::text('name') }}
{{ Former::text('website') }}
{{ Former::text('work_phone')->label('Phone') }}
{{ Former::legend('Address') }}
{{ Former::text('address1')->label('Street') }}
{{ Former::text('address2')->label('Apt/Floor') }}
{{ Former::text('city') }}
{{ Former::text('state') }}
{{ Former::text('postal_code') }}
{{ Former::select('country_id')->addOption('','')->label('Country')
->fromQuery($countries, 'name', 'id')->select($client ? $client->country_id : '') }}
</div> </div>
</div> </div>

View File

@ -62,8 +62,8 @@
<div class="col-md-6"> <div class="col-md-6">
<h3>Standing</h3> <h3>Standing</h3>
<h3>$0.00 <small>Paid to Date USD</small></h3> <h3>{{ Utils::formatMoney($client->paid_to_date, $client->currency_id); }} <small>Paid to Date USD</small></h3>
<h3>$0.00 <small>Balance USD</small></h3> <h3>{{ Utils::formatMoney($client->balance, $client->currency_id); }} <small>Balance USD</small></h3>
</div> </div>
</div> </div>
@ -81,7 +81,7 @@
<div class="tab-pane active" id="activity"> <div class="tab-pane active" id="activity">
{{ Datatable::table() {{ Datatable::table()
->addColumn('Date', 'Message', 'Balance') ->addColumn('Date', 'Message', 'Balance', 'Adjustment')
->setUrl(url('api/activities/'. $client->public_id)) ->setUrl(url('api/activities/'. $client->public_id))
->setOptions('sPaginationType', 'bootstrap') ->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false) ->setOptions('bFilter', false)

View File

@ -15,6 +15,8 @@
@if ($credit) @if ($credit)
{{ Former::populate($credit) }} {{ Former::populate($credit) }}
@else
{{ Former::populateField('credit_date', date('Y-m-d')) }}
@endif @endif
@ -27,11 +29,11 @@
{{ Former::legend('New Credit') }} {{ Former::legend('New Credit') }}
@endif @endif
{{ Former::select('client')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('credit_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('credit_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -48,10 +50,20 @@
<script type="text/javascript"> <script type="text/javascript">
var clients = {{ $clients }};
$(function() { $(function() {
var $input = $('select#client'); var $input = $('select#client');
for (var i=0; i<clients.length; i++) {
var client = clients[i];
$input.append(new Option(getClientDisplayName(client), client.public_id));
}
@if ($clientPublicId)
$('select#client').val({{ $clientPublicId }});
@endif
$input.combobox(); $input.combobox();
$('#currency_id').combobox(); $('#currency_id').combobox();
$('#credit_date').datepicker({ $('#credit_date').datepicker({

View File

@ -37,6 +37,7 @@
jQuery('.{{ $class }}').dataTable({ jQuery('.{{ $class }}').dataTable({
// Disable sorting on the first column // Disable sorting on the first column
"aaSorting": [], "aaSorting": [],
"bAutoWidth": false,
@if (isset($hasCheckboxes) && $hasCheckboxes) @if (isset($hasCheckboxes) && $hasCheckboxes)
"aoColumnDefs" : [ { "aoColumnDefs" : [ {
'bSortable' : false, 'bSortable' : false,

View File

@ -13,12 +13,13 @@
{{ Former::open($url)->method($method)->addClass('main_form')->rules(array( {{ Former::open($url)->method($method)->addClass('main_form')->rules(array(
'client' => 'required', 'client' => 'required',
'email' => 'required',
'product_key' => 'max:14', 'product_key' => 'max:14',
)); }} )); }}
<div class="row" style="min-height:195px" onkeypress="formEnterClick(event)"> <div class="row" style="min-height:195px" onkeypress="formEnterClick(event)">
<div class="col-md-5" id="col_1"> <div class="col-md-5" id="col_1">
{{ Former::select('client')->addOption('', '')->fromQuery($clients, 'name', 'public_id')->data_bind("dropdown: client") {{ Former::select('client')->addOption('', '')->data_bind("dropdown: client")
->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">
@ -38,6 +39,7 @@
</div> </div>
</div> </div>
{{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'") }} {{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'") }}
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") }}
</div> </div>
<div class="col-md-4" id="col_2"> <div class="col-md-4" id="col_2">
@ -51,13 +53,14 @@
{{ Former::text('start_date')->data_bind("value: start_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('start_date')->data_bind("value: start_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{ Former::text('end_date')->data_bind("value: end_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('end_date')->data_bind("value: end_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
</div> </div>
@if ($invoice && $invoice->recurring_invoice_id)
<div class="pull-right" style="padding-top: 6px">
Created by a {{ link_to('/invoices/'.$invoice->recurring_invoice_id, 'recurring invoice') }}
</div>
@else
<div data-bind="visible: invoice_status_id() < CONSTS.INVOICE_STATUS_SENT"> <div data-bind="visible: invoice_status_id() < CONSTS.INVOICE_STATUS_SENT">
{{ Former::checkbox('recurring')->text('Enable | <a href="#" rel="tooltip" data-toggle="tooltip" title="Recurring invoices are automatically sent. Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well. ie, :MONTH-1.">Learn more</a>')->data_bind("checked: is_recurring") {{ Former::checkbox('recurring')->text('Enable | <a href="#" rel="tooltip" data-toggle="tooltip" title="Recurring invoices are automatically sent. Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well. ie, :MONTH-1.">Learn more</a>')->data_bind("checked: is_recurring")
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::timestampToDateString($invoice->last_sent_date) : '') }} ->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') }}
</div>
@if ($invoice && $invoice->recurring_invoice_id)
<div style="padding-top: 6px">
Created by a {{ link_to('/invoices/'.$invoice->recurring_invoice_id, 'recurring invoice') }}
</div> </div>
@endif @endif
@ -131,16 +134,16 @@
<td colspan="2">Subtotal</td> <td colspan="2">Subtotal</td>
<td style="text-align: right"><span data-bind="text: subtotal"/></td> <td style="text-align: right"><span data-bind="text: subtotal"/></td>
</tr> </tr>
<tr>
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/>
<td colspan="2">Paid to Date</td>
<td style="text-align: right"></td>
</tr>
<tr data-bind="visible: discount() > 0"> <tr data-bind="visible: discount() > 0">
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/> <td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/>
<td colspan="2">Discount</td> <td colspan="2">Discount</td>
<td style="text-align: right"><span data-bind="text: discounted"/></td> <td style="text-align: right"><span data-bind="text: discounted"/></td>
</tr> </tr>
<tr>
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/>
<td colspan="2">Paid to Date</td>
<td style="text-align: right"></td>
</tr>
<tr> <tr>
<td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/> <td class="hide-border" data-bind="attr: {colspan: tax_rates().length > 1 ? 4 : 3}"/>
<td colspan="2"><b>Balance Due</b></td> <td colspan="2"><b>Balance Due</b></td>
@ -200,23 +203,6 @@
<div style="background-color: #F6F6F6" class="row" data-bind="with: client" onkeypress="clientModalEnterClick(event)"> <div style="background-color: #F6F6F6" class="row" data-bind="with: client" onkeypress="clientModalEnterClick(event)">
<div class="col-md-6" style="margin-left:0px;margin-right:0px" > <div class="col-md-6" style="margin-left:0px;margin-right:0px" >
{{ Former::legend('Organization') }}
{{ 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::legend('Address') }}
{{ Former::text('address1')->label('Street')->data_bind("value: address1, valueUpdate: 'afterkeydown'") }}
{{ Former::text('address2')->label('Apt/Floor')->data_bind("value: address2, valueUpdate: 'afterkeydown'") }}
{{ Former::text('city')->data_bind("value: city, valueUpdate: 'afterkeydown'") }}
{{ Former::text('state')->data_bind("value: state, valueUpdate: 'afterkeydown'") }}
{{ Former::text('postal_code')->data_bind("value: postal_code, valueUpdate: 'afterkeydown'") }}
{{ Former::select('country_id')->addOption('','')->label('Country')->addGroupClass('country_select')
->fromQuery($countries, 'name', 'id')->data_bind("dropdown: country_id") }}
</div>
<div class="col-md-6" style="margin-left:0px;margin-right:0px" >
{{ Former::legend('Contacts') }} {{ Former::legend('Contacts') }}
<div data-bind='template: { foreach: contacts, <div data-bind='template: { foreach: contacts,
@ -247,14 +233,33 @@
->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }} ->fromQuery($clientSizes, 'name', 'id')->select($client ? $client->client_size_id : '') }}
{{ Former::select('client_industry_id')->addOption('','')->label('Industry')->data_bind('value: client_industry_id') {{ Former::select('client_industry_id')->addOption('','')->label('Industry')->data_bind('value: client_industry_id')
->fromQuery($clientIndustries, 'name', 'id')->select($client ? $client->client_industry_id : '') }} ->fromQuery($clientIndustries, 'name', 'id')->select($client ? $client->client_industry_id : '') }}
{{ Former::textarea('notes') }} {{ Former::textarea('private_notes')->data_bind('value: private_notes') }}
</div>
<div class="col-md-6" style="margin-left:0px;margin-right:0px" >
{{ Former::legend('Organization') }}
{{ 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::legend('Address') }}
{{ Former::text('address1')->label('Street')->data_bind("value: address1, valueUpdate: 'afterkeydown'") }}
{{ Former::text('address2')->label('Apt/Floor')->data_bind("value: address2, valueUpdate: 'afterkeydown'") }}
{{ Former::text('city')->data_bind("value: city, valueUpdate: 'afterkeydown'") }}
{{ Former::text('state')->data_bind("value: state, valueUpdate: 'afterkeydown'") }}
{{ Former::text('postal_code')->data_bind("value: postal_code, valueUpdate: 'afterkeydown'") }}
{{ Former::select('country_id')->addOption('','')->label('Country')->addGroupClass('country_select')
->fromQuery($countries, 'name', 'id')->data_bind("dropdown: country_id") }}
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer" style="margin-top: 0px"> <div class="modal-footer" style="margin-top: 0px">
<span class="error-block" id="nameError" style="display:none;float:left">Please provide a value for the name field.</span><span>&nbsp;</span> <span class="error-block" id="emailError" style="display:none;float:left">Please provide a valid email address.</span><span>&nbsp;</span>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" data-bind="click: clientFormComplete">Done</button> <button type="button" class="btn btn-primary" data-bind="click: clientFormComplete">Done</button>
</div> </div>
@ -316,7 +321,7 @@
$(function() { $(function() {
$('form').change(refreshPDF); //$('form').change(refreshPDF);
$('#country_id').combobox(); $('#country_id').combobox();
$('[rel=tooltip]').tooltip(); $('[rel=tooltip]').tooltip();
@ -344,8 +349,12 @@
} else { } else {
model.client.public_id(0); // TODO_FIX model.client.public_id(0); // TODO_FIX
} }
refreshPDF();
}).trigger('change'); }).trigger('change');
$('#terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discout, #currency_id').change(function() {
refreshPDF();
});
$('.country_select input.form-control').on('change', function(e) { $('.country_select input.form-control').on('change', function(e) {
var countryId = parseInt($('input[name=country_id]').val(), 10); var countryId = parseInt($('input[name=country_id]').val(), 10);
@ -360,7 +369,7 @@
@endif @endif
$('#clientModal').on('shown.bs.modal', function () { $('#clientModal').on('shown.bs.modal', function () {
$('#name').focus(); $('#first_name').focus();
}).on('hidden.bs.modal', function () { }).on('hidden.bs.modal', function () {
if (model.clientBackup) { if (model.clientBackup) {
model.loadClient(model.clientBackup); model.loadClient(model.clientBackup);
@ -521,6 +530,7 @@
self.frequency_id = ko.observable(''); self.frequency_id = ko.observable('');
self.currency_id = ko.observable({{ Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY) }}); self.currency_id = ko.observable({{ Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY) }});
self.terms = ko.observable(''); self.terms = ko.observable('');
self.public_notes = 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.invoice_number = ko.observable('');
@ -548,12 +558,26 @@
self.wrapped_terms = ko.computed({ self.wrapped_terms = ko.computed({
read: function() { read: function() {
$('#terms').height(this.terms().split('\n').length * 36);
return this.terms(); return this.terms();
}, },
write: function(value) { write: function(value) {
value = wordWrapText(value, 250); value = wordWrapText(value, 340);
self.terms(value); self.terms(value);
$('#terms').height(value.split('\n').length * 22); $('#terms').height(value.split('\n').length * 36);
},
owner: this
});
self.wrapped_notes = ko.computed({
read: function() {
$('#public_notes').height(this.public_notes().split('\n').length * 36);
return this.public_notes();
},
write: function(value) {
value = wordWrapText(value, 340);
self.public_notes(value);
$('#public_notes').height(value.split('\n').length * 36);
}, },
owner: this owner: this
}); });
@ -584,14 +608,17 @@
$('#clientModal #country_id').val(''); $('#clientModal #country_id').val('');
} }
$('#nameError').css( "display", "none" ); $('#emailError').css( "display", "none" );
$('#clientModal').modal('show'); $('#clientModal').modal('show');
} }
self.clientFormComplete = function() { self.clientFormComplete = function() {
var email = $('#email').val();
var firstName = $('#first_name').val();
var lastName = $('#last_name').val();
var name = $('#name').val(); var name = $('#name').val();
if (!name) { if (!email || !isValidEmailAddress(email)) {
if (!name) $('#nameError').css( "display", "inline" ); $('#emailError').css( "display", "inline" );
return; return;
} }
@ -599,10 +626,19 @@
if (self.client.public_id() == 0) { if (self.client.public_id() == 0) {
self.client.public_id(-1); self.client.public_id(-1);
} }
if (name) {
//
} else if (firstName || lastName) {
name = firstName + ' ' + lastName;
} else {
name = email;
}
$('.client_select input.form-control').val(name); $('.client_select input.form-control').val(name);
$('.client_select .combobox-container').addClass('combobox-selected'); $('.client_select .combobox-container').addClass('combobox-selected');
$('#nameError').css( "display", "none" ); $('#emailError').css( "display", "none" );
$('.client_select input.form-control').focus(); $('.client_select input.form-control').focus();
refreshPDF(); refreshPDF();
@ -673,7 +709,7 @@
self.public_id = ko.observable(0); self.public_id = ko.observable(0);
self.name = ko.observable(''); self.name = ko.observable('');
self.work_phone = ko.observable(''); self.work_phone = ko.observable('');
self.notes = ko.observable(''); self.private_notes = ko.observable('');
self.address1 = ko.observable(''); self.address1 = ko.observable('');
self.address2 = ko.observable(''); self.address2 = ko.observable('');
self.city = ko.observable(''); self.city = ko.observable('');
@ -711,6 +747,16 @@
self.contacts.remove(this); self.contacts.remove(this);
} }
self.placeholderName = ko.computed(function() {
if (self.contacts().length == 0) return;
var contact = self.contacts()[0];
if (contact.first_name() || contact.last_name()) {
return contact.first_name() + ' ' + contact.last_name();
} else {
return '';
}
});
if (data) { if (data) {
ko.mapping.fromJS(data, {}, this); ko.mapping.fromJS(data, {}, this);
} else { } else {
@ -905,6 +951,7 @@
var products = {{ $products }}; var products = {{ $products }};
var clients = {{ $clients }}; var clients = {{ $clients }};
var clientMap = {}; var clientMap = {};
var $clientSelect = $('select#client');
for (var i=0; i<clients.length; i++) { for (var i=0; i<clients.length; i++) {
var client = clients[i]; var client = clients[i];
@ -913,9 +960,9 @@
contact.send_invoice = contact.is_primary; contact.send_invoice = contact.is_primary;
} }
clientMap[client.public_id] = client; clientMap[client.public_id] = client;
$clientSelect.append(new Option(getClientDisplayName(client), client.public_id));
} }
window.model = new InvoiceModel(); window.model = new InvoiceModel();
@foreach ($taxRates as $taxRate) @foreach ($taxRates as $taxRate)
model.addTaxRate({{ $taxRate }}); model.addTaxRate({{ $taxRate }});
@ -932,8 +979,9 @@
contact.send_invoice = invitationContactIds.indexOf(contact.public_id) >= 0; contact.send_invoice = invitationContactIds.indexOf(contact.public_id) >= 0;
} }
@else @else
model.invoice_date('{{ date('Y-m-d') }}');
model.invoice_number('{{ $invoiceNumber }}'); model.invoice_number('{{ $invoiceNumber }}');
model.terms(wordWrapText('{{ $account->invoice_terms }}', 250)); model.terms(wordWrapText('{{ $account->invoice_terms }}', 340));
@endif @endif
model.addItem(); model.addItem();
ko.applyBindings(model); ko.applyBindings(model);

View File

@ -10,11 +10,14 @@
{{ Former::open($url)->addClass('col-md-10 col-md-offset-1 main_form')->method($method)->rules(array( {{ Former::open($url)->addClass('col-md-10 col-md-offset-1 main_form')->method($method)->rules(array(
'client' => 'required', 'client' => 'required',
'invoice' => 'required',
'amount' => 'required', 'amount' => 'required',
)); }} )); }}
@if ($payment) @if ($payment)
{{-- Former::populate($payment) --}} {{-- Former::populate($payment) --}}
@else
{{ Former::populateField('payment_date', date('Y-m-d')) }}
@endif @endif
@ -29,10 +32,10 @@
{{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }} {{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('payment_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }} {{ Former::text('payment_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id')->select(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY)) }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -53,7 +56,7 @@
var clients = {{ $clients }}; var clients = {{ $clients }};
var clientMap = {}; var clientMap = {};
var invoiceMap = {}; var invoiceMap = {};
var invoicesForClientMap = {};
/* /*
function compareClient(a,b) { function compareClient(a,b) {
@ -73,23 +76,29 @@
var invoice = invoices[i]; var invoice = invoices[i];
var client = invoice.client; var client = invoice.client;
if (!invoiceMap.hasOwnProperty(client.public_id)) { if (!invoicesForClientMap.hasOwnProperty(client.public_id)) {
invoiceMap[client.public_id] = []; invoicesForClientMap[client.public_id] = [];
} }
invoiceMap[client.public_id].push(invoice); invoicesForClientMap[client.public_id].push(invoice);
clientMap[invoice.public_id] = invoice.client; invoiceMap[invoice.public_id] = invoice;
//clientMap[invoice.public_id] = invoice.client;
}
for (var i=0; i<clients.length; i++) {
var client = clients[i];
clientMap[client.public_id] = client;
} }
//clients.sort(compareClient); //clients.sort(compareClient);
$input.append(new Option('', '')); $input.append(new Option('', ''));
for (var i=0; i<clients.length; i++) { for (var i=0; i<clients.length; i++) {
var client = clients[i]; var client = clients[i];
$input.append(new Option(client.name, client.public_id)); $input.append(new Option(getClientDisplayName(client), client.public_id));
} }
@if ($client) @if ($clientPublicId)
$('select#client').val({{ $client->public_id }}); $('select#client').val({{ $clientPublicId }});
@endif @endif
$input.combobox(); $input.combobox();
@ -97,7 +106,8 @@
console.log('client change'); console.log('client change');
var clientId = $('input[name=client]').val(); var clientId = $('input[name=client]').val();
var invoiceId = $('input[name=invoice]').val(); var invoiceId = $('input[name=invoice]').val();
if (clientMap.hasOwnProperty(invoiceId) && clientMap[invoiceId].public_id == clientId) { var invoice = invoiceMap[invoiceId];
if (invoice && invoice.client.public_id == clientId) {
console.log('values the same:' + $('select#client').prop('selected')) console.log('values the same:' + $('select#client').prop('selected'))
e.preventDefault(); e.preventDefault();
return; return;
@ -106,10 +116,11 @@
$invoiceCombobox = $('select#invoice'); $invoiceCombobox = $('select#invoice');
$invoiceCombobox.find('option').remove().end().combobox('refresh'); $invoiceCombobox.find('option').remove().end().combobox('refresh');
$invoiceCombobox.append(new Option('', '')); $invoiceCombobox.append(new Option('', ''));
var list = clientId ? (invoiceMap.hasOwnProperty(clientId) ? invoiceMap[clientId] : []) : invoices; var list = clientId ? (invoicesForClientMap.hasOwnProperty(clientId) ? invoicesForClientMap[clientId] : []) : invoices;
for (var i=0; i<list.length; i++) { for (var i=0; i<list.length; i++) {
var invoice = list[i]; var invoice = list[i];
$invoiceCombobox.append(new Option(invoice.invoice_number + ' - ' + invoice.client.name, invoice.public_id)); var client = clientMap[invoice.client.public_id];
$invoiceCombobox.append(new Option(invoice.invoice_number + ' - ' + getClientDisplayName(client), invoice.public_id));
} }
$('select#invoice').combobox('refresh'); $('select#invoice').combobox('refresh');
}).trigger('change'); }).trigger('change');
@ -118,8 +129,9 @@
$clientCombobox = $('select#client'); $clientCombobox = $('select#client');
var invoiceId = $('input[name=invoice]').val(); var invoiceId = $('input[name=invoice]').val();
if (invoiceId) { if (invoiceId) {
var client = clientMap[invoiceId]; var invoice = invoiceMap[invoiceId];
setComboboxValue($('.client-select'), client.public_id, client.name); var client = clientMap[invoice.client.public_id];
setComboboxValue($('.client-select'), client.public_id, getClientDisplayName(client));
} }
}); });
$input.combobox(); $input.combobox();

View File

@ -9,6 +9,7 @@ function generatePDF(invoice) {
var headerLeft = 360; var headerLeft = 360;
var headerRight = 540; var headerRight = 540;
var rowHeight = 15; var rowHeight = 15;
var tableRowHeight = 20;
var footerLeft = 420; var footerLeft = 420;
var tableTop = 240; var tableTop = 240;
@ -20,16 +21,15 @@ function generatePDF(invoice) {
var lineTotalRight = 540; var lineTotalRight = 540;
var hasTaxes = true; var hasTaxes = false;
for (var i=0; i<invoice.invoice_items.length; i++) for (var i=0; i<invoice.invoice_items.length; i++)
{ {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
if (item.tax_rate > 0) { if (item.tax && item.tax.rate > 0) {
hasTaxes = true; hasTaxes = true;
break; break;
} }
} }
if (hasTaxes) if (hasTaxes)
{ {
descriptionLeft -= 20; descriptionLeft -= 20;
@ -66,7 +66,7 @@ function generatePDF(invoice) {
doc.setFontType("normal"); doc.setFontType("normal");
if (invoice.client) { if (invoice.client) {
var y = headerTop; var y = headerTop;
doc.text(marginLeft, y, invoice.client.name); doc.text(marginLeft, y, getClientDisplayName(invoice.client));
y += rowHeight; y += rowHeight;
doc.text(marginLeft, y, invoice.client.address1); doc.text(marginLeft, y, invoice.client.address1);
if (invoice.client.address2) { if (invoice.client.address2) {
@ -126,6 +126,7 @@ function generatePDF(invoice) {
var line = 1; var line = 1;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
doc.setDrawColor(220,220,220);
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
@ -158,7 +159,8 @@ function generatePDF(invoice) {
var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize()); var qtyX = qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize());
var taxX = taxRight - (doc.getStringUnitWidth(tax) * doc.internal.getFontSize()); var taxX = taxRight - (doc.getStringUnitWidth(tax) * doc.internal.getFontSize());
var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize()); var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize());
var x = tableTop + (line * rowHeight) + 6; var x = tableTop + (line * tableRowHeight) + 6;
if (i==0) x -= 4;
doc.text(tableLeft, x, productKey); doc.text(tableLeft, x, productKey);
doc.text(descriptionLeft, x, notes); doc.text(descriptionLeft, x, notes);
@ -171,14 +173,21 @@ function generatePDF(invoice) {
} }
line += doc.splitTextToSize(item.notes, 200).length; line += doc.splitTextToSize(item.notes, 200).length;
if (i < invoice.invoice_items.length - 2) {
doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, tableTop + (line * tableRowHeight) - 8);
}
} }
/* table footer */ /* table footer */
var x = tableTop + (line * rowHeight); doc.setDrawColor(200,200,200);
var x = tableTop + (line * tableRowHeight);
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); doc.text(tableLeft, x+16, invoice.terms);
doc.text(tableLeft, x+16 + (doc.splitTextToSize(invoice.terms, 340).length * rowHeight), invoice.public_notes);
x += 16; x += 16;
doc.text(footerLeft, x, 'Subtotal'); doc.text(footerLeft, x, 'Subtotal');
@ -205,7 +214,7 @@ function generatePDF(invoice) {
x += 16; x += 16;
doc.setFontType("bold"); doc.setFontType("bold");
doc.text(footerLeft, x, 'Total'); doc.text(footerLeft, x, 'Balance Due');
var total = formatMoney(total, currencyId); var total = formatMoney(total, currencyId);
var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize()); var totalX = headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
@ -652,12 +661,33 @@ function wordWrapText(value, width)
while (j++ < lines[i].length) { while (j++ < lines[i].length) {
if (lines[i].charAt(j) === " ") space = j; if (lines[i].charAt(j) === " ") space = j;
} }
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || ""); lines[i + 1] = lines[i].substring(space + 1) + ' ' + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space); lines[i] = lines[i].substring(0, space);
} }
return lines.slice(0, 6).join("\n"); var newValue = (lines.join("\n")).trim();
if (value == newValue) {
return newValue;
} else {
return wordWrapText(newValue, width);
} }
}
function getClientDisplayName(client)
{
var contact = client.contacts[0];
if (client.name) {
return client.name;
} else if (contact.first_name || contact.last_name) {
return contact.first_name + ' ' + contact.last_name;
} else {
return contact.email;
}
}
var CONSTS = {}; var CONSTS = {};
CONSTS.INVOICE_STATUS_DRAFT = 1; CONSTS.INVOICE_STATUS_DRAFT = 1;