This commit is contained in:
Troels Liebe Bentsen 2014-10-05 15:19:20 +02:00
commit 20bf88b028
35 changed files with 1916 additions and 1407 deletions

View File

@ -9,13 +9,15 @@ If you'd like to use our code to sell your own invoicing app get in touch.
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. Additionally, the hope is the codebase will serve as a sample site for Laravel as well as other JavaScript technologies.
To setup the site you can either use this [zip file](http://hillelcoren.com/invoice-ninja/self-hosting/) (easier to setup) or checkout the code from GitHub following the instructions below (easier to stay up to date). There's also a more detailed setup guide [available here](http://hillelcoren.com/invoice-ninja/laravel-ubuntu-virtualbox/). To deploy the app with [Docker](http://www.docker.com/) you can use [this project](https://github.com/rollbrettler/Dockerfiles/tree/master/invoice-ninja). To setup the site you can either use this [zip file](http://hillelcoren.com/invoice-ninja/self-hosting/) (easier to setup) or checkout the code from GitHub following the instructions below (easier to stay up to date). There's also a more detailed setup guide [available here](http://hillelcoren.com/invoice-ninja/laravel-ubuntu-virtualbox/).
For a WAMP/MAMP/LAMP setup you can one-click install using Softaculous's [AMPPS](http://www.ampps.com/). To deploy the app with [Docker](http://www.docker.com/) you can use [this project](https://github.com/rollbrettler/Dockerfiles/tree/master/invoice-ninja).
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). 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. If you'd like to translate the site please use [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) for the starter files.
Site design by [kantorp-wegl.in](http://kantorp-wegl.in/) Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kantorp-wegl.in](http://kantorp-wegl.in/).
### Features ### Features
@ -25,6 +27,8 @@ Site design by [kantorp-wegl.in](http://kantorp-wegl.in/)
* Recurring invoices * Recurring invoices
* Tax rates and payment terms * Tax rates and payment terms
* Multi-user support * Multi-user support
* [Zapier](https://zapier.com/) integration
* [D3.js](http://d3js.org/) visualizations
### Steps to setup ### Steps to setup

View File

@ -0,0 +1,88 @@
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class CreateRandomData extends Command {
protected $name = 'ninja:create-data';
protected $description = 'Create random data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running CreateRandomData...');
$user = User::first();
if (!$user) {
$this->error("Error: please create user account by logging in");
return;
}
$productNames = ['Arkansas', 'New York', 'Arizona', 'California', 'Colorado', 'Alabama', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'Alaska', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
$clientNames = ['IBM', 'Nestle', 'Mitsubishi UFJ Financial', 'Vodafone', 'Eni', 'Procter & Gamble', 'Johnson & Johnson', 'American International Group', 'Banco Santander', 'BHP Billiton', 'Pfizer', 'Itaú Unibanco Holding', 'Ford Motor', 'BMW Group', 'Commonwealth Bank', 'EDF', 'Statoil', 'Google', 'Siemens', 'Novartis', 'Royal Bank of Canada', 'Sumitomo Mitsui Financial', 'Comcast', 'Sberbank', 'Goldman Sachs Group', 'Westpac Banking Group', 'Nippon Telegraph & Tel', 'Ping An Insurance Group', 'Banco Bradesco', 'Anheuser-Busch InBev', 'Bank of Communications', 'China Life Insurance', 'General Motors', 'Telefónica', 'MetLife', 'Honda Motor', 'Enel', 'BASF', 'Softbank', 'National Australia Bank', 'ANZ', 'ConocoPhillips', 'TD Bank Group', 'Intel', 'UBS', 'Hewlett-Packard', 'Coca-Cola', 'Cisco Systems', 'UnitedHealth Group', 'Boeing', 'Zurich Insurance Group', 'Hyundai Motor', 'Sanofi', 'Credit Agricole', 'United Technologies', 'Roche Holding', 'Munich Re', 'PepsiCo', 'Oracle', 'Bank of Nova Scotia'];
for ($i=1; $i<=40; $i++) {
$product = Product::createNew($user);
$product->id = $i;
$product->product_key = $productNames[$i-1];
$product->save();
}
for ($i=0; $i<60; $i++) {
$client = Client::createNew($user);
$client->name = $clientNames[$i];
$client->save();
$contact = Contact::createNew($user);
$contact->email = "client@aol.com";
$contact->is_primary = 1;
$client->contacts()->save($contact);
$numInvoices = rand(1, 25);
if ($numInvoices == 4 || $numInvoices == 10 || $numInvoices == 25) {
// leave these
} else if ($numInvoices % 3 == 0) {
$numInvoices = 1;
} else if ($numInvoices > 10) {
$numInvoices = $numInvoices % 2;
}
$paidUp = rand(0, 1) == 1;
for ($j=1; $j<=$numInvoices; $j++) {
$price = rand(10, 1000);
if ($price < 900) {
$price = rand(10, 150);
}
$invoice = Invoice::createNew($user);
$invoice->invoice_number = $user->account->getNextInvoiceNumber();
$invoice->amount = $invoice->balance = $price;
$invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
$client->invoices()->save($invoice);
$productId = rand(0, 40);
if ($productId > 20) {
$productId = ($productId % 2) + rand(0, 2);
}
$invoiceItem = InvoiceItem::createNew($user);
$invoiceItem->product_id = $productId+1;
$invoiceItem->product_key = $productNames[$invoiceItem->product_id];
$invoiceItem->cost = $invoice->amount;
$invoiceItem->qty = 1;
$invoice->invoice_items()->save($invoiceItem);
if ($paidUp || rand(0,2) > 1) {
$payment = Payment::createNew($user);
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->amount;
$client->payments()->save($payment);
}
}
}
}
}

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class ResetData extends Command {
protected $name = 'ninja:reset-data';
protected $description = 'Reset data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running ResetData...');
if (!Utils::isNinjaDev()) {
return;
}
Artisan::call('migrate:reset');
Artisan::call('migrate');
Artisan::call('db:seed');
}
}

View File

@ -36,6 +36,18 @@ class AccountController extends \BaseController {
public function update() public function update()
{ {
if (!Utils::isNinja()) { if (!Utils::isNinja()) {
// populate migrations if the application was initially setup using database.sql
$migrations = DB::table('migrations')->get();
if (Schema::hasTable('accounts') && count($migrations) == 0) {
$migrations = ['2013_11_05_180133_confide_setup_users_table', '2013_11_28_195703_setup_countries_table', '2014_02_13_151500_add_cascase_drops', '2014_02_19_151817_add_support_for_invoice_designs', '2014_03_03_155556_add_phone_to_account', '2014_03_19_201454_add_language_support', '2014_03_20_200300_create_payment_libraries', '2014_03_23_051736_enable_forcing_jspdf', '2014_03_25_102200_add_sort_and_recommended_to_gateways', '2014_04_03_191105_add_pro_plan', '2014_04_17_100523_add_remember_token', '2014_04_17_145108_add_custom_fields', '2014_04_23_170909_add_products_settings', '2014_04_29_174315_add_advanced_settings', '2014_05_17_175626_add_quotes', '2014_06_17_131940_add_accepted_credit_cards_to_account_gateways', '2014_07_13_142654_one_click_install', '2014_07_17_205900_support_hiding_quantity', '2014_07_24_171214_add_zapier_support'];
foreach ($migrations as $migration) {
DB::table('migrations')->insert([
'migration' => $migration,
'batch' => 1
]);
}
}
try { try {
Artisan::call('migrate'); Artisan::call('migrate');
} catch (Exception $e) { } catch (Exception $e) {
@ -46,6 +58,7 @@ class AccountController extends \BaseController {
return Redirect::to('/'); return Redirect::to('/');
} }
/*
public function reset() public function reset()
{ {
if (Utils::isNinjaDev()) { if (Utils::isNinjaDev()) {
@ -61,6 +74,7 @@ class AccountController extends \BaseController {
return Redirect::to('/'); return Redirect::to('/');
} }
*/
public function getStarted() public function getStarted()
{ {
@ -152,7 +166,7 @@ class AccountController extends \BaseController {
{ {
$accountGateway = $account->account_gateways[0]; $accountGateway = $account->account_gateways[0];
$config = $accountGateway->config; $config = $accountGateway->config;
$selectedCards = $accountGateway->accepted_credit_cards; $selectedCards = $accountGateway->accepted_credit_cards;
$configFields = json_decode($config); $configFields = json_decode($config);
@ -160,6 +174,9 @@ class AccountController extends \BaseController {
{ {
$configFields->$configField = str_repeat('*', strlen($value)); $configFields->$configField = str_repeat('*', strlen($value));
} }
} else {
$accountGateway = AccountGateway::createNew();
$accountGateway->gateway_id = GATEWAY_MOOLAH;
} }
$recommendedGateways = Gateway::remember(DEFAULT_QUERY_CACHE) $recommendedGateways = Gateway::remember(DEFAULT_QUERY_CACHE)
@ -197,24 +214,11 @@ class AccountController extends \BaseController {
); );
$recommendedGatewayArray['Other Options'] = $otherItem; $recommendedGatewayArray['Other Options'] = $otherItem;
$data = [ $gateways = Gateway::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get();
'account' => $account,
'accountGateway' => $accountGateway,
'config' => $configFields,
'gateways' => Gateway::remember(DEFAULT_QUERY_CACHE)
->orderBy('name')
->get(),
'dropdownGateways' => Gateway::remember(DEFAULT_QUERY_CACHE)
->where('recommended', '=', '0')
->orderBy('name')
->get(),
'recommendedGateways' => $recommendedGatewayArray,
'creditCardTypes' => $creditCards,
];
foreach ($data['gateways'] as $gateway) foreach ($gateways as $gateway)
{ {
$paymentLibrary = $gateway->paymentlibrary; $paymentLibrary = $gateway->paymentlibrary;
$gateway->fields = $gateway->getFields(); $gateway->fields = $gateway->getFields();
@ -224,6 +228,19 @@ class AccountController extends \BaseController {
} }
} }
$data = [
'account' => $account,
'accountGateway' => $accountGateway,
'config' => $configFields,
'gateways' => $gateways,
'dropdownGateways' => Gateway::remember(DEFAULT_QUERY_CACHE)
->where('recommended', '=', '0')
->orderBy('name')
->get(),
'recommendedGateways' => $recommendedGatewayArray,
'creditCardTypes' => $creditCards,
];
return View::make('accounts.payments', $data); return View::make('accounts.payments', $data);
} }
else if ($section == ACCOUNT_NOTIFICATIONS) else if ($section == ACCOUNT_NOTIFICATIONS)

View File

@ -3,18 +3,20 @@
use ninja\repositories\PaymentRepository; use ninja\repositories\PaymentRepository;
use ninja\repositories\InvoiceRepository; use ninja\repositories\InvoiceRepository;
use ninja\repositories\AccountRepository; use ninja\repositories\AccountRepository;
use ninja\mailers\ContactMailer;
class PaymentController extends \BaseController class PaymentController extends \BaseController
{ {
protected $creditRepo; protected $creditRepo;
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo) public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer)
{ {
parent::__construct(); parent::__construct();
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
} }
public function index() public function index()
@ -423,6 +425,9 @@ class PaymentController extends \BaseController
'hideHeader' => true 'hideHeader' => true
]; ];
$name = "{$license->first_name} {$license->last_name}";
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, LICENSE_PRICE, $license->license_key);
return View::make('public.license', $data); return View::make('public.license', $data);
//return Redirect::away(Session::get('return_url') . "?license_key={$license->license_key}"); //return Redirect::away(Session::get('return_url') . "?license_key={$license->license_key}");

View File

@ -2,6 +2,31 @@
class ReportController extends \BaseController { class ReportController extends \BaseController {
public function d3()
{
$message = '';
if (Auth::user()->account->isPro()) {
$account = Auth::user()->account;
$account = $account->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
$account = $account->hideFieldsForViz();
$clients = $account->clients->toJson();
} else if (isset($_ENV['DATA_VIZ_SAMPLE'])) {
$clients = $_ENV['DATA_VIZ_SAMPLE'];
$message = trans('texts.sample_data');
} else {
$clients = '[]';
}
$data = [
'feature' => ACCOUNT_DATA_VISUALIZATIONS,
'clients' => $clients,
'message' => $message
];
return View::make('reports.d3', $data);
}
public function report() public function report()
{ {
if (Input::all()) if (Input::all())

View File

@ -12,6 +12,20 @@ class PaymentLibrariesSeeder extends Seeder
array('name'=>'Psigate', 'provider'=>'Psigate', 'payment_library_id' => 2) array('name'=>'Psigate', 'provider'=>'Psigate', 'payment_library_id' => 2)
]; ];
foreach ($gateways as $gateway)
{
Gateway::create($gateway);
}
Gateway::create([
'name' => 'moolah',
'provider' => 'AuthorizeNet_AIM',
'sort_order' => 1,
'recommended' => 1,
'site_url' => 'https://invoiceninja.mymoolah.com/',
]);
/*
$updateProviders = array( $updateProviders = array(
0 => 'AuthorizeNet_AIM', 0 => 'AuthorizeNet_AIM',
//1 => 'BeanStream', //1 => 'BeanStream',
@ -21,11 +35,6 @@ class PaymentLibrariesSeeder extends Seeder
5 => 'TwoCheckout' 5 => 'TwoCheckout'
); );
foreach ($gateways as $gateway)
{
Gateway::create($gateway);
}
Gateway::whereIn('provider', $updateProviders)->update(array('recommended' => 1)); Gateway::whereIn('provider', $updateProviders)->update(array('recommended' => 1));
Gateway::where('provider', '=', 'AuthorizeNet_AIM')->update(array('sort_order' => 5, 'site_url' => 'http://reseller.authorize.net/application/?id=5560364')); Gateway::where('provider', '=', 'AuthorizeNet_AIM')->update(array('sort_order' => 5, 'site_url' => 'http://reseller.authorize.net/application/?id=5560364'));
@ -33,5 +42,7 @@ class PaymentLibrariesSeeder extends Seeder
//Gateway::where('provider', '=', 'FirstData_Connect')->update(array('sort_order' => 20, 'site_url' => 'https://www.firstdata.com/')); //Gateway::where('provider', '=', 'FirstData_Connect')->update(array('sort_order' => 20, 'site_url' => 'https://www.firstdata.com/'));
Gateway::where('provider', '=', 'PayPal_Pro')->update(array('sort_order' => 25, 'site_url' => 'https://www.paypal.com/')); Gateway::where('provider', '=', 'PayPal_Pro')->update(array('sort_order' => 25, 'site_url' => 'https://www.paypal.com/'));
Gateway::where('provider', '=', 'TwoCheckout')->update(array('sort_order' => 30, 'site_url' => 'https://www.2checkout.com/referral?r=2c37ac2298')); Gateway::where('provider', '=', 'TwoCheckout')->update(array('sort_order' => 30, 'site_url' => 'https://www.2checkout.com/referral?r=2c37ac2298'));
*/
} }
} }

View File

@ -333,6 +333,13 @@ return array(
'created_product' => 'Produkt erfolgreich erstellt', 'created_product' => 'Produkt erfolgreich erstellt',
'archived_product' => 'Produkt erfolgreich archiviert', 'archived_product' => 'Produkt erfolgreich archiviert',
'product_library' => 'Produktbibliothek', 'product_library' => 'Produktbibliothek',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Diagrammersteller', 'chart_builder' => 'Diagrammersteller',
'ninja_email_footer' => 'Nutze :site um Kunden eine Rechnung zu stellen und online bezahlt zu werden, kostenlos!', 'ninja_email_footer' => 'Nutze :site um Kunden eine Rechnung zu stellen und online bezahlt zu werden, kostenlos!',
@ -407,10 +414,13 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -422,10 +422,14 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled,re you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -332,6 +332,13 @@ return array(
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product', 'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product', 'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder', 'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -405,10 +412,13 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -333,6 +333,13 @@ return array(
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product', 'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product', 'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder', 'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -407,10 +414,14 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -333,6 +333,13 @@ return array(
'updated_product' => 'Prodotto aggiornato con successo', 'updated_product' => 'Prodotto aggiornato con successo',
'created_product' => 'Prodotto creato con successo', 'created_product' => 'Prodotto creato con successo',
'archived_product' => 'Prodotto archiviato con successo', 'archived_product' => 'Prodotto archiviato con successo',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Creatore grafico', 'chart_builder' => 'Creatore grafico',
'ninja_email_footer' => 'Usa :site per fatturare ai tuoi clienti e venire pagato online gratis!', 'ninja_email_footer' => 'Usa :site per fatturare ai tuoi clienti e venire pagato online gratis!',
@ -407,10 +414,13 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -422,11 +422,15 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -422,10 +422,14 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -334,6 +334,13 @@ return array(
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product', 'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product', 'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder', 'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -408,10 +415,14 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -322,6 +322,13 @@ return array(
'updated_product' => 'Successfully updated product', 'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product', 'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product', 'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder', 'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -396,10 +403,14 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account', 'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back', 'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
); );

View File

@ -252,4 +252,49 @@ class Account extends Eloquent
{ {
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first(); return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first();
} }
public function hideFieldsForViz()
{
foreach ($this->clients as $client)
{
$client->setVisible([
'public_id',
'name',
'balance',
'paid_to_date',
'invoices',
'contacts',
]);
foreach ($client->invoices as $invoice)
{
$invoice->setVisible([
'public_id',
'invoice_number',
'amount',
'balance',
'invoice_status_id',
'invoice_items',
'created_at',
]);
foreach ($invoice->invoice_items as $invoiceItem)
{
$invoiceItem->setVisible([
'product_key',
'cost',
'qty',
]);
}
}
foreach ($client->contacts as $contact)
{
$contact->setVisible(['public_id']);
}
}
return $this;
}
} }

View File

@ -51,7 +51,7 @@ class Activity extends Eloquent
if ($entity) if ($entity)
{ {
$activity->user_id = $entity->user_id; $activity->user_id = $entity instanceof User ? $entity->id : $entity->user_id;
$activity->account_id = $entity->account_id; $activity->account_id = $entity->account_id;
} }
else if (Auth::check()) else if (Auth::check())
@ -272,7 +272,7 @@ class Activity extends Eloquent
} }
else else
{ {
$activity = Activity::getBlank(); $activity = Activity::getBlank($client);
$message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered ' . $payment->getName() . ' for '; $message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered ' . $payment->getName() . ' for ';
$activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice); $activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice);
} }

View File

@ -204,6 +204,7 @@ class Client extends EntityModel
return $this->created_at->format('m/d/y h:i a'); return $this->created_at->format('m/d/y h:i a');
} }
} }
} }
/* /*

View File

@ -5,7 +5,7 @@ class EntityModel extends Eloquent
protected $softDelete = true; protected $softDelete = true;
public $timestamps = true; public $timestamps = true;
protected $hidden = ['id', 'created_at', 'deleted_at', 'updated_at']; protected $hidden = ['id'];
public static function createNew($parent = false) public static function createNew($parent = false)
{ {
@ -14,7 +14,7 @@ class EntityModel extends Eloquent
if ($parent) if ($parent)
{ {
$entity->user_id = $parent->user_id; $entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id; $entity->account_id = $parent->account_id;
} }
else if (Auth::check()) else if (Auth::check())

View File

@ -73,4 +73,21 @@ class ContactMailer extends Mailer {
$user = $payment->invitation->user; $user = $payment->invitation->user;
$this->sendTo($payment->contact->email, $user->email, $user->getDisplayName(), $subject, $view, $data); $this->sendTo($payment->contact->email, $user->email, $user->getDisplayName(), $subject, $view, $data);
} }
public function sendLicensePaymentConfirmation($name, $email, $amount, $license)
{
$view = 'payment_confirmation';
$subject = trans('texts.payment_subject');
$data = [
'accountName' => trans('texts.email_from'),
'clientName' => $name,
'emailFooter' => false,
'paymentAmount' => Utils::formatMoney($amount, 1),
'showNinjaFooter' => false,
'emailMessage' => "Your license: $license",
];
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
} }

View File

@ -80,6 +80,7 @@ Route::group(array('before' => 'auth'), function()
Route::resource('products', 'ProductController'); Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive'); Route::get('products/{product_id}/archive', 'ProductController@archive');
Route::get('company/advanced_settings/data_visualizations', 'ReportController@d3');
Route::get('company/advanced_settings/chart_builder', 'ReportController@report'); Route::get('company/advanced_settings/chart_builder', 'ReportController@report');
Route::post('company/advanced_settings/chart_builder', 'ReportController@report'); Route::post('company/advanced_settings/chart_builder', 'ReportController@report');
@ -170,7 +171,7 @@ define('ACCOUNT_CUSTOM_FIELDS', 'custom_fields');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design'); define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder'); define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management'); define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
define('DEFAULT_INVOICE_NUMBER', '0001'); define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8); define('RECENTLY_VIEWED_LIMIT', 8);
@ -223,6 +224,7 @@ define('GATEWAY_AUTHORIZE_NET', 1);
define('GATEWAY_PAYPAL_EXPRESS', 17); define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_BEANSTREAM', 29); define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30); define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2); define('EVENT_CREATE_INVOICE', 2);

View File

@ -12,3 +12,5 @@
*/ */
Artisan::resolve('SendRecurringInvoices'); Artisan::resolve('SendRecurringInvoices');
Artisan::resolve('CreateRandomData');
Artisan::resolve('ResetData');

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
<ul class="nav nav-tabs nav nav-justified"> <ul class="nav nav-tabs nav nav-justified">
{{ HTML::nav_link('company/advanced_settings/custom_fields', 'custom_fields') }} {{ HTML::nav_link('company/advanced_settings/custom_fields', 'custom_fields') }}
{{ HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') }} {{ HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') }}
{{ HTML::nav_link('company/advanced_settings/data_visualizations', 'data_visualizations') }}
{{ HTML::nav_link('company/advanced_settings/chart_builder', 'chart_builder') }} {{ HTML::nav_link('company/advanced_settings/chart_builder', 'chart_builder') }}
{{ HTML::nav_link('company/advanced_settings/user_management', 'user_management') }} {{ HTML::nav_link('company/advanced_settings/user_management', 'user_management') }}
</ul> </ul>

View File

@ -11,28 +11,32 @@
@if ($accountGateway) @if ($accountGateway)
{{ Former::populateField('gateway_id', $accountGateway->gateway_id) }} {{ Former::populateField('gateway_id', $accountGateway->gateway_id) }}
{{ Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) }} {{ Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) }}
@foreach ($accountGateway->fields as $field => $junk) @if ($config)
@if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName'])) @foreach ($accountGateway->fields as $field => $junk)
{{-- do nothing --}} @if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName']))
@else {{-- do nothing --}}
{{ Former::populateField($accountGateway->gateway_id.'_'.$field, $config->$field) }} @else
@endif {{ Former::populateField($accountGateway->gateway_id.'_'.$field, $config->$field) }}
@endforeach @endif
@endforeach
@endif
@endif @endif
<div class="two-column"> <div class="two-column">
{{ Former::checkboxes('creditCardTypes[]')->label('Accepted Credit Cards') {{ Former::checkboxes('creditCardTypes[]')->label('Accepted Credit Cards')
->checkboxes($creditCardTypes)->class('creditcard-types') ->checkboxes($creditCardTypes)->class('creditcard-types')
}} }}
</div> </div>
<p/>&nbsp;<p/>
<div class="two-column"> <div class="two-column">
{{ Former::radios('recommendedGateway_id')->label('Recommended Gateways') {{ Former::radios('recommendedGateway_id')->label('Recommended Gateway')
->radios($recommendedGateways)->class('recommended-gateway') ->radios($recommendedGateways)->class('recommended-gateway')
}} }}
</div> </div>
{{ Former::select('gateway_id')->label('PayPal & Other Gateways')->addOption('', '') {{ Former::select('gateway_id')->label('Select Gateway')->addOption('', '')
->dataClass('gateway-dropdown') ->dataClass('gateway-dropdown')
->fromQuery($dropdownGateways, 'name', 'id') ->fromQuery($dropdownGateways, 'name', 'id')
->onchange('setFieldsShown()'); }} ->onchange('setFieldsShown()'); }}
@ -57,6 +61,8 @@
@endforeach @endforeach
<p/>&nbsp;<p/>
{{ Former::actions( Button::lg_success_submit('Save')->append_with_icon('floppy-disk') ) }} {{ Former::actions( Button::lg_success_submit('Save')->append_with_icon('floppy-disk') ) }}
{{ Former::close() }} {{ Former::close() }}

View File

@ -9,6 +9,10 @@
{{ trans('texts.payment_message', ['amount' => $paymentAmount]) }}<p/> {{ trans('texts.payment_message', ['amount' => $paymentAmount]) }}<p/>
@if (isset($emailMessage) && $emailMessage)
{{ $emailMessage }}<p/>
@endif
@if ($emailFooter) @if ($emailFooter)
{{ nl2br($emailFooter) }} {{ nl2br($emailFooter) }}
@else @else

View File

@ -2,6 +2,10 @@
{{ trans('texts.payment_message', ['amount' => $paymentAmount]) }} {{ trans('texts.payment_message', ['amount' => $paymentAmount]) }}
@if (isset($emailMessage) && $emailMessage)
{{ $emailMessage }}
@endif
@if ($emailFooter) @if ($emailFooter)
{{ $emailFooter }} {{ $emailFooter }}
@else @else

View File

@ -11,7 +11,7 @@
<div class="cell">Multi-user support</div> <div class="cell">Multi-user support</div>
<div class="cell">Quotes/pro-forma invoices</div> <div class="cell">Quotes/pro-forma invoices</div>
<div class="cell">Custom invoice fields and colors</div> <div class="cell">Custom invoice fields and colors</div>
<div class="cell">Dynamic chart builder</div> <div class="cell">Dynamic data vizualizations</div>
<div class="cell">Priority email support</div> <div class="cell">Priority email support</div>
<div class="cell">Remove "Created by Invoice Ninja"</div> <div class="cell">Remove "Created by Invoice Ninja"</div>
<div class="cell">Latest and greatest features</div> <div class="cell">Latest and greatest features</div>
@ -29,7 +29,7 @@
<div class="cell"><div class="hide-desktop">Multi-user support</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Multi-user support</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Quotes/pro-forma invoices</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Quotes/pro-forma invoices</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Custom fields and invoice colors</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Custom fields and invoice colors</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Dynamic chart builder</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Dynamic data vizualizations</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Priority email support</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Priority email support</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Remove "Created by Invoice Ninja"</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Remove "Created by Invoice Ninja"</div><span class="glyphicon glyphicon-remove"></div>
<div class="cell"><div class="hide-desktop">Latest and greatest features</div><span class="glyphicon glyphicon-remove"></div> <div class="cell"><div class="hide-desktop">Latest and greatest features</div><span class="glyphicon glyphicon-remove"></div>
@ -47,7 +47,7 @@
<div class="cell"><div class="hide-desktop">Multi-user support</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Multi-user support</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Quotes/pro-forma invoices</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Quotes/pro-forma invoices</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Custom invoice fields and colors</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Custom invoice fields and colors</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Dynamic chart builder</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Dynamic data vizualizations</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Priority email support</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Priority email support</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Remove "Created by Invoice Ninja"</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Remove "Created by Invoice Ninja"</div><span class="glyphicon glyphicon-ok"></div>
<div class="cell"><div class="hide-desktop">Latest and greatest features</div><span class="glyphicon glyphicon-ok"></div> <div class="cell"><div class="hide-desktop">Latest and greatest features</div><span class="glyphicon glyphicon-ok"></div>

View File

@ -0,0 +1,301 @@
@extends('accounts.nav')
@section('head')
@parent
<script src="{{ asset('vendor/d3/d3.min.js') }}" type="text/javascript"></script>
<style type="text/css">
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px 10px 2px 10px;
background-color: #F6F6F6;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
}
.no-pointer-events {
pointer-events: none;
}
</style>
@stop
@section('content')
@parent
@include('accounts.nav_advanced')
{{ Former::open() }}
{{ Former::legend('data_visualizations') }}
{{ Former::close() }}
<div id="tooltip" class="hidden">
<p>
<strong><span id="tooltipTitle"></span></strong>
<a class="pull-right" href="#" target="_blank">View</a>
</p>
<p>Total <span id="tooltipTotal" class="pull-right"></span></p>
<p>Balance <span id="tooltipBalance" class="pull-right"></span></p>
<p>Age <span id="tooltipAge" class="pull-right"></span></p>
</div>
<form class="form-inline" role="form">
Group By &nbsp;&nbsp;
<select id="groupBySelect" class="form-control" onchange="update()">
<option>Clients</option>
<option>Invoices</option>
<option>Products</option>
</select>
&nbsp;&nbsp; {{ $message }}
</form>
<p>&nbsp;</p>
<div class="svg-div"/>
<script type="text/javascript">
// store data as JSON
var data = {{ $clients }};
_.each(data, function(client) {
_.each(client.invoices, function(invoice) {
_.each(invoice.invoice_items, function(invoice_item) {
invoice_item.invoice = invoice;
});
});
});
// pre-process the possible groupings (clients, invoices and products)
var clients = data.concat();
var invoices = _.flatten(_.pluck(clients, 'invoices'));
// remove quotes and recurring invoices
invoices = _.filter(invoices, function(invoice) {
return !parseInt(invoice.is_quote) && !parseInt(invoice.is_recurring);
});
var products = _.flatten(_.pluck(invoices, 'invoice_items'));
products = d3.nest()
.key(function(d) { return d.product_key; })
.sortKeys(d3.ascending)
.rollup(function(d) { return {
amount: d3.sum(d, function(g) {
return g.qty * g.cost;
}),
paid: d3.sum(d, function(g) {
return g.invoice && g.invoice.invoice_status_id == 5 ? (g.qty * g.cost) : 0;
}),
age: d3.median(d, function(g) {
return calculateInvoiceAge(g.invoice) || null;
}),
}})
.entries(products);
// create standardized display properties
_.each(clients, function(client) {
client.displayName = getClientDisplayName(client);
client.displayTotal = +client.paid_to_date + +client.balance;
client.displayBalance = +client.balance;
client.displayPercent = (+client.paid_to_date / (+client.paid_to_date + +client.balance)).toFixed(2);
var oldestInvoice = _.max(client.invoices, function(invoice) { return calculateInvoiceAge(invoice) });
client.displayAge = oldestInvoice ? calculateInvoiceAge(oldestInvoice) : 0;
})
_.each(invoices, function(invoice) {
invoice.displayName = invoice.invoice_number;
invoice.displayTotal = +invoice.amount;
invoice.displayBalance = +invoice.balance;
invoice.displayPercent = parseInt((+invoice.amount - +invoice.balance) / +invoice.amount);
invoice.displayAge = calculateInvoiceAge(invoice);
})
_.each(products, function(product) {
product.displayName = product.key;
product.displayTotal = product.values.amount;
product.displayBalance = product.values.amount - product.values.paid;
product.displayPercent = (product.values.paid / product.values.amount).toFixed(2);
product.displayAge = product.values.age;
})
/*
_.each(clients, function(client) {
_.each(client.invoices, function(invoice) {
_.each(invoice.invoice_items, function(invoice_item) {
delete invoice_item.invoice;
});
});
});
*/
//console.log(JSON.stringify(clients));
//console.log(JSON.stringify(invoices));
//console.log(JSON.stringify(products));
var arc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 2 })
.outerRadius(function(d) { return d.r - 8 })
.startAngle(0);
var fullArc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 3 })
.outerRadius(function(d) { return d.r - 7 })
.startAngle(0)
.endAngle(2 * Math.PI);
var diameter = 1050,
format = d3.format(",d");
//color = d3.scale.category10();
var color = d3.scale.linear()
.domain([0, 100])
.range(["yellow", "red"]);
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return Math.max(30, d.displayTotal) })
.padding(12);
var svg = d3.select(".svg-div").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("class", "bubble");
svg.append("rect")
.attr("stroke-width", "1")
.attr("stroke", "rgb(150,150,150)")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
function update() {
var data = {};
var groupBy = $('#groupBySelect').val().toLowerCase();
data.children = window[groupBy];
data = bubble.nodes(data).filter(function(d) {
return !d.children && d.displayTotal && d.displayName;
});
var selection = svg.selectAll(".node")
.data(data, function(d) { return d.displayName; });
//.data(data);
var node = selection.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + (d.x+20) + "," + (d.y+20) + ")"; });
var visibleTooltip = false;
node.on("mousemove", function(d) {
if (!visibleTooltip || visibleTooltip != d.displayName) {
d3.select("#tooltip")
.classed("hidden", false)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + "px");
visibleTooltip = d.displayName;
}
d3.select("#tooltipTitle").text(truncate(d.displayName, 18));
d3.select("#tooltipTotal").text(formatMoney(d.displayTotal));
d3.select("#tooltipBalance").text(formatMoney(d.displayBalance));
d3.select("#tooltipAge").text(pluralize('? day', parseInt(d.displayAge)));
if (groupBy == "products" || !d.public_id) {
d3.select("#tooltip a").classed("hidden", true);
} else {
d3.select("#tooltip a").classed("hidden", false);
d3.select("#tooltip a").attr("href", "/" + groupBy + "/" + d.public_id);
}
});
svg.on("click", function() {
visibleTooltip = false;
d3.select("#tooltip")
.classed("hidden", true);
});
node.append("circle")
.attr("fill", "#ffffff")
.attr("r", function(d) { return d.r });
node.append("path")
.each(function(d) { d.endAngle = 0; })
.attr("class", "no-pointer-events")
.attr("class", "animate-fade")
.attr("d", fullArc)
.style("fill", function(d, i) { return 'white'; });
node.append("text")
.attr("dy", ".3em")
.attr("class", "no-pointer-events")
.style("text-anchor", "middle")
.text(function(d) { return d.displayName; });
node.append("path")
.each(function(d) { d.endAngle = 0; })
.attr("class", "no-pointer-events")
.attr("class", "animate-grow")
.attr("d", arc)
.style("fill", function(d, i) { return 'grey'; });
d3.selectAll("path.animate-grow")
.transition()
.delay(function(d, i) { return (Math.random() * 500) })
.duration(1000)
.call(arcTween, 5);
d3.selectAll("path.animate-fade")
.transition()
.duration(1000)
.style("fill", function(d, i) {
return d.displayAge ? color(d.displayAge) : 'grey';
});
selection.exit().remove();
}
update();
// http://bl.ocks.org/mbostock/5100636
function arcTween(transition, newAngle) {
transition.attrTween("d", function(d) {
var interpolate = d3.interpolate( 0, 360 * d.displayPercent * Math.PI/180 );
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
}
function calculateInvoiceAge(invoice) {
if (!invoice || invoice.invoice_status_id == 5) {
return 0;
}
return parseInt((new Date().getTime() - Date.parse(invoice.created_at)) / (1000*60*60*24));
}
function pluralize(string, count) {
string = string.replace('?', count);
if (count !== 1) {
string += 's';
}
return string;
};
</script>
@stop

View File

@ -10,6 +10,10 @@
@parent @parent
@include('accounts.nav_advanced') @include('accounts.nav_advanced')
{{ Former::open() }}
{{ Former::legend('chart_builder') }}
{{ Former::close() }}
<div class="row"> <div class="row">
<div class="col-lg-4"> <div class="col-lg-4">

View File

@ -18,10 +18,10 @@
"accounting": "~0.*", "accounting": "~0.*",
"pdfjs": "*", "pdfjs": "*",
"spectrum": "~1.3.4", "spectrum": "~1.3.4",
"handsontable": "*" "handsontable": "*",
"d3": "~3.4.11"
}, },
"resolutions": { "resolutions": {
"datatables": "~1.*",
"jquery": "~1.11" "jquery": "~1.11"
} }
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because it is too large Load Diff