diff --git a/README.md b/README.md index 5f58ffeedf68..af0fc4db125f 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,9 @@ ### [https://www.invoiceninja.com](https://www.invoiceninja.com) ### Introduction -Most online invoicing sites are expensive. They shouldn't be. The aim of this project is to provide a free, open-source alternative. Additionally, the hope is the codebase will serve as a sample site for Laravel as well as other JavaScript technologies. +Most online invoicing sites are expensive. They shouldn't be. The aim of this project is to provide a free, open-source alternative. [This guide](http://hillelcoren.com/invoice-ninja/self-hosting/) is the simplest way to setup the site. The high level instructions for setting up the site using Git are below but there's also a more detailed [setup guide](http://hillelcoren.com/invoice-ninja/laravel-ubuntu-virtualbox/). -[This guide](http://hillelcoren.com/invoice-ninja/self-hosting/) is the simplest way to setup the site. The high level instructions for setting up the site using Git are below but there's also a more detailed [setup guide](http://hillelcoren.com/invoice-ninja/laravel-ubuntu-virtualbox/). - -For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). For discussion of the code please use the [Google Group](https://groups.google.com/d/forum/invoiceninja). +To connect follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). For discussion of the code please use the [Google Group](https://groups.google.com/d/forum/invoiceninja). If you'd like to translate the site please use [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) for the starter files. diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 1e6b4237db06..db2627cd1469 100755 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -50,7 +50,7 @@ class AccountController extends \BaseController { Auth::login($user, true); Event::fire('user.login'); - return Redirect::to('invoices/create'); + return Redirect::to('invoices/create'); } public function enableProPlan() @@ -78,7 +78,7 @@ class AccountController extends \BaseController { return Response::json($data); } - public function showSection($section = ACCOUNT_DETAILS) + public function showSection($section = ACCOUNT_DETAILS, $subSection = false) { if ($section == ACCOUNT_DETAILS) { @@ -184,10 +184,11 @@ class AccountController extends \BaseController { else if ($section == ACCOUNT_ADVANCED_SETTINGS) { $data = [ - 'account' => Auth::user()->account + 'account' => Auth::user()->account, + 'feature' => $subSection ]; - return View::make('accounts.advanced_settings', $data); + return View::make("accounts.{$subSection}", $data); } else if ($section == ACCOUNT_PRODUCTS) { @@ -227,7 +228,14 @@ class AccountController extends \BaseController { } else if ($section == ACCOUNT_ADVANCED_SETTINGS) { - return AccountController::saveAdvancedSettings(); + if ($subSection == ACCOUNT_CUSTOM_FIELDS) + { + return AccountController::saveCustomFields(); + } + else if ($subSection == ACCOUNT_INVOICE_DESIGN) + { + return AccountController::saveInvoiceDesign(); + } } else if ($section == ACCOUNT_PRODUCTS) { @@ -247,23 +255,37 @@ class AccountController extends \BaseController { return Redirect::to('company/products'); } - private function saveAdvancedSettings() + private function saveCustomFields() { - $account = Auth::user()->account; + if (!Auth::user()->account->isPro()) + { + $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(); - $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'); + Session::flash('message', trans('texts.updated_settings')); + } - $account->primary_color = Input::get('primary_color');// ? Input::get('primary_color') : null; - $account->secondary_color = Input::get('secondary_color');// ? Input::get('secondary_color') : null; + return Redirect::to('company/advanced_settings'); + } - $account->save(); + private function saveInvoiceDesign() + { + if (!Auth::user()->account->isPro()) + { + $account = Auth::user()->account; + $account->primary_color = Input::get('primary_color');// ? Input::get('primary_color') : null; + $account->secondary_color = Input::get('secondary_color');// ? Input::get('secondary_color') : null; + $account->save(); - Session::flash('message', trans('texts.updated_settings')); + Session::flash('message', trans('texts.updated_settings')); + } + return Redirect::to('company/advanced_settings'); } diff --git a/app/controllers/PaymentController.php b/app/controllers/PaymentController.php index 1568eb36cd96..648b2839b96d 100755 --- a/app/controllers/PaymentController.php +++ b/app/controllers/PaymentController.php @@ -38,7 +38,7 @@ class PaymentController extends \BaseController } $table->addColumn('transaction_reference', function($model) { return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; }) - ->addColumn('payment_type', function($model) { return $model->payment_type ? $model->payment_type : ($model->transaction_reference ? 'Online payment' : ''); }); + ->addColumn('payment_type', function($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? 'Online payment' : ''); }); return $table->addColumn('amount', function($model) { return Utils::formatMoney($model->amount, $model->currency_id); }) ->addColumn('payment_date', function($model) { return Utils::dateToString($model->payment_date); }) diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php index 7f0e95c25dfa..8404f839e66f 100755 --- a/app/controllers/ReportController.php +++ b/app/controllers/ReportController.php @@ -24,54 +24,58 @@ class ReportController extends \BaseController { $datasets = []; $labels = []; $maxTotals = 0; - - foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) + $width = 10; + + if (Auth::user()->account->isPro()) { - $records = DB::table($entityType.'s') - ->select(DB::raw('sum(amount) as total, '.$groupBy.'('.$entityType.'_date) as '.$groupBy)) - ->where($entityType.'s.deleted_at', '=', null) - ->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d')) - ->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d')) - ->groupBy($groupBy); - - $totals = $records->lists('total'); - $dates = $records->lists($groupBy); - $data = array_combine($dates, $totals); - - $interval = new DateInterval('P1'.substr($groupBy, 0, 1)); - $period = new DatePeriod($startDate, $interval, $endDate); - - $totals = []; - - foreach ($period as $d) + foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) { - $dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n'); - $date = $d->format($dateFormat); - $totals[] = isset($data[$date]) ? $data[$date] : 0; + $records = DB::table($entityType.'s') + ->select(DB::raw('sum(amount) as total, '.$groupBy.'('.$entityType.'_date) as '.$groupBy)) + ->where($entityType.'s.deleted_at', '=', null) + ->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d')) + ->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d')) + ->groupBy($groupBy); + + $totals = $records->lists('total'); + $dates = $records->lists($groupBy); + $data = array_combine($dates, $totals); + + $interval = new DateInterval('P1'.substr($groupBy, 0, 1)); + $period = new DatePeriod($startDate, $interval, $endDate); - if ($entityType == ENTITY_INVOICE) + $totals = []; + + foreach ($period as $d) { - $labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F'); - $label = $d->format($labelFormat); - $labels[] = $label; + $dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n'); + $date = $d->format($dateFormat); + $totals[] = isset($data[$date]) ? $data[$date] : 0; + + if ($entityType == ENTITY_INVOICE) + { + $labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F'); + $label = $d->format($labelFormat); + $labels[] = $label; + } + } + + $max = max($totals); + + if ($max > 0) + { + $datasets[] = [ + 'totals' => $totals, + 'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107') + ]; + $maxTotals = max($max, $maxTotals); } } - $max = max($totals); - - if ($max > 0) - { - $datasets[] = [ - 'totals' => $totals, - 'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107') - ]; - $maxTotals = max($max, $maxTotals); - } + $width = (ceil( $maxTotals / 100 ) * 100) / 10; + $width = max($width, 10); } - $width = (ceil( $maxTotals / 100 ) * 100) / 10; - $width = max($width, 10); - $dateTypes = [ 'DAYOFYEAR' => 'Daily', 'WEEK' => 'Weekly', @@ -92,7 +96,8 @@ class ReportController extends \BaseController { 'chartType' => $chartType, 'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)), 'endDate' => $endDate->modify('-1'.$padding)->format(Session::get(SESSION_DATE_FORMAT)), - 'groupBy' => $groupBy + 'groupBy' => $groupBy, + 'feature' => ACCOUNT_CHART_BUILDER, ]; return View::make('reports.report_builder', $params); diff --git a/app/database/seeds/ConstantsSeeder.php b/app/database/seeds/ConstantsSeeder.php index 0e31b564c3ae..f5ef0f6ee0d1 100755 --- a/app/database/seeds/ConstantsSeeder.php +++ b/app/database/seeds/ConstantsSeeder.php @@ -46,6 +46,7 @@ class ConstantsSeeder extends Seeder PaymentType::create(array('name' => 'Credit Card Other')); PaymentType::create(array('name' => 'PayPal')); PaymentType::create(array('name' => 'Google Wallet')); + PaymentType::create(array('name' => 'Check')); Theme::create(array('name' => 'amelia')); Theme::create(array('name' => 'cerulean')); diff --git a/app/lang/de/texts.php b/app/lang/de/texts.php index b8a05f5564f7..80994e330da9 100644 --- a/app/lang/de/texts.php +++ b/app/lang/de/texts.php @@ -334,5 +334,8 @@ return array( 'archived_product' => 'Produkt erfolgreich archiviert', 'product_library' => 'Produktbibliothek', + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', + ); diff --git a/app/lang/en/texts.php b/app/lang/en/texts.php index 6bf89a1c6213..211ca6629a8d 100644 --- a/app/lang/en/texts.php +++ b/app/lang/en/texts.php @@ -348,5 +348,8 @@ return array( 'specify_colors' => 'Specify colors', 'specify_colors_label' => 'Select the colors used in the invoice', + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', ); + diff --git a/app/lang/es/texts.php b/app/lang/es/texts.php index b79e23eeb930..b03704badd13 100644 --- a/app/lang/es/texts.php +++ b/app/lang/es/texts.php @@ -332,5 +332,8 @@ return array( 'updated_product' => 'Successfully updated product', 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', + + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', ); diff --git a/app/lang/fr/texts.php b/app/lang/fr/texts.php index 55549733eea6..d3f677279d08 100644 --- a/app/lang/fr/texts.php +++ b/app/lang/fr/texts.php @@ -333,5 +333,8 @@ return array( 'updated_product' => 'Successfully updated product', 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', + + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', ); diff --git a/app/lang/it/texts.php b/app/lang/it/texts.php index c7bc93202d53..bba41f289c46 100644 --- a/app/lang/it/texts.php +++ b/app/lang/it/texts.php @@ -334,4 +334,7 @@ return array( 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', + ); diff --git a/app/lang/nl/texts.php b/app/lang/nl/texts.php index 3696b00543db..fa4deda05369 100644 --- a/app/lang/nl/texts.php +++ b/app/lang/nl/texts.php @@ -334,5 +334,8 @@ return array( 'updated_product' => 'Successfully updated product', 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', + + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', ); diff --git a/app/lang/pt_BR/texts.php b/app/lang/pt_BR/texts.php index e6a0fb30f69c..c7597a53c2b6 100644 --- a/app/lang/pt_BR/texts.php +++ b/app/lang/pt_BR/texts.php @@ -323,5 +323,8 @@ return array( 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', + 'chart_builder' => 'Chart Builder', + 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', + ); diff --git a/app/libraries/utils.php b/app/libraries/utils.php index 42c2e9c897d9..b0b76f5feab0 100755 --- a/app/libraries/utils.php +++ b/app/libraries/utils.php @@ -219,7 +219,7 @@ class Utils return $date->format($format); } - public static function toSqlDate($date) + public static function toSqlDate($date, $formatResult = true) { if (!$date) { @@ -229,10 +229,12 @@ class Utils $timezone = Session::get(SESSION_TIMEZONE); $format = Session::get(SESSION_DATE_FORMAT); - return DateTime::createFromFormat($format, $date, new DateTimeZone($timezone))->format('Y-m-d'); + + $dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone)); + return $formatResult ? $dateTime->format('Y-m-d') : $dateTime; } - public static function fromSqlDate($date) + public static function fromSqlDate($date, $formatResult = true) { if (!$date || $date == '0000-00-00') { @@ -241,8 +243,9 @@ class Utils $timezone = Session::get(SESSION_TIMEZONE); $format = Session::get(SESSION_DATE_FORMAT); - - return DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone))->format($format); + + $dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone)); + return $formatResult ? $dateTime->format($format) : $dateTime; } public static function today($formatResult = true) diff --git a/app/ninja/repositories/AccountRepository.php b/app/ninja/repositories/AccountRepository.php index d6d5102fb0ea..63aeb24ec486 100755 --- a/app/ninja/repositories/AccountRepository.php +++ b/app/ninja/repositories/AccountRepository.php @@ -198,6 +198,7 @@ class AccountRepository $client = new Client; $client->public_id = Auth::user()->account_id; $client->user_id = $ninjaAccount->users()->first()->id; + $client->currency_id = 1; foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone'] as $field) { $client->$field = Auth::user()->account->$field; diff --git a/app/ninja/repositories/PaymentRepository.php b/app/ninja/repositories/PaymentRepository.php index d9a126e2c253..f252aede935f 100755 --- a/app/ninja/repositories/PaymentRepository.php +++ b/app/ninja/repositories/PaymentRepository.php @@ -18,7 +18,7 @@ class PaymentRepository ->where('payments.account_id', '=', \Auth::user()->account_id) ->where('clients.deleted_at', '=', null) ->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', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type'); + ->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', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id'); if (!\Session::get('show_trash')) { @@ -99,6 +99,7 @@ class PaymentRepository $payment->payment_type_id = $paymentTypeId; $payment->payment_date = Utils::toSqlDate($input['payment_date']); $payment->amount = $amount; + $payment->transaction_reference = trim($input['transaction_reference']); $payment->save(); return $payment; diff --git a/app/routes.php b/app/routes.php index d26ab474c544..58445778a4f7 100755 --- a/app/routes.php +++ b/app/routes.php @@ -11,8 +11,6 @@ | */ - - //apc_clear_cache(); //Cache::flush(); @@ -74,9 +72,12 @@ Route::group(array('before' => 'auth'), function() Route::post('company/products/{product_id?}', 'AccountController@saveProduct'); */ + Route::get('company/advanced_settings/chart_builder', 'ReportController@report'); + Route::post('company/advanced_settings/chart_builder', 'ReportController@report'); + Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); - Route::get('company/{section?}', 'AccountController@showSection'); - Route::post('company/{section?}', 'AccountController@doSection'); + Route::get('company/{section?}/{sub_section?}', 'AccountController@showSection'); + Route::post('company/{section?}/{sub_section?}', 'AccountController@doSection'); Route::post('user/setTheme', 'UserController@setTheme'); Route::post('remove_logo', 'AccountController@removeLogo'); Route::post('account/go_pro', 'AccountController@enableProPlan'); @@ -104,19 +105,17 @@ Route::group(array('before' => 'auth'), function() Route::resource('credits', 'CreditController'); Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create'); Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); - Route::post('credits/bulk', 'CreditController@bulk'); - - Route::get('reports', 'ReportController@report'); - Route::post('reports', 'ReportController@report'); + Route::post('credits/bulk', 'CreditController@bulk'); }); +// If you're self hosting set this to a value you think is fair +define('PRO_PLAN_PRICE', 50); define('CONTACT_EMAIL', 'contact@invoiceninja.com'); define('CONTACT_NAME', 'Invoice Ninja'); define('SITE_URL', 'https://www.invoiceninja.com'); - define('ENV_DEVELOPMENT', 'local'); define('ENV_STAGING', 'staging'); define('ENV_PRODUCTION', 'fortrabbit'); @@ -137,8 +136,12 @@ define('ACCOUNT_IMPORT_EXPORT', 'import_export'); define('ACCOUNT_PAYMENTS', 'payments'); define('ACCOUNT_MAP', 'import_map'); define('ACCOUNT_EXPORT', 'export'); -define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings'); define('ACCOUNT_PRODUCTS', 'products'); +define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings'); +define('ACCOUNT_CUSTOM_FIELDS', 'custom_fields'); +define('ACCOUNT_INVOICE_DESIGN', 'invoice_design'); +define('ACCOUNT_CHART_BUILDER', 'chart_builder'); + define('DEFAULT_INVOICE_NUMBER', '0001'); define('RECENTLY_VIEWED_LIMIT', 8); @@ -191,7 +194,6 @@ define('GATEWAY_PAYPAL_EXPRESS', 17); define('GATEWAY_BEANSTREAM', 29); define('GATEWAY_PSIGATE', 30); -define('PRO_PLAN_PRICE', 50); define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN'); define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'); define('NINJA_GATEWAY_ID', GATEWAY_AUTHORIZE_NET); @@ -225,7 +227,7 @@ HTML::macro('menu_link', function($type) { $types = $type.'s'; $Type = ucfirst($type); $Types = ucfirst($types); - $class = ( Request::is($types) || Request::is('*'.$type.'*')) ? ' active' : ''; + $class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*advanced_settings*') ? ' active' : ''; return '
+ +
+ +@if (!Auth::user()->account->isPro()) +