1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-19 16:01:34 +02:00

Improving data model

This commit is contained in:
Hillel Coren 2013-12-03 19:32:33 +02:00
parent 06eb6ad240
commit 2142538e15
28 changed files with 415 additions and 238 deletions

View File

@ -54,4 +54,5 @@ Configure config/database.php and then initialize the database
* [Chumper/Datatable](https://github.com/Chumper/Datatable) - This is a laravel 4 package for the server and client side of datatables
* [omnipay/omnipay](https://github.com/omnipay/omnipay) - A framework agnostic, multi-gateway payment processing library for PHP 5.3+
* [Intervention/image](https://github.com/Intervention/image) - PHP Image Manipulation
* [webpatser/laravel-countries](https://github.com/webpatser/laravel-countries) - Almost ISO 3166_2, 3166_3, currency, Capital and more for all countries
* [webpatser/laravel-countries](https://github.com/webpatser/laravel-countries) - Almost ISO 3166_2, 3166_3, currency, Capital and more for all countries
* [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) - A simple API extension for DateTime with PHP 5.3+

View File

@ -10,7 +10,7 @@ class AccountController extends \BaseController {
if ($guestKey)
{
//$user = User::where('key','=',$guestKey)->firstOrFail();
//$user = User::where('password', '=', $guestKey)->firstOrFail();
$user = User::where('password', '=', $guestKey)->first();
if ($user && !$user->is_guest)
@ -29,10 +29,7 @@ class AccountController extends \BaseController {
$random = str_random(20);
$user = new User;
//$user->username = $random.'@gmail.com';
//$user->password = $random;
//$user->email = $random.'@gmail.com';
//$user->password_confirmation = $random;
$user->password = $random;
$account->users()->save($user);
}
@ -46,7 +43,7 @@ class AccountController extends \BaseController {
{
if ($section == ACCOUNT_DETAILS)
{
$account = Account::with('users')->find(Auth::user()->account_id);
$account = Account::with('users')->findOrFail(Auth::user()->account_id);
$countries = Country::orderBy('name')->get();
$timezones = Timezone::orderBy('location')->get();
@ -54,7 +51,7 @@ class AccountController extends \BaseController {
}
else if ($section == ACCOUNT_SETTINGS)
{
$account = Account::with('account_gateways')->find(Auth::user()->account_id);
$account = Account::with('account_gateways')->findOrFail(Auth::user()->account_id);
$accountGateway = null;
$config = null;
@ -355,7 +352,7 @@ class AccountController extends \BaseController {
if ($gatewayId = Input::get('gateway_id'))
{
$gateway = Gateway::find($gatewayId);
$gateway = Gateway::findOrFail($gatewayId);
$fields = Omnipay::create($gateway->provider)->getDefaultParameters();
foreach ($fields as $field => $details)
@ -377,7 +374,7 @@ class AccountController extends \BaseController {
}
else
{
$account = Account::find(Auth::user()->account_id);
$account = Account::findOrFail(Auth::user()->account_id);
$account->account_gateways()->forceDelete();
if ($gatewayId)
@ -417,7 +414,7 @@ class AccountController extends \BaseController {
}
else
{
$account = Account::find(Auth::user()->account_id);
$account = Account::findOrFail(Auth::user()->account_id);
$account->name = Input::get('name');
$account->address1 = Input::get('address1');
$account->address2 = Input::get('address2');
@ -436,7 +433,7 @@ class AccountController extends \BaseController {
$user->save();
if (Input::get('timezone_id')) {
$timezone = Timezone::find(Input::get('timezone_id'));
$timezone = Timezone::findOrFail(Input::get('timezone_id'));
Session::put('tz', $timezone->name);
}
@ -452,4 +449,57 @@ class AccountController extends \BaseController {
return Redirect::to('account/details');
}
}
public function checkEmail()
{
$email = User::where('email', '=', Input::get('email'))->first();
if ($email) {
return "taken";
} else {
return "available";
}
}
public function submitSignup()
{
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'password' => 'required|min:6',
'email' => 'email|required'
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails())
{
return Redirect::to(Input::get('path'));
}
$user = Auth::user();
$user->first_name = Input::get('first_name');
$user->last_name = Input::get('last_name');
$user->email = Input::get('email');
$user->password = Input::get('password');
$user->registered = true;
$user->save();
$activities = Activity::scope()->get();
foreach ($activities as $activity) {
$activity->message = str_replace('Guest', $user->getFullName(), $activity->message);
$activity->save();
}
/*
Mail::send(array('html'=>'emails.welcome_html','text'=>'emails.welcome_text'), $data, function($message) use ($user)
{
$message->from('hillelcoren@gmail.com', 'Hillel Coren');
$message->to($user->email);
});
*/
Session::flash('message', 'Successfully registered');
return Redirect::to(Input::get('path'));
}
}

View File

@ -4,8 +4,7 @@ class ActivityController extends \BaseController {
public function getDatatable($clientId)
{
return Datatable::collection(Activity::where('account_id','=',Auth::user()->account_id)
->where('client_id','=',$clientId)->get())
return Datatable::collection(Activity::scope()->where('client_id','=',$clientId)->get())
->addColumn('date', function($model) { return $model->created_at->format('m/d/y h:i a'); })
->addColumn('message', function($model) { return $model->message; })
->addColumn('balance', function($model) { return '$' . $model->balance; })

View File

@ -15,4 +15,8 @@ class BaseController extends Controller {
}
}
public function __construct()
{
$this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put')));
}
}

View File

@ -20,7 +20,7 @@ class ClientController extends \BaseController {
public function getDatatable()
{
$clients = Client::with('contacts')->where('account_id','=',Auth::user()->account_id)->get();
$clients = Client::scope()->with('contacts')->get();
return Datatable::collection($clients)
->addColumn('checkbox', function($model) { return '<input type="checkbox" name="ids[]" value="' . $model->id . '">'; })
@ -38,10 +38,11 @@ class ClientController extends \BaseController {
Select <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('clients/'.$model->id.'/edit') . '">Edit</a></li>
<li><a href="' . URL::to('invoices/create/'.$model->id) . '">New Invoice</a></li>
<li><a href="' . URL::to('clients/'.$model->id.'/edit') . '">Edit Client</a></li>
<li class="divider"></li>
<li><a href="' . URL::to('clients/'.$model->id.'/archive') . '">Archive</a></li>
<li><a href="javascript:deleteEntity(' . $model->id. ')">Delete</a></li>
<li><a href="' . URL::to('clients/'.$model->id.'/archive') . '">Archive Client</a></li>
<li><a href="javascript:deleteEntity(' . $model->id. ')">Delete Client</a></li>
</ul>
</div>';
})
@ -84,9 +85,8 @@ class ClientController extends \BaseController {
*/
public function show($id)
{
$client = Client::with('contacts')->find($id);
$client = Client::scope()->with('contacts')->findOrFail($id);
trackViewed($client->name);
return View::make('clients.show')->with('client', $client);
}
@ -99,7 +99,7 @@ class ClientController extends \BaseController {
*/
public function edit($id)
{
$client = Client::with('contacts')->find($id);
$client = Client::scope()->with('contacts')->findOrFail($id);
$data = array(
'client' => $client,
'method' => 'PUT',
@ -133,7 +133,7 @@ class ClientController extends \BaseController {
->withInput(Input::except('password'));
} else {
if ($id) {
$client = Client::find($id);
$client = Client::scope()->findOrFail($id);
} else {
$client = new Client;
$client->account_id = Auth::user()->account_id;
@ -159,7 +159,7 @@ class ClientController extends \BaseController {
{
if (isset($contact->id) && $contact->id)
{
$record = Contact::find($contact->id);
$record = Contact::findOrFail($contact->id);
}
else
{
@ -192,8 +192,8 @@ class ClientController extends \BaseController {
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('ids');
$clients = Client::find($ids);
$ids = Input::get('ids') ? Input::get('ids') : [Input::get('id')];
$clients = Client::scope()->findOrFail($ids);
foreach ($clients as $client) {
if ($action == 'archive') {
@ -211,7 +211,7 @@ class ClientController extends \BaseController {
public function archive($id)
{
$client = Client::find($id);
$client = Client::scope()->findOrFail($id);
$client->delete();
foreach ($client->invoices as $invoice)
@ -225,7 +225,7 @@ class ClientController extends \BaseController {
public function delete($id)
{
$client = Client::find($id);
$client = Client::scope()->findOrFail($id);
$client->forceDelete();
Session::flash('message', 'Successfully deleted ' . $client->name);

View File

@ -17,7 +17,7 @@ class CreditController extends \BaseController {
public function getDatatable($clientId = null)
{
$collection = Credit::with('client')->where('account_id','=',Auth::user()->account_id);
$collection = Credit::scope()->with('client');
if ($clientId) {
$collection->where('client_id','=',$clientId);
@ -43,7 +43,7 @@ class CreditController extends \BaseController {
public function archive($id)
{
$credit = Credit::find($id);
$credit = Credit::scope()->findOrFail($id);
$creidt->delete();
Session::flash('message', 'Successfully archived credit ' . $credit->credit_number);
@ -52,7 +52,7 @@ class CreditController extends \BaseController {
public function delete($id)
{
$credit = Credit::find($id);
$credit = Credit::scope()->findOrFail($id);
$credit->forceDelete();
Session::flash('message', 'Successfully deleted credit ' . $credit->credit_number);

View File

@ -17,7 +17,7 @@ class InvoiceController extends \BaseController {
public function getDatatable($clientId = null)
{
$collection = Invoice::with('client','invoice_items','invoice_status')->where('account_id','=',Auth::user()->account_id);
$collection = Invoice::scope()->with('client','invoice_items','invoice_status');
if ($clientId) {
$collection->where('client_id','=',$clientId);
@ -47,10 +47,10 @@ class InvoiceController extends \BaseController {
Select <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('invoices/'.$model->id.'/edit') . '">Edit</a></li>
<li><a href="' . URL::to('invoices/'.$model->id.'/edit') . '">Edit Invoice</a></li>
<li class="divider"></li>
<li><a href="' . URL::to('invoices/'.$model->id.'/archive') . '">Archive</a></li>
<li><a href="javascript:deleteEntity(' . $model->id . ')">Delete</a></li>
<li><a href="' . URL::to('invoices/'.$model->id.'/archive') . '">Archive Invoice</a></li>
<li><a href="javascript:deleteEntity(' . $model->id . ')">Delete Invoice</a></li>
</ul>
</div>';
})
@ -64,13 +64,20 @@ class InvoiceController extends \BaseController {
$invitation = Invitation::with('user', 'invoice.account', 'invoice.invoice_items', 'invoice.client.account.account_gateways')->where('key', '=', $key)->firstOrFail();
$user = $invitation->user;
$invoice = $invitation->invoice;
$now = Carbon::now()->toDateTimeString();
$invitation->viewed_date = Carbon::now()->toDateTimeString();
$invitation->viewed_date = $now;
$invitation->save();
$client = $invoice->client;
$client->last_login = $now;
$client->save();
Activity::viewInvoice($invitation);
return View::make('invoices.view')->with('invoice', $invitation->invoice);
return View::make('invoices.view')->with('invoice', $invoice);
}
private function createGateway($accountGateway)
@ -201,7 +208,7 @@ class InvoiceController extends \BaseController {
public function edit($id)
{
$invoice = Invoice::with('account.country', 'client', 'invoice_items')->find($id);
$invoice = Invoice::scope()->with('account.country', 'client', 'invoice_items')->findOrFail($id);
trackViewed($invoice->invoice_number . ' - ' . $invoice->client->name);
$data = array(
@ -211,9 +218,9 @@ class InvoiceController extends \BaseController {
'url' => 'invoices/' . $id,
'title' => 'Edit',
'account' => Auth::user()->account,
'products' => Product::getProducts()->get(),
'products' => Product::scope()->get(),
'client' => $invoice->client,
'clients' => Client::where('account_id','=',Auth::user()->account_id)->orderBy('name')->get());
'clients' => Client::scope()->orderBy('name')->get());
return View::make('invoices.edit', $data);
}
@ -221,7 +228,11 @@ class InvoiceController extends \BaseController {
{
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
$account = Account::with('country')->find(Auth::user()->account_id);
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
if ($clientId) {
$client = Client::scope()->findOrFail($clientId);
}
$data = array(
'account' => $account,
@ -233,8 +244,8 @@ class InvoiceController extends \BaseController {
'client' => $client,
'items' => json_decode(Input::old('items')),
'account' => Auth::user()->account,
'products' => Product::getProducts()->get(),
'clients' => Client::where('account_id','=',Auth::user()->account_id)->orderBy('name')->get());
'products' => Product::scope()->get(),
'clients' => Client::scope()->orderBy('name')->get());
return View::make('invoices.edit', $data);
}
@ -250,6 +261,17 @@ class InvoiceController extends \BaseController {
private function save($id = null)
{
$action = Input::get('action');
if ($action == 'archive')
{
return InvoiceController::archive($id);
}
else if ($action == 'delete')
{
return InvoiceController::delete($id);
}
$rules = array(
'client' => 'required',
'invoice_number' => 'required',
@ -279,12 +301,12 @@ class InvoiceController extends \BaseController {
}
else
{
$client = Client::with('contacts')->find($clientId);
$client = Client::scope()->with('contacts')->findOrFail($clientId);
$contact = $client->contacts()->first();
}
if ($id) {
$invoice = Invoice::find($id);
$invoice = Invoice::scope()->findOrFail($id);
$invoice->invoice_items()->forceDelete();
} else {
$invoice = new Invoice;
@ -308,11 +330,16 @@ class InvoiceController extends \BaseController {
if (!isset($item->qty))
{
$item->qty = 0;
}
}
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes)
{
continue;
}
if ($item->product_key)
{
$product = Product::findProduct($item->product_key);
$product = Product::findProductByKey($item->product_key);
if (!$product)
{
@ -340,7 +367,7 @@ class InvoiceController extends \BaseController {
$invoice->invoice_items()->save($invoiceItem);
}
if (Input::get('send_email_checkBox'))
if ($action == 'email')
{
$data = array('link' => URL::to('view') . '/' . $invoice->invoice_key);
/*
@ -404,7 +431,7 @@ class InvoiceController extends \BaseController {
{
$action = Input::get('action');
$ids = Input::get('ids');
$invoices = Invoice::find($ids);
$invoices = Invoice::scope()->findOrFail($ids);
foreach ($invoices as $invoice) {
if ($action == 'archive') {
@ -422,16 +449,16 @@ class InvoiceController extends \BaseController {
public function archive($id)
{
$invoice = Invoice::find($id);
$invoice = Invoice::scope()->findOrFail($id);
$invoice->delete();
Session::flash('message', 'Successfully archived invoice ' . $invoice->invoice_number);
return Redirect::to('invoices');
}
public function delete($id)
{
$invoice = Invoice::find($id);
$invoice = Invoice::scope()->findOrFail($id);
$invoice->forceDelete();
Session::flash('message', 'Successfully deleted invoice ' . $invoice->invoice_number);

View File

@ -9,7 +9,7 @@ class PaymentController extends \BaseController
public function getDatatable($clientId = null)
{
$collection = Payment::with('invoice.client')->where('account_id', '=', Auth::user()->account_id);
$collection = Payment::scope()->with('invoice.client');
if ($clientId) {
$collection->where('client_id','=',$clientId);
@ -42,7 +42,7 @@ class PaymentController extends \BaseController
public function archive($id)
{
$payment = Payment::find($id);
$payment = Payment::scope()->findOrFail($id);
$payment->delete();
Session::flash('message', 'Successfully archived payment');
@ -51,7 +51,7 @@ class PaymentController extends \BaseController
public function delete($id)
{
$payment = Payment::find($id);
$payment = Payment::scope()->findOrFail($id);
$payment->forceDelete();
Session::flash('message', 'Successfully deleted payment');

View File

@ -66,7 +66,8 @@ class ConfideSetupUsersTable extends Migration {
$t->string('ip');
$t->string('logo_path');
$t->string('key')->unique();
$t->timestamp('last_login');
$t->string('address1');
$t->string('address2');
$t->string('city');
@ -115,7 +116,7 @@ class ConfideSetupUsersTable extends Migration {
$t->string('last_name');
$t->string('phone');
$t->string('username');
$t->string('email')->unique();
$t->string('email');
$t->string('password');
$t->string('confirmation_code');
$t->boolean('registered')->default(false);
@ -149,6 +150,7 @@ class ConfideSetupUsersTable extends Migration {
$t->string('work_phone');
$t->text('notes');
$t->decimal('balance', 10, 2);
$t->timestamp('last_login');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('country_id')->references('id')->on('countries');

View File

@ -73,23 +73,8 @@ Route::filter('guest', function()
Route::filter('csrf', function()
{
if (!Input::get('_token'))
{
return;
}
$tokenInput = Input::get('_token');
$tokenSession = Session::token();
/*
if ($url = Session::get($tokenInput))
{
return Redirect::to($url);
}
*/
if ($tokenSession != $tokenInput)
{
$token = Request::ajax() ? Request::header('X-CSRF-Token') : Input::get('_token');
if (Session::token() != $token) {
throw new Illuminate\Session\TokenMismatchException;
}
}
});

View File

@ -18,6 +18,11 @@ define("ACTIVITY_TYPE_DELETE_CREDIT", 14);
class Activity extends Eloquent
{
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
private static function getBlank()
{
$user = Auth::user();

View File

@ -3,7 +3,7 @@
class Client extends Eloquent implements iEntity
{
protected $softDelete = true;
public static $fieldName = 'Client - Name';
public static $fieldPhone = 'Client - Phone';
public static $fieldAddress1 = 'Client - Street';
@ -14,6 +14,11 @@ class Client extends Eloquent implements iEntity
public static $fieldNotes = 'Client - Notes';
public static $fieldCountry = 'Client - Country';
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function account()
{
return $this->belongsTo('Account');

View File

@ -4,6 +4,11 @@ class Credit extends Eloquent implements iEntity
{
protected $softDelete = true;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function invoice()
{
return $this->belongsTo('Invoice');

View File

@ -4,6 +4,11 @@ class Invitation extends Eloquent
{
protected $softDelete = true;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function invoice()
{
return $this->belongsTo('Invoice');

View File

@ -4,6 +4,11 @@ class Invoice extends Eloquent implements iEntity
{
protected $softDelete = true;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function account()
{
return $this->belongsTo('Account');

View File

@ -4,6 +4,11 @@ class Payment extends Eloquent implements iEntity
{
protected $softDelete = true;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function invoice()
{
return $this->belongsTo('Invoice');

View File

@ -4,14 +4,14 @@ class Product extends Eloquent
{
protected $softDelete = true;
public static function getProducts()
public function scopeScope($query)
{
return Product::where('account_id','=',Auth::user()->account_id);
return $query->whereAccountId(Auth::user()->account_id);
}
public static function findProduct($key)
public static function findProductByKey($key)
{
return Product::getProducts()->where('key','=',$key)->first();
return Product::scope()->where('key','=',$key)->first();
}
public static function getProductKeys($products)

View File

@ -30,12 +30,13 @@ Route::filter('auth', function()
}
});
Route::group(array('before' => array('auth', 'csrf')), function()
Route::group(array('before' => 'auth'), function()
{
Route::get('home', function() { return View::make('header'); });
Route::get('account/{section?}', 'AccountController@showSection');
Route::post('account/{section?}', 'AccountController@doSection');
Route::post('signup/validate', 'AccountController@checkEmail');
Route::post('signup/submit', 'AccountController@submitSignup');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
@ -127,7 +128,16 @@ function toSpaceCase($camelStr)
return preg_replace('/([a-z])([A-Z])/s','$1 $2', $camelStr);
}
/*
function timestampToDateTimeString($timestamp) {
$tz = Session::get('tz');
if (!$tz) {
$tz = 'US/Eastern';
}
$date = new Carbon($timestamp);
$date->tz = $tz;
return $date->toDayDateTimeString();
}
function toDateString($date)
{
if ($date->year < 1900) {
@ -140,7 +150,7 @@ function toDateString($date)
$date->tz = $tz;
return $date->toFormattedDateString();
}
*/
function toDateTimeString($date)
{

View File

@ -1,14 +0,0 @@
@extends('header')
@section('content')
{{ Button::primary_link(URL::to('clients/create'), 'New Client', array('class' => 'pull-right')) }}
{{ Datatable::table()
->addColumn('Client', 'Contact', 'Balance', 'Last Login', 'Date Created', 'Email', 'Phone')
->setUrl(route('api.clients'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->render() }}
@stop

View File

@ -4,11 +4,33 @@
<div class="pull-right">
{{ Button::link(URL::to('clients/' . $client->id . '/edit'), 'Edit Client') }}
{{ Former::open('clients/bulk')->addClass('mainForm') }}
<div style="display:none">
{{ Former::text('action') }}
{{ Former::text('id')->value($client->id) }}
</div>
{{ DropdownButton::normal('Edit Client',
Navigation::links(
array(
array('Edit Client', URL::to('clients/' . $client->id . '/edit')),
array(Navigation::DIVIDER),
array('Archive Client', "javascript:onArchiveClick()"),
array('Delete Client', "javascript:onDeleteClick()"),
)
)
, array('id'=>'actionDropDown'))->split(); }}
{{ Button::primary_link(URL::to('invoices/create/' . $client->id), 'Create Invoice') }}
{{ Former::close() }}
</div>
<h2>{{ $client->name }}</h2>
@if ($client->last_login > 0)
<h3 style="margin-top:0px"><small>
Last logged in {{ timestampToDateTimeString($client->last_login); }}
</small></h3>
@endif
<div class="row">
@ -90,9 +112,23 @@
<script type="text/javascript">
$(function() {
$('#actionDropDown > button:first').click(function() {
window.location = '{{ URL::to('clients/' . $client->id . '/edit') }}';
});
});
function onArchiveClick() {
$('#action').val('archive');
$('.mainForm').submit();
}
function onDeleteClick() {
if (confirm('Are you sure you want to delete this client?')) {
$('#action').val('delete');
$('.mainForm').submit();
}
}
</script>
@stop

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
</head>
<body>
<h2>Welcome</h2>
</body>
</html>

View File

@ -0,0 +1 @@
Welcome

View File

@ -6,7 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<meta name="csrf-token" content="<?= csrf_token() ?>">
<title>Invoice Ninja</title>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@ -54,7 +55,7 @@
body > div.container {
/*max-width: 850px;*/
min-height: 600px;
}
label.control-label {
@ -62,6 +63,7 @@
}
div.panel {
padding-left: 0px !important;
padding-right: 0px !important;
@ -228,18 +230,29 @@
@endif
<div class="container">
<p/>
<div>
<span style="font-size:30px">Invoice Ninja</span>
<div style="float:right;text-align:right">
<div style="float:right">
@if (Auth::user()->registered)
{{ Auth::user()->email }} &nbsp;
@else
{{ Button::sm_primary('Sign up', array('data-toggle'=>'modal', 'data-target'=>'#signUpModal')); }} &nbsp;
{{ link_to('account/details', 'My Account'); }}
{{ Button::sm_primary('Sign up', array('data-toggle'=>'modal', 'data-target'=>'#signUpModal')); }}
@endif
<p class="text-danger">This is a sample site, the data is erased.</p>
<div class="btn-group">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
My Account <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>{{ link_to('account/details', 'Details'); }}</li>
<li>{{ link_to('account/settings', 'Settings'); }}</li>
<li>{{ link_to('account/import', 'Import'); }}</li>
<li>{{ link_to('account/export', 'Export'); }}</li>
<li class="divider"></li>
<li><a href="#">Logout</a></li>
</ul>
</div>
</div>
</div>
@ -293,6 +306,13 @@
@yield('content')
</div>
<div class="container">
<div class="footer">
Powered by {{ link_to('https://github.com/hillelcoren/invoice-ninja', 'InvoiceNinja', array('target'=>'_blank')) }}
<p class="text-danger">This is a demo site, the data is erased.</p>
</div>
</div>
</div>
@ -303,20 +323,29 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Sign Up</h4>
<h4 class="modal-title" id="myModalLabel">Sign up</h4>
</div>
<div style="padding-right:20px" onkeyup="validateSignUp()" onkeydown="checkForEnter(event)">
{{ Former::open(); }}
<div style="padding-right:20px" id="signUpDiv" onkeyup="validateSignUp()" onkeydown="checkForEnter(event)">
{{ Former::open('signup/submit')->addClass('signUpForm') }}
{{ Former::populate(Auth::user()) }}
{{ Former::text('first_name'); }}
{{ Former::text('last_name'); }}
{{ Former::text('email'); }}
{{ Former::password('password'); }}
{{ Former::close(); }}
{{ Former::hidden('path')->value(Request::path()) }}
{{ Former::text('first_name') }}
{{ Former::text('last_name') }}
{{ Former::text('email') }}
{{ Former::password('password') }}
{{ Former::close() }}
<center><div id="errorTaken" style="display:none">&nbsp;<br/>The email address is already regiestered</div></center>
</div>
<div class="modal-footer">
<div style="padding-left:40px;padding-right:40px;display:none;min-height:130px" id="working">
<h3>Working...</h3>
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
</div>
</div>
<div class="modal-footer" id="signUpFooter">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="submitSignUp()">Save</button>
</div>
@ -334,26 +363,25 @@
@if (!Auth::user()->registered)
function validateSignUp(showError)
{
var isValid = true;
var isFormValid = true;
$(['first_name','last_name','email','password']).each(function(i, field) {
var $input = $('#'+field),
var $input = $('form.signUpForm #'+field),
val = $.trim($input.val());
var isValid = val && val.length > (field == 'password' ? 6 : 0);
var isValid = val && val.length >= (field == 'password' ? 6 : 1);
if (isValid && field == 'email') {
isValid = isValidEmailAddress(val);
}
if (isValid) {
$input.closest('div.form-group').removeClass('has-error');
$input.closest('div.form-group').addClass('has-success');
$input.closest('div.form-group').removeClass('has-error').addClass('has-success');
} else {
isValid = false;
isFormValid = false;
$input.closest('div.form-group').removeClass('has-success');
if (showError) {
$input.closest('div.form-group').addClass('has-error');
}
}
});
return isValid;
return isFormValid;
}
function submitSignUp()
@ -361,6 +389,26 @@
if (!validateSignUp(true)) {
return;
}
$('#signUpDiv, #signUpFooter').hide();
$('#working').show();
$.ajax({
type: 'POST',
url: '{{ URL::to('signup/validate') }}',
data: 'email=' + $('form.signUpForm #email').val() + '&path={{ Request::path() }}',
success: function(result) {
console.log(result)
if (result == 'available') {
$('.signUpForm').submit();
} else {
$('#errorTaken').show();
$('form.signUpForm #email').closest('div.form-group').removeClass('has-success').addClass('has-error');
$('#signUpDiv, #signUpFooter').show();
$('#working').hide();
}
}
});
}
function checkForEnter(event)
@ -383,11 +431,21 @@
@if (!Auth::user()->registered)
validateSignUp();
$('#signUpModal').on('shown.bs.modal', function () {
$(['first_name','last_name','email','password']).each(function(i, field) {
var $input = $('form.signUpForm #'+field);
if (!$input.val()) {
console.log('focus: %s', field);
$input.focus();
return false;
}
});
})
@endif
@yield('onReady')
});
</script>
</script>
</html>

View File

@ -4,6 +4,7 @@
<p>&nbsp;</p>
{{ Former::open($url)->method($method)->addClass('main_form')->rules(array(
'invoice_number' => 'required',
'invoice_date' => 'required',
@ -111,13 +112,26 @@
</tfoot>
</table>
<input type="checkbox" name="send_email_checkBox" id="send_email_checkBox" value="true" style="display:none"/>
<p>&nbsp;</p>
<div class="form-actions">
{{ Button::primary('Download PDF', array('onclick' => 'onDownloadClick()')) }}
{{ Button::primary_submit('Save Invoice', array('onclick' => 'onSaveClick()')) }}
@if ($invoice)
<div style="display:none">{{ Former::text('action') }}</div>
{{ DropdownButton::normal('Download PDF',
Navigation::links(
array(
array('Download PDF', "javascript:onDownloadClick()"),
array(Navigation::DIVIDER),
array('Archive Invoice', "javascript:onArchiveClick()"),
array('Delete Invoice', "javascript:onDeleteClick()"),
)
)
, array('id'=>'actionDropDown','style'=>'text-align:left'))->split(); }}
@else
{{ Button::normal('Download PDF', array('onclick' => 'onDownloadClick()')) }}
@endif
{{ Button::primary('Save Invoice', array('onclick' => 'onSaveClick()')) }}
{{ Button::primary('Send Email', array('onclick' => 'onEmailClick()')) }}
</div>
<p>&nbsp;</p>
@ -160,8 +174,6 @@
{{ Former::close() }}
<script type="text/javascript">
/*
@ -192,9 +204,32 @@
var $input = $('select#client');
$input.combobox();
$('.combobox-container input.form-control').attr('name', 'client_combobox').on('change', function(e) {
$('.combobox-container input.form-control').attr('name', 'client_combobox').on('change', function(e) {
refreshPDF();
}).mouseleave(function() {
$(this).css('text-decoration','none');
}).on('change keyup mouseenter', function(e) {
if ($(this).closest('.combobox-container').hasClass('combobox-selected')) {
$(this).css('text-decoration','underline');
$(this).css('cursor','pointer');
} else {
$(this).css('text-decoration','none');
$(this).css('cursor','text');
}
}).on('focusout mouseleave', function(e) {
$(this).css('text-decoration','none');
$(this).css('cursor','text');
}).on('click', function() {
var clientId = $('.combobox-container input[name=client]').val();
if ($(this).closest('.combobox-container').hasClass('combobox-selected')) {
if (parseInt(clientId) > 0) {
window.open('{{ URL::to('clients') }}' + '/' + clientId, '_blank');
} else {
$('#myModal').modal('show');
}
};
});
@if ($client)
$('input#invoice_number').focus();
@ -214,6 +249,11 @@
$('#invoice_number').change(refreshPDF);
$('#actionDropDown > button:first').click(function() {
onDownloadClick();
});
applyComboboxListeners();
refreshPDF();
});
@ -224,7 +264,7 @@
value = $(this).val();
}).on('blur', function() {
if (value != $(this).val()) refreshPDF();
}).on('input', function() {
}).on('input', function() {
var key = $(this).val();
for (var i=0; i<products.length; i++) {
var product = products[i];
@ -298,7 +338,7 @@
function onEmailClick() {
if (confirm('Are you sure you want to email this invoice?')) {
$('#send_email_checkBox').prop("checked",true);
$('#action').val('email');
$('.main_form').submit();
}
}
@ -307,6 +347,18 @@
$('.main_form').submit();
}
function onArchiveClick() {
$('#action').val('archive');
$('.main_form').submit();
}
function onDeleteClick() {
if (confirm('Are you sure you want to delete this invoice?')) {
$('#action').val('delete');
$('.main_form').submit();
}
}
function newClient() {
var name = $('#client_name').val();
var email = $('#client_email').val();
@ -361,19 +413,10 @@
item.qty("{{ $item->qty }}");
self.items.push(item);
@endforeach
@else
self.items.push(new ItemModel());
@endif
@else
var model1 = new ItemModel();
/*
model1.item('TEST');
model1.notes('Some test text');
model1.cost(10);
model1.qty(1);
*/
self.items.push(model1);
@endif
@endif
self.items.push(new ItemModel());
self.removeItem = function(item) {
self.items.remove(item);

View File

@ -1,71 +0,0 @@
@extends('header')
@section('content')
{{ Former::open('invoices/action') }}
<div style="display:none">{{ Former::text('action') }}</div>
{{ DropdownButton::normal('Archive',
Navigation::links(
array(
array('Archive', "javascript:submitForm('archive')"),
array('Delete', "javascript:submitForm('delete')"),
)
)
, array('id'=>'archive'))->split(); }}
{{ Button::primary_link(URL::to('invoices/create'), 'New Invoice', array('class' => 'pull-right')) }}
{{ Datatable::table()
->addColumn('checkbox', 'Invoice Number', 'Client', 'Total', 'Amount Due', 'Invoice Date', 'Due Date', 'Status')
->setUrl(route('api.invoices'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->render('datatable') }}
{{ Former::close() }}
<script type="text/javascript">
function submitForm(action) {
$('#action').val(action);
$('form').submit();
}
</script>
@stop
@section('onReady')
window.onDatatableReady = function() {
$(':checkbox').click(function() {
setArchiveEnabled();
});
$('tbody tr').click(function() {
$checkbox = $(this).closest('tr').find(':checkbox');
var checked = $checkbox.prop('checked');
$checkbox.prop('checked', !checked);
setArchiveEnabled();
});
}
$('#archive > button').prop('disabled', true);
$('#archive > button:first').click(function() {
submitForm('archive');
});
$('#selectAll').click(function() {
$(':checkbox').prop('checked', this.checked);
});
function setArchiveEnabled() {
var checked = $('tbody :checkbox:checked').length > 0;
$('#archive > button').prop('disabled', !checked);
}
@stop

View File

@ -2,7 +2,7 @@
@section('content')
{{ Former::open($entityType . 's/bulk') }}
{{ Former::open($entityType . 's/bulk')->addClass('listForm') }}
<div style="display:none">{{ Former::text('action') }}</div>
{{ DropdownButton::normal('Archive',
@ -37,7 +37,7 @@
}
}
$('#action').val(action);
$('form').submit();
$('form.listForm').submit();
}
function deleteEntity(id) {

View File

@ -1,12 +0,0 @@
@extends('header')
@section('content')
{{ Datatable::table()
->addColumn('Client', 'Invoice', 'Amount', 'Date')
->setUrl(route('api.payments'))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->render() }}
@stop

View File

@ -64,12 +64,21 @@ function generatePDF(invoice) {
doc.setFontType("normal");
var line = 1;
var total = 0;
var shownItem = false;
for(var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i];
var cost = formatNumber(item.cost);
var qty = item.qty ? parseInt(item.qty, 10) + '' : '';
var notes = item.notes;
var productKey = item.product_key;
// show at most one blank line
if (shownItem && !cost && !qty && !notes && !productKey) {
continue;
}
shownItem = true;
var lineTotal = item.cost * item.qty;
if (lineTotal) total += lineTotal;
lineTotal = formatNumber(lineTotal);
@ -79,8 +88,8 @@ function generatePDF(invoice) {
var totalX = lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize());
var x = tableTop + (line * rowHeight) + 6;
doc.text(tableLeft, x, item.product_key);
doc.text(descriptionLeft, x, item.notes);
doc.text(tableLeft, x, productKey);
doc.text(descriptionLeft, x, notes);
doc.text(costX, x, cost);
doc.text(qtyX, x, qty);
doc.text(totalX, x, lineTotal);
@ -89,7 +98,7 @@ function generatePDF(invoice) {
}
/* table footer */
var x = tableTop + (line * rowHeight) + 14;
var x = tableTop + (line * rowHeight);
doc.lines([[0,0],[headerRight-tableLeft+5,0]],tableLeft - 8, x);
x += 16;
@ -361,4 +370,13 @@ function isStorageSupported() {
function isValidEmailAddress(emailAddress) {
var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
return pattern.test(emailAddress);
};
};
$(function() {
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
});