1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00
This commit is contained in:
David Bomba 2016-03-02 06:46:47 +11:00
commit 1e39a85066
94 changed files with 3710 additions and 1917 deletions

View File

@ -20,11 +20,25 @@ MAIL_FROM_ADDRESS
MAIL_FROM_NAME MAIL_FROM_NAME
MAIL_PASSWORD MAIL_PASSWORD
#POSTMARK_API_TOKEN=
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single LOG=single
REQUIRE_HTTPS=false REQUIRE_HTTPS=false
API_SECRET=password API_SECRET=password
GOOGLE_CLIENT_ID #TRUSTED_PROXIES=
GOOGLE_CLIENT_SECRET
GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google #SESSION_DRIVER=
#SESSION_DOMAIN=
#SESSION_ENCRYPT=
#SESSION_SECURE=
#CACHE_DRIVER=
#CACHE_HOST=
#CACHE_PORT1=
#CACHE_PORT2=
#GOOGLE_CLIENT_ID=
#GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google

View File

@ -3,7 +3,7 @@ language: php
sudo: true sudo: true
php: php:
- 5.5 - 5.5.9
# - 5.6 # - 5.6
# - 7.0 # - 7.0
# - hhvm # - hhvm
@ -64,16 +64,17 @@ before_script:
script: script:
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php
#- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
#- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php

View File

@ -95,19 +95,19 @@ module.exports = function(grunt) {
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/typeahead.js/dist/typeahead.min.js', 'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
'public/vendor/accounting/accounting.min.js', 'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js', 'public/vendor/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js', 'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.min.js', 'public/vendor/moment/min/moment.min.js',
'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js', 'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js',
'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js', 'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js',
'public/vendor/fuse.js/src/fuse.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js', //'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js', //'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js', //'public/vendor/pdfmake/build/pdfmake.min.js',
//'public/vendor/pdfmake/build/vfs_fonts.js', //'public/vendor/pdfmake/build/vfs_fonts.js',
//'public/js/vfs_fonts.js', //'public/js/vfs_fonts.js',
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js', 'public/js/bootstrap-combobox.js',
'public/js/script.js', 'public/js/script.js',
'public/js/pdf.pdfmake.js', 'public/js/pdf.pdfmake.js',
@ -140,7 +140,6 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.css', 'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css', 'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css', 'public/css/typeahead.js-bootstrap.css',
'public/css/lightbox.css',
//'public/vendor/handsontable/dist/jquery.handsontable.full.css', //'public/vendor/handsontable/dist/jquery.handsontable.full.css',
'public/css/style.css', 'public/css/style.css',
], ],

View File

@ -0,0 +1,63 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\AccountRepository;
use App\Services\PaymentService;
use App\Models\Invoice;
class ChargeRenewalInvoices extends Command
{
protected $name = 'ninja:charge-renewals';
protected $description = 'Charge renewal invoices';
protected $mailer;
protected $accountRepo;
protected $paymentService;
public function __construct(Mailer $mailer, AccountRepository $repo, PaymentService $paymentService)
{
parent::__construct();
$this->mailer = $mailer;
$this->accountRepo = $repo;
$this->paymentService = $paymentService;
}
public function fire()
{
$this->info(date('Y-m-d').' ChargeRenewalInvoices...');
$account = $this->accountRepo->getNinjaAccount();
$invoices = Invoice::whereAccountId($account->id)
->whereDueDate(date('Y-m-d'))
->with('client')
->orderBy('id')
->get();
$this->info(count($invoices).' invoices found');
foreach ($invoices as $invoice) {
$this->info("Charging invoice {$invoice->invoice_number}");
$this->paymentService->autoBillInvoice($invoice);
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -47,7 +47,7 @@ class SendRenewalInvoices extends Command
} }
$client = $this->accountRepo->getNinjaClient($account); $client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client); $invitation = $this->accountRepo->createNinjaInvoice($client, $account);
// set the due date to 10 days from now // set the due date to 10 days from now
$invoice = $invitation->invoice; $invoice = $invitation->invoice;

View File

@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel
'App\Console\Commands\ResetData', 'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData', 'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendRenewalInvoices',
'App\Console\Commands\ChargeRenewalInvoices',
'App\Console\Commands\SendReminders', 'App\Console\Commands\SendReminders',
'App\Console\Commands\TestOFX', 'App\Console\Commands\TestOFX',
'App\Console\Commands\GenerateResources', 'App\Console\Commands\GenerateResources',

View File

@ -47,6 +47,8 @@ class Handler extends ExceptionHandler {
if ($e instanceof ModelNotFoundException) { if ($e instanceof ModelNotFoundException) {
return Redirect::to('/'); return Redirect::to('/');
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) { } elseif ($e instanceof \Illuminate\Session\TokenMismatchException) {
// prevent loop since the page auto-submits
if ($request->path() != 'get_started') {
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e // https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
return redirect() return redirect()
->back() ->back()
@ -55,6 +57,7 @@ class Handler extends ExceptionHandler {
'warning' => trans('texts.token_expired') 'warning' => trans('texts.token_expired')
]); ]);
} }
}
// In production, except for maintenance mode, we'll show a custom error screen // In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) { if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {

View File

@ -117,4 +117,77 @@ class AccountApiController extends BaseAPIController
return $this->response($account); return $this->response($account);
} }
public function addDeviceToken(Request $request)
{
$account = Auth::user()->account;
//scan if this user has a token already registered (tokens can change, so we need to use the users email as key)
$devices = json_decode($account->devices,TRUE);
for($x=0; $x<count($devices); $x++)
{
if ($devices[$x]['email'] == Auth::user()->username) {
$devices[$x]['token'] = $request->token; //update
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
}
//User does not have a device, create new record
$newDevice = [
'token' => $request->token,
'email' => $request->email,
'device' => $request->device,
'notify_sent' => TRUE,
'notify_viewed' => TRUE,
'notify_approved' => TRUE,
'notify_paid' => TRUE,
];
$devices[] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
public function updatePushNotifications(Request $request)
{
$account = Auth::user()->account;
$devices = json_decode($account->devices, TRUE);
if(count($devices)<1)
return $this->errorResponse(['message'=>'no devices exist'], 400);
for($x=0; $x<count($devices); $x++)
{
if($devices[$x]['email'] == Auth::user()->username)
{
unset($devices[$x]);
$newDevice = [
'token' => $request->token,
'email' => $request->email,
'device' => $request->device,
'notify_sent' => $request->notify_sent,
'notify_viewed' => $request->notify_viewed,
'notify_approved' => $request->notify_approved,
'notify_paid' => $request->notify_paid,
];
$devices[] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
}
}
} }

View File

@ -15,6 +15,7 @@ use Response;
use Request; use Request;
use App\Models\Affiliate; use App\Models\Affiliate;
use App\Models\License; use App\Models\License;
use App\Models\Invoice;
use App\Models\User; use App\Models\User;
use App\Models\Account; use App\Models\Account;
use App\Models\Gateway; use App\Models\Gateway;
@ -393,6 +394,21 @@ class AccountController extends BaseController
if ($section == ACCOUNT_CUSTOMIZE_DESIGN) { if ($section == ACCOUNT_CUSTOMIZE_DESIGN) {
$data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design; $data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design;
// sample invoice to help determine variables
$invoice = Invoice::scope()
->with('client', 'account')
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->first();
if ($invoice) {
$invoice->hidePrivateFields();
unset($invoice->account);
unset($invoice->invoice_items);
unset($invoice->client->contacts);
$data['sampleInvoice'] = $invoice;
}
} }
return View::make("accounts.{$section}", $data); return View::make("accounts.{$section}", $data);
@ -416,6 +432,7 @@ class AccountController extends BaseController
'client_view_css' => $css, 'client_view_css' => $css,
'title' => trans("texts.client_portal"), 'title' => trans("texts.client_portal"),
'section' => ACCOUNT_CLIENT_PORTAL, 'section' => ACCOUNT_CLIENT_PORTAL,
'account' => $account,
]; ];
return View::make("accounts.client_portal", $data); return View::make("accounts.client_portal", $data);
@ -528,6 +545,7 @@ class AccountController extends BaseController
$account = Auth::user()->account; $account = Auth::user()->account;
$account->client_view_css = $sanitized_css; $account->client_view_css = $sanitized_css;
$account->enable_client_portal = Input::get('enable_client_portal') ? true : false;
$account->save(); $account->save();
Session::flash('message', trans('texts.updated_settings')); Session::flash('message', trans('texts.updated_settings'));
@ -668,6 +686,8 @@ class AccountController extends BaseController
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false; $account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
$account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1')); $account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1'));
$account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2')); $account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2'));
$account->custom_invoice_item_label1 = trim(Input::get('custom_invoice_item_label1'));
$account->custom_invoice_item_label2 = trim(Input::get('custom_invoice_item_label2'));
$account->invoice_number_counter = Input::get('invoice_number_counter'); $account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix'); $account->quote_number_prefix = Input::get('quote_number_prefix');
@ -676,6 +696,7 @@ class AccountController extends BaseController
$account->invoice_footer = Input::get('invoice_footer'); $account->invoice_footer = Input::get('invoice_footer');
$account->quote_terms = Input::get('quote_terms'); $account->quote_terms = Input::get('quote_terms');
$account->auto_convert_quote = Input::get('auto_convert_quote'); $account->auto_convert_quote = Input::get('auto_convert_quote');
$account->recurring_invoice_number_prefix = Input::get('recurring_invoice_number_prefix');
if (Input::has('recurring_hour')) { if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour'); $account->recurring_hour = Input::get('recurring_hour');

View File

@ -243,6 +243,7 @@ class AppController extends BaseController
if (!Utils::isNinjaProd()) { if (!Utils::isNinjaProd()) {
try { try {
set_time_limit(60 * 5); set_time_limit(60 * 5);
Artisan::call('optimize', array('--force' => true));
Cache::flush(); Cache::flush();
Session::flush(); Session::flush();
Artisan::call('migrate', array('--force' => true)); Artisan::call('migrate', array('--force' => true));
@ -250,11 +251,14 @@ class AppController extends BaseController
'PaymentLibraries', 'PaymentLibraries',
'Fonts', 'Fonts',
'Banks', 'Banks',
'InvoiceStatus' 'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) { ] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder")); Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
} }
Artisan::call('optimize', array('--force' => true));
Event::fire(new UserSettingsChanged()); Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates')); Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) { } catch (Exception $e) {
@ -288,7 +292,7 @@ class AppController extends BaseController
} }
if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) { if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) {
$payments = DB::table('accounts') $data = DB::table('accounts')
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id') ->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id') ->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY) ->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
@ -300,15 +304,9 @@ class AppController extends BaseController
'payments.amount' 'payments.amount'
]); ]);
} else { } else {
$payments = DB::table('accounts') $data = DB::table('users')->count();
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
->where('payments.is_deleted', '=', false)
->groupBy('clients.id')
->count();
} }
return json_encode($payments); return json_encode($data);
} }
} }

View File

@ -10,8 +10,6 @@ use App\Events\UserLoggedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
use App\Services\AuthService; use App\Services\AuthService;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Registrar;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller { class AuthController extends Controller {
@ -41,16 +39,38 @@ class AuthController extends Controller {
* @param \Illuminate\Contracts\Auth\Registrar $registrar * @param \Illuminate\Contracts\Auth\Registrar $registrar
* @return void * @return void
*/ */
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo, AuthService $authService) public function __construct(AccountRepository $repo, AuthService $authService)
{ {
$this->auth = $auth;
$this->registrar = $registrar;
$this->accountRepo = $repo; $this->accountRepo = $repo;
$this->authService = $authService; $this->authService = $authService;
//$this->middleware('guest', ['except' => 'getLogout']); //$this->middleware('guest', ['except' => 'getLogout']);
} }
public function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
public function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
public function authLogin($provider, Request $request) public function authLogin($provider, Request $request)
{ {
return $this->authService->execute($provider, $request->has('code')); return $this->authService->execute($provider, $request->has('code'));

View File

@ -1,8 +1,6 @@
<?php namespace App\Http\Controllers\Auth; <?php namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller { class PasswordController extends Controller {
@ -29,11 +27,8 @@ class PasswordController extends Controller {
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwords * @param \Illuminate\Contracts\Auth\PasswordBroker $passwords
* @return void * @return void
*/ */
public function __construct(Guard $auth, PasswordBroker $passwords) public function __construct()
{ {
$this->auth = $auth;
$this->passwords = $passwords;
$this->middleware('guest'); $this->middleware('guest');
} }

View File

@ -109,8 +109,7 @@ class ExportController extends BaseController
if ($request->input(ENTITY_CLIENT)) { if ($request->input(ENTITY_CLIENT)) {
$data['clients'] = Client::scope() $data['clients'] = Client::scope()
->with('user', 'contacts', 'country') ->with('user', 'contacts', 'country')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
$data['contacts'] = Contact::scope() $data['contacts'] = Contact::scope()
@ -126,33 +125,36 @@ class ExportController extends BaseController
if ($request->input(ENTITY_TASK)) { if ($request->input(ENTITY_TASK)) {
$data['tasks'] = Task::scope() $data['tasks'] = Task::scope()
->with('user', 'client.contacts') ->with('user', 'client.contacts')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
} }
if ($request->input(ENTITY_INVOICE)) { if ($request->input(ENTITY_INVOICE)) {
$data['invoices'] = Invoice::scope() $data['invoices'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status') ->with('user', 'client.contacts', 'invoice_status')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->where('is_quote', '=', false) ->where('is_quote', '=', false)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->get(); ->get();
$data['quotes'] = Invoice::scope() $data['quotes'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status') ->with('user', 'client.contacts', 'invoice_status')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->where('is_quote', '=', true) ->where('is_quote', '=', true)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->get(); ->get();
$data['recurringInvoices'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status', 'frequency')
->withArchived()
->where('is_quote', '=', false)
->where('is_recurring', '=', true)
->get();
} }
if ($request->input(ENTITY_PAYMENT)) { if ($request->input(ENTITY_PAYMENT)) {
$data['payments'] = Payment::scope() $data['payments'] = Payment::scope()
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway') ->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway')
->get(); ->get();
} }
@ -161,14 +163,14 @@ class ExportController extends BaseController
if ($request->input(ENTITY_VENDOR)) { if ($request->input(ENTITY_VENDOR)) {
$data['clients'] = Vendor::scope() $data['clients'] = Vendor::scope()
->with('user', 'vendorcontacts', 'country') ->with('user', 'vendorcontacts', 'country')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
$data['vendor_contacts'] = VendorContact::scope() $data['vendor_contacts'] = VendorContact::scope()
->with('user', 'vendor.contacts') ->with('user', 'vendor.contacts')
->withTrashed() ->withTrashed()
->get(); ->get();
/* /*
$data['expenses'] = Credit::scope() $data['expenses'] = Credit::scope()
->with('user', 'client.contacts') ->with('user', 'client.contacts')

View File

@ -34,10 +34,7 @@ class PublicClientController extends BaseController
public function view($invitationKey) public function view($invitationKey)
{ {
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [ return $this->returnError();
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
} }
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
@ -118,6 +115,7 @@ class PublicClientController extends BaseController
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(), 'hideHeader' => $account->isNinjaAccount(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(), 'invoice' => $invoice->hidePrivateFields(),
@ -188,11 +186,16 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$client = $invoice->client; $client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$data = [ $data = [
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,
@ -244,6 +247,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
@ -275,6 +279,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
@ -312,6 +317,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
@ -332,13 +338,11 @@ class PublicClientController extends BaseController
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch')); return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
} }
private function returnError() private function returnError($error = false)
{ {
return response()->view('error', [ return response()->view('error', [
'error' => trans('texts.invoice_not_found'), 'error' => $error ?: trans('texts.invoice_not_found'),
'hideHeader' => true, 'hideHeader' => true,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
]); ]);
} }

View File

@ -10,6 +10,9 @@ use DatePeriod;
use Session; use Session;
use View; use View;
use App\Models\Account; use App\Models\Account;
use App\Models\Client;
use App\Models\Payment;
use App\Models\Expense;
class ReportController extends BaseController class ReportController extends BaseController
{ {
@ -47,6 +50,7 @@ class ReportController extends BaseController
$groupBy = Input::get('group_by'); $groupBy = Input::get('group_by');
$chartType = Input::get('chart_type'); $chartType = Input::get('chart_type');
$reportType = Input::get('report_type'); $reportType = Input::get('report_type');
$dateField = Input::get('date_field');
$startDate = Utils::toSqlDate(Input::get('start_date'), false); $startDate = Utils::toSqlDate(Input::get('start_date'), false);
$endDate = Utils::toSqlDate(Input::get('end_date'), false); $endDate = Utils::toSqlDate(Input::get('end_date'), false);
$enableReport = Input::get('enable_report') ? true : false; $enableReport = Input::get('enable_report') ? true : false;
@ -55,6 +59,7 @@ class ReportController extends BaseController
$groupBy = 'MONTH'; $groupBy = 'MONTH';
$chartType = 'Bar'; $chartType = 'Bar';
$reportType = ENTITY_INVOICE; $reportType = ENTITY_INVOICE;
$dateField = FILTER_INVOICE_DATE;
$startDate = Utils::today(false)->modify('-3 month'); $startDate = Utils::today(false)->modify('-3 month');
$endDate = Utils::today(false); $endDate = Utils::today(false);
$enableReport = true; $enableReport = true;
@ -76,6 +81,8 @@ class ReportController extends BaseController
ENTITY_CLIENT => trans('texts.client'), ENTITY_CLIENT => trans('texts.client'),
ENTITY_INVOICE => trans('texts.invoice'), ENTITY_INVOICE => trans('texts.invoice'),
ENTITY_PAYMENT => trans('texts.payment'), ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expenses'),
ENTITY_TAX_RATE => trans('texts.taxes'),
]; ];
$params = [ $params = [
@ -94,10 +101,11 @@ class ReportController extends BaseController
if (Auth::user()->account->isPro()) { if (Auth::user()->account->isPro()) {
if ($enableReport) { if ($enableReport) {
$params = array_merge($params, self::generateReport($reportType, $groupBy, $startDate, $endDate)); $isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
if ($action == 'export') { if ($isExport) {
self::export($params['exportData'], $params['reportTotals']); self::export($params['displayData'], $params['columns'], $params['reportTotals']);
} }
} }
if ($enableChart) { if ($enableChart) {
@ -106,11 +114,7 @@ class ReportController extends BaseController
} else { } else {
$params['columns'] = []; $params['columns'] = [];
$params['displayData'] = []; $params['displayData'] = [];
$params['reportTotals'] = [ $params['reportTotals'] = [];
'amount' => [],
'balance' => [],
'paid' => [],
];
$params['labels'] = []; $params['labels'] = [];
$params['datasets'] = []; $params['datasets'] = [];
$params['scaleStepWidth'] = 100; $params['scaleStepWidth'] = 100;
@ -212,165 +216,314 @@ class ReportController extends BaseController
]; ];
} }
private function generateReport($reportType, $groupBy, $startDate, $endDate) private function generateReport($reportType, $startDate, $endDate, $dateField, $isExport)
{ {
if ($reportType == ENTITY_CLIENT) { if ($reportType == ENTITY_CLIENT) {
$columns = ['client', 'amount', 'paid', 'balance']; return $this->generateClientReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_INVOICE) { } elseif ($reportType == ENTITY_INVOICE) {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance']; return $this->generateInvoiceReport($startDate, $endDate, $isExport);
} else { } elseif ($reportType == ENTITY_PAYMENT) {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; return $this->generatePaymentReport($startDate, $endDate, $isExport);
} } elseif ($reportType == ENTITY_TAX_RATE) {
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
$query = DB::table('invoices') } elseif ($reportType == ENTITY_EXPENSE) {
->join('accounts', 'accounts.id', '=', 'invoices.account_id') return $this->generateExpenseReport($startDate, $endDate, $isExport);
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('invoices.is_deleted', '=', false)
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->where('invoices.invoice_date', '>=', $startDate->format('Y-m-d'))
->where('invoices.invoice_date', '<=', $endDate->format('Y-m-d'))
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true);
$select = [
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
'accounts.country_id',
'contacts.first_name',
'contacts.last_name',
'contacts.email',
'clients.name as client_name',
'clients.public_id as client_public_id',
'invoices.public_id as invoice_public_id'
];
if ($reportType == ENTITY_CLIENT) {
$query->groupBy('clients.id');
array_push($select, DB::raw('sum(invoices.amount) amount'), DB::raw('sum(invoices.balance) balance'), DB::raw('sum(invoices.amount - invoices.balance) paid'));
} else {
$query->orderBy('invoices.id');
array_push($select, 'invoices.invoice_number', 'invoices.amount', 'invoices.balance', 'invoices.invoice_date');
if ($reportType == ENTITY_INVOICE) {
array_push($select, DB::raw('(invoices.amount - invoices.balance) paid'));
} else {
$query->join('payments', 'payments.invoice_id', '=', 'invoices.id')
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id');
array_push($select, 'payments.payment_date', 'payments.amount as paid', 'payment_types.name as payment_type', 'gateways.name as gateway');
} }
} }
$query->select($select); private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport)
$data = $query->get(); {
$columns = ['tax_name', 'tax_rate', 'amount', 'paid'];
$lastInvoiceId = null; $account = Auth::user()->account;
$sameAsLast = false;
$displayData = []; $displayData = [];
$reportTotals = [];
$exportData = []; $clients = Client::scope()
$reportTotals = [ ->withArchived()
'amount' => [], ->with('contacts')
'balance' => [], ->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) {
'paid' => [], $query->withArchived();
if ($dateField == FILTER_PAYMENT_DATE) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->whereHas('payments', function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived();
})
->with(['payments' => function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived()
->with('payment_type', 'account_gateway.gateway');
}, 'invoice_items']);
}
}]);
foreach ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
$amount = 0;
$paid = 0;
$taxTotals = [];
foreach ($client->invoices as $invoice) {
foreach ($invoice->getTaxes(true) as $key => $tax) {
if ( ! isset($taxTotals[$currencyId])) {
$taxTotals[$currencyId] = [];
}
if (isset($taxTotals[$currencyId][$key])) {
$taxTotals[$currencyId][$key]['amount'] += $tax['amount'];
$taxTotals[$currencyId][$key]['paid'] += $tax['paid'];
} else {
$taxTotals[$currencyId][$key] = $tax;
}
}
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
foreach ($taxTotals as $currencyId => $taxes) {
foreach ($taxes as $tax) {
$displayData[] = [
$tax['name'],
$tax['rate'] . '%',
$account->formatMoney($tax['amount'], $client),
$account->formatMoney($tax['paid'], $client)
]; ];
foreach ($data as $record) {
$sameAsLast = ($lastInvoiceId == $record->invoice_public_id);
$lastInvoiceId = $record->invoice_public_id;
$displayRow = [];
if ($sameAsLast) {
array_push($displayRow, '', '', '', '');
} else {
array_push($displayRow, link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record)));
if ($reportType != ENTITY_CLIENT) {
array_push($displayRow,
link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number),
Utils::fromSqlDate($record->invoice_date, true)
);
}
array_push($displayRow, Utils::formatMoney($record->amount, $record->currency_id, $record->country_id));
}
if ($reportType != ENTITY_PAYMENT) {
array_push($displayRow, Utils::formatMoney($record->paid, $record->currency_id, $record->country_id));
}
if ($reportType == ENTITY_PAYMENT) {
array_push($displayRow,
Utils::fromSqlDate($record->payment_date, true),
Utils::formatMoney($record->paid, $record->currency_id, $record->country_id),
$record->gateway ?: $record->payment_type
);
} else {
array_push($displayRow, Utils::formatMoney($record->balance, $record->currency_id, $record->country_id));
} }
// export data $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']);
$exportRow = []; $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']);
if ($sameAsLast) {
$exportRow[trans('texts.client')] = ' ';
$exportRow[trans('texts.invoice_number')] = ' ';
$exportRow[trans('texts.invoice_date')] = ' ';
$exportRow[trans('texts.amount')] = ' ';
} else {
$exportRow[trans('texts.client')] = Utils::getClientDisplayName($record);
if ($reportType != ENTITY_CLIENT) {
$exportRow[trans('texts.invoice_number')] = $record->invoice_number;
$exportRow[trans('texts.invoice_date')] = Utils::fromSqlDate($record->invoice_date, true);
} }
$exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id, $record->country_id);
}
if ($reportType != ENTITY_PAYMENT) {
$exportRow[trans('texts.paid')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
}
if ($reportType == ENTITY_PAYMENT) {
$exportRow[trans('texts.payment_date')] = Utils::fromSqlDate($record->payment_date, true);
$exportRow[trans('texts.payment_amount')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
$exportRow[trans('texts.method')] = $record->gateway ?: $record->payment_type;
} else {
$exportRow[trans('texts.balance')] = Utils::formatMoney($record->balance, $record->currency_id, $record->country_id);
}
$displayData[] = $displayRow;
$exportData[] = $exportRow;
$accountCurrencyId = Auth::user()->account->currency_id;
$currencyId = $record->currency_id ? $record->currency_id : ($accountCurrencyId ? $accountCurrencyId : DEFAULT_CURRENCY);
if (!isset($reportTotals['amount'][$currencyId])) {
$reportTotals['amount'][$currencyId] = 0;
$reportTotals['balance'][$currencyId] = 0;
$reportTotals['paid'][$currencyId] = 0;
}
if (!$sameAsLast) {
$reportTotals['amount'][$currencyId] += $record->amount;
$reportTotals['balance'][$currencyId] += $record->balance;
}
$reportTotals['paid'][$currencyId] += $record->paid;
} }
return [ return [
'columns' => $columns, 'columns' => $columns,
'displayData' => $displayData, 'displayData' => $displayData,
'reportTotals' => $reportTotals, 'reportTotals' => $reportTotals,
'exportData' => $exportData ];
}
private function generatePaymentReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$payments = Payment::scope()
->withTrashed()
->where('is_deleted', '=', false)
->whereHas('client', function($query) {
$query->where('is_deleted', '=', false);
})
->whereHas('invoice', function($query) {
$query->where('is_deleted', '=', false);
})
->with('client.contacts', 'invoice', 'payment_type', 'account_gateway.gateway')
->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate);
foreach ($payments->get() as $payment) {
$invoice = $payment->invoice;
$client = $payment->client;
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment->present()->payment_date,
$account->formatMoney($payment->amount, $client),
$payment->present()->method,
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
]; ];
} }
private function export($data, $totals) private function generateInvoiceReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withTrashed()
->with('contacts')
->where('is_deleted', '=', false)
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('is_deleted', '=', false)
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->with(['payments' => function($query) {
$query->withTrashed()
->with('payment_type', 'account_gateway.gateway')
->where('is_deleted', '=', false);
}, 'invoice_items'])
->withTrashed();
}]);
foreach ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
foreach ($client->invoices as $invoice) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$account->formatMoney($invoice->getAmountPaid(), $client),
$account->formatMoney($invoice->balance, $client),
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $invoice->getAmountPaid());
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
}
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function generateClientReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'amount', 'paid', 'balance'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withArchived()
->with('contacts')
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->withArchived();
}]);
foreach ($clients->get() as $client) {
$amount = 0;
$paid = 0;
foreach ($client->invoices as $invoice) {
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$account->formatMoney($amount, $client),
$account->formatMoney($paid, $client),
$account->formatMoney($amount - $paid, $client)
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $paid);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $amount - $paid);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function generateExpenseReport($startDate, $endDate, $isExport)
{
$columns = ['vendor', 'client', 'date', 'expense_amount', 'invoiced_amount'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$expenses = Expense::scope()
->withTrashed()
->with('client.contacts', 'vendor')
->where('expense_date', '>=', $startDate)
->where('expense_date', '<=', $endDate);
foreach ($expenses->get() as $expense) {
$amount = $expense->amount;
$invoiced = $expense->present()->invoiced_amount;
$displayData[] = [
$expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '',
$expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '',
$expense->present()->expense_date,
Utils::formatMoney($amount, $expense->currency_id),
Utils::formatMoney($invoiced, $expense->invoice_currency_id),
];
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'invoiced', $invoiced);
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'invoiced', 0);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function addToTotals($data, $currencyId, $field, $value) {
$currencyId = $currencyId ?: Auth::user()->account->getCurrencyId();
if (!isset($data[$currencyId][$field])) {
$data[$currencyId][$field] = 0;
}
$data[$currencyId][$field] += $value;
return $data;
}
private function export($data, $columns, $totals)
{ {
$output = fopen('php://output', 'w') or Utils::fatalError(); $output = fopen('php://output', 'w') or Utils::fatalError();
header('Content-Type:application/csv'); header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv'); header('Content-Disposition:attachment;filename=ninja-report.csv');
Utils::exportData($output, $data); Utils::exportData($output, $data, Utils::trans($columns));
foreach (['amount', 'paid', 'balance'] as $type) { fwrite($output, trans('texts.totals'));
$csv = trans("texts.{$type}").','; foreach ($totals as $currencyId => $fields) {
foreach ($totals[$type] as $currencyId => $amount) { foreach ($fields as $key => $value) {
$csv .= Utils::formatMoney($amount, $currencyId).','; fwrite($output, ',' . trans("texts.{$key}"));
}
fwrite($output, "\n");
break;
}
foreach ($totals as $currencyId => $fields) {
$csv = Utils::getFromCache($currencyId, 'currencies')->name . ',';
foreach ($fields as $key => $value) {
$csv .= '"' . Utils::formatMoney($value, $currencyId).'",';
} }
fwrite($output, $csv."\n"); fwrite($output, $csv."\n");
} }

View File

@ -48,6 +48,9 @@ class StartupCheck
$file = storage_path() . '/version.txt'; $file = storage_path() . '/version.txt';
$version = @file_get_contents($file); $version = @file_get_contents($file);
if ($version != NINJA_VERSION) { if ($version != NINJA_VERSION) {
if (version_compare(phpversion(), '5.5.9', '<')) {
dd('Please update PHP to >= 5.5.9');
}
$handle = fopen($file, 'w'); $handle = fopen($file, 'w');
fwrite($handle, NINJA_VERSION); fwrite($handle, NINJA_VERSION);
fclose($handle); fclose($handle);

View File

@ -1,5 +1,6 @@
<?php <?php
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Routes | Application Routes
@ -234,6 +235,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('tax_rates', 'TaxRateApiController'); Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController'); Route::resource('users', 'UserApiController');
Route::resource('expenses','ExpenseApiController'); Route::resource('expenses','ExpenseApiController');
Route::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
// Vendor // Vendor
Route::resource('vendors', 'VendorApiController'); Route::resource('vendors', 'VendorApiController');
@ -527,6 +530,8 @@ if (!defined('CONTACT_EMAIL')) {
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'); define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup');
define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'); define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all');
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
define('COUNT_FREE_DESIGNS', 4); define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1); define('PRODUCT_ONE_CLICK_INSTALL', 1);
@ -549,6 +554,9 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_PASSWORD', 'password'); define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET'); define('API_SECRET', 'API_SECRET');
define('IOS_PRODUCTION_PUSH','ninjaIOS');
define('IOS_DEV_PUSH','devNinjaIOS');
define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3); define('TOKEN_BILLING_OPT_OUT', 3);
@ -572,6 +580,9 @@ if (!defined('CONTACT_EMAIL')) {
define('REMINDER_FIELD_DUE_DATE', 1); define('REMINDER_FIELD_DUE_DATE', 1);
define('REMINDER_FIELD_INVOICE_DATE', 2); define('REMINDER_FIELD_INVOICE_DATE', 2);
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
define('SOCIAL_GOOGLE', 'Google'); define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook'); define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub'); define('SOCIAL_GITHUB', 'GitHub');

View File

@ -72,7 +72,7 @@ class Utils
public static function requireHTTPS() public static function requireHTTPS()
{ {
if (Request::root() === 'http://ninja.dev:8000') { if (Request::root() === 'http://ninja.dev' || Request::root() === 'http://ninja.dev:8000') {
return false; return false;
} }
@ -767,9 +767,11 @@ class Utils
return $str; return $str;
} }
public static function exportData($output, $data) public static function exportData($output, $data, $headers = false)
{ {
if (count($data) > 0) { if ($headers) {
fputcsv($output, $headers);
} elseif (count($data) > 0) {
fputcsv($output, array_keys($data[0])); fputcsv($output, array_keys($data[0]));
} }

View File

@ -9,16 +9,19 @@ use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Events\QuoteInvitationWasApproved; use App\Events\QuoteInvitationWasApproved;
use App\Events\PaymentWasCreated; use App\Events\PaymentWasCreated;
use App\Ninja\Notifications;
class NotificationListener class NotificationListener
{ {
protected $userMailer; protected $userMailer;
protected $contactMailer; protected $contactMailer;
protected $pushService;
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer) public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, Notifications\PushService $pushService)
{ {
$this->userMailer = $userMailer; $this->userMailer = $userMailer;
$this->contactMailer = $contactMailer; $this->contactMailer = $contactMailer;
$this->pushService = $pushService;
} }
private function sendEmails($invoice, $type, $payment = null) private function sendEmails($invoice, $type, $payment = null)
@ -35,26 +38,31 @@ class NotificationListener
public function emailedInvoice(InvoiceWasEmailed $event) public function emailedInvoice(InvoiceWasEmailed $event)
{ {
$this->sendEmails($event->invoice, 'sent'); $this->sendEmails($event->invoice, 'sent');
$this->pushService->sendNotification($event->invoice, 'sent');
} }
public function emailedQuote(QuoteWasEmailed $event) public function emailedQuote(QuoteWasEmailed $event)
{ {
$this->sendEmails($event->quote, 'sent'); $this->sendEmails($event->quote, 'sent');
$this->pushService->sendNotification($event->quote, 'sent');
} }
public function viewedInvoice(InvoiceInvitationWasViewed $event) public function viewedInvoice(InvoiceInvitationWasViewed $event)
{ {
$this->sendEmails($event->invoice, 'viewed'); $this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
} }
public function viewedQuote(QuoteInvitationWasViewed $event) public function viewedQuote(QuoteInvitationWasViewed $event)
{ {
$this->sendEmails($event->quote, 'viewed'); $this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
} }
public function approvedQuote(QuoteInvitationWasApproved $event) public function approvedQuote(QuoteInvitationWasApproved $event)
{ {
$this->sendEmails($event->quote, 'approved'); $this->sendEmails($event->quote, 'approved');
$this->pushService->sendNotification($event->quote, 'approved');
} }
public function createdPayment(PaymentWasCreated $event) public function createdPayment(PaymentWasCreated $event)
@ -66,6 +74,8 @@ class NotificationListener
$this->contactMailer->sendPaymentConfirmation($event->payment); $this->contactMailer->sendPaymentConfirmation($event->payment);
$this->sendEmails($event->payment->invoice, 'paid', $event->payment); $this->sendEmails($event->payment->invoice, 'paid', $event->payment);
$this->pushService->sendNotification($event->payment->invoice, 'paid');
} }
} }

View File

@ -24,29 +24,49 @@ class SubscriptionListener
{ {
public function createdClient(ClientWasCreated $event) public function createdClient(ClientWasCreated $event)
{ {
if ( ! Auth::check()) {
return;
}
$transformer = new ClientTransformer(Auth::user()->account); $transformer = new ClientTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer); $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer);
} }
public function createdQuote(QuoteWasCreated $event) public function createdQuote(QuoteWasCreated $event)
{ {
if ( ! Auth::check()) {
return;
}
$transformer = new InvoiceTransformer(Auth::user()->account); $transformer = new InvoiceTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
} }
public function createdPayment(PaymentWasCreated $event) public function createdPayment(PaymentWasCreated $event)
{ {
if ( ! Auth::check()) {
return;
}
$transformer = new PaymentTransformer(Auth::user()->account); $transformer = new PaymentTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]); $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
} }
public function createdCredit(CreditWasCreated $event) public function createdCredit(CreditWasCreated $event)
{ {
if ( ! Auth::check()) {
return;
}
//$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit); //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit);
} }
public function createdInvoice(InvoiceWasCreated $event) public function createdInvoice(InvoiceWasCreated $event)
{ {
if ( ! Auth::check()) {
return;
}
$transformer = new InvoiceTransformer(Auth::user()->account); $transformer = new InvoiceTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
} }

View File

@ -439,7 +439,7 @@ class Account extends Eloquent
return $height; return $height;
} }
public function createInvoice($entityType, $clientId = null) public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null)
{ {
$invoice = Invoice::createNew(); $invoice = Invoice::createNew();

View File

@ -272,6 +272,11 @@ class Client extends EntityModel
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false; return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
} }
public function getAmount()
{
return $this->balance + $this->paid_to_date;
}
public function getCurrencyId() public function getCurrencyId()
{ {
if ($this->currency_id) { if ($this->currency_id) {

View File

@ -81,6 +81,11 @@ class EntityModel extends Eloquent
return $query; return $query;
} }
public function scopeWithArchived($query)
{
return $query->withTrashed()->where('is_deleted', '=', false);
}
public function getName() public function getName()
{ {
return $this->public_id; return $this->public_id;

View File

@ -129,14 +129,22 @@ class Invoice extends EntityModel implements BalanceAffecting
return false; return false;
} }
public function getAmountPaid() public function getAmountPaid($calculate = false)
{ {
if ($this->is_quote || $this->is_recurring) { if ($this->is_quote || $this->is_recurring) {
return 0; return 0;
} }
if ($calculate) {
$amount = 0;
foreach ($this->payments as $payment) {
$amount += $payment->amount;
}
return $amount;
} else {
return ($this->amount - $this->balance); return ($this->amount - $this->balance);
} }
}
public function trashed() public function trashed()
{ {
@ -192,6 +200,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id'); return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
} }
public function frequency()
{
return $this->belongsTo('App\Models\Frequency');
}
public function invitations() public function invitations()
{ {
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -442,12 +455,16 @@ class Invoice extends EntityModel implements BalanceAffecting
'show_item_taxes', 'show_item_taxes',
'custom_invoice_text_label1', 'custom_invoice_text_label1',
'custom_invoice_text_label2', 'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
]); ]);
foreach ($this->invoice_items as $invoiceItem) { foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([ $invoiceItem->setVisible([
'product_key', 'product_key',
'notes', 'notes',
'custom_value1',
'custom_value2',
'cost', 'cost',
'qty', 'qty',
'tax_name', 'tax_name',
@ -752,6 +769,98 @@ class Invoice extends EntityModel implements BalanceAffecting
return Utils::decodePDF($pdfString); return Utils::decodePDF($pdfString);
} }
public function getItemTaxable($invoiceItem, $invoiceTotal)
{
$total = $invoiceItem->qty * $invoiceItem->cost;
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $invoiceTotal ? ($total / $invoiceTotal * $this->discount) : 0;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
return $total;
}
public function getTaxable()
{
$total = 0;
foreach ($this->invoice_items as $invoiceItem) {
$total += $invoiceItem->qty * $invoiceItem->cost;
}
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $this->discount;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
if ($this->custom_value1 && $this->custom_taxes1) {
$total += $this->custom_value1;
}
if ($this->custom_value2 && $this->custom_taxes2) {
$total += $this->custom_value2;
}
return $total;
}
public function getTaxes($calculatePaid = false)
{
$taxes = [];
$taxable = $this->getTaxable();
if ($this->tax_rate && $this->tax_name) {
$taxAmount = $taxable * ($this->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($taxAmount) {
$taxes[$this->tax_name.$this->tax_rate] = [
'name' => $this->tax_name,
'rate' => $this->tax_rate,
'amount' => $taxAmount,
'paid' => round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2)
];
}
}
foreach ($this->invoice_items as $invoiceItem) {
if ( ! $invoiceItem->tax_rate || ! $invoiceItem->tax_name) {
continue;
}
$taxAmount = $this->getItemTaxable($invoiceItem, $taxable);
$taxAmount = $taxable * ($invoiceItem->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($taxAmount) {
$key = $invoiceItem->tax_name.$invoiceItem->tax_rate;
if ( ! isset($taxes[$key])) {
$taxes[$key] = [
'amount' => 0,
'paid' => 0
];
}
$taxes[$key]['amount'] += $taxAmount;
$taxes[$key]['paid'] += $this->amount && $taxAmount ? round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) : 0;
$taxes[$key]['name'] = $invoiceItem->tax_name;
$taxes[$key]['rate'] = $invoiceItem->tax_rate;
}
}
return $taxes;
}
} }
Invoice::creating(function ($invoice) { Invoice::creating(function ($invoice) {

View File

@ -0,0 +1,96 @@
<?php
namespace App\Ninja\Notifications;
use Davibennun\LaravelPushNotification\Facades\PushNotification;
use Illuminate\Http\Request;
/**
* Class PushFactory
* @package App\Ninja\Notifications
*/
class PushFactory
{
/**
* PushFactory constructor.
*
* @param $this->certificate - Development or production.
*
* Static variables defined in routes.php
*
* IOS_PRODUCTION_PUSH
* IOS_DEV_PUSH
*/
public function __construct()
{
$this->certificate = IOS_DEV_PUSH;
}
/**
* customMessage function
*
* Send a message with a nested custom payload to perform additional trickery within application
*
* @access public
*
* @param $token
* @param $message
* @param $messageArray
*
* @return void
*/
public function customMessage($token, $message, $messageArray)
{
$customMessage = PushNotification::Message($message, $messageArray);
$this->message($token, $customMessage);
}
/**
* message function
*
* Send a plain text only message to a single device.
*
* @access public
*
* @param $token - device token
* @param $message - user specific message
*
* @return void
*
*/
public function message($token, $message)
{
PushNotification::app($this->certificate)
->to($token)
->send($message);
}
/**
* getFeedback function
*
* Returns an array of expired/invalid tokens to be removed from iOS PUSH notifications.
*
* We need to run this once ~ 24hrs
*
* @access public
*
* @param string $token - A valid token (can be any valid token)
* @param string $message - Nil value for message
*
* @return array
*/
public function getFeedback($token, $message = '')
{
$feedback = PushNotification::app($this->certificate)
->to($token)
->send($message);
return $feedback->getFeedback();
}
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters; <?php namespace App\Ninja\Presenters;
use URL;
use Utils; use Utils;
use Laracasts\Presenter\Presenter; use Laracasts\Presenter\Presenter;
@ -26,6 +27,15 @@ class ClientPresenter extends Presenter {
} }
return "<span class=\"label label-{$class}\">{$text}</span>"; return "<span class=\"label label-{$class}\">{$text}</span>";
}
public function url()
{
return URL::to('/clients/' . $this->entity->public_id);
}
public function link()
{
return link_to('/clients/' . $this->entity->public_id, $this->entity->getDisplayName());
} }
} }

View File

@ -20,4 +20,14 @@ class ExpensePresenter extends Presenter {
{ {
return round($this->entity->amount * $this->entity->exchange_rate, 2); return round($this->entity->amount * $this->entity->exchange_rate, 2);
} }
public function invoiced_amount()
{
return $this->entity->invoice_id ? $this->converted_amount() : 0;
}
public function link()
{
return link_to('/expenses/' . $this->entity->public_id, $this->entity->name);
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters; <?php namespace App\Ninja\Presenters;
use URL;
use Utils; use Utils;
use Laracasts\Presenter\Presenter; use Laracasts\Presenter\Presenter;
@ -40,10 +41,16 @@ class InvoicePresenter extends Presenter {
public function status() public function status()
{ {
if ($this->entity->is_deleted) {
return trans('texts.deleted');
} elseif ($this->entity->trashed()) {
return trans('texts.archived');
} else {
$status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft'; $status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft';
$status = strtolower($status); $status = strtolower($status);
return trans("texts.status_{$status}"); return trans("texts.status_{$status}");
} }
}
public function invoice_date() public function invoice_date()
{ {
@ -55,4 +62,24 @@ class InvoicePresenter extends Presenter {
return Utils::fromSqlDate($this->entity->due_date); return Utils::fromSqlDate($this->entity->due_date);
} }
public function frequency()
{
return $this->entity->frequency ? $this->entity->frequency->name : '';
}
public function url()
{
return URL::to('/invoices/' . $this->entity->public_id);
}
public function link()
{
return link_to('/invoices/' . $this->entity->public_id, $this->entity->invoice_number);
}
public function email()
{
$client = $this->entity->client;
return count($client->contacts) ? $client->contacts[0]->email : '';
}
} }

View File

@ -9,4 +9,9 @@ class VendorPresenter extends Presenter {
{ {
return $this->entity->country ? $this->entity->country->name : ''; return $this->entity->country ? $this->entity->country->name : '';
} }
public function link()
{
return link_to('/vendors/' . $this->entity->public_id, $this->entity->name);
}
} }

View File

@ -5,6 +5,7 @@ use Request;
use Session; use Session;
use Utils; use Utils;
use DB; use DB;
use URL;
use stdClass; use stdClass;
use Validator; use Validator;
use Schema; use Schema;
@ -71,47 +72,104 @@ class AccountRepository
public function getSearchData() public function getSearchData()
{ {
$clients = \DB::table('clients') $data = $this->getAccountSearchData();
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("clients.name <> ''")
->select(\DB::raw("'clients' as type, '" . trans('texts.clients') . "' as trans_type, clients.public_id, clients.name, '' as token"));
$contacts = \DB::table('clients') $data['navigation'] = $this->getNavigationSearchData();
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''")
->select(\DB::raw("'clients' as type, '" . trans('texts.contacts') . "' as trans_type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token"));
$invoices = \DB::table('clients') return $data;
->join('invoices', 'invoices.client_id', '=', 'clients.id')
->where('clients.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null)
->select(\DB::raw("'invoices' as type, '" . trans('texts.invoices') . "' as trans_type, invoices.public_id, CONCAT(invoices.invoice_number, ': ', clients.name) as name, invoices.invoice_number as token"));
$data = [];
foreach ($clients->union($contacts)->union($invoices)->get() as $row) {
$type = $row->trans_type;
if (!isset($data[$type])) {
$data[$type] = [];
} }
$tokens = explode(' ', $row->name); private function getAccountSearchData()
$tokens[] = $type; {
$data = [
'clients' => [],
'contacts' => [],
'invoices' => [],
'quotes' => [],
];
if ($type == 'Invoices') { $clients = Client::scope()
$tokens[] = intVal($row->token).''; ->with('contacts', 'invoices')
->get();
foreach ($clients as $client) {
if ($client->name) {
$data['clients'][] = [
'value' => $client->name,
'url' => $client->present()->url,
];
} }
$data[$type][] = [ foreach ($client->contacts as $contact) {
'value' => $row->name, if ($contact->getFullName()) {
'public_id' => $row->public_id, $data['contacts'][] = [
'tokens' => $tokens, 'value' => $contact->getDisplayName(),
'entity_type' => $row->type, 'url' => $client->present()->url,
];
}
if ($contact->email) {
$data[trans('texts.contacts')][] = [
'value' => $contact->email,
'url' => $client->present()->url,
];
}
}
foreach ($client->invoices as $invoice) {
$entityType = $invoice->getEntityType();
$data["{$entityType}s"][] = [
'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(),
'url' => $invoice->present()->url,
];
}
}
return $data;
}
private function getNavigationSearchData()
{
$entityTypes = [
ENTITY_INVOICE,
ENTITY_CLIENT,
ENTITY_QUOTE,
ENTITY_TASK,
ENTITY_EXPENSE,
ENTITY_RECURRING_INVOICE,
ENTITY_PAYMENT,
ENTITY_CREDIT
];
foreach ($entityTypes as $entityType) {
$features[] = [
"new_{$entityType}",
"/{$entityType}s/create",
];
$features[] = [
"list_{$entityType}s",
"/{$entityType}s",
];
}
$features[] = ['dashboard', '/dashboard'];
$features[] = ['customize_design', '/settings/customize_design'];
$features[] = ['new_tax_rate', '/tax_rates/create'];
$features[] = ['new_product', '/products/create'];
$features[] = ['new_user', '/users/create'];
$settings = array_merge(Account::$basicSettings, Account::$advancedSettings);
foreach ($settings as $setting) {
$features[] = [
$setting,
"/settings/{$setting}",
];
}
foreach ($features as $feature) {
$data[] = [
'value' => trans('texts.' . $feature[0]),
'url' => URL::to($feature[1])
]; ];
} }
@ -127,8 +185,10 @@ class AccountRepository
$account = Auth::user()->account; $account = Auth::user()->account;
$client = $this->getNinjaClient($account); $client = $this->getNinjaClient($account);
$invitation = $this->createNinjaInvoice($client, $account); $invitation = $this->createNinjaInvoice($client, $account);
return $invitation; return $invitation;
} }
public function createNinjaInvoice($client, $clientAccount) public function createNinjaInvoice($client, $clientAccount)
{ {
$account = $this->getNinjaAccount(); $account = $this->getNinjaAccount();

View File

@ -1,6 +1,7 @@
<?php namespace App\Ninja\Repositories; <?php namespace App\Ninja\Repositories;
use DB; use DB;
use Cache;
use App\Ninja\Repositories\BaseRepository; use App\Ninja\Repositories\BaseRepository;
use App\Models\Client; use App\Models\Client;
use App\Models\Contact; use App\Models\Contact;
@ -74,6 +75,17 @@ class ClientRepository extends BaseRepository
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = Client::scope($publicId)->with('contacts')->firstOrFail();
} }
// convert currency code to id
if (isset($data['currency_code'])) {
$currencyCode = strtolower($data['currency_code']);
$currency = Cache::get('currencies')->filter(function($item) use ($currencyCode) {
return strtolower($item->code) == $currencyCode;
})->first();
if ($currency) {
$data['currency_id'] = $currency->id;
}
}
$client->fill($data); $client->fill($data);
$client->save(); $client->save();

View File

@ -398,7 +398,7 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) { if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) {
continue; continue;
} }
@ -439,6 +439,13 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->qty = Utils::parseFloat($item['qty']); $invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0; $invoiceItem->tax_rate = 0;
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
}
if (isset($item['custom_value2'])) {
$invoiceItem->custom_value2 = $item['custom_value2'];
}
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) { if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']); $invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']); $invoiceItem['tax_name'] = trim($item['tax_name']);
@ -603,7 +610,7 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::createNew($recurInvoice); $invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id; $invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id; $invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber($recurInvoice); $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice);
$invoice->amount = $recurInvoice->amount; $invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d'); $invoice->invoice_date = date_create()->format('Y-m-d');

View File

@ -21,7 +21,11 @@ class UserTransformer extends EntityTransformer
'registered' => (bool) $user->registered, 'registered' => (bool) $user->registered,
'confirmed' => (bool) $user->confirmed, 'confirmed' => (bool) $user->confirmed,
'oauth_user_id' => $user->oauth_user_id, 'oauth_user_id' => $user->oauth_user_id,
'oauth_provider_id' => $user->oauth_provider_id 'oauth_provider_id' => $user->oauth_provider_id,
'notify_sent' => (bool) $user->notify_sent,
'notify_viewed' => (bool) $user->notify_viewed,
'notify_paid' => (bool) $user->notify_paid,
'notify_approved' => (bool) $user->notify_approved,
]; ];
} }
} }

View File

@ -223,11 +223,6 @@ class AppServiceProvider extends ServiceProvider {
'Illuminate\Contracts\Auth\Registrar', 'Illuminate\Contracts\Auth\Registrar',
'App\Services\Registrar' 'App\Services\Registrar'
); );
$this->app->bind(
'App\Ninja\Import\DataImporterServiceInterface',
'App\Ninja\Import\FreshBooks\FreshBooksDataImporterService'
);
} }
} }

View File

@ -4,7 +4,6 @@ use stdClass;
use Utils; use Utils;
use URL; use URL;
use Hash; use Hash;
use App\Models\Gateway;
use App\Models\BankSubaccount; use App\Models\BankSubaccount;
use App\Models\Vendor; use App\Models\Vendor;
use App\Models\Expense; use App\Models\Expense;
@ -37,7 +36,7 @@ class BankAccountService extends BaseService
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true) public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
{ {
if ( ! $bankId || ! $username || ! $password) { if (! $bankId || ! $username || ! $password) {
return false; return false;
} }
@ -47,12 +46,13 @@ class BankAccountService extends BaseService
->withTrashed() ->withTrashed()
->get(['transaction_id']) ->get(['transaction_id'])
->toArray(); ->toArray();
$expenses = array_flip(array_map(function($val) { $expenses = array_flip(array_map(function ($val) {
return $val['transaction_id']; return $val['transaction_id'];
}, $expenses)); }, $expenses));
$vendorMap = $this->createVendorMap();
$bankAccounts = BankSubaccount::scope() $bankAccounts = BankSubaccount::scope()
->whereHas('bank_account', function($query) use ($bankId) { ->whereHas('bank_account', function ($query) use ($bankId) {
$query->where('bank_id', '=', $bankId); $query->where('bank_id', '=', $bankId);
}) })
->get(); ->get();
@ -70,7 +70,7 @@ class BankAccountService extends BaseService
$login->setup(); $login->setup();
foreach ($login->accounts as $account) { foreach ($login->accounts as $account) {
$account->setup($includeTransactions); $account->setup($includeTransactions);
if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)) { if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)) {
$data[] = $account; $data[] = $account;
} }
} }
@ -83,9 +83,9 @@ class BankAccountService extends BaseService
} }
} }
private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions) private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)
{ {
$obj = new stdClass; $obj = new stdClass();
$obj->account_name = ''; $obj->account_name = '';
// look up bank account name // look up bank account name
@ -106,7 +106,7 @@ class BankAccountService extends BaseService
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR); $obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
if ($includeTransactions) { if ($includeTransactions) {
$ofxParser = new \OfxParser\Parser; $ofxParser = new \OfxParser\Parser();
$ofx = $ofxParser->loadFromString($account->response); $ofx = $ofxParser->loadFromString($account->response);
$obj->start_date = $ofx->BankAccount->Statement->startDate; $obj->start_date = $ofx->BankAccount->Statement->startDate;
@ -121,7 +121,13 @@ class BankAccountService extends BaseService
if ($transaction->amount >= 0) { if ($transaction->amount >= 0) {
continue; continue;
} }
$transaction->vendor = $this->prepareValue(substr($transaction->name, 0, 20));
// if vendor has already been imported use current name
$vendorName = trim(substr($transaction->name, 0, 20));
$key = strtolower($vendorName);
$vendor = isset($vendorMap[$key]) ? $vendorMap[$key] : null;
$transaction->vendor = $vendor ? $vendor->name : $this->prepareValue($vendorName);
$transaction->info = $this->prepareValue(substr($transaction->name, 20)); $transaction->info = $this->prepareValue(substr($transaction->name, 20));
$transaction->memo = $this->prepareValue($transaction->memo); $transaction->memo = $this->prepareValue($transaction->memo);
$transaction->date = \Auth::user()->account->formatDate($transaction->date); $transaction->date = \Auth::user()->account->formatDate($transaction->date);
@ -133,15 +139,13 @@ class BankAccountService extends BaseService
return $obj; return $obj;
} }
private function prepareValue($value) { private function prepareValue($value)
{
return ucwords(strtolower(trim($value))); return ucwords(strtolower(trim($value)));
} }
public function importExpenses($bankId, $input) { private function createVendorMap()
$countVendors = 0; {
$countExpenses = 0;
// create a vendor map
$vendorMap = []; $vendorMap = [];
$vendors = Vendor::scope() $vendors = Vendor::scope()
->withTrashed() ->withTrashed()
@ -151,6 +155,15 @@ class BankAccountService extends BaseService
$vendorMap[strtolower($vendor->transaction_name)] = $vendor; $vendorMap[strtolower($vendor->transaction_name)] = $vendor;
} }
return $vendorMap;
}
public function importExpenses($bankId, $input)
{
$vendorMap = $this->createVendorMap();
$countVendors = 0;
$countExpenses = 0;
foreach ($input as $transaction) { foreach ($input as $transaction) {
$vendorName = $transaction['vendor']; $vendorName = $transaction['vendor'];
$key = strtolower($vendorName); $key = strtolower($vendorName);
@ -165,7 +178,7 @@ class BankAccountService extends BaseService
$field => $info, $field => $info,
'name' => $vendorName, 'name' => $vendorName,
'transaction_name' => $transaction['vendor_orig'], 'transaction_name' => $transaction['vendor_orig'],
'vendorcontact' => [] 'vendorcontact' => [],
]); ]);
$vendorMap[$key] = $vendor; $vendorMap[$key] = $vendor;
$vendorMap[$transaction['vendor_orig']] = $vendor; $vendorMap[$transaction['vendor_orig']] = $vendor;
@ -191,7 +204,8 @@ class BankAccountService extends BaseService
]); ]);
} }
private function determineInfoField($value) { private function determineInfoField($value)
{
if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) { if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) {
return 'work_phone'; return 'work_phone';
} elseif (strpos($value, '.') !== false) { } elseif (strpos($value, '.') !== false) {
@ -215,7 +229,7 @@ class BankAccountService extends BaseService
'bank_name', 'bank_name',
function ($model) { function ($model) {
return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name); return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name);
} },
], ],
[ [
'bank_library_id', 'bank_library_id',
@ -233,9 +247,8 @@ class BankAccountService extends BaseService
uctrans('texts.edit_bank_account'), uctrans('texts.edit_bank_account'),
function ($model) { function ($model) {
return URL::to("bank_accounts/{$model->public_id}/edit"); return URL::to("bank_accounts/{$model->public_id}/edit");
} },
] ]
]; ];
} }
} }

View File

@ -0,0 +1,171 @@
<?php
namespace App\Services;
use Illuminate\Http\Request;
use App\Ninja\Notifications\PushFactory;
/**
* Class PushService
* @package App\Ninja\Notifications
*/
/**
* $account->devices Definition
*
* @param string token (push notification device token)
* @param string email (user email address - required for use as key)
* @param string device (ios, gcm etc etc)
* @param bool notify_sent
* @param bool notify_paid
* @param bool notify_approved
* @param bool notify_viewed
*/
class PushService
{
protected $pushFactory;
/**
* @param PushFactory $pushFactory
*/
public function __construct(PushFactory $pushFactory)
{
$this->pushFactory = $pushFactory;
}
/**
* @param $invoice - Invoice object
* @param $type - Type of notification, ie. Quote APPROVED, Invoice PAID, Invoice/Quote SENT, Invoice/Quote VIEWED
*/
public function sendNotification($invoice, $type)
{
//check user has registered for push notifications
if(!$this->checkDeviceExists($invoice->account))
return;
//Harvest an array of devices that are registered for this notification type
$devices = json_decode($invoice->account->devices, TRUE);
foreach($devices as $device)
{
if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios'))
$this->pushMessage($invoice, $device['token'], $device["notify_{$type}"]);
}
}
/**
* pushMessage function
*
* method to dispatch iOS notifications
*
* @param $invoice
* @param $token
* @param $type
*/
private function pushMessage($invoice, $token, $type)
{
$this->pushFactory->message($token, $this->messageType($invoice, $type));
}
/**
* checkDeviceExists function
*
* Returns a boolean if this account has devices registered for PUSH notifications
*
* @param $account
* @return bool
*/
private function checkDeviceExists($account)
{
$devices = json_decode($account->devices, TRUE);
if(count($devices) >= 1)
return TRUE;
else
return FALSE;
}
/**
* messageType function
*
* method which formats an appropriate message depending on message type
*
* @param $invoice
* @param $type
* @return string
*/
private function messageType($invoice, $type)
{
switch($type)
{
case 'notify_sent':
return $this->entitySentMessage($invoice);
break;
case 'notify_paid':
return $this->invoicePaidMessage($invoice);
break;
case 'notify_approved':
return $this->quoteApprovedMessage($invoice);
break;
case 'notify_viewed':
return $this->entityViewedMessage($invoice);
break;
}
}
/**
* @param $invoice
* @return string
*/
private function entitySentMessage($invoice)
{
if($invoice->is_quote)
return 'Quote #{$invoice->invoice_number} sent!';
else
return 'Invoice #{$invoice->invoice_number} sent!';
}
/**
* @param $invoice
* @return string
*/
private function invoicePaidMessage($invoice)
{
return 'Invoice #{$invoice->invoice_number} paid!';
}
/**
* @param $invoice
* @return string
*/
private function quoteApprovedMessage($invoice)
{
return 'Quote #{$invoice->invoice_number} approved!';
}
/**
* @param $invoice
* @return string
*/
private function entityViewedMessage($invoice)
{
if($invoice->is_quote)
return 'Quote #{$invoice->invoice_number} viewed!';
else
return 'Invoice #{$invoice->invoice_number} viewed!';
}
}

View File

@ -1,39 +0,0 @@
<?php namespace App\Services;
use App\Model\User;
use Validator;
use Illuminate\Contracts\Auth\Registrar as RegistrarContract;
class Registrar implements RegistrarContract {
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
public function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
public function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}

View File

@ -30,7 +30,7 @@ require __DIR__.'/../vendor/autoload.php';
| |
*/ */
$compiledPath = __DIR__.'/../vendor/compiled.php'; $compiledPath = __DIR__.'/cache/compiled.php';
if (file_exists($compiledPath)) if (file_exists($compiledPath))
{ {

2
bootstrap/cache/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -14,7 +14,7 @@
"underscore": "1.7.0", "underscore": "1.7.0",
"jspdf": "1.0.272", "jspdf": "1.0.272",
"bootstrap-datepicker": "1.4.0", "bootstrap-datepicker": "1.4.0",
"typeahead.js": "0.9.3", "typeahead.js": "0.11.1",
"accounting": "0.3.2", "accounting": "0.3.2",
"spectrum": "1.3.4", "spectrum": "1.3.4",
"d3": "3.4.11", "d3": "3.4.11",
@ -25,7 +25,8 @@
"moment-timezone": "~0.4.0", "moment-timezone": "~0.4.0",
"quill": "~0.20.0", "quill": "~0.20.0",
"datetimepicker": "~2.4.5", "datetimepicker": "~2.4.5",
"stacktrace-js": "~1.0.1" "stacktrace-js": "~1.0.1",
"fuse.js": "~2.0.2"
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

View File

@ -10,11 +10,12 @@
} }
], ],
"require": { "require": {
"turbo124/laravel-push-notification": "dev-laravel5",
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525", "omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"omnipay/gocardless": "dev-master", "omnipay/gocardless": "dev-master",
"omnipay/stripe": "2.3.0", "omnipay/stripe": "2.3.0",
"laravel/framework": "5.0.*", "laravel/framework": "5.1.*",
"patricktalmadge/bootstrapper": "5.5.x", "patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev", "anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0.2", "barryvdh/laravel-debugbar": "~2.0.2",
@ -27,7 +28,6 @@
"jsanc623/phpbenchtime": "2.x", "jsanc623/phpbenchtime": "2.x",
"lokielse/omnipay-alipay": "dev-master", "lokielse/omnipay-alipay": "dev-master",
"coatesap/omnipay-datacash": "~2.0", "coatesap/omnipay-datacash": "~2.0",
"alfaproject/omnipay-neteller": "1.0.*@dev",
"mfauveau/omnipay-pacnet": "~2.0", "mfauveau/omnipay-pacnet": "~2.0",
"coatesap/omnipay-paymentsense": "2.0.0", "coatesap/omnipay-paymentsense": "2.0.0",
"coatesap/omnipay-realex": "~2.0", "coatesap/omnipay-realex": "~2.0",

1556
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,7 @@ return [
'Illuminate\Translation\TranslationServiceProvider', 'Illuminate\Translation\TranslationServiceProvider',
'Illuminate\Validation\ValidationServiceProvider', 'Illuminate\Validation\ValidationServiceProvider',
'Illuminate\View\ViewServiceProvider', 'Illuminate\View\ViewServiceProvider',
'Illuminate\Broadcasting\BroadcastServiceProvider',
/* /*
* Additional Providers * Additional Providers
@ -163,6 +164,7 @@ return [
'App\Providers\RouteServiceProvider', 'App\Providers\RouteServiceProvider',
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
'Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider',
], ],
/* /*
@ -248,6 +250,7 @@ return [
'Rocketeer' => 'Rocketeer\Facades\Rocketeer', 'Rocketeer' => 'Rocketeer\Facades\Rocketeer',
'Socialite' => 'Laravel\Socialite\Facades\Socialite', 'Socialite' => 'Laravel\Socialite\Facades\Socialite',
'Excel' => 'Maatwebsite\Excel\Facades\Excel', 'Excel' => 'Maatwebsite\Excel\Facades\Excel',
'PushNotification' => 'Davibennun\LaravelPushNotification\Facades\PushNotification',
], ],

View File

@ -0,0 +1,23 @@
<?php
return [
'devNinjaIOS' => [
'environment' =>'development',
'certificate'=>app_path().'/certs/ninjaIOS.pem',
'passPhrase' =>'',
'service' =>'apns'
],
'ninjaIOS' => [
'environment' =>'production',
'certificate'=>app_path().'/certs/productionNinjaIOS.pem',
'passPhrase' =>'',
'service' =>'apns'
],
'ninjaAndroid' => [
'environment' =>'production',
'apiKey' =>'yourAPIKey',
'service' =>'gcm'
]
];

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCustomInvoiceFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table) {
$table->string('custom_invoice_item_label1')->nullable();
$table->string('custom_invoice_item_label2')->nullable();
$table->string('recurring_invoice_number_prefix')->default('R');
$table->boolean('enable_client_portal')->default(true);
$table->text('invoice_fields')->nullable();
$table->text('devices')->nullable();
});
Schema::table('invoice_items', function($table) {
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table) {
$table->dropColumn('custom_invoice_item_label1');
$table->dropColumn('custom_invoice_item_label2');
$table->dropColumn('recurring_invoice_number_prefix');
$table->dropColumn('enable_client_portal');
$table->dropColumn('invoice_fields');
$table->dropColumn('devices');
});
Schema::table('accounts', function($table) {
$table->dropColumn('custom_value1');
$table->dropColumn('custom_value2');
});
}
}

View File

@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Database\Eloquent\Model as Eloquent;
use App\Models\Country;
class CountriesSeeder extends Seeder { class CountriesSeeder extends Seeder {
@ -13,6 +14,7 @@ class CountriesSeeder extends Seeder {
//Empty the countries table //Empty the countries table
DB::table('countries')->delete(); DB::table('countries')->delete();
if (DB::table('countries')->count() == 0) {
//Get all of the countries //Get all of the countries
$countries = Countries::getList(); $countries = Countries::getList();
foreach ($countries as $countryId => $country){ foreach ($countries as $countryId => $country){
@ -34,4 +36,134 @@ class CountriesSeeder extends Seeder {
)); ));
} }
} }
// Source: http://www.bitboost.com/ref/international-address-formats.html
// Source: https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro
$countries = [
'AR' => [
'swap_postal_code' => true,
],
'AT' => [ // Austria
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'BE' => [
'swap_postal_code' => true,
],
'BG' => [ // Belgium
'swap_currency_symbol' => true,
],
'CH' => [
'swap_postal_code' => true,
],
'CZ' => [ // Czech Republic
'swap_currency_symbol' => true,
],
'DE' => [ // Germany
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'DK' => [
'swap_postal_code' => true,
],
'EE' => [ // Estonia
'swap_currency_symbol' => true,
],
'ES' => [ // Spain
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'FI' => [ // Finland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'FR' => [ // France
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'GR' => [ // Greece
'swap_currency_symbol' => true,
],
'HR' => [ // Croatia
'swap_currency_symbol' => true,
],
'HU' => [ // Hungary
'swap_currency_symbol' => true,
],
'GL' => [
'swap_postal_code' => true,
],
'IE' => [ // Ireland
'thousand_separator' => ',',
'decimal_separator' => '.',
],
'IL' => [
'swap_postal_code' => true,
],
'IS' => [ // Iceland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'IT' => [ // Italy
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'LT' => [ // Lithuania
'swap_currency_symbol' => true,
],
'LU' => [
'swap_postal_code' => true,
],
'MY' => [
'swap_postal_code' => true,
],
'MX' => [
'swap_postal_code' => true,
],
'NL' => [
'swap_postal_code' => true,
],
'PL' => [ // Poland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'PT' => [ // Portugal
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'RO' => [ // Romania
'swap_currency_symbol' => true,
],
'SE' => [ // Sweden
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'SI' => [ // Slovenia
'swap_currency_symbol' => true,
],
'SK' => [ // Slovakia
'swap_currency_symbol' => true,
],
'UY' => [
'swap_postal_code' => true,
],
];
foreach ($countries as $code => $data) {
$country = Country::where('iso_3166_2', '=', $code)->first();
if (isset($data['swap_postal_code'])) {
$country->swap_postal_code = true;
}
if (isset($data['swap_currency_symbol'])) {
$country->swap_currency_symbol = true;
}
if (isset($data['thousand_separator'])) {
$country->thousand_separator = $data['thousand_separator'];
}
if (isset($data['decimal_separator'])) {
$country->decimal_separator = $data['decimal_separator'];
}
$country->save();
}
}
} }

View File

@ -0,0 +1,69 @@
<?php
use App\Models\Currency;
class CurrenciesSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
$currencies = [
['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'South African Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'],
['name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => 'E£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Chinese Renminbi', 'code' => 'CNY', 'symbol' => 'RMB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Rwandan Franc', 'code' => 'RWF', 'symbol' => 'RF ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Tanzanian Shilling', 'code' => 'TZS', 'symbol' => 'TSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Netherlands Antillean Guilder', 'code' => 'ANG', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Trinidad and Tobago Dollar', 'code' => 'TTD', 'symbol' => 'TT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'East Caribbean Dollar', 'code' => 'XCD', 'symbol' => 'EC$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Ghanaian Cedi', 'code' => 'GHS', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {
$record = Currency::whereCode($currency['code'])->first();
if ($record) {
$record->name = $currency['name'];
$record->symbol = $currency['symbol'];
$record->thousand_separator = $currency['thousand_separator'];
$record->decimal_separator = $currency['decimal_separator'];
$record->save();
} else {
Currency::create($currency);
}
}
}
}

View File

@ -19,5 +19,9 @@ class DatabaseSeeder extends Seeder
$this->call('FontsSeeder'); $this->call('FontsSeeder');
$this->call('BanksSeeder'); $this->call('BanksSeeder');
$this->call('InvoiceStatusSeeder'); $this->call('InvoiceStatusSeeder');
$this->call('CurrenciesSeeder');
$this->call('DateFormatsSeeder');
$this->call('InvoiceDesignsSeeder');
$this->call('PaymentTermsSeeder');
} }
} }

View File

@ -0,0 +1,102 @@
<?php
use App\Models\DateFormat;
use App\Models\DatetimeFormat;
class DateFormatsSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
// Date formats
$formats = [
['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'],
['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'],
['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'],
['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'],
['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'],
['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'],
['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'],
['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'],
['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'],
['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013']
];
foreach ($formats as $format) {
$record = DateFormat::whereLabel($format['label'])->first();
if ($record) {
$record->format = $format['format'];
$record->picker_format = $format['picker_format'];
$record->save();
} else {
DateFormat::create($format);
}
}
// Date/time formats
$formats = [
[
'format' => 'd/M/Y g:i a',
'format_moment' => 'DD/MMM/YYYY h:mm:ss a',
'label' => '10/Mar/2013'
],
[
'format' => 'd-M-Y g:i a',
'format_moment' => 'DD-MMM-YYYY h:mm:ss a',
'label' => '10-Mar-2013'
],
[
'format' => 'd/F/Y g:i a',
'format_moment' => 'DD/MMMM/YYYY h:mm:ss a',
'label' => '10/March/2013'
],
[
'format' => 'd-F-Y g:i a',
'format_moment' => 'DD-MMMM-YYYY h:mm:ss a',
'label' => '10-March-2013'
],
[
'format' => 'M j, Y g:i a',
'format_moment' => 'MMM D, YYYY h:mm:ss a',
'label' => 'Mar 10, 2013 6:15 pm'
],
[
'format' => 'F j, Y g:i a',
'format_moment' => 'MMMM D, YYYY h:mm:ss a',
'label' => 'March 10, 2013 6:15 pm'
],
[
'format' => 'D M jS, Y g:i a',
'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a',
'label' => 'Mon March 10th, 2013 6:15 pm'
],
[
'format' => 'Y-m-d g:i a',
'format_moment' => 'YYYY-MMM-DD h:mm:ss a',
'label' => '2013-03-10 6:15 pm'
],
[
'format' => 'd-m-Y g:i a',
'format_moment' => 'DD-MM-YYYY h:mm:ss a',
'label' => '20-03-2013 6:15 pm'
],
[
'format' => 'm/d/Y g:i a',
'format_moment' => 'MM/DD/YYYY h:mm:ss a',
'label' => '03/20/2013 6:15 pm'
]
];
foreach ($formats as $format) {
$record = DatetimeFormat::whereLabel($format['label'])->first();
if ($record) {
$record->format = $format['format'];
$record->format_moment = $format['format_moment'];
$record->save();
} else {
DatetimeFormat::create($format);
}
}
}
}

View File

@ -0,0 +1,43 @@
<?php
use App\Models\InvoiceDesign;
class InvoiceDesignsSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
$designs = [
'Clean',
'Bold',
'Modern',
'Plain',
'Business',
'Creative',
'Elegant',
'Hipster',
'Playful',
'Photo',
];
for ($i=0; $i<count($designs); $i++) {
$design = $designs[$i];
$fileName = storage_path() . '/templates/' . strtolower($design) . '.js';
if (file_exists($fileName)) {
$pdfmake = file_get_contents($fileName);
if ($pdfmake) {
$record = InvoiceDesign::whereName($design)->first();
if (!$record) {
$record = new InvoiceDesign;
$record->id = $i + 1;
$record->name = $design;
}
$record->pdfmake = $pdfmake;
$record->save();
}
}
}
}
}

View File

@ -14,16 +14,6 @@ class PaymentLibrariesSeeder extends Seeder
{ {
Eloquent::unguard(); Eloquent::unguard();
$this->createGateways();
$this->createPaymentTerms();
$this->createDateFormats();
$this->createDatetimeFormats();
$this->createInvoiceDesigns();
$this->updateLocalization();
}
private function createGateways() {
$gateways = [ $gateways = [
['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2], ['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2],
['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2], ['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2],
@ -32,7 +22,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard', 'payment_library_id' => 1], ['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard', 'payment_library_id' => 1],
['name' => 'Coinbase', 'provider' => 'Coinbase', 'payment_library_id' => 1], ['name' => 'Coinbase', 'provider' => 'Coinbase', 'payment_library_id' => 1],
['name' => 'DataCash', 'provider' => 'DataCash', 'payment_library_id' => 1], ['name' => 'DataCash', 'provider' => 'DataCash', 'payment_library_id' => 1],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 1], ['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 2],
['name' => 'Pacnet', 'provider' => 'Pacnet', 'payment_library_id' => 1], ['name' => 'Pacnet', 'provider' => 'Pacnet', 'payment_library_id' => 1],
['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1], ['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1],
['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1], ['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1],
@ -71,334 +61,4 @@ class PaymentLibrariesSeeder extends Seeder
} }
} }
private function createPaymentTerms() {
$paymentTerms = [
['num_days' => -1, 'name' => 'Net 0'],
];
foreach ($paymentTerms as $paymentTerm) {
if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) {
PaymentTerm::create($paymentTerm);
}
}
$currencies = [
['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'South African Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'],
['name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => 'E£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Chinese Renminbi', 'code' => 'CNY', 'symbol' => 'RMB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Rwandan Franc', 'code' => 'RWF', 'symbol' => 'RF ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Tanzanian Shilling', 'code' => 'TZS', 'symbol' => 'TSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Netherlands Antillean Guilder', 'code' => 'ANG', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Trinidad and Tobago Dollar', 'code' => 'TTD', 'symbol' => 'TT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'East Caribbean Dollar', 'code' => 'XCD', 'symbol' => 'EC$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Ghanaian Cedi', 'code' => 'GHS', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {
$record = Currency::whereCode($currency['code'])->first();
if ($record) {
$record->name = $currency['name'];
$record->symbol = $currency['symbol'];
$record->thousand_separator = $currency['thousand_separator'];
$record->decimal_separator = $currency['decimal_separator'];
$record->save();
} else {
Currency::create($currency);
}
}
}
private function createDateFormats() {
$formats = [
['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'],
['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'],
['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'],
['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'],
['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'],
['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'],
['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'],
['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'],
['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'],
['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013']
];
foreach ($formats as $format) {
$record = DateFormat::whereLabel($format['label'])->first();
if ($record) {
$record->format = $format['format'];
$record->picker_format = $format['picker_format'];
$record->save();
} else {
DateFormat::create($format);
}
}
}
private function createDatetimeFormats() {
$formats = [
[
'format' => 'd/M/Y g:i a',
'format_moment' => 'DD/MMM/YYYY h:mm:ss a',
'label' => '10/Mar/2013'
],
[
'format' => 'd-M-Y g:i a',
'format_moment' => 'DD-MMM-YYYY h:mm:ss a',
'label' => '10-Mar-2013'
],
[
'format' => 'd/F/Y g:i a',
'format_moment' => 'DD/MMMM/YYYY h:mm:ss a',
'label' => '10/March/2013'
],
[
'format' => 'd-F-Y g:i a',
'format_moment' => 'DD-MMMM-YYYY h:mm:ss a',
'label' => '10-March-2013'
],
[
'format' => 'M j, Y g:i a',
'format_moment' => 'MMM D, YYYY h:mm:ss a',
'label' => 'Mar 10, 2013 6:15 pm'
],
[
'format' => 'F j, Y g:i a',
'format_moment' => 'MMMM D, YYYY h:mm:ss a',
'label' => 'March 10, 2013 6:15 pm'
],
[
'format' => 'D M jS, Y g:i a',
'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a',
'label' => 'Mon March 10th, 2013 6:15 pm'
],
[
'format' => 'Y-m-d g:i a',
'format_moment' => 'YYYY-MMM-DD h:mm:ss a',
'label' => '2013-03-10 6:15 pm'
],
[
'format' => 'd-m-Y g:i a',
'format_moment' => 'DD-MM-YYYY h:mm:ss a',
'label' => '20-03-2013 6:15 pm'
],
[
'format' => 'm/d/Y g:i a',
'format_moment' => 'MM/DD/YYYY h:mm:ss a',
'label' => '03/20/2013 6:15 pm'
]
];
foreach ($formats as $format) {
$record = DatetimeFormat::whereLabel($format['label'])->first();
if ($record) {
$record->format = $format['format'];
$record->format_moment = $format['format_moment'];
$record->save();
} else {
DatetimeFormat::create($format);
}
}
}
private function createInvoiceDesigns() {
$designs = [
'Clean',
'Bold',
'Modern',
'Plain',
'Business',
'Creative',
'Elegant',
'Hipster',
'Playful',
'Photo',
];
for ($i=0; $i<count($designs); $i++) {
$design = $designs[$i];
$fileName = storage_path() . '/templates/' . strtolower($design) . '.js';
if (file_exists($fileName)) {
$pdfmake = file_get_contents($fileName);
if ($pdfmake) {
$record = InvoiceDesign::whereName($design)->first();
if (!$record) {
$record = new InvoiceDesign;
$record->id = $i + 1;
$record->name = $design;
}
$record->pdfmake = $pdfmake;
$record->save();
}
}
}
}
private function updateLocalization() {
// Source: http://www.bitboost.com/ref/international-address-formats.html
// Source: https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro
$countries = [
'AR' => [
'swap_postal_code' => true,
],
'AT' => [ // Austria
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'BE' => [
'swap_postal_code' => true,
],
'BG' => [ // Belgium
'swap_currency_symbol' => true,
],
'CH' => [
'swap_postal_code' => true,
],
'CZ' => [ // Czech Republic
'swap_currency_symbol' => true,
],
'DE' => [ // Germany
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'DK' => [
'swap_postal_code' => true,
],
'EE' => [ // Estonia
'swap_currency_symbol' => true,
],
'ES' => [ // Spain
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'FI' => [ // Finland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'FR' => [ // France
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'GR' => [ // Greece
'swap_currency_symbol' => true,
],
'HR' => [ // Croatia
'swap_currency_symbol' => true,
],
'HU' => [ // Hungary
'swap_currency_symbol' => true,
],
'GL' => [
'swap_postal_code' => true,
],
'IE' => [ // Ireland
'thousand_separator' => ',',
'decimal_separator' => '.',
],
'IL' => [
'swap_postal_code' => true,
],
'IS' => [ // Iceland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'IT' => [ // Italy
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'LT' => [ // Lithuania
'swap_currency_symbol' => true,
],
'LU' => [
'swap_postal_code' => true,
],
'MY' => [
'swap_postal_code' => true,
],
'MX' => [
'swap_postal_code' => true,
],
'NL' => [
'swap_postal_code' => true,
],
'PL' => [ // Poland
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'PT' => [ // Portugal
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'RO' => [ // Romania
'swap_currency_symbol' => true,
],
'SE' => [ // Sweden
'swap_postal_code' => true,
'swap_currency_symbol' => true,
],
'SI' => [ // Slovenia
'swap_currency_symbol' => true,
],
'SK' => [ // Slovakia
'swap_currency_symbol' => true,
],
'UY' => [
'swap_postal_code' => true,
],
];
foreach ($countries as $code => $data) {
$country = Country::where('iso_3166_2', '=', $code)->first();
if (isset($data['swap_postal_code'])) {
$country->swap_postal_code = true;
}
if (isset($data['swap_currency_symbol'])) {
$country->swap_currency_symbol = true;
}
if (isset($data['thousand_separator'])) {
$country->thousand_separator = $data['thousand_separator'];
}
if (isset($data['decimal_separator'])) {
$country->decimal_separator = $data['decimal_separator'];
}
$country->save();
}
}
} }

View File

@ -0,0 +1,22 @@
<?php
use App\Models\PaymentTerm;
class PaymentTermsSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
$paymentTerms = [
['num_days' => -1, 'name' => 'Net 0'],
];
foreach ($paymentTerms as $paymentTerm) {
if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) {
PaymentTerm::create($paymentTerm);
}
}
}
}

File diff suppressed because one or more lines are too long

318
public/css/built.css vendored
View File

@ -2054,301 +2054,77 @@ See http://bgrins.github.io/spectrum/themes/ for instructions.
.combobox-container:not(.combobox-selected) .fa-times { .combobox-container:not(.combobox-selected) .fa-times {
display: none; display: none;
} }
.twitter-typeahead .tt-query, /**********************************************************
.twitter-typeahead .tt-hint { * typeahead.js v0.11.1 - twitter bootstrap v3.3.5 *
margin-bottom: 0; **********************************************************/
/*root typeahead class*/
.twitter-typeahead {
/*display: inherit !important;*/
width: 100%;
} }
.tt-dropdown-menu { .twitter-typeahead .tt-input[disabled] {
min-width: 160px; background-color : #eeeeee !important;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
} }
.tt-suggestion { /*Added to input that's initialized into a typeahead*/
display: block; .twitter-typeahead .tt-input {
padding: 3px 20px;
} }
.tt-suggestion.tt-is-under-cursor { /*Added to hint input.*/
color: #fff; .twitter-typeahead .hint {
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
} }
.tt-suggestion.tt-is-under-cursor a { /*Added to menu element*/
color: #fff; .twitter-typeahead .tt-menu {
} width: 100%;
max-height: 500px;
.tt-suggestion p { overflow-y: none;
margin: 0; border: 1px solid #cccccc;
}
/*
.tt-hint {
padding: 6px 12px;
}
*/
.twitter-typeahead .tt-hint
{
display: block;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
border: 1px solid transparent;
border-radius:4px; border-radius:4px;
-moz-box-shadow: 12px 14px 30px -7px #616161;
-webkit-box-shadow: 12px 14px 30px -7px #616161;
box-shadow: 12px 14px 30px -7px #616161;
} }
.twitter-typeahead .hint-small /*Added to dataset elements*/
{ .twitter-typeahead .tt-dataset {
height: 30px;
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
line-height: 1.5;
} }
.twitter-typeahead .hint-large /*dded to suggestion elements*/
{ .twitter-typeahead .tt-suggestion {
height: 45px; padding: 3px 20px;
padding: 10px 16px; white-space: nowrap;
font-size: 18px;
border-radius: 6px;
line-height: 1.33;
}
/* Preload images */
body:after {
content: url(../images/lightbox/close.png) url(../images/lightbox/loading.gif) url(../images/lightbox/prev.png) url(../images/lightbox/next.png);
display: none;
} }
.lightboxOverlay { /*Added to menu element when it contains no content*/
position: absolute; .twitter-typeahead .tt-empty {
top: 0;
left: 0;
z-index: 9999;
background-color: black;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
opacity: 0.8;
display: none;
}
.lightbox {
position: absolute;
left: 0;
width: 100%;
z-index: 10000;
text-align: center;
line-height: 0;
font-weight: normal;
}
.lightbox .lb-image {
display: block;
height: auto;
max-width: inherit;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
}
.lightbox a img {
border: none;
}
.lb-outerContainer {
position: relative;
background-color: white; background-color: white;
*zoom: 1;
width: 250px;
height: 250px;
margin: 0 auto;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
} }
.lb-outerContainer:after { /*Added to menu element when it is opened*/
content: ""; .twitter-typeahead .tt-open {
display: table; background-color: white;
clear: both;
} }
.lb-container { /*Added to suggestion element when menu cursor moves to said suggestion*/
padding: 4px; .twitter-typeahead .tt-suggestion:hover,
.twitter-typeahead .tt-suggestion:focus,
.twitter-typeahead .tt-cursor {
cursor: hand !important;
background-color: #337ab7;
color: white;
} }
.lb-loader { /*Added to the element that wraps highlighted text*/
position: absolute; .twitter-typeahead .tt-highlight {
top: 43%;
left: 0;
height: 25%;
width: 100%;
text-align: center;
line-height: 0;
}
.lb-cancel {
display: block;
width: 32px;
height: 32px;
margin: 0 auto;
background: url(../images/lightbox/loading.gif) no-repeat;
} }
.lb-nav {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 10;
}
.lb-container > .nav {
left: 0;
}
.lb-nav a {
outline: none;
background-image: url('data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');
}
.lb-prev, .lb-next {
height: 100%;
cursor: pointer;
display: block;
}
.lb-nav a.lb-prev {
width: 34%;
left: 0;
float: left;
background: url(../images/lightbox/prev.png) left 48% no-repeat;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
-o-transition: opacity 0.6s;
transition: opacity 0.6s;
}
.lb-nav a.lb-prev:hover {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
.lb-nav a.lb-next {
width: 64%;
right: 0;
float: right;
background: url(../images/lightbox/next.png) right 48% no-repeat;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
-o-transition: opacity 0.6s;
transition: opacity 0.6s;
}
.lb-nav a.lb-next:hover {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
.lb-dataContainer {
margin: 0 auto;
padding-top: 5px;
*zoom: 1;
width: 100%;
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.lb-dataContainer:after {
content: "";
display: table;
clear: both;
}
.lb-data {
padding: 0 4px;
color: #ccc;
}
.lb-data .lb-details {
width: 85%;
float: left;
text-align: left;
line-height: 1.1em;
}
.lb-data .lb-caption {
font-size: 13px;
font-weight: bold;
line-height: 1em;
}
.lb-data .lb-number {
display: block;
clear: left;
padding-bottom: 1em;
font-size: 12px;
color: #999999;
}
.lb-data .lb-close {
display: block;
float: right;
width: 30px;
height: 30px;
background: url(../images/lightbox/close.png) top right no-repeat;
text-align: right;
outline: none;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
opacity: 0.7;
-webkit-transition: opacity 0.2s;
-moz-transition: opacity 0.2s;
-o-transition: opacity 0.2s;
transition: opacity 0.2s;
}
.lb-data .lb-close:hover {
cursor: pointer;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
body { background: #f8f8f8 !important; body { background: #f8f8f8 !important;
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 15px; font-size: 15px;

View File

@ -1,84 +1,71 @@
.twitter-typeahead .tt-query, /**********************************************************
.twitter-typeahead .tt-hint { * typeahead.js v0.11.1 - twitter bootstrap v3.3.5 *
margin-bottom: 0; **********************************************************/
/*root typeahead class*/
.twitter-typeahead {
/*display: inherit !important;*/
width: 100%;
} }
.tt-dropdown-menu { .twitter-typeahead .tt-input[disabled] {
min-width: 160px; background-color : #eeeeee !important;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
} }
.tt-suggestion { /*Added to input that's initialized into a typeahead*/
display: block; .twitter-typeahead .tt-input {
padding: 3px 20px;
} }
.tt-suggestion.tt-is-under-cursor { /*Added to hint input.*/
color: #fff; .twitter-typeahead .hint {
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
} }
.tt-suggestion.tt-is-under-cursor a { /*Added to menu element*/
color: #fff; .twitter-typeahead .tt-menu {
} width: 100%;
max-height: 500px;
.tt-suggestion p { overflow-y: none;
margin: 0; border: 1px solid #cccccc;
}
/*
.tt-hint {
padding: 6px 12px;
}
*/
.twitter-typeahead .tt-hint
{
display: block;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
border: 1px solid transparent;
border-radius:4px; border-radius:4px;
-moz-box-shadow: 12px 14px 30px -7px #616161;
-webkit-box-shadow: 12px 14px 30px -7px #616161;
box-shadow: 12px 14px 30px -7px #616161;
} }
.twitter-typeahead .hint-small /*Added to dataset elements*/
{ .twitter-typeahead .tt-dataset {
height: 30px;
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
line-height: 1.5;
} }
.twitter-typeahead .hint-large /*dded to suggestion elements*/
{ .twitter-typeahead .tt-suggestion {
height: 45px; padding: 3px 20px;
padding: 10px 16px; white-space: nowrap;
font-size: 18px; }
border-radius: 6px;
line-height: 1.33; /*Added to menu element when it contains no content*/
.twitter-typeahead .tt-empty {
background-color: white;
}
/*Added to menu element when it is opened*/
.twitter-typeahead .tt-open {
background-color: white;
}
/*Added to suggestion element when menu cursor moves to said suggestion*/
.twitter-typeahead .tt-suggestion:hover,
.twitter-typeahead .tt-suggestion:focus,
.twitter-typeahead .tt-cursor {
cursor: hand !important;
background-color: #337ab7;
color: white;
}
/*Added to the element that wraps highlighted text*/
.twitter-typeahead .tt-highlight {
} }

View File

@ -209,18 +209,28 @@ NINJA.decodeJavascript = function(invoice, javascript)
} }
// search/replace values // search/replace values
var regExp = new RegExp('"\\$[\\\w\\\.]*?Value"', 'g'); var regExp = new RegExp('"\\$[a-z][\\\w\\\.]*?[Value]?"', 'g');
var matches = javascript.match(regExp); var matches = javascript.match(regExp);
if (matches) { if (matches) {
for (var i=0; i<matches.length; i++) { for (var i=0; i<matches.length; i++) {
var match = matches[i]; var match = matches[i];
// reserved words
if (['"$none"', '"$firstAndLast"', '"$notFirstAndLastColumn"', '"$notFirst"', '"$amount"', '"$primaryColor"', '"$secondaryColor"'].indexOf(match) >= 0) {
continue;
}
// legacy style had 'Value' at the end
if (endsWith(match, 'Value"')) {
field = match.substring(2, match.indexOf('Value')); field = match.substring(2, match.indexOf('Value'));
} else {
field = match.substring(2, match.length - 1);
}
field = toSnakeCase(field); field = toSnakeCase(field);
var value = getDescendantProp(invoice, field) || ' '; var value = getDescendantProp(invoice, field) || ' ';
value = doubleDollarSign(value); value = doubleDollarSign(value);
javascript = javascript.replace(match, '"'+value+'"'); javascript = javascript.replace(match, '"'+value+'"');
} }
} }
@ -249,7 +259,21 @@ NINJA.notesAndTerms = function(invoice)
NINJA.invoiceColumns = function(invoice) NINJA.invoiceColumns = function(invoice)
{ {
var account = invoice.account; var account = invoice.account;
var columns = ["15%", "*"]; var columns = [];
if (invoice.has_product_key) {
columns.push("15%");
}
columns.push("*")
if (account.custom_invoice_item_label1) {
columns.push("10%");
}
if (account.custom_invoice_item_label2) {
columns.push("10%");
}
var count = 3; var count = 3;
if (account.hide_quantity == '1') { if (account.hide_quantity == '1') {
count--; count--;
@ -260,6 +284,7 @@ NINJA.invoiceColumns = function(invoice)
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
columns.push("14%"); columns.push("14%");
} }
return columns; return columns;
} }
@ -283,16 +308,28 @@ NINJA.taxWidth = function(invoice)
} }
NINJA.invoiceLines = function(invoice) { NINJA.invoiceLines = function(invoice) {
var account = invoice.account;
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var hideQuantity = invoice.account.hide_quantity == '1'; var hideQuantity = invoice.account.hide_quantity == '1';
var showItemTaxes = invoice.account.show_item_taxes == '1'; var showItemTaxes = invoice.account.show_item_taxes == '1';
var grid = [[ var grid = [[]];
{text: invoiceLabels.item, style: ['tableHeader', 'itemTableHeader']},
{text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']}, if (invoice.has_product_key) {
{text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']} grid[0].push({text: invoiceLabels.item, style: ['tableHeader', 'itemTableHeader']});
]]; }
grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']});
if (account.custom_invoice_item_label1) {
grid[0].push({text: account.custom_invoice_item_label1, style: ['tableHeader', 'custom1TableHeader']});
}
if (account.custom_invoice_item_label2) {
grid[0].push({text: account.custom_invoice_item_label2, style: ['tableHeader', 'custom2TableHeader']});
}
grid[0].push({text: invoiceLabels.unit_cost, style: ['tableHeader', 'costTableHeader']});
if (!hideQuantity) { if (!hideQuantity) {
grid[0].push({text: invoiceLabels.quantity, style: ['tableHeader', 'qtyTableHeader']}); grid[0].push({text: invoiceLabels.quantity, style: ['tableHeader', 'qtyTableHeader']});
@ -339,8 +376,16 @@ NINJA.invoiceLines = function(invoice) {
rowStyle = (i % 2 == 0) ? 'odd' : 'even'; rowStyle = (i % 2 == 0) ? 'odd' : 'even';
if (invoice.has_product_key) {
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
}
row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]}); row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]});
if (account.custom_invoice_item_label1) {
row.push({style:["customValue1", rowStyle], text:item.custom_value1 || ' '});
}
if (account.custom_invoice_item_label2) {
row.push({style:["customValue2", rowStyle], text:item.custom_value2 || ' '});
}
row.push({style:["cost", rowStyle], text:cost}); row.push({style:["cost", rowStyle], text:cost});
if (!hideQuantity) { if (!hideQuantity) {
row.push({style:["quantity", rowStyle], text:qty || ' '}); row.push({style:["quantity", rowStyle], text:qty || ' '});

View File

@ -589,6 +589,7 @@ function calculateAmounts(invoice) {
var total = 0; var total = 0;
var hasTaxes = false; var hasTaxes = false;
var taxes = {}; var taxes = {};
invoice.has_product_key = false;
// sum line item // sum line item
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
@ -604,6 +605,12 @@ function calculateAmounts(invoice) {
var taxRate = 0; var taxRate = 0;
var taxName = ''; var taxName = '';
if (item.product_key) {
invoice.has_product_key = true;
} else if (invoice.invoice_items.length == 1 && !item.qty) {
invoice.has_product_key = true;
}
// the object structure differs if it's read from the db or created by knockoutJS // the object structure differs if it's read from the db or created by knockoutJS
if (item.tax && parseFloat(item.tax.rate)) { if (item.tax && parseFloat(item.tax.rate)) {
taxRate = parseFloat(item.tax.rate); taxRate = parseFloat(item.tax.rate);
@ -961,6 +968,11 @@ function truncate(str, length) {
return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str; return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str;
} }
// http://stackoverflow.com/questions/280634/endswith-in-javascript
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
// http://codeaid.net/javascript/convert-seconds-to-hours-minutes-and-seconds-%28javascript%29 // http://codeaid.net/javascript/convert-seconds-to-hours-minutes-and-seconds-%28javascript%29
function secondsToTime(secs) function secondsToTime(secs)
{ {
@ -993,6 +1005,11 @@ function toSnakeCase(str) {
return str.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();}); return str.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
} }
// https://coderwall.com/p/iprsng/convert-snake-case-to-camelcase
function snakeToCamel(s){
return s.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
}
function getDescendantProp(obj, desc) { function getDescendantProp(obj, desc) {
var arr = desc.split("."); var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()])); while(arr.length && (obj = obj[arr.shift()]));
@ -1001,6 +1018,7 @@ function getDescendantProp(obj, desc) {
function doubleDollarSign(str) { function doubleDollarSign(str) {
if (!str) return ''; if (!str) return '';
if (!str.replace) return str;
return str.replace(/\$/g, '\$\$\$'); return str.replace(/\$/g, '\$\$\$');
} }
@ -1025,3 +1043,57 @@ function actionListHandler() {
} }
}); });
} }
function loadImages(selector) {
$(selector + ' img').each(function(index, item) {
var src = $(item).attr('data-src');
$(item).attr('src', src);
$(item).attr('data-src', src);
});
}
// http://stackoverflow.com/questions/4810841/how-can-i-pretty-print-json-using-javascript
function prettyJson(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
match = snakeToCamel(match);
return '<span class="' + cls + '">' + match + '</span>';
});
}
function searchData(data, key, fuzzy) {
return function findMatches(q, cb) {
var matches, substringRegex;
if (fuzzy) {
var options = {
keys: [key],
}
var fuse = new Fuse(data, options);
matches = fuse.search(q);
} else {
matches = [];
substrRegex = new RegExp(q, 'i');
$.each(data, function(i, obj) {
if (substrRegex.test(obj[key])) {
matches.push(obj);
}
});
}
cb(matches);
}
};

View File

@ -19,7 +19,8 @@
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) - $30 * [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) - $30
### Requirements ### Requirements
* PHP >= 5.4
* PHP >= 5.5.9
* MCrypt PHP Extension * MCrypt PHP Extension
* MySQL * MySQL
@ -68,6 +69,8 @@ We're using the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-mode
* [patricktalmadge/bootstrapper](https://github.com/patricktalmadge/bootstrapper) - Laravel Twitter Bootstrap Bundle * [patricktalmadge/bootstrapper](https://github.com/patricktalmadge/bootstrapper) - Laravel Twitter Bootstrap Bundle
* [danielfarrell/bootstrap-combobox](https://github.com/danielfarrell/bootstrap-combobox) - A combobox plugin * [danielfarrell/bootstrap-combobox](https://github.com/danielfarrell/bootstrap-combobox) - A combobox plugin
* [eternicode/bootstrap-datepicker](https://github.com/eternicode/bootstrap-datepicker) - A datepicker for @twitter bootstrap * [eternicode/bootstrap-datepicker](https://github.com/eternicode/bootstrap-datepicker) - A datepicker for @twitter bootstrap
* [twitter/typeahead.js](https://github.com/twitter/typeahead.js) - a fast and fully-featured autocomplete library
* [krisk/Fuse](https://github.com/krisk/Fuse) - Lightweight fuzzy-search, in JavaScript
* [knockout/knockout](https://github.com/knockout/knockout) - Knockout makes it easier to create rich, responsive UIs with JavaScript * [knockout/knockout](https://github.com/knockout/knockout) - Knockout makes it easier to create rich, responsive UIs with JavaScript
* [rniemeyer/knockout-sortable](https://github.com/rniemeyer/knockout-sortable) - A Knockout.js binding to connect observableArrays with jQuery UI sortable functionality * [rniemeyer/knockout-sortable](https://github.com/rniemeyer/knockout-sortable) - A Knockout.js binding to connect observableArrays with jQuery UI sortable functionality
* [bpampuch/pdfmake](https://github.com/bpampuch/pdfmake) - Client/server side PDF printing in pure JavaScript * [bpampuch/pdfmake](https://github.com/bpampuch/pdfmake) - Client/server side PDF printing in pure JavaScript

View File

@ -747,8 +747,7 @@ return array(
'primary_user' => 'Primær bruger', 'primary_user' => 'Primær bruger',
'help' => 'Hjælp', 'help' => 'Hjælp',
'customize_help' => '<p>Vi bruger <a href="http://pdfmake.org/" target="_blank">pdfmake</a> til at definere faktura design felter. pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">legeplads</a> giver en god mulighed for at se biblioteket i aktion.</p> 'customize_help' => '<p>Vi bruger <a href="http://pdfmake.org/" target="_blank">pdfmake</a> til at definere faktura design felter. pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">legeplads</a> giver en god mulighed for at se biblioteket i aktion.</p>
<p>Du kan tilgå alle faktura felter ved at tilføje <code>Value</code> til slutningen. For eksempel viser <code>$invoiceNumberValue</code> fakturanummeret.</p> <p>For at tilgå under indstillingerne ved hjælp af dot notation. For eksempel kan man for at vise klient navnet bruge <code>$client.name</code>.</p>
<p>For at tilgå under indstillingerne ved hjælp af dot notation. For eksempel kan man for at vise klient navnet bruge <code>$client.nameValue</code>.</p>
<p>Hvis du mangler svar nogen spørgsmål post et spørgsmål i vores <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>Hvis du mangler svar nogen spørgsmål post et spørgsmål i vores <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date', 'invoice_due_date' => 'Due Date',

View File

@ -747,8 +747,7 @@ return array(
'primary_user' => 'Primärer Benutzer', 'primary_user' => 'Primärer Benutzer',
'help' => 'Hilfe', 'help' => 'Hilfe',
'customize_help' => '<p>Wir benutzen zur deklarativen Definition der Rechnungsdesigns <a href="http://pdfmake.org/" target="_blank">pdfmake</a>. Der pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> bietet Gelegenheit die Bibliothek in Aktion zu sehen.</p> 'customize_help' => '<p>Wir benutzen zur deklarativen Definition der Rechnungsdesigns <a href="http://pdfmake.org/" target="_blank">pdfmake</a>. Der pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> bietet Gelegenheit die Bibliothek in Aktion zu sehen.</p>
<p>Man kann jedes Rechnungsfeld nutzen, in dem man <code>Value</code> hinten anhängt. Zum Beispiel zeigt <code>$invoiceNumberValue</code> die Rechnungsnummer.</p> <p>Mit der <i>dot notation</i> kann auf Kind-Eigenschaften zugegriffen werden. Für den Kundennamen kann man zum Beispiel <code>$client.name</code> benutzen.</p>
<p>Mit der <i>dot notation</i> kann auf Kind-Eigenschaften zugegriffen werden. Für den Kundennamen kann man zum Beispiel <code>$client.nameValue</code> benutzen.</p>
<p>Wenn du Hilfe brauchst schreibe uns gern im <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">Support Forum</a> (Englisch).</p>', <p>Wenn du Hilfe brauchst schreibe uns gern im <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">Support Forum</a> (Englisch).</p>',
'invoice_due_date' => 'Fällig am', 'invoice_due_date' => 'Fällig am',

View File

@ -81,7 +81,7 @@ $LANG = array(
'company_details' => 'Company Details', 'company_details' => 'Company Details',
'online_payments' => 'Online Payments', 'online_payments' => 'Online Payments',
'notifications' => 'Email Notifications', 'notifications' => 'Email Notifications',
'import_export' => 'Import/Export/Cancel', 'import_export' => 'Import | Export | Cancel',
'done' => 'Done', 'done' => 'Done',
'save' => 'Save', 'save' => 'Save',
'create' => 'Create', 'create' => 'Create',
@ -122,8 +122,8 @@ $LANG = array(
'filter' => 'Filter', 'filter' => 'Filter',
'new_client' => 'New Client', 'new_client' => 'New Client',
'new_invoice' => 'New Invoice', 'new_invoice' => 'New Invoice',
'new_payment' => 'Enter Payment', 'new_payment' => 'New Payment',
'new_credit' => 'Enter Credit', 'new_credit' => 'New Credit',
'contact' => 'Contact', 'contact' => 'Contact',
'date_created' => 'Date Created', 'date_created' => 'Date Created',
'last_login' => 'Last Login', 'last_login' => 'Last Login',
@ -655,8 +655,7 @@ $LANG = array(
'primary_user' => 'Primary User', 'primary_user' => 'Primary User',
'help' => 'Help', 'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date', 'invoice_due_date' => 'Due Date',
'quote_due_date' => 'Valid Until', 'quote_due_date' => 'Valid Until',
@ -846,10 +845,10 @@ $LANG = array(
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.',
'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', 'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.',
'custom_client_fields_helps' => 'Add a text input to the client create/edit page and display the label and value on the PDF.', 'custom_client_fields_helps' => 'Add a field when creating a client and display the label and value on the PDF.',
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.',
'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.', 'custom_invoice_fields_helps' => 'Add a field when creating an invoice and display the label and value on the PDF.',
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', 'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.', 'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link', 'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.', 'button_confirmation_message' => 'Click to confirm your email address.',
@ -868,7 +867,7 @@ $LANG = array(
'white_label_purchase_link' => 'Purchase a white label license', 'white_label_purchase_link' => 'Purchase a white label license',
'expense' => 'Expense', 'expense' => 'Expense',
'expenses' => 'Expenses', 'expenses' => 'Expenses',
'new_expense' => 'Enter Expense', 'new_expense' => 'New Expense',
'enter_expense' => 'Enter Expense', 'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors', 'vendors' => 'Vendors',
'new_vendor' => 'New Vendor', 'new_vendor' => 'New Vendor',
@ -1028,6 +1027,31 @@ $LANG = array(
'user_unconfirmed' => 'Please confirm your account to send emails', 'user_unconfirmed' => 'Please confirm your account to send emails',
'invalid_contact_email' => 'Invalid contact email', 'invalid_contact_email' => 'Invalid contact email',
], ],
'navigation' => 'Navigation',
'list_invoices' => 'List Invoices',
'list_clients' => 'List Clients',
'list_quotes' => 'List Quotes',
'list_tasks' => 'List Tasks',
'list_expenses' => 'List Expenses',
'list_recurring_invoices' => 'List Recurring Invoices',
'list_payments' => 'List Payments',
'list_credits' => 'List Credits',
'tax_name' => 'Tax Name',
'report_settings' => 'Report Settings',
'search_hotkey' => 'shortcut is /',
'new_user' => 'New User',
'new_product' => 'New Product',
'new_tax_rate' => 'New Tax Rate',
'invoiced_amount' => 'Invoiced Amount',
'invoice_item_fields' => 'Invoice Item Fields',
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
'recurring_invoice_number' => 'Recurring Invoice Number',
'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.',
'enable_client_portal' => 'Dashboard',
'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.',
); );
return $LANG; return $LANG;

View File

@ -725,8 +725,7 @@ return array(
'primary_user' => 'Usuario Primario', 'primary_user' => 'Usuario Primario',
'help' => 'Ayuda', 'help' => 'Ayuda',
'customize_help' => '<p>Nosotros usamos <a href="http://pdfmake.org/" target="_blank">pdfmake</a> para definir los diseños de las facturas de manera declarativa. El <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> de pdfmake es una excelente manera de ver a la librería en acción.</p> 'customize_help' => '<p>Nosotros usamos <a href="http://pdfmake.org/" target="_blank">pdfmake</a> para definir los diseños de las facturas de manera declarativa. El <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> de pdfmake es una excelente manera de ver a la librería en acción.</p>
<p>Puedes acceder cualquier campo de una factura agregando <code>Value</code> al final. Por ejemplo, <code>$invoiceNumberValue</code> muestra el número de factura.</p> <p>Para acceder a una propiedad hija usando notación de punto.Por ejemplo, para mostrar el nombre de un cliente se puede usar <code>$client.name</code>.</p>
<p>Para acceder a una propiedad hija usando notación de punto.Por ejemplo, para mostrar el nombre de un cliente se puede usar <code>$client.nameValue</code>.</p>
<p>Si necesitas ayuda entendiendo algo puede preguntar en nuestro <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">foro de soporte</a>.</p>', <p>Si necesitas ayuda entendiendo algo puede preguntar en nuestro <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">foro de soporte</a>.</p>',
'invoice_due_date' => 'Fecha de Vencimiento', 'invoice_due_date' => 'Fecha de Vencimiento',

View File

@ -746,8 +746,7 @@ return array(
'primary_user' => 'Usuario Principal', 'primary_user' => 'Usuario Principal',
'help' => 'Ayuda', 'help' => 'Ayuda',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Fecha de Pago', 'invoice_due_date' => 'Fecha de Pago',

View File

@ -738,8 +738,7 @@ return array(
'primary_user' => 'Utilisateur principal', 'primary_user' => 'Utilisateur principal',
'help' => 'Aide', 'help' => 'Aide',
'customize_help' => '<p>Nous utilisons <a href="http://pdfmake.org/" target="_blank">pdfmake</a> pour définir le design des factures. Le <a href="http://pdfmake.org/playground.html" target="_blank">bac à sable<a> de pdfmake est une bonne façon de voir cette bibliothèque en action.</p> 'customize_help' => '<p>Nous utilisons <a href="http://pdfmake.org/" target="_blank">pdfmake</a> pour définir le design des factures. Le <a href="http://pdfmake.org/playground.html" target="_blank">bac à sable<a> de pdfmake est une bonne façon de voir cette bibliothèque en action.</p>
<p>Vous pouvez accéder à n\'importe quel champ de facture en ajoutant <code>Value</code> à la fin. Par exemple <code>$invoiceNumberValue</code> affiche le numéro de facture.</p> <p>Pour accéder à une propriété héritée avec la notation par point. Par exemple pour montrer le nom du client vous pouvez utiliser <code>$client.name</code>.</p>
<p>Pour accéder à une propriété héritée avec la notation par point. Par exemple pour montrer le nom du client vous pouvez utiliser <code>$client.nameValue</code>.</p>
<p>Si vous avez besoin d\'aide pour comprendre quelque chose envoyez une question à notre <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">forum de support</a>.</p>', <p>Si vous avez besoin d\'aide pour comprendre quelque chose envoyez une question à notre <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">forum de support</a>.</p>',
'invoice_due_date' => 'Date limite', 'invoice_due_date' => 'Date limite',

View File

@ -741,8 +741,7 @@ return array(
'primary_user' => 'Utilisateur principal', 'primary_user' => 'Utilisateur principal',
'help' => 'Aide', 'help' => 'Aide',
'customize_help' => '<p>Nous utilisons <a href="http://pdfmake.org/" target="_blank">pdfmake</a> pour définir le design des factures de façon déclarative. L\'<a href="http://pdfmake.org/playground.html" target="_blank">environnement</a> pdfmake permet de voir la librairie en action.</p> 'customize_help' => '<p>Nous utilisons <a href="http://pdfmake.org/" target="_blank">pdfmake</a> pour définir le design des factures de façon déclarative. L\'<a href="http://pdfmake.org/playground.html" target="_blank">environnement</a> pdfmake permet de voir la librairie en action.</p>
<p>Vous pouvez accéder à n\'importe quel champ de facture en ajoutant <code>Value</code> à la fin. Par exemple <code>$invoiceNumberValue</code> affiche le numéro de facture.</p> <p>Pour accéder à une propriété enfant en utilisant la notation par point. Par exemple <code>$client.name</code>affiche le nom du client.</p>
<p>Pour accéder à une propriété enfant en utilisant la notation par point. Par exemple <code>$client.nameValue</code>affiche le nom du client.</p>
<p>Si vous avez besoin d\'aide à cet effet, n\'hésitez pas à publier une question sur notre <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">forum d\'aide (en anglais)</a>.</p>', <p>Si vous avez besoin d\'aide à cet effet, n\'hésitez pas à publier une question sur notre <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">forum d\'aide (en anglais)</a>.</p>',
'invoice_due_date' => 'Échéance', 'invoice_due_date' => 'Échéance',

View File

@ -743,8 +743,7 @@ return array(
'primary_user' => 'Primary User', 'primary_user' => 'Primary User',
'help' => 'Help', 'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date', 'invoice_due_date' => 'Due Date',

View File

@ -750,8 +750,7 @@ return array(
'primary_user' => 'Primary User', 'primary_user' => 'Primary User',
'help' => 'Help', 'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date', 'invoice_due_date' => 'Due Date',

View File

@ -746,8 +746,7 @@ return array(
'primary_user' => 'Hovedbruker', 'primary_user' => 'Hovedbruker',
'help' => 'Hjelp', 'help' => 'Hjelp',
'customize_help' => '<p>Vi bruker <a href="http://pdfmake.org/" target="_blank">pdfmake</a> for å definere faktura designene deklarativt. Pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> gir en flott måte å se biblioteket i aksjon.</p> 'customize_help' => '<p>Vi bruker <a href="http://pdfmake.org/" target="_blank">pdfmake</a> for å definere faktura designene deklarativt. Pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> gir en flott måte å se biblioteket i aksjon.</p>
<p>Du kan tilgang til hvilket som helst faktura felt ved å legge til <code>Value</code> i slutten. For eksempel <code>$invoiceNumberValue</code> viser faktura nummeret.</p> <p>For å tilgang til et underelementet ved bruk av prikk notasjon. For eksempel for å vise klientens navn, kan du bruke <code>$client.name</code>.</p>
<p>For å tilgang til et underelementet ved bruk av prikk notasjon. For eksempel for å vise klientens navn, kan du bruke <code>$client.nameValue</code>.</p>
<p>Om du trenger hjelp til å finne ut noe, poster et spørsmål til vårt <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">brukerforum</a>.</p>', <p>Om du trenger hjelp til å finne ut noe, poster et spørsmål til vårt <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">brukerforum</a>.</p>',
'invoice_due_date' => 'Tidsfrist', 'invoice_due_date' => 'Tidsfrist',

View File

@ -741,8 +741,7 @@ return array(
'primary_user' => 'Primaire gebruiker', 'primary_user' => 'Primaire gebruiker',
'help' => 'Help', 'help' => 'Help',
'customize_help' => '<p>We gebruiken <a href="http://pdfmake.org/" target="_blank">pdfmake</a> om de factuur ontwerpen declaratief te definieren. De pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> is een interessante manier om de library in actie te zien.</p> 'customize_help' => '<p>We gebruiken <a href="http://pdfmake.org/" target="_blank">pdfmake</a> om de factuur ontwerpen declaratief te definieren. De pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> is een interessante manier om de library in actie te zien.</p>
<p>Je kan elk factuur veld gebruiken door <code>Veld</code> toe te voegen op het einde. Bijvoorbeeld <code>$invoiceNumberValue</code> toont de factuur nummer.</p> <p>Gebruik dot notatie om een "kind eigenschap" te gebruiken. Bijvoorbeeld voor de klant naam te tonen gebruik je <code>$client.name</code>.</p>
<p>Gebruik dot notatie om een "kind eigenschap" te gebruiken. Bijvoorbeeld voor de klant naam te tonen gebruik je <code>$client.nameValue</code>.</p>
<p>Als je ergens hulp bij nodig hebt, post dan een vraag op ons <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>Als je ergens hulp bij nodig hebt, post dan een vraag op ons <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Vervaldatum', 'invoice_due_date' => 'Vervaldatum',

View File

@ -739,8 +739,7 @@ return array(
'primary_user' => 'Usuário Principal', 'primary_user' => 'Usuário Principal',
'help' => 'Ajuda', 'help' => 'Ajuda',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Data de vencimento', 'invoice_due_date' => 'Data de vencimento',

View File

@ -745,8 +745,7 @@ return array(
'primary_user' => 'Primary User', 'primary_user' => 'Primary User',
'help' => 'Help', 'help' => 'Help',
'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p> 'customize_help' => '<p>We use <a href="http://pdfmake.org/" target="_blank">pdfmake</a> to define the invoice designs declaratively. The pdfmake <a href="http://pdfmake.org/playground.html" target="_blank">playground</a> provide\'s a great way to see the library in action.</p>
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p> <p>To access a child property using dot notation. For example to show the client name you could use <code>$client.name</code>.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>', <p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date', 'invoice_due_date' => 'Due Date',

View File

@ -12,6 +12,7 @@
{!! Former::open_for_files() {!! Former::open_for_files()
->addClass('warn-on-exit') !!} ->addClass('warn-on-exit') !!}
{!! Former::populateField('enable_client_portal', intval($account->enable_client_portal)) !!}
{!! Former::populateField('client_view_css', $client_view_css) !!} {!! Former::populateField('client_view_css', $client_view_css) !!}
@if (!Utils::isNinja() && !Auth::user()->account->isWhiteLabel()) @if (!Utils::isNinja() && !Auth::user()->account->isWhiteLabel())
@ -27,6 +28,19 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.client_portal') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_client_portal')
->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_help')) !!}
</div>
</div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3> <h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>

View File

@ -22,6 +22,14 @@
background: #FFFFFF !important; background: #FFFFFF !important;
} }
/* http://stackoverflow.com/questions/4810841/how-can-i-pretty-print-json-using-javascript */
pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; }
.string { color: green; }
.number { color: red; }
.boolean { color: blue; }
.null { color: gray; }
.key { color: black; }
</style> </style>
@stop @stop
@ -142,6 +150,12 @@
}); });
refreshPDF(true); refreshPDF(true);
@if (isset($sampleInvoice) && $sampleInvoice)
var sample = {!! $sampleInvoice->toJSON() !!}
console.log(sample);
$('#sampleData').show().html(prettyJson(sample));
@endif
}); });
</script> </script>
@ -206,6 +220,8 @@
<div class="panel-body" style="background-color: #fff"> <div class="panel-body" style="background-color: #fff">
{!! trans('texts.customize_help') !!} {!! trans('texts.customize_help') !!}
<pre id="sampleData" style="display:none;height:200px;padding-top:16px;"></pre>
</div> </div>
<div class="modal-footer" style="margin-top: 0px"> <div class="modal-footer" style="margin-top: 0px">

View File

@ -8,6 +8,8 @@
<script src="{{ asset('js/vfs_fonts/'.$font.'.js') }}" type="text/javascript"></script> <script src="{{ asset('js/vfs_fonts/'.$font.'.js') }}" type="text/javascript"></script>
@endforeach @endforeach
<script src="{{ asset('pdf.built.js') }}" type="text/javascript"></script> <script src="{{ asset('pdf.built.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/lightbox.min.js') }}" type="text/javascript"></script>
<link href="{{ asset('css/lightbox.css') }}" rel="stylesheet" type="text/css"/>
@stop @stop

View File

@ -35,8 +35,15 @@
<div role="tabpanel"> <div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none"> <ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#invoiceNumber" aria-controls="invoiceNumber" role="tab" data-toggle="tab">{{ trans('texts.invoice_number') }}</a></li> <li role="presentation" class="active">
<li role="presentation"><a href="#quoteNumber" aria-controls="quoteNumber" role="tab" data-toggle="tab">{{ trans('texts.quote_number') }}</a></li> <a href="#invoiceNumber" aria-controls="invoiceNumber" role="tab" data-toggle="tab">{{ trans('texts.invoice_number') }}</a>
</li>
<li role="presentation">
<a href="#quoteNumber" aria-controls="quoteNumber" role="tab" data-toggle="tab">{{ trans('texts.quote_number') }}</a>
</li>
<li role="presentation">
<a href="#recurringInvoiceNumber" aria-controls="recurringInvoiceNumber" role="tab" data-toggle="tab">{{ trans('texts.recurring_invoice_number') }}</a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -92,6 +99,15 @@
trans('texts.next_quote_number', ['number' => $account->previewNextInvoiceNumber(ENTITY_QUOTE)])) !!} trans('texts.next_quote_number', ['number' => $account->previewNextInvoiceNumber(ENTITY_QUOTE)])) !!}
</div>
</div>
<div role="tabpanel" class="tab-pane" id="recurringInvoiceNumber">
<div class="panel-body">
{!! Former::text('recurring_invoice_number_prefix')
->label(trans('texts.prefix'))
->help(trans('texts.recurring_invoice_number_prefix_help')) !!}
</div> </div>
</div> </div>
</div> </div>
@ -108,10 +124,21 @@
<div role="tabpanel"> <div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none"> <ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#clientFields" aria-controls="clientFields" role="tab" data-toggle="tab">{{ trans('texts.client_fields') }}</a></li> <li role="presentation" class="active">
<li role="presentation"><a href="#companyFields" aria-controls="companyFields" role="tab" data-toggle="tab">{{ trans('texts.company_fields') }}</a></li> <a href="#clientFields" aria-controls="clientFields" role="tab" data-toggle="tab">{{ trans('texts.client_fields') }}</a>
<li role="presentation"><a href="#invoiceFields" aria-controls="invoiceFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_fields') }}</a></li> </li>
<li role="presentation"><a href="#invoiceCharges" aria-controls="invoiceCharges" role="tab" data-toggle="tab">{{ trans('texts.invoice_charges') }}</a></li> <li role="presentation">
<a href="#companyFields" aria-controls="companyFields" role="tab" data-toggle="tab">{{ trans('texts.company_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceFields" aria-controls="invoiceFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceItemFields" aria-controls="invoiceItemFields" role="tab" data-toggle="tab">{{ trans('texts.invoice_item_fields') }}</a>
</li>
<li role="presentation">
<a href="#invoiceCharges" aria-controls="invoiceCharges" role="tab" data-toggle="tab">{{ trans('texts.invoice_charges') }}</a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -153,6 +180,17 @@
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="invoiceItemFields">
<div class="panel-body">
{!! Former::text('custom_invoice_item_label1')
->label(trans('texts.field_label')) !!}
{!! Former::text('custom_invoice_item_label2')
->label(trans('texts.field_label'))
->help(trans('texts.custom_invoice_item_fields_help')) !!}
</div>
</div>
<div role="tabpanel" class="tab-pane" id="invoiceCharges"> <div role="tabpanel" class="tab-pane" id="invoiceCharges">
<div class="panel-body"> <div class="panel-body">

View File

@ -22,10 +22,8 @@
</div> </div>
<div class="list-group"> <div class="list-group">
@foreach ($settings as $section) @foreach ($settings as $section)
@if ($section != ACCOUNT_CLIENT_PORTAL || !Utils::isNinjaProd())
<a href="{{ URL::to("settings/{$section}") }}" class="list-group-item {{ $selected === $section ? 'selected' : '' }}" <a href="{{ URL::to("settings/{$section}") }}" class="list-group-item {{ $selected === $section ? 'selected' : '' }}"
style="width:100%;text-align:left">{{ trans("texts.{$section}") }}</a> style="width:100%;text-align:left">{{ trans("texts.{$section}") }}</a>
@endif
@endforeach @endforeach
@if ($type === ADVANCED_SETTINGS && !Utils::isNinjaProd()) @if ($type === ADVANCED_SETTINGS && !Utils::isNinjaProd())
<a href="{{ URL::to("settings/system_settings") }}" class="list-group-item {{ $selected === 'system_settings' ? 'selected' : '' }}" <a href="{{ URL::to("settings/system_settings") }}" class="list-group-item {{ $selected === 'system_settings' ? 'selected' : '' }}"

View File

@ -35,6 +35,11 @@
@include('export.invoices', ['entityType' => ENTITY_QUOTE]) @include('export.invoices', ['entityType' => ENTITY_QUOTE])
@endif @endif
@if (isset($recurringInvoices) && $recurringInvoices && count($recurringInvoices))
<tr><td>{{ strtoupper(trans('texts.recurring_invoices')) }}</td></tr>
@include('export.recurring_invoices', ['entityType' => ENTITY_RECURRING_INVOICE])
@endif
@if (isset($payments) && $payments && count($payments)) @if (isset($payments) && $payments && count($payments))
<tr><td>{{ strtoupper(trans('texts.payments')) }}</td></tr> <tr><td>{{ strtoupper(trans('texts.payments')) }}</td></tr>
@include('export.payments') @include('export.payments')

View File

@ -1,5 +1,6 @@
<tr> <tr>
<td>{{ trans('texts.client') }}</td> <td>{{ trans('texts.client') }}</td>
<td>{{ trans('texts.email') }}</td>
@if ($multiUser) @if ($multiUser)
<td>{{ trans('texts.user') }}</td> <td>{{ trans('texts.user') }}</td>
@endif @endif
@ -28,6 +29,7 @@
@if (!$invoice->client->is_deleted) @if (!$invoice->client->is_deleted)
<tr> <tr>
<td>{{ $invoice->present()->client }}</td> <td>{{ $invoice->present()->client }}</td>
<td>{{ $invoice->present()->email }}</td>
@if ($multiUser) @if ($multiUser)
<td>{{ $invoice->present()->user }}</td> <td>{{ $invoice->present()->user }}</td>
@endif @endif

View File

@ -0,0 +1,55 @@
<tr>
<td>{{ trans('texts.client') }}</td>
<td>{{ trans('texts.email') }}</td>
@if ($multiUser)
<td>{{ trans('texts.user') }}</td>
@endif
<td>{{ trans('texts.frequency') }}</td>
<td>{{ trans('texts.balance') }}</td>
<td>{{ trans('texts.amount') }}</td>
<td>{{ trans('texts.po_number') }}</td>
<td>{{ trans('texts.status') }}</td>
@if ($account->custom_invoice_label1)
<td>{{ $account->custom_invoice_label1 }}</td>
@endif
@if ($account->custom_invoice_label2)
<td>{{ $account->custom_invoice_label2 }}</td>
@endif
@if ($account->custom_invoice_text_label1)
<td>{{ $account->custom_invoice_text_label1 }}</td>
@endif
@if ($account->custom_invoice_text_label2)
<td>{{ $account->custom_invoice_text_label2 }}</td>
@endif
</tr>
@foreach ($recurringInvoices as $invoice)
@if (!$invoice->client->is_deleted)
<tr>
<td>{{ $invoice->present()->client }}</td>
<td>{{ $invoice->present()->email }}</td>
@if ($multiUser)
<td>{{ $invoice->present()->user }}</td>
@endif
<td>{{ $invoice->present()->frequency }}</td>
<td>{{ $account->formatMoney($invoice->balance, $invoice->client) }}</td>
<td>{{ $account->formatMoney($invoice->amount, $invoice->client) }}</td>
<td>{{ $invoice->po_number }}</td>
<td>{{ $invoice->present()->status }}</td>
@if ($account->custom_invoice_label1)
<td>{{ $invoice->custom_value1 }}</td>
@endif
@if ($account->custom_invoice_label2)
<td>{{ $invoice->custom_value2 }}</td>
@endif
@if ($account->custom_invoice_label1)
<td>{{ $invoice->custom_text_value1 }}</td>
@endif
@if ($account->custom_invoice_label2)
<td>{{ $invoice->custom_text_value2 }}</td>
@endif
</tr>
@endif
@endforeach
<tr><td></td></tr>

View File

@ -259,33 +259,34 @@
} }
function showSearch() { function showSearch() {
$('#search').typeahead('setQuery', ''); $('#search').typeahead('val', '');
$('#navbar-options').hide(); $('#navbar-options').hide();
$('#search-form').show(); $('#search-form').show();
if (window.hasOwnProperty('searchData')) { if (window.hasOwnProperty('loadedSearchData')) {
$('#search').focus(); $('#search').focus();
} else { } else {
trackEvent('/activity', '/search'); trackEvent('/activity', '/search');
$.get('{{ URL::route('getSearchData') }}', function(data) { $.get('{{ URL::route('getSearchData') }}', function(data) {
window.searchData = true; window.loadedSearchData = true;
var datasets = [];
for (var type in data) $('#search').typeahead({
{ hint: true,
if (!data.hasOwnProperty(type)) continue; highlight: true,
datasets.push({
name: type,
header: '&nbsp;<b>' + type + '</b>',
local: data[type]
});
} }
if (datasets.length == 0) { @foreach (['clients', 'contacts', 'invoices', 'quotes', 'navigation'] as $type)
return; ,{
name: 'data',
display: 'value',
source: searchData(data['{{ $type }}'], 'value', true),
templates: {
header: '&nbsp;<span style="font-weight:600;font-size:16px">{{ trans("texts.{$type}") }}</span>'
} }
$('#search').typeahead(datasets).on('typeahead:selected', function(element, datum, name) { }
var type = name == 'Contacts' ? 'clients' : name.toLowerCase(); @endforeach
window.location = '{{ URL::to('/') }}' + '/' + datum.entity_type + '/' + datum.public_id; ).on('typeahead:selected', function(element, datum, name) {
}).focus().typeahead('setQuery', $('#search').val()); window.location = datum.url;
}).focus();
}); });
} }
} }
@ -498,7 +499,7 @@
<form id="search-form" class="navbar-form navbar-right" role="search" style="display:none"> <form id="search-form" class="navbar-form navbar-right" role="search" style="display:none">
<div class="form-group"> <div class="form-group">
<input type="text" id="search" style="width: 240px;padding-top:0px;padding-bottom:0px" <input type="text" id="search" style="width: 240px;padding-top:0px;padding-bottom:0px"
class="form-control" placeholder="{{ trans('texts.search') }}"> class="form-control" placeholder="{{ trans('texts.search') . ': ' . trans('texts.search_hotkey')}}">
</div> </div>
</form> </form>
@ -736,7 +737,7 @@
@if (Auth::user()->account->isWhiteLabel()) @if (Auth::user()->account->isWhiteLabel())
{{ trans('texts.white_labeled') }} {{ trans('texts.white_labeled') }}
@else @else
<a href="#" onclick="$('#whiteLabelModal').modal('show');">{{ trans('texts.white_label_link') }}</a> <a href="#" onclick="loadImages('#whiteLabelModal');$('#whiteLabelModal').modal('show');">{{ trans('texts.white_label_link') }}</a>
<div class="modal fade" id="whiteLabelModal" tabindex="-1" role="dialog" aria-labelledby="whiteLabelModalLabel" aria-hidden="true"> <div class="modal fade" id="whiteLabelModal" tabindex="-1" role="dialog" aria-labelledby="whiteLabelModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
@ -751,11 +752,11 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h4>{{ trans('texts.before') }}</h4> <h4>{{ trans('texts.before') }}</h4>
{!! HTML::image('images/pro_plan/white_label_before.png', 'before', ['width' => '100%']) !!} <img src="{{ BLANK_IMAGE }}" data-src="http://ninja.dev/images/pro_plan/white_label_before.png" width="100%" alt="before">
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h4>{{ trans('texts.after') }}</h4> <h4>{{ trans('texts.after') }}</h4>
{!! HTML::image('images/pro_plan/white_label_after.png', 'after', ['width' => '100%']) !!} <img src="{{ BLANK_IMAGE }}" data-src="http://ninja.dev/images/pro_plan/white_label_after.png" width="100%" alt="after">
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,13 +4,15 @@
@parent @parent
@include('money_script') @include('money_script')
@foreach ($account->getFontFolders() as $font) @foreach ($account->getFontFolders() as $font)
<script src="{{ asset('js/vfs_fonts/'.$font.'.js') }}" type="text/javascript"></script> <script src="{{ asset('js/vfs_fonts/'.$font.'.js') }}" type="text/javascript"></script>
@endforeach @endforeach
<script src="{{ asset('pdf.built.js') }}" type="text/javascript"></script> <script src="{{ asset('pdf.built.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/lightbox.min.js') }}" type="text/javascript"></script>
<link href="{{ asset('css/lightbox.css') }}" rel="stylesheet" type="text/css"/>
<style type="text/css"> <style type="text/css">
/* the value is auto set so we're removing the bold formatting */ /* the value is auto set so we're removing the bold formatting */
label.control-label[for=invoice_number] { label.control-label[for=invoice_number] {
font-weight: normal !important; font-weight: normal !important;
@ -187,6 +189,12 @@
<th style="min-width:32px;" class="hide-border"></th> <th style="min-width:32px;" class="hide-border"></th>
<th style="min-width:160px">{{ $invoiceLabels['item'] }}</th> <th style="min-width:160px">{{ $invoiceLabels['item'] }}</th>
<th style="width:100%">{{ $invoiceLabels['description'] }}</th> <th style="width:100%">{{ $invoiceLabels['description'] }}</th>
@if ($account->custom_invoice_item_label1)
<th style="min-width:120px">{{ $account->custom_invoice_item_label1 }}</th>
@endif
@if ($account->custom_invoice_item_label2)
<th style="min-width:120px">{{ $account->custom_invoice_item_label2 }}</th>
@endif
<th style="min-width:120px" data-bind="text: costLabel">{{ $invoiceLabels['unit_cost'] }}</th> <th style="min-width:120px" data-bind="text: costLabel">{{ $invoiceLabels['unit_cost'] }}</th>
<th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}" data-bind="text: qtyLabel">{{ $invoiceLabels['quantity'] }}</th> <th style="{{ $account->hide_quantity ? 'display:none' : 'min-width:120px' }}" data-bind="text: qtyLabel">{{ $invoiceLabels['quantity'] }}</th>
<th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th> <th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th>
@ -201,11 +209,7 @@
$parent.invoice_items().length > 1" class="fa fa-sort"></i> $parent.invoice_items().length > 1" class="fa fa-sort"></i>
</td> </td>
<td> <td>
{!! Former::text('product_key')->useDatalist($products->toArray(), 'product_key') <input id="product_key" type="text" data-bind="typeahead: product_key, items: $root.products, key: 'product_key', valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][product_key]'}" class="form-control invoice-item handled"/>
->data_bind("value: product_key, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + \$index() + '][product_key]'}")
->addClass('datalist')
->raw()
!!}
</td> </td>
<td> <td>
<textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}" <textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}"
@ -213,6 +217,16 @@
<input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/> <input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/>
<input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/> <input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/>
</td> </td>
@if ($account->custom_invoice_item_label1)
<td>
<input data-bind="value: custom_value1, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][custom_value1]'}" class="form-control invoice-item"/>
</td>
@endif
@if ($account->custom_invoice_item_label2)
<td>
<input data-bind="value: custom_value2, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][custom_value2]'}" class="form-control invoice-item"/>
</td>
@endif
<td> <td>
<input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}" <input data-bind="value: prettyCost, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][cost]'}"
style="text-align: right" class="form-control invoice-item"/> style="text-align: right" class="form-control invoice-item"/>
@ -241,7 +255,7 @@
<tfoot> <tfoot>
<tr> <tr>
<td class="hide-border"/> <td class="hide-border"/>
<td class="hide-border" colspan="2" rowspan="6" style="vertical-align:top"> <td class="hide-border" colspan="{{ 2 + ($account->custom_invoice_item_label1 ? 1 : 0) + ($account->custom_invoice_item_label2 ? 1 : 0) }}" rowspan="6" style="vertical-align:top">
<br/> <br/>
<div role="tabpanel"> <div role="tabpanel">
@ -897,6 +911,9 @@
function applyComboboxListeners() { function applyComboboxListeners() {
var selectorStr = '.invoice-table input, .invoice-table textarea'; var selectorStr = '.invoice-table input, .invoice-table textarea';
$(selectorStr).off('change').on('change', function(event) { $(selectorStr).off('change').on('change', function(event) {
if ($(event.target).hasClass('handled')) {
return;
}
onItemChange(); onItemChange();
refreshPDF(true); refreshPDF(true);
}); });
@ -912,38 +929,6 @@
$(this).height($(this).height()+1); $(this).height($(this).height()+1);
}; };
}); });
@if (Auth::user()->account->fill_products)
$('.datalist').off('input').on('input', function() {
var key = $(this).val();
for (var i=0; i<products.length; i++) {
var product = products[i];
if (product.product_key == key) {
var model = ko.dataFor(this);
if (model.expense_public_id()) {
return;
}
if (product.notes) {
model.notes(product.notes);
}
if (product.cost) {
model.cost(accounting.toFixed(product.cost, 2));
}
if (!model.qty()) {
model.qty(1);
}
@if ($account->invoice_item_taxes)
if (product.default_tax_rate) {
model.tax(self.model.getTaxRateById(product.default_tax_rate.public_id));
}
@endif
model.product_key(key);
onItemChange();
break;
}
}
});
@endif
} }
function createInvoiceModel() { function createInvoiceModel() {

View File

@ -9,7 +9,7 @@ function ViewModel(data) {
self.expense_currency_id = ko.observable(); self.expense_currency_id = ko.observable();
self.tax_rates = ko.observableArray(); self.tax_rates = ko.observableArray();
self.tax_rates.push(new TaxRateModel()); // add blank row self.tax_rates.push(new TaxRateModel()); // add blank row
self.products = {!! $products !!};
self.loadClient = function(client) { self.loadClient = function(client) {
ko.mapping.fromJS(client, model.invoice().client().mapping, model.invoice().client); ko.mapping.fromJS(client, model.invoice().client().mapping, model.invoice().client);
@ -714,6 +714,8 @@ function ItemModel(data) {
self.notes = ko.observable(''); self.notes = ko.observable('');
self.cost = ko.observable(0); self.cost = ko.observable(0);
self.qty = ko.observable(0); self.qty = ko.observable(0);
self.custom_value1 = ko.observable('');
self.custom_value2 = ko.observable('');
self.tax_name = ko.observable(''); self.tax_name = ko.observable('');
self.tax_rate = ko.observable(0); self.tax_rate = ko.observable(0);
self.task_public_id = ko.observable(''); self.task_public_id = ko.observable('');
@ -805,4 +807,56 @@ function ItemModel(data) {
this.onSelect = function() {} this.onSelect = function() {}
} }
/* Custom binding for product key typeahead */
ko.bindingHandlers.typeahead = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var allBindings = allBindingsAccessor();
$element.typeahead({
highlight: true,
minLength: 0,
},
{
name: 'data',
display: allBindings.key,
source: searchData(allBindings.items, allBindings.key)
}).on('typeahead:select', function(element, datum, name) {
@if (Auth::user()->account->fill_products)
var model = ko.dataFor(this);
if (model.expense_public_id()) {
return;
}
if (datum.notes) {
model.notes(datum.notes);
}
if (datum.cost) {
model.cost(accounting.toFixed(datum.cost, 2));
}
if (!model.qty()) {
model.qty(1);
}
@if ($account->invoice_item_taxes)
if (datum.default_tax_rate) {
model.tax(self.model.getTaxRateById(datum.default_tax_rate.public_id));
}
@endif
@endif
onItemChange();
}).on('typeahead:change', function(element, datum, name) {
var value = valueAccessor();
value(datum);
onItemChange();
refreshPDF(true);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).typeahead('val', value);
}
}
};
</script> </script>

View File

@ -22,13 +22,25 @@
<center id="designThumbs"> <center id="designThumbs">
<p>&nbsp;</p> <p>&nbsp;</p>
<a href="{{ asset('/images/designs/business.png') }}" data-lightbox="more-designs" data-title="Business"><img src="{{ asset('/images/designs/business_thumb.png') }}"/></a>&nbsp;&nbsp;&nbsp;&nbsp; <a href="{{ asset('/images/designs/business.png') }}" data-lightbox="more-designs" data-title="Business">
<a href="{{ asset('/images/designs/creative.png') }}" data-lightbox="more-designs" data-title="Creative"><img src="{{ asset('/images/designs/creative_thumb.png') }}"/></a>&nbsp;&nbsp;&nbsp;&nbsp; <img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/business_thumb.png') }}"/>
<a href="{{ asset('/images/designs/elegant.png') }}" data-lightbox="more-designs" data-title="Elegant"><img src="{{ asset('/images/designs/elegant_thumb.png') }}"/></a> </a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{ asset('/images/designs/creative.png') }}" data-lightbox="more-designs" data-title="Creative">
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/creative_thumb.png') }}"/>
</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{ asset('/images/designs/elegant.png') }}" data-lightbox="more-designs" data-title="Elegant">
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/elegant_thumb.png') }}"/>
</a>
<p>&nbsp;</p> <p>&nbsp;</p>
<a href="{{ asset('/images/designs/hipster.png') }}" data-lightbox="more-designs" data-title="Hipster"><img src="{{ asset('/images/designs/hipster_thumb.png') }}"/></a>&nbsp;&nbsp;&nbsp;&nbsp; <a href="{{ asset('/images/designs/hipster.png') }}" data-lightbox="more-designs" data-title="Hipster">
<a href="{{ asset('/images/designs/playful.png') }}" data-lightbox="more-designs" data-title="Playful"><img src="{{ asset('/images/designs/playful_thumb.png') }}"/></a>&nbsp;&nbsp;&nbsp;&nbsp; <img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/hipster_thumb.png') }}"/>
<a href="{{ asset('/images/designs/photo.png') }}" data-lightbox="more-designs" data-title="Photo"><img src="{{ asset('/images/designs/photo_thumb.png') }}"/></a> </a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{ asset('/images/designs/playful.png') }}" data-lightbox="more-designs" data-title="Playful">
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/playful_thumb.png') }}"/>
</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{ asset('/images/designs/photo.png') }}" data-lightbox="more-designs" data-title="Photo">
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('/images/designs/photo_thumb.png') }}"/>
</a>
<p>&nbsp;</p> <p>&nbsp;</p>
</center> </center>
@ -133,6 +145,7 @@
} }
function showMoreDesigns() { function showMoreDesigns() {
loadImages('#designThumbs');
trackEvent('/account', '/view_more_designs'); trackEvent('/account', '/view_more_designs');
$('#moreDesignsModal').modal('show'); $('#moreDesignsModal').modal('show');
} }

View File

@ -76,9 +76,11 @@
<div id="navbar" class="collapse navbar-collapse"> <div id="navbar" class="collapse navbar-collapse">
@if (!isset($hideHeader) || !$hideHeader) @if (!isset($hideHeader) || !$hideHeader)
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
@if (!isset($hideDashboard) || !$hideDashboard)
<li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}> <li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}>
{!! link_to('/client/dashboard', trans('texts.dashboard') ) !!} {!! link_to('/client/dashboard', trans('texts.dashboard') ) !!}
</li> </li>
@endif
<li {{ Request::is('*client/quotes') ? 'class="active"' : '' }}> <li {{ Request::is('*client/quotes') ? 'class="active"' : '' }}>
{!! link_to('/client/quotes', trans('texts.quotes') ) !!} {!! link_to('/client/quotes', trans('texts.quotes') ) !!}
</li> </li>

View File

@ -26,7 +26,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.settings') !!}</h3> <h3 class="panel-title">{!! trans('texts.report_settings') !!}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="row"> <div class="row">
@ -48,7 +48,7 @@
@if (!Auth::user()->isPro()) @if (!Auth::user()->isPro())
<script> <script>
$(function() { $(function() {
$('form.warn-on-exit').find('input, select, button').prop('disabled', true); $('form.warn-on-exit').find('input, button').prop('disabled', true);
}); });
</script> </script>
@endif @endif
@ -57,7 +57,12 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
{!! Former::checkbox('enable_report')->text(trans('texts.enable')) !!} {!! Former::checkbox('enable_report')->text(trans('texts.enable')) !!}
{!! Former::select('report_type')->options($reportTypes, $reportType)->label(trans('texts.group_by')) !!} {!! Former::select('report_type')->options($reportTypes, $reportType)->label(trans('texts.type')) !!}
<div id="dateField" style="display:{{ $reportType == ENTITY_TAX_RATE ? 'block' : 'none' }}">
{!! Former::select('date_field')->label(trans('texts.filter'))
->addOption(trans('texts.invoice_date'), FILTER_INVOICE_DATE)
->addOption(trans('texts.payment_date'), FILTER_PAYMENT_DATE) !!}
</div>
<p>&nbsp;</p> <p>&nbsp;</p>
{!! Former::checkbox('enable_chart')->text(trans('texts.enable')) !!} {!! Former::checkbox('enable_chart')->text(trans('texts.enable')) !!}
{!! Former::select('group_by')->options($dateTypes, $groupBy) !!} {!! Former::select('group_by')->options($dateTypes, $groupBy) !!}
@ -77,53 +82,51 @@
<thead> <thead>
<tr> <tr>
@foreach ($columns as $column) @foreach ($columns as $column)
<th> <th>{{ trans("texts.{$column}") }}</th>
{{ trans("texts.{$column}") }}
</th>
@endforeach @endforeach
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@if (count($displayData))
@foreach ($displayData as $record) @foreach ($displayData as $record)
<tr> <tr>
@foreach ($record as $field) @foreach ($record as $field)
<td> <td>{!! $field !!}</td>
{!! $field !!} @endforeach
</td> </tr>
@endforeach
@else
<tr>
<td colspan="10" style="text-align: center">{{ trans('texts.empty_table') }}</td>
</tr>
@endif
</tbody>
</table>
<p>&nbsp;</p>
@if (count(array_values($reportTotals)))
<table class="table table-striped invoice-table">
<thead>
<tr>
<th>{{ trans("texts.totals") }}</th>
@foreach (array_values($reportTotals)[0] as $key => $val)
<th>{{ trans("texts.{$key}") }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($reportTotals as $currencyId => $val)
<tr>
<td>{!! Utils::getFromCache($currencyId, 'currencies')->name !!}</td>
@foreach ($val as $id => $field)
<td>{!! Utils::formatMoney($field, $currencyId) !!}</td>
@endforeach @endforeach
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
<tfoot>
<tr>
<td><b>{{ trans('texts.totals') }}</b></td>
@if ($reportType != ENTITY_CLIENT)
<td></td>
<td></td>
@endif
<td>
@foreach ($reportTotals['amount'] as $currencyId => $total)
<b>{{ Utils::formatMoney($total, $currencyId) }}</b><br/>
@endforeach
</td>
@if ($reportType == ENTITY_PAYMENT)
<td></td>
@endif
<td>
@foreach ($reportTotals['paid'] as $currencyId => $total)
<b>{{ Utils::formatMoney($total, $currencyId) }}</b><br/>
@endforeach
</td>
@if ($reportType != ENTITY_PAYMENT)
<td>
@foreach ($reportTotals['balance'] as $currencyId => $total)
<b>{{ Utils::formatMoney($total, $currencyId) }}</b><br/>
@endforeach
</td>
@endif
</tr>
</tfoot>
</table> </table>
@endif
</div> </div>
</div> </div>
@ -194,6 +197,15 @@
$('.end_date .input-group-addon').click(function() { $('.end_date .input-group-addon').click(function() {
toggleDatePicker('end_date'); toggleDatePicker('end_date');
}); });
$('#report_type').change(function() {
var val = $('#report_type').val();
if (val == '{{ ENTITY_TAX_RATE }}') {
$('#dateField').fadeIn();
} else {
$('#dateField').fadeOut();
}
});
}) })

View File

@ -128,12 +128,12 @@
var arc = d3.svg.arc() var arc = d3.svg.arc()
.innerRadius(function(d) { return d.r }) .innerRadius(function(d) { return d.r })
.outerRadius(function(d) { return d.r - 5 }) .outerRadius(function(d) { return d.r - 8 })
.startAngle(0); .startAngle(0);
var fullArc = d3.svg.arc() var fullArc = d3.svg.arc()
.innerRadius(function(d) { return d.r }) .innerRadius(function(d) { return d.r - 1 })
.outerRadius(function(d) { return d.r - 5 }) .outerRadius(function(d) { return d.r - 7 })
.startAngle(0) .startAngle(0)
.endAngle(2 * Math.PI); .endAngle(2 * Math.PI);

View File

@ -26,8 +26,8 @@
<div class="jumbotron"> <div class="jumbotron">
<h2>Invoice Ninja Setup</h2> <h2>Invoice Ninja Setup</h2>
@if (version_compare(phpversion(), '5.4.0', '<')) @if (version_compare(phpversion(), '5.5.9', '<'))
<div class="alert alert-warning">Warning: The application requires PHP >= 5.4.0</div> <div class="alert alert-warning">Warning: The application requires PHP >= 5.5.9</div>
@endif @endif
@if (!function_exists('proc_open')) @if (!function_exists('proc_open'))
<div class="alert alert-warning">Warning: <a href="http://php.net/manual/en/function.proc-open.php" target="_blank">proc_open</a> must be enabled.</div> <div class="alert alert-warning">Warning: <a href="http://php.net/manual/en/function.proc-open.php" target="_blank">proc_open</a> must be enabled.</div>

View File

@ -1,4 +1,4 @@
<?php //[STAMP] fd572cb1f679911978b9f48a842ed64b <?php //[STAMP] 5d3610ae822b6990504d5dce501c103b
namespace _generated; namespace _generated;
// This class was automatically generated by build task // This class was automatically generated by build task
@ -17,6 +17,17 @@ trait AcceptanceTesterActions
abstract protected function getScenario(); abstract protected function getScenario();
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Print out latest Selenium Logs in debug mode
* @see \Codeception\Module\WebDriver::debugWebDriverLogs()
*/
public function debugWebDriverLogs() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('debugWebDriverLogs', func_get_args()));
}
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
@ -166,7 +177,7 @@ trait AcceptanceTesterActions
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Sets a cookie with the given name and value. * Sets a cookie with the given name and value.
* You can set additional cookie params like `domain`, `path`, `expire`, `secure` in array passed as last argument. * You can set additional cookie params like `domain`, `path`, `expires`, `secure` in array passed as last argument.
* *
* ``` php * ``` php
* <?php * <?php
@ -249,7 +260,6 @@ trait AcceptanceTesterActions
* $I->amOnPage('/'); * $I->amOnPage('/');
* // opens /register page * // opens /register page
* $I->amOnPage('/register'); * $I->amOnPage('/register');
* ?>
* ``` * ```
* *
* @param $page * @param $page
@ -263,17 +273,32 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks that the current page contains the given string. * Checks that the current page contains the given string (case insensitive).
* Specify a locator as the second parameter to match a specific region. *
* You can specify a specific HTML element (via CSS or XPath) as the second
* parameter to only search within that element.
* *
* ``` php * ``` php
* <?php * <?php
* $I->see('Logout'); // I can suppose user is logged in * $I->see('Logout'); // I can suppose user is logged in
* $I->see('Sign Up','h1'); // I can suppose it's a signup page * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page
* $I->see('Sign Up','//body/h1'); // with XPath * $I->see('Sign Up', '//body/h1'); // with XPath
* ?>
* ``` * ```
* *
* Note that the search is done after stripping all HTML tags from the body,
* so `$I->see('strong')` will return true for strings like:
*
* - `<p>I am Stronger than thou</p>`
* - `<script>document.createElement('strong');</script>`
*
* But will *not* be true for strings like:
*
* - `<strong>Home</strong>`
* - `<div class="strong">Home</strong>`
* - `<!-- strong -->`
*
* For checking the raw source code, use `seeInSource()`.
*
* @param $text * @param $text
* @param null $selector * @param null $selector
* Conditional Assertion: Test won't be stopped on fail * Conditional Assertion: Test won't be stopped on fail
@ -285,17 +310,32 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks that the current page contains the given string. * Checks that the current page contains the given string (case insensitive).
* Specify a locator as the second parameter to match a specific region. *
* You can specify a specific HTML element (via CSS or XPath) as the second
* parameter to only search within that element.
* *
* ``` php * ``` php
* <?php * <?php
* $I->see('Logout'); // I can suppose user is logged in * $I->see('Logout'); // I can suppose user is logged in
* $I->see('Sign Up','h1'); // I can suppose it's a signup page * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page
* $I->see('Sign Up','//body/h1'); // with XPath * $I->see('Sign Up', '//body/h1'); // with XPath
* ?>
* ``` * ```
* *
* Note that the search is done after stripping all HTML tags from the body,
* so `$I->see('strong')` will return true for strings like:
*
* - `<p>I am Stronger than thou</p>`
* - `<script>document.createElement('strong');</script>`
*
* But will *not* be true for strings like:
*
* - `<strong>Home</strong>`
* - `<div class="strong">Home</strong>`
* - `<!-- strong -->`
*
* For checking the raw source code, use `seeInSource()`.
*
* @param $text * @param $text
* @param null $selector * @param null $selector
* @see \Codeception\Module\WebDriver::see() * @see \Codeception\Module\WebDriver::see()
@ -308,7 +348,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks that the current page doesn't contain the text specified. * Checks that the current page doesn't contain the text specified (case insensitive).
* Give a locator as the second parameter to match a specific region. * Give a locator as the second parameter to match a specific region.
* *
* ```php * ```php
@ -316,9 +356,22 @@ trait AcceptanceTesterActions
* $I->dontSee('Login'); // I can suppose user is already logged in * $I->dontSee('Login'); // I can suppose user is already logged in
* $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page
* $I->dontSee('Sign Up','//body/h1'); // with XPath * $I->dontSee('Sign Up','//body/h1'); // with XPath
* ?>
* ``` * ```
* *
* Note that the search is done after stripping all HTML tags from the body,
* so `$I->dontSee('strong')` will fail on strings like:
*
* - `<p>I am Stronger than thou</p>`
* - `<script>document.createElement('strong');</script>`
*
* But will ignore strings like:
*
* - `<strong>Home</strong>`
* - `<div class="strong">Home</strong>`
* - `<!-- strong -->`
*
* For checking the raw source code, use `seeInSource()`.
*
* @param $text * @param $text
* @param null $selector * @param null $selector
* Conditional Assertion: Test won't be stopped on fail * Conditional Assertion: Test won't be stopped on fail
@ -330,7 +383,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks that the current page doesn't contain the text specified. * Checks that the current page doesn't contain the text specified (case insensitive).
* Give a locator as the second parameter to match a specific region. * Give a locator as the second parameter to match a specific region.
* *
* ```php * ```php
@ -338,9 +391,22 @@ trait AcceptanceTesterActions
* $I->dontSee('Login'); // I can suppose user is already logged in * $I->dontSee('Login'); // I can suppose user is already logged in
* $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page
* $I->dontSee('Sign Up','//body/h1'); // with XPath * $I->dontSee('Sign Up','//body/h1'); // with XPath
* ?>
* ``` * ```
* *
* Note that the search is done after stripping all HTML tags from the body,
* so `$I->dontSee('strong')` will fail on strings like:
*
* - `<p>I am Stronger than thou</p>`
* - `<script>document.createElement('strong');</script>`
*
* But will ignore strings like:
*
* - `<strong>Home</strong>`
* - `<div class="strong">Home</strong>`
* - `<!-- strong -->`
*
* For checking the raw source code, use `seeInSource()`.
*
* @param $text * @param $text
* @param null $selector * @param null $selector
* @see \Codeception\Module\WebDriver::dontSee() * @see \Codeception\Module\WebDriver::dontSee()
@ -350,6 +416,80 @@ trait AcceptanceTesterActions
} }
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the current page contains the given string in its
* raw source code.
*
* ``` php
* <?php
* $I->seeInSource('<h1>Green eggs &amp; ham</h1>');
* ```
*
* @param $raw
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\WebDriver::seeInSource()
*/
public function canSeeInSource($raw) {
return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInSource', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the current page contains the given string in its
* raw source code.
*
* ``` php
* <?php
* $I->seeInSource('<h1>Green eggs &amp; ham</h1>');
* ```
*
* @param $raw
* @see \Codeception\Module\WebDriver::seeInSource()
*/
public function seeInSource($raw) {
return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInSource', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the current page contains the given string in its
* raw source code.
*
* ```php
* <?php
* $I->dontSeeInSource('<h1>Green eggs &amp; ham</h1>');
* ```
*
* @param $raw
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\WebDriver::dontSeeInSource()
*/
public function cantSeeInSource($raw) {
return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInSource', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the current page contains the given string in its
* raw source code.
*
* ```php
* <?php
* $I->dontSeeInSource('<h1>Green eggs &amp; ham</h1>');
* ```
*
* @param $raw
* @see \Codeception\Module\WebDriver::dontSeeInSource()
*/
public function dontSeeInSource($raw) {
return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInSource', func_get_args()));
}
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
@ -790,7 +930,6 @@ trait AcceptanceTesterActions
* *
* @param null $uri * @param null $uri
* *
* @internal param $url
* @return mixed * @return mixed
* @see \Codeception\Module\WebDriver::grabFromCurrentUrl() * @see \Codeception\Module\WebDriver::grabFromCurrentUrl()
*/ */
@ -1388,7 +1527,7 @@ trait AcceptanceTesterActions
* *
* @param $cssOrXpath * @param $cssOrXpath
* @param $attribute * @param $attribute
* @internal param $element *
* @return mixed * @return mixed
* @see \Codeception\Module\WebDriver::grabAttributeFrom() * @see \Codeception\Module\WebDriver::grabAttributeFrom()
*/ */
@ -1425,7 +1564,28 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Grabs either the text content, or attribute values, of nodes
* matched by $cssOrXpath and returns them as an array.
* *
* ```html
* <a href="#first">First</a>
* <a href="#second">Second</a>
* <a href="#third">Third</a>
* ```
*
* ```php
* <?php
* // would return ['First', 'Second', 'Third']
* $aLinkText = $I->grabMultiple('a');
*
* // would return ['#first', '#second', '#third']
* $aLinks = $I->grabMultiple('a', 'href');
* ?>
* ```
*
* @param $cssOrXpath
* @param $attribute
* @return string[]
* @see \Codeception\Module\WebDriver::grabMultiple() * @see \Codeception\Module\WebDriver::grabMultiple()
*/ */
public function grabMultiple($cssOrXpath, $attribute = null) { public function grabMultiple($cssOrXpath, $attribute = null) {
@ -1640,6 +1800,27 @@ trait AcceptanceTesterActions
} }
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM()
*/
public function canSeeNumberOfElementsInDOM($selector, $expected) {
return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElementsInDOM', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM()
*/
public function seeNumberOfElementsInDOM($selector, $expected) {
return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberOfElementsInDOM', func_get_args()));
}
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
@ -2179,7 +2360,7 @@ trait AcceptanceTesterActions
* If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly: * If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly:
* *
* ``` php * ``` php
* $I->executeInSelenium(function(\Facebook\WebDriver\RemoteWebDriver $webdriver) { * $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
* $webdriver->get('http://google.com'); * $webdriver->get('http://google.com');
* }); * });
* ``` * ```
@ -2222,7 +2403,7 @@ trait AcceptanceTesterActions
* *
* ``` php * ``` php
* <?php * <?php
* $I->executeInSelenium(function (\Facebook\WebDriver\RemoteWebDriver $webdriver) { * $I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
* $handles=$webdriver->getWindowHandles(); * $handles=$webdriver->getWindowHandles();
* $last_window = end($handles); * $last_window = end($handles);
* $webdriver->switchTo()->window($last_window); * $webdriver->switchTo()->window($last_window);
@ -2463,34 +2644,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Saves current cookies into named snapshot in order to restore them in other tests * @param string $name
* This is useful to save session state between tests.
* For example, if user needs log in to site for each test this scenario can be executed once
* while other tests can just restore saved cookies.
*
* ``` php
* <?php
* // inside AcceptanceTester class:
*
* public function login()
* {
* // if snapshot exists - skipping login
* if ($I->loadSessionSnapshot('login')) return;
*
* // logging in
* $I->amOnPage('/login');
* $I->fillField('name', 'jon');
* $I->fillField('password', '123345');
* $I->click('Login');
*
* // saving snapshot
* $I->saveSessionSnapshot('login');
* }
* ?>
* ```
*
* @param $name
* @return mixed
* @see \Codeception\Module\WebDriver::saveSessionSnapshot() * @see \Codeception\Module\WebDriver::saveSessionSnapshot()
*/ */
public function saveSessionSnapshot($name) { public function saveSessionSnapshot($name) {
@ -2501,11 +2655,8 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Loads cookies from saved snapshot. * @param string $name
* * @return bool
* @param $name
* @see saveSessionSnapshot
* @return mixed
* @see \Codeception\Module\WebDriver::loadSessionSnapshot() * @see \Codeception\Module\WebDriver::loadSessionSnapshot()
*/ */
public function loadSessionSnapshot($name) { public function loadSessionSnapshot($name) {
@ -2516,7 +2667,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Inserts SQL record into database. This record will be erased after the test. * Inserts an SQL record into a database. This record will be erased after the test.
* *
* ``` php * ``` php
* <?php * <?php
@ -2538,7 +2689,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks if a row with given column values exists. * Asserts that a row with the given column values exists.
* Provide table name and column values. * Provide table name and column values.
* *
* Example: * Example:
@ -2566,7 +2717,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Checks if a row with given column values exists. * Asserts that a row with the given column values exists.
* Provide table name and column values. * Provide table name and column values.
* *
* Example: * Example:
@ -2595,7 +2746,7 @@ trait AcceptanceTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Asserts that found number of records in database * Asserts that the given number of records were found in the database.
* *
* ``` php * ``` php
* <?php * <?php
@ -2603,19 +2754,19 @@ trait AcceptanceTesterActions
* ?> * ?>
* ``` * ```
* *
* @param int $num Expected number * @param int $expectedNumber Expected number
* @param string $table Table name * @param string $table Table name
* @param array $criteria Search criteria [Optional] * @param array $criteria Search criteria [Optional]
* Conditional Assertion: Test won't be stopped on fail * Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\Db::seeNumRecords() * @see \Codeception\Module\Db::seeNumRecords()
*/ */
public function canSeeNumRecords($num, $table, $criteria = null) { public function canSeeNumRecords($expectedNumber, $table, $criteria = null) {
return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumRecords', func_get_args())); return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumRecords', func_get_args()));
} }
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] Method is generated. Documentation taken from corresponding module.
* *
* Asserts that found number of records in database * Asserts that the given number of records were found in the database.
* *
* ``` php * ``` php
* <?php * <?php
@ -2623,12 +2774,12 @@ trait AcceptanceTesterActions
* ?> * ?>
* ``` * ```
* *
* @param int $num Expected number * @param int $expectedNumber Expected number
* @param string $table Table name * @param string $table Table name
* @param array $criteria Search criteria [Optional] * @param array $criteria Search criteria [Optional]
* @see \Codeception\Module\Db::seeNumRecords() * @see \Codeception\Module\Db::seeNumRecords()
*/ */
public function seeNumRecords($num, $table, $criteria = null) { public function seeNumRecords($expectedNumber, $table, $criteria = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumRecords', func_get_args())); return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumRecords', func_get_args()));
} }
@ -2638,7 +2789,7 @@ trait AcceptanceTesterActions
* *
* Effect is opposite to ->seeInDatabase * Effect is opposite to ->seeInDatabase
* *
* Checks if there is no record with such column values in database. * Asserts that there is no record with the given column values in a database.
* Provide table name and column values. * Provide table name and column values.
* *
* Example: * Example:
@ -2668,7 +2819,7 @@ trait AcceptanceTesterActions
* *
* Effect is opposite to ->seeInDatabase * Effect is opposite to ->seeInDatabase
* *
* Checks if there is no record with such column values in database. * Asserts that there is no record with the given column values in a database.
* Provide table name and column values. * Provide table name and column values.
* *
* Example: * Example:

View File

@ -44,6 +44,7 @@ class CheckBalanceCest
$I->amOnPage('/invoices/create'); $I->amOnPage('/invoices/create');
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->click('Save'); $I->click('Save');
$I->wait(1); $I->wait(1);
$I->see($clientEmail); $I->see($clientEmail);

View File

@ -0,0 +1,56 @@
<?php
use Faker\Factory;
use Codeception\Util\Fixtures;
class ExpenseCest
{
/**
* @var \Faker\Generator
*/
private $faker;
public function _before(AcceptanceTester $I)
{
$I->checkIfLogin($I);
$this->faker = Factory::create();
}
public function createExpense(AcceptanceTester $I)
{
$I->wantTo('Create an expense');
$vendorName = $this->faker->name;
$clientEmail = $this->faker->safeEmail;
$amount = $this->faker->numberBetween(10, 20);
// create vendor
$I->amOnPage('/vendors/create');
$I->fillField(['name' => 'name'], $vendorName);
$I->click('Save');
$I->see($vendorName);
$vendorId = $I->grabFromDatabase('vendors', 'id', ['name' => $vendorName]);
// create client
$I->amOnPage('/clients/create');
$I->fillField(['name' => 'contacts[0][email]'], $clientEmail);
$I->click('Save');
$I->see($clientEmail);
// create expense
$I->amOnPage('/expenses/create');
$I->fillField(['name' => 'amount'], $amount);
$I->selectDropdown($I, $vendorName, '.vendor-select .dropdown-toggle');
$I->selectDropdown($I, $clientEmail, '.client-select .dropdown-toggle');
$I->click('Save');
$I->seeInDatabase('expenses', ['vendor_id' => $vendorId]);
// invoice expense
$I->executeJS('submitAction(\'invoice\')');
$I->click('Save');
$I->wait(1);
$I->see($clientEmail);
$I->see($amount);
}
}

View File

@ -53,6 +53,7 @@ class OnlinePaymentCest
$I->amOnPage('/invoices/create'); $I->amOnPage('/invoices/create');
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->click('Save'); $I->click('Save');
$I->see($clientEmail); $I->see($clientEmail);
@ -88,6 +89,7 @@ class OnlinePaymentCest
// create recurring invoice and auto-bill // create recurring invoice and auto-bill
$I->amOnPage('/recurring_invoices/create'); $I->amOnPage('/recurring_invoices/create');
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->checkOption('#auto_bill'); $I->checkOption('#auto_bill');
$I->executeJS('preparePdfData(\'email\')'); $I->executeJS('preparePdfData(\'email\')');

View File

@ -40,6 +40,7 @@ class PaymentCest
$I->amOnPage('/invoices/create'); $I->amOnPage('/invoices/create');
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->click('Save'); $I->click('Save');
$I->see($clientEmail); $I->see($clientEmail);

View File

@ -68,6 +68,7 @@ class TaxRatesCest
$I->amOnPage('/invoices/create'); $I->amOnPage('/invoices/create');
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->selectOption('#taxRateSelect', $invoiceTaxName . ' ' . $invoiceTaxRate . '%'); $I->selectOption('#taxRateSelect', $invoiceTaxName . ' ' . $invoiceTaxRate . '%');
$I->wait(2); $I->wait(2);