Working on custom fields for pro plan

This commit is contained in:
Hillel Coren 2014-04-18 11:57:31 +03:00
parent 0d912373e7
commit 01ec2cb838
19 changed files with 348 additions and 129 deletions

View File

@ -132,13 +132,14 @@ class AccountController extends \BaseController {
);
$recommendedGatewayArray[$recommendedGateway->name] = $arrayItem;
}
$otherItem = array(
'value' => 1000000,
'other' => 'true',
'data-imageUrl' => '',
'data-siteUrl' => ''
);
$recommendedGatewayArray['Other Options'] = $otherItem;
$otherItem = array(
'value' => 1000000,
'other' => 'true',
'data-imageUrl' => '',
'data-siteUrl' => ''
);
$recommendedGatewayArray['Other Options'] = $otherItem;
$data = [
'account' => $account,
@ -180,6 +181,14 @@ class AccountController extends \BaseController {
{
return View::make('accounts.import_export');
}
else if ($section == ACCOUNT_CUSTOM_FIELDS)
{
$data = [
'account' => Auth::user()->account
];
return View::make('accounts.custom_fields', $data);
}
}
public function doSection($section = ACCOUNT_DETAILS)
@ -208,6 +217,26 @@ class AccountController extends \BaseController {
{
return AccountController::export();
}
else if ($section == ACCOUNT_CUSTOM_FIELDS)
{
return AccountController::saveCustomFields();
}
}
private function saveCustomFields()
{
$account = Auth::user()->account;
$account->custom_label1 = Input::get('custom_label1');
$account->custom_value1 = Input::get('custom_value1');
$account->custom_label2 = Input::get('custom_label2');
$account->custom_value2 = Input::get('custom_value2');
$account->custom_client_label1 = Input::get('custom_client_label1');
$account->custom_client_label2 = Input::get('custom_client_label2');
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/custom_fields');
}
private function export()
@ -466,7 +495,7 @@ class AccountController extends \BaseController {
private function saveNotifications()
{
$account = Account::findOrFail(Auth::user()->account_id);
$account = Auth::user()->account;
$account->invoice_terms = Input::get('invoice_terms');
$account->email_footer = Input::get('email_footer');
$account->save();

View File

@ -60,31 +60,7 @@ class ClientController extends \BaseController {
->make();
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (Client::scope()->count() > Auth::user()->getMaxNumClients())
{
return View::make('error', ['error' => "Sorry, you've exceeded the limit of " . Auth::user()->getMaxNumClients() . " clients"]);
}
$data = array(
'client' => null,
'method' => 'POST',
'url' => 'clients',
'title' => '- New Client',
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get());
return View::make('clients.edit', $data);
}
/**
* Store a newly created resource in storage.
@ -118,6 +94,29 @@ class ClientController extends \BaseController {
return View::make('clients.show', $data);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (Client::scope()->count() > Auth::user()->getMaxNumClients())
{
return View::make('error', ['error' => "Sorry, you've exceeded the limit of " . Auth::user()->getMaxNumClients() . " clients"]);
}
$data = [
'client' => null,
'method' => 'POST',
'url' => 'clients',
'title' => '- New Client'
];
$data = array_merge($data, self::getViewModel());
return View::make('clients.edit', $data);
}
/**
* Show the form for editing the specified resource.
*
@ -127,18 +126,29 @@ class ClientController extends \BaseController {
public function edit($publicId)
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
$data = array(
$data = [
'client' => $client,
'method' => 'PUT',
'url' => 'clients/' . $publicId,
'title' => '- ' . $client->name,
'title' => '- ' . $client->name
];
$data = array_merge($data, self::getViewModel());
return View::make('clients.edit', $data);
}
private static function getViewModel()
{
return [
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get());
return View::make('clients.edit', $data);
}
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'customLabel1' => Auth::user()->account->custom_client_label1,
'customLabel2' => Auth::user()->account->custom_client_label2,
];
}
/**
* Update the specified resource in storage.
@ -178,6 +188,8 @@ class ClientController extends \BaseController {
$client->name = trim(Input::get('name'));
$client->work_phone = trim(Input::get('work_phone'));
$client->custom_value1 = trim(Input::get('custom_value1'));
$client->custom_value2 = trim(Input::get('custom_value2'));
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));

View File

@ -182,7 +182,7 @@ class InvoiceController extends \BaseController {
'url' => 'invoices/' . $publicId,
'title' => '- ' . $invoice->invoice_number,
'client' => $invoice->client);
$data = array_merge($data, InvoiceController::getViewModel());
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
@ -206,11 +206,11 @@ class InvoiceController extends \BaseController {
'url' => 'invoices',
'title' => '- New Invoice',
'client' => $client);
$data = array_merge($data, InvoiceController::getViewModel());
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
public static function getViewModel()
private static function getViewModel()
{
return [
'account' => Auth::user()->account,

View File

@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCustomFields extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->string('custom_label1');
$table->string('custom_value1');
$table->string('custom_label2');
$table->string('custom_value2');
$table->string('custom_client_label1');
$table->string('custom_client_label2');
});
Schema::table('clients', function($table)
{
$table->string('custom_value1');
$table->string('custom_value2');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('custom_label1');
$table->dropColumn('custom_value1');
$table->dropColumn('custom_label2');
$table->dropColumn('custom_value2');
$table->dropColumn('custom_client_label1');
$table->dropColumn('custom_client_label2');
});
Schema::table('clients', function($table)
{
$table->dropColumn('custom_value1');
$table->dropColumn('custom_value2');
});
}
}

View File

@ -311,5 +311,11 @@ return array(
'pro_plan_success' => 'Thanks for joining! Once the invoice is paid your Pro Plan membership will begin.',
'unsaved_changes' => 'You have unsaved changes',
'custom_fields' => 'Custom fields',
'company_fields' => 'Company Fields',
'client_fields' => 'Client Fields',
'field_label' => 'Field Label',
'field_value' => 'Field Value',
'edit' => 'Edit',
);

View File

@ -12,6 +12,11 @@ class Utils
return App::environment() == ENV_PRODUCTION;
}
public static function isNinja()
{
return self::isNinjaProd() || self::isNinjaDev();
}
public static function isNinjaProd()
{
return $_SERVER['SERVER_NAME'] == 'www.invoiceninja.com';
@ -22,6 +27,20 @@ class Utils
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
}
public static function getProLabel($feature)
{
if (Auth::check()
&& !Auth::user()->isPro()
&& $feature == 'custom_fields')
{
return '&nbsp;<sup class="pro-label">PRO</sup>';
}
else
{
return '';
}
}
public static function basePath()
{
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);

View File

@ -215,6 +215,11 @@ class Account extends Eloquent
public function isPro()
{
if (Utils::isNinja())
{
return true;
}
if ($this->account_key == NINJA_ACCOUNT_KEY)
{
return true;

View File

@ -155,6 +155,24 @@ class Client extends EntityModel
return $str;
}
public function getCustomFields()
{
$str = '';
$account = $this->account;
if ($account->custom_client_label1 && $this->custom_value1)
{
$str .= "{$account->custom_client_label1}: {$this->custom_value1}<br/>";
}
if ($account->custom_client_label2 && $this->custom_value2)
{
$str .= "{$account->custom_client_label2}: {$this->custom_value2}<br/>";
}
return $str;
}
public function getWebsite()
{
if (!$this->website)

View File

@ -46,8 +46,11 @@ class ClientRepository
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
}
$client->name = trim($data['name']);
$client->work_phone = trim($data['work_phone']);
$client->work_phone = trim($data['work_phone']);
$client->custom_value1 = trim($data['custom_value1']);
$client->custom_value2 = trim($data['custom_value2']);
$client->address1 = trim($data['address1']);
$client->address2 = trim($data['address2']);
$client->city = trim($data['city']);

View File

@ -99,80 +99,9 @@ Route::group(array('before' => 'auth'), function()
HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2) ) ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.trans("texts.$text").'</a></li>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>';
});
HTML::macro('menu_link', function($type) {
$types = $type.'s';
$Type = ucfirst($type);
$Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) ? ' active' : '';
return '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>
</ul>
</li>';
});
HTML::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
});
HTML::macro('breadcrumbs', function() {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']);
$path = $parts[0];
if ($basePath != '/')
{
$path = str_replace($basePath, '', $path);
}
$crumbs = explode('/', $path);
foreach ($crumbs as $key => $val)
{
if (is_numeric($val))
{
unset($crumbs[$key]);
}
}
$crumbs = array_values($crumbs);
for ($i=0; $i<count($crumbs); $i++) {
$crumb = trim($crumbs[$i]);
if (!$crumb) continue;
if ($crumb == 'company') return '';
$name = trans("texts.$crumb");
if ($i==count($crumbs)-1)
{
$str .= "<li class='active'>$name</li>";
}
else
{
$str .= '<li>'.link_to($crumb, $name).'</li>';
}
}
return $str . '</ol>';
});
define('CONTACT_EMAIL', 'contact@invoiceninja.com');
define('CONTACT_NAME', 'Invoice Ninja');
define('NINJA_URL', 'https://www.invoiceninja.com');
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
@ -194,6 +123,7 @@ define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'payments');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_CUSTOM_FIELDS', 'custom_fields');
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8);
@ -259,6 +189,84 @@ define('GATEWAY_GOOGLE', 33);
define('GATEWAY_QUICKBOOKS', 35);
*/
HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2) ) ? ' class="active"' : '';
$title = ucwords(trans("texts.$text")) . Utils::getProLabel($text);
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$title.'</a></li>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>';
});
HTML::macro('menu_link', function($type) {
$types = $type.'s';
$Type = ucfirst($type);
$Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) ? ' active' : '';
return '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>
</ul>
</li>';
});
HTML::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
});
HTML::macro('breadcrumbs', function() {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']);
$path = $parts[0];
if ($basePath != '/')
{
$path = str_replace($basePath, '', $path);
}
$crumbs = explode('/', $path);
foreach ($crumbs as $key => $val)
{
if (is_numeric($val))
{
unset($crumbs[$key]);
}
}
$crumbs = array_values($crumbs);
for ($i=0; $i<count($crumbs); $i++) {
$crumb = trim($crumbs[$i]);
if (!$crumb) continue;
if ($crumb == 'company') return '';
$name = trans("texts.$crumb");
if ($i==count($crumbs)-1)
{
$str .= "<li class='active'>$name</li>";
}
else
{
$str .= '<li>'.link_to($crumb, $name).'</li>';
}
}
return $str . '</ol>';
});
function uctrans($text)
{
return ucwords(trans($text));
}
if (Auth::check() && !Session::has(SESSION_TIMEZONE))
{
Event::fire('user.refresh');
@ -276,8 +284,8 @@ Validator::extend('has_credit', function($attribute, $value, $parameters)
$client = Client::scope($publicClientId)->firstOrFail();
$credit = $client->getTotalCredit();
return $credit >= $amount;
return $credit >= $amount;
});

View File

@ -0,0 +1,33 @@
@extends('accounts.nav')
@section('content')
@parent
{{ Former::open()->addClass('col-md-8 col-md-offset-2 warn-on-exit') }}
{{ Former::populate($account) }}
{{ Former::legend('company_fields') }}
{{ Former::text('custom_label1')->label(trans('texts.field_label')) }}
{{ Former::text('custom_value1')->label(trans('texts.field_value')) }}
<p>&nbsp;</p>
{{ Former::text('custom_label2')->label(trans('texts.field_label')) }}
{{ Former::text('custom_value2')->label(trans('texts.field_value')) }}
{{ Former::legend('client_fields') }}
{{ Former::text('custom_client_label1')->label(trans('texts.field_label')) }}
{{ Former::text('custom_client_label2')->label(trans('texts.field_label')) }}
@if (Auth::user()->isPro())
{{ Former::actions( Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk') ) }}
@else
<script>
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
});
</script>
@endif
{{ Former::close() }}
@stop

View File

@ -14,7 +14,7 @@
{{ Former::open_for_files()->addClass('col-md-10 col-md-offset-1 warn-on-exit')->rules(array(
'name' => 'required',
'email' => 'email|required'
)); }}
)) }}
{{ Former::populate($account) }}
{{ Former::populateField('first_name', $account->users()->first()->first_name) }}

View File

@ -5,8 +5,9 @@
<ul class="nav nav-tabs nav nav-justified">
{{ HTML::nav_link('company/details', 'company_details') }}
{{ HTML::nav_link('company/payments', 'online_payments') }}
{{ HTML::nav_link('company/notifications', 'notifications') }}
{{ HTML::nav_link('company/notifications', 'notifications') }}
{{ HTML::nav_link('company/import_export', 'import_export', 'company/import_map') }}
{{-- HTML::nav_link('company/custom_fields', 'custom_fields') --}}
</ul>
<p>&nbsp;</p>

View File

@ -25,7 +25,15 @@
{{ Former::text('name')->data_bind("attr { placeholder: placeholderName }") }}
{{ Former::text('website') }}
{{ Former::text('work_phone') }}
@if (Auth::user()->isPro())
@if ($customLabel1)
{{ Former::text('custom_value1')->label($customLabel1) }}
@endif
@if ($customLabel2)
{{ Former::text('custom_value2')->label($customLabel2) }}
@endif
@endif
{{ Former::legend('address') }}
{{ Former::text('address1') }}

View File

@ -48,6 +48,7 @@
<div class="col-md-3">
<h3>{{ trans('texts.details') }}</h3>
<p>{{ $client->getAddress() }}</p>
<p>{{ $client->getCustomFields() }}</p>
<p>{{ $client->getPhone() }}</p>
<p>{{ $client->getNotes() }}</p>
<p>{{ $client->getIndustry() }}</p>

View File

@ -1,7 +1,6 @@
@extends('master')
@section('head')
<meta name="csrf-token" content="<?= csrf_token() ?>">
@ -90,7 +89,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="{{ URL::to('/') }}" class='navbar-brand'>
<a href="{{ Utils::isNinja() || Auth::check() ? URL::to('/') : NINJA_URL }}" class='navbar-brand'>
<img src="{{ asset('images/invoiceninja-logo.png') }}" style="height:18px;width:auto"/>
</a>
</div>
@ -139,10 +138,11 @@
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>{{ link_to('company/details', trans('texts.company_details')) }}</li>
<li>{{ link_to('company/payments', trans('texts.online_payments')) }}</li>
<li>{{ link_to('company/notifications', trans('texts.notifications')) }}</li>
<li>{{ link_to('company/import_export', trans('texts.import_export')) }}</li>
<li>{{ link_to('company/details', uctrans('texts.company_details')) }}</li>
<li>{{ link_to('company/payments', uctrans('texts.online_payments')) }}</li>
<li>{{ link_to('company/notifications', uctrans('texts.notifications')) }}</li>
<li>{{ link_to('company/import_export', uctrans('texts.import_export')) }}</li>
<!--<li><a href="{{ url('company/custom_fields') }}">{{ uctrans('texts.custom_fields') . Utils::getProLabel(ACCOUNT_CUSTOM_FIELDS) }}</a></li>-->
<li class="divider"></li>
<li>{{ link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) }}</li>

View File

@ -294,7 +294,17 @@
{{ Former::text('name')->data_bind("value: name, valueUpdate: 'afterkeydown', attr { placeholder: name.placeholder }") }}
{{ Former::text('website')->data_bind("value: website, valueUpdate: 'afterkeydown'") }}
{{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") }}
@if (Auth::user()->isPro())
@if ($account->custom_client_label1)
{{ Former::text('custom_value1')->label($account->custom_client_label1)
->data_bind("value: custom_value1, valueUpdate: 'afterkeydown'") }}
@endif
@if ($account->custom_client_label2)
{{ Former::text('custom_value2')->label($account->custom_client_label2)
->data_bind("value: custom_value2, valueUpdate: 'afterkeydown'") }}
@endif
@endif
{{ Former::legend('address') }}
{{ Former::text('address1')->data_bind("value: address1, valueUpdate: 'afterkeydown'") }}
@ -1217,6 +1227,8 @@
self.public_id = ko.observable(0);
self.name = ko.observable('');
self.work_phone = ko.observable('');
self.custom_value1 = ko.observable('');
self.custom_value2 = ko.observable('');
self.private_notes = ko.observable('');
self.address1 = ko.observable('');
self.address2 = ko.observable('');

View File

@ -70,16 +70,17 @@
var NINJA = NINJA || {};
NINJA.formIsChanged = false;
$(function() {
$('form.warn-on-exit input, form.warn-on-exit textarea, form.warn-on-exit select').on('change', function() {
NINJA.formIsChanged = true;
$('form.warn-on-exit input, form.warn-on-exit textarea, form.warn-on-exit select').change(function() {
NINJA.formIsChanged = true;
});
});
$('form').submit(function() {
NINJA.formIsChanged = false;
});
$(window).on('beforeunload', function() {
/*
if (NINJA.formIsChanged) {
return "{{ trans('texts.unsaved_changes') }}";
}
*/
});
//$('a[rel!=ext]').click(function() { $(window).off('beforeunload') });
</script>

View File

@ -637,6 +637,9 @@ div.fb_iframe_widget {
div.fb_iframe_widget > span {
vertical-align: top !important;
}
.pro-label {
font-size:9px;
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu > li > a {