1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-11 21:52:35 +01:00

Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
David Bomba 2016-04-18 08:29:35 +10:00
commit 08024aba5e
125 changed files with 5564 additions and 1745 deletions

View File

@ -42,3 +42,29 @@ API_SECRET=password
#GOOGLE_CLIENT_ID= #GOOGLE_CLIENT_ID=
#GOOGLE_CLIENT_SECRET= #GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google #GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
#GOOGLE_MAPS_API_KEY=
#S3_KEY=
#S3_SECRET=
#S3_REGION=
#S3_BUCKET=
#RACKSPACE_USERNAME=
#RACKSPACE_KEY=
#RACKSPACE_CONTAINER=
#RACKSPACE_REGION=
#RACKSPACE_TEMP_URL_SECRET=
# If this is set to anything, the URL secret will be set the next
# time a file is downloaded through the client portal.
# Only set this temporarily, as it slows things down.
#RACKSPACE_TEMP_URL_SECRET_SET=
#DOCUMENT_FILESYSTEM=
#MAX_DOCUMENT_SIZE # KB
#MAX_EMAIL_DOCUMENTS_SIZE # Total KB
#MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed)
#DOCUMENT_PREVIEW_SIZE # Pixels

View File

@ -87,6 +87,8 @@ after_script:
- mysql -u root -e 'select * from clients;' ninja - mysql -u root -e 'select * from clients;' ninja
- mysql -u root -e 'select * from invoices;' ninja - mysql -u root -e 'select * from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja - mysql -u root -e 'select * from invoice_items;' ninja
- mysql -u root -e 'select * from payments;' ninja
- mysql -u root -e 'select * from credits;' ninja
- cat storage/logs/laravel.log - cat storage/logs/laravel.log
notifications: notifications:

View File

@ -95,6 +95,7 @@ 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/dropzone/dist/min/dropzone.min.js',
'public/vendor/typeahead.js/dist/typeahead.jquery.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',
@ -137,6 +138,7 @@ module.exports = function(grunt) {
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css', 'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css', 'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css', 'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/dropzone/dist/min/dropzone.min.css',
'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',
@ -169,7 +171,7 @@ module.exports = function(grunt) {
'public/js/pdf_viewer.js', 'public/js/pdf_viewer.js',
'public/js/compatibility.js', 'public/js/compatibility.js',
'public/js/pdfmake.min.js', 'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js', 'public/js/vfs.js',
], ],
dest: 'public/pdf.built.js', dest: 'public/pdf.built.js',
nonull: true nonull: true

View File

@ -0,0 +1,41 @@
<?php namespace App\Console\Commands;
use DateTime;
use App\Models\Document;
use Illuminate\Console\Command;
class RemoveOrphanedDocuments extends Command
{
protected $name = 'ninja:remove-orphaned-documents';
protected $description = 'Removes old documents not associated with an expense or invoice';
public function fire()
{
$this->info(date('Y-m-d').' Running RemoveOrphanedDocuments...');
$documents = Document::whereRaw('invoice_id IS NULL AND expense_id IS NULL AND updated_at <= ?', array(new DateTime('-1 hour')))
->get();
$this->info(count($documents).' orphaned document(s) found');
foreach ($documents as $document) {
$document->delete();
}
$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

@ -13,6 +13,7 @@ class Kernel extends ConsoleKernel
*/ */
protected $commands = [ protected $commands = [
'App\Console\Commands\SendRecurringInvoices', 'App\Console\Commands\SendRecurringInvoices',
'App\Console\Commands\RemoveOrphanedDocuments',
'App\Console\Commands\ResetData', 'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData', 'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendRenewalInvoices',

View File

@ -4,7 +4,11 @@ use Redirect;
use Utils; use Utils;
use Exception; use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
class Handler extends ExceptionHandler { class Handler extends ExceptionHandler {
@ -14,7 +18,10 @@ class Handler extends ExceptionHandler {
* @var array * @var array
*/ */
protected $dontReport = [ protected $dontReport = [
'Symfony\Component\HttpKernel\Exception\HttpException' AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
]; ];
/** /**
@ -27,6 +34,11 @@ class Handler extends ExceptionHandler {
*/ */
public function report(Exception $e) public function report(Exception $e)
{ {
// don't show these errors in the logs
if ($e instanceof HttpResponseException) {
return false;
}
if (Utils::isNinja()) { if (Utils::isNinja()) {
Utils::logError(Utils::getErrorString($e)); Utils::logError(Utils::getErrorString($e));
return false; return false;
@ -59,6 +71,9 @@ class Handler extends ExceptionHandler {
} }
} }
return parent::render($request, $e);
/*
// 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()) {
$data = [ $data = [
@ -70,5 +85,6 @@ class Handler extends ExceptionHandler {
} else { } else {
return parent::render($request, $e); return parent::render($request, $e);
} }
*/
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -83,23 +83,35 @@ class AppController extends BaseController
return Redirect::to('/'); return Redirect::to('/');
} }
$config = "APP_ENV=production\n". $_ENV['APP_ENV'] = 'production';
"APP_DEBUG={$app['debug']}\n". $_ENV['APP_DEBUG'] = $app['debug'];
"APP_URL={$app['url']}\n". $_ENV['APP_URL'] = $app['url'];
"APP_KEY={$app['key']}\n\n". $_ENV['APP_KEY'] = $app['key'];
"DB_TYPE={$dbType}\n". $_ENV['DB_TYPE'] = $dbType;
"DB_HOST={$database['type']['host']}\n". $_ENV['DB_HOST'] = $database['type']['host'];
"DB_DATABASE={$database['type']['database']}\n". $_ENV['DB_DATABASE'] = $database['type']['database'];
"DB_USERNAME={$database['type']['username']}\n". $_ENV['DB_USERNAME'] = $database['type']['username'];
"DB_PASSWORD={$database['type']['password']}\n\n". $_ENV['DB_PASSWORD'] = $database['type']['password'];
"MAIL_DRIVER={$mail['driver']}\n". $_ENV['MAIL_DRIVER'] = $mail['driver'];
"MAIL_PORT={$mail['port']}\n". $_ENV['MAIL_PORT'] = $mail['port'];
"MAIL_ENCRYPTION={$mail['encryption']}\n". $_ENV['MAIL_ENCRYPTION'] = $mail['encryption'];
"MAIL_HOST={$mail['host']}\n". $_ENV['MAIL_HOST'] = $mail['host'];
"MAIL_USERNAME={$mail['username']}\n". $_ENV['MAIL_USERNAME'] = $mail['username'];
"MAIL_FROM_NAME={$mail['from']['name']}\n". $_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
"MAIL_PASSWORD={$mail['password']}\n\n". $_ENV['MAIL_PASSWORD'] = $mail['password'];
"PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'"; $_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address';
$config = '';
foreach ($_ENV as $key => $val) {
if (is_array($val)) {
continue;
}
if (preg_match('/\s/', $val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n";
}
// Write Config Settings // Write Config Settings
$fp = fopen(base_path()."/.env", 'w'); $fp = fopen(base_path()."/.env", 'w');
@ -166,6 +178,9 @@ class AppController extends BaseController
$config = ''; $config = '';
foreach ($_ENV as $key => $val) { foreach ($_ENV as $key => $val) {
if (preg_match('/\s/',$val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n"; $config .= "{$key}={$val}\n";
} }

View File

@ -112,10 +112,10 @@ class ClientController extends BaseController
$actionLinks = []; $actionLinks = [];
if(Task::canCreate()){ if(Task::canCreate()){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
} }
if (Utils::isPro() && Invoice::canCreate()) { if (Utils::isPro() && Invoice::canCreate()) {
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
} }
if(!empty($actionLinks)){ if(!empty($actionLinks)){
@ -123,15 +123,15 @@ class ClientController extends BaseController
} }
if(Payment::canCreate()){ if(Payment::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => URL::to('/payments/create/'.$client->public_id)];
} }
if(Credit::canCreate()){ if(Credit::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => URL::to('/credits/create/'.$client->public_id)];
} }
if(Expense::canCreate()){ if(Expense::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => URL::to('/expenses/create/0/'.$client->public_id)];
} }
$data = array( $data = array(

View File

@ -158,19 +158,21 @@ class DashboardApiController extends BaseAPIController
} }
} }
$data = [ $data = [
'paidToDate' => $paidToDate, 'id' => 1,
'balances' => $balances, 'paidToDate' => $paidToDate[0]->value,
'averageInvoice' => $averageInvoice, 'paidToDateCurrency' => $paidToDate[0]->currency_id,
'balances' => $balances[0]->value,
'balancesCurrency' => $balances[0]->currency_id,
'averageInvoice' => $averageInvoice[0]->invoice_avg,
'averageInvoiceCurrency' => $averageInvoice[0]->currency_id,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'payments' => $payments,
'title' => trans('texts.dashboard'),
'hasQuotes' => $hasQuotes,
]; ];
return $this->response($data); return $this->response($data);
} }

View File

@ -11,7 +11,7 @@ class DashboardController extends BaseController
{ {
public function index() public function index()
{ {
$view_all = !Auth::user()->hasPermission('view_all'); $view_all = Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id; $user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients // total_income, billed_clients, invoice_sent and active_clients
@ -105,6 +105,7 @@ class DashboardController extends BaseController
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false) //->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0) ->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
@ -129,6 +130,7 @@ class DashboardController extends BaseController
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false) //->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0) ->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)

View File

@ -0,0 +1,140 @@
<?php namespace App\Http\Controllers;
use Datatable;
use Input;
use Redirect;
use Session;
use URL;
use Utils;
use View;
use Validator;
use Response;
use App\Models\Document;
use App\Ninja\Repositories\DocumentRepository;
class DocumentController extends BaseController
{
protected $documentRepo;
protected $model = 'App\Models\Document';
public function __construct(DocumentRepository $documentRepo)
{
// parent::__construct();
$this->documentRepo = $documentRepo;
}
public function get($publicId)
{
$document = Document::scope($publicId)
->firstOrFail();
if(!$this->checkViewPermission($document, $response)){
return $response;
}
return static::getDownloadResponse($document);
}
public static function getDownloadResponse($document){
$direct_url = $document->getDirectUrl();
if($direct_url){
return redirect($direct_url);
}
$stream = $document->getStream();
if($stream){
$headers = [
'Content-Type' => Document::$types[$document->type]['mime'],
'Content-Length' => $document->size,
];
$response = Response::stream(function() use ($stream) {
fpassthru($stream);
}, 200, $headers);
}
else{
$response = Response::make($document->getRaw(), 200);
$response->header('content-type', Document::$types[$document->type]['mime']);
}
return $response;
}
public function getPreview($publicId)
{
$document = Document::scope($publicId)
->firstOrFail();
if(!$this->checkViewPermission($document, $response)){
return $response;
}
if(empty($document->preview)){
return Response::view('error', array('error'=>'Preview does not exist!'), 404);
}
$direct_url = $document->getDirectPreviewUrl();
if($direct_url){
return redirect($direct_url);
}
$previewType = pathinfo($document->preview, PATHINFO_EXTENSION);
$response = Response::make($document->getRawPreview(), 200);
$response->header('content-type', Document::$types[$previewType]['mime']);
return $response;
}
public function getVFSJS($publicId, $name){
$document = Document::scope($publicId)
->firstOrFail();
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
if(!$this->checkViewPermission($document, $response)){
return $response;
}
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
public function postUpload()
{
if (!Utils::isPro()) {
return;
}
if(!$this->checkCreatePermission($response)){
return $response;
}
$result = $this->documentRepo->upload(Input::all()['file'], $doc_array);
if(is_string($result)){
return Response::json([
'error' => $result,
'code' => 400
], 400);
} else {
return Response::json([
'error' => false,
'document' => $doc_array,
'code' => 200
], 200);
}
}
}

View File

@ -99,7 +99,7 @@ class ExpenseController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$expense = Expense::scope($publicId)->firstOrFail(); $expense = Expense::scope($publicId)->with('documents')->firstOrFail();
if(!$this->checkEditPermission($expense, $response)){ if(!$this->checkEditPermission($expense, $response)){
return $response; return $response;
@ -146,12 +146,6 @@ class ExpenseController extends BaseController
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
}
}
return View::make('expenses.edit', $data); return View::make('expenses.edit', $data);
} }
@ -163,7 +157,14 @@ class ExpenseController extends BaseController
*/ */
public function update(UpdateExpenseRequest $request) public function update(UpdateExpenseRequest $request)
{ {
$expense = $this->expenseService->save($request->input()); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$expense = $this->expenseService->save($data, true);
Session::flash('message', trans('texts.updated_expense')); Session::flash('message', trans('texts.updated_expense'));
@ -177,7 +178,14 @@ class ExpenseController extends BaseController
public function store(CreateExpenseRequest $request) public function store(CreateExpenseRequest $request)
{ {
$expense = $this->expenseService->save($request->input()); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$expense = $this->expenseService->save($data);
Session::flash('message', trans('texts.created_expense')); Session::flash('message', trans('texts.created_expense'));
@ -195,7 +203,6 @@ class ExpenseController extends BaseController
$expenses = Expense::scope($ids)->with('client')->get(); $expenses = Expense::scope($ids)->with('client')->get();
$clientPublicId = null; $clientPublicId = null;
$currencyId = null; $currencyId = null;
$data = [];
// Validate that either all expenses do not have a client or if there is a client, it is the same client // Validate that either all expenses do not have a client or if there is a client, it is the same client
foreach ($expenses as $expense) foreach ($expenses as $expense)
@ -220,19 +227,11 @@ class ExpenseController extends BaseController
Session::flash('error', trans('texts.expense_error_invoiced')); Session::flash('error', trans('texts.expense_error_invoiced'));
return Redirect::to('expenses'); return Redirect::to('expenses');
} }
$account = Auth::user()->account;
$data[] = [
'publicId' => $expense->public_id,
'description' => $expense->public_notes,
'qty' => 1,
'cost' => $expense->present()->converted_amount,
];
} }
return Redirect::to("invoices/create/{$clientPublicId}") return Redirect::to("invoices/create/{$clientPublicId}")
->with('expenseCurrencyId', $currencyId) ->with('expenseCurrencyId', $currencyId)
->with('expenses', $data); ->with('expenses', $ids);
break; break;
default: default:

View File

@ -166,6 +166,7 @@ class InvoiceApiController extends BaseAPIController
'state', 'state',
'postal_code', 'postal_code',
'private_notes', 'private_notes',
'currency_code',
] as $field) { ] as $field) {
if (isset($data[$field])) { if (isset($data[$field])) {
$clientData[$field] = $data[$field]; $clientData[$field] = $data[$field];
@ -258,10 +259,11 @@ class InvoiceApiController extends BaseAPIController
// initialize the line items // initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) { if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)]; $data['invoice_items'] = [self::prepareItem($data)];
// make sure the tax isn't applied twice (for the invoice and the line item) // make sure the tax isn't applied twice (for the invoice and the line item)
unset($data['invoice_items'][0]['tax_name']); unset($data['invoice_items'][0]['tax_name1']);
unset($data['invoice_items'][0]['tax_rate']); unset($data['invoice_items'][0]['tax_rate1']);
unset($data['invoice_items'][0]['tax_name2']);
unset($data['invoice_items'][0]['tax_rate2']);
} else { } else {
foreach ($data['invoice_items'] as $index => $item) { foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item); $data['invoice_items'][$index] = self::prepareItem($item);

View File

@ -17,12 +17,14 @@ use App\Models\Invoice;
use App\Models\Client; use App\Models\Client;
use App\Models\Account; use App\Models\Account;
use App\Models\Product; use App\Models\Product;
use App\Models\Expense;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Models\Activity; use App\Models\Activity;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Services\InvoiceService; use App\Services\InvoiceService;
use App\Services\RecurringInvoiceService; use App\Services\RecurringInvoiceService;
use App\Http\Requests\SaveInvoiceWithClientRequest; use App\Http\Requests\SaveInvoiceWithClientRequest;
@ -32,11 +34,12 @@ class InvoiceController extends BaseController
protected $mailer; protected $mailer;
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; protected $clientRepo;
protected $documentRepo;
protected $invoiceService; protected $invoiceService;
protected $recurringInvoiceService; protected $recurringInvoiceService;
protected $model = 'App\Models\Invoice'; protected $model = 'App\Models\Invoice';
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService)
{ {
// parent::__construct(); // parent::__construct();
@ -89,7 +92,7 @@ class InvoiceController extends BaseController
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$invoice = Invoice::scope($publicId) $invoice = Invoice::scope($publicId)
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'payments')
->withTrashed() ->withTrashed()
->firstOrFail(); ->firstOrFail();
@ -155,6 +158,14 @@ class InvoiceController extends BaseController
if (!$invoice->is_recurring && $invoice->balance > 0) { if (!$invoice->is_recurring && $invoice->balance > 0) {
$actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')];
} }
foreach ($invoice->payments as $payment) {
$label = trans("texts.view_payment");
if (count($invoice->payments) > 1) {
$label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client);
}
$actions[] = ['url' => $payment->present()->url, 'label' => $label];
}
} }
if (count($actions) > 3) { if (count($actions) > 3) {
@ -183,7 +194,7 @@ class InvoiceController extends BaseController
'isRecurring' => $invoice->is_recurring, 'isRecurring' => $invoice->is_recurring,
'actions' => $actions, 'actions' => $actions,
'lastSent' => $lastSent); 'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel($invoice));
if ($clone) { if ($clone) {
$data['formIsChanged'] = true; $data['formIsChanged'] = true;
@ -203,6 +214,7 @@ class InvoiceController extends BaseController
$contact->email_error = $invitation->email_error; $contact->email_error = $invitation->email_error;
$contact->invitation_link = $invitation->getLink(); $contact->invitation_link = $invitation->getLink();
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false; $contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus(); $contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
} }
} }
@ -232,6 +244,11 @@ class InvoiceController extends BaseController
$invoice = $account->createInvoice($entityType, $clientId); $invoice = $account->createInvoice($entityType, $clientId);
$invoice->public_id = 0; $invoice->public_id = 0;
if(Session::get('expenses')){
$invoice->expenses = Expense::scope(Session::get('expenses'))->with('documents')->get();
}
$clients = Client::scope()->with('contacts', 'country')->orderBy('name'); $clients = Client::scope()->with('contacts', 'country')->orderBy('name');
if(!Auth::user()->hasPermission('view_all')){ if(!Auth::user()->hasPermission('view_all')){
$clients = $clients->where('clients.user_id', '=', Auth::user()->id); $clients = $clients->where('clients.user_id', '=', Auth::user()->id);
@ -245,7 +262,7 @@ class InvoiceController extends BaseController
'url' => 'invoices', 'url' => 'invoices',
'title' => trans('texts.new_invoice'), 'title' => trans('texts.new_invoice'),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel($invoice));
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
@ -255,7 +272,7 @@ class InvoiceController extends BaseController
return self::create($clientPublicId, true); return self::create($clientPublicId, true);
} }
private static function getViewModel() private static function getViewModel($invoice)
{ {
$recurringHelp = ''; $recurringHelp = '';
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) { foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
@ -316,11 +333,37 @@ class InvoiceController extends BaseController
} }
} }
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
// Check for any taxes which have been deleted
if ($invoice->exists) {
foreach ($invoice->getTaxes() as $key => $rate) {
if (isset($options[$key])) {
continue;
}
$options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%';
}
}
return [ return [
'data' => Input::old('data'), 'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'), 'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(), 'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'currencies' => Cache::get('currencies'), 'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'), 'languages' => Cache::get('languages'),
'sizes' => Cache::get('sizes'), 'sizes' => Cache::get('sizes'),
@ -342,7 +385,6 @@ class InvoiceController extends BaseController
'recurringDueDateHelp' => $recurringDueDateHelp, 'recurringDueDateHelp' => $recurringDueDateHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null, 'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null, 'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
]; ];
@ -356,6 +398,7 @@ class InvoiceController extends BaseController
public function store(SaveInvoiceWithClientRequest $request) public function store(SaveInvoiceWithClientRequest $request)
{ {
$data = $request->input(); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){ if(!$this->checkUpdatePermission($data, $response)){
return $response; return $response;
@ -396,6 +439,7 @@ class InvoiceController extends BaseController
public function update(SaveInvoiceWithClientRequest $request) public function update(SaveInvoiceWithClientRequest $request)
{ {
$data = $request->input(); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){ if(!$this->checkUpdatePermission($data, $response)){
return $response; return $response;
@ -526,7 +570,7 @@ class InvoiceController extends BaseController
public function invoiceHistory($publicId) public function invoiceHistory($publicId)
{ {
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail(); $invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
$invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country'); $invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro(); $invoice->is_pro = Auth::user()->isPro();

View File

@ -460,6 +460,8 @@ class PaymentController extends BaseController
$ref = $response->getData()['m_payment_id']; $ref = $response->getData()['m_payment_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) { } elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
$ref = $response->getData()['signature']; $ref = $response->getData()['signature'];
} elseif ($accountGateway->gateway_id == GATEWAY_CYBERSOURCE) {
$ref = $response->getData()['transaction_uuid'];
} else { } else {
$ref = $response->getTransactionReference(); $ref = $response->getTransactionReference();
} }
@ -551,7 +553,15 @@ class PaymentController extends BaseController
} }
try { try {
if (method_exists($gateway, 'completePurchase') if ($accountGateway->isGateway(GATEWAY_CYBERSOURCE)) {
if (Input::get('decision') == 'ACCEPT') {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
} else {
Session::flash('error', Input::get('message'));
}
return Redirect::to($invitation->getLink());
} elseif (method_exists($gateway, 'completePurchase')
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT) && !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) { && !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway); $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
@ -572,11 +582,9 @@ class PaymentController extends BaseController
} else { } else {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId); $payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment')); Session::flash('message', trans('texts.applied_payment'));
return Redirect::to($invitation->getLink()); return Redirect::to($invitation->getLink());
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e); $this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to($invitation->getLink()); return Redirect::to($invitation->getLink());
} }
@ -642,6 +650,6 @@ class PaymentController extends BaseController
$message .= $error ?: trans('texts.payment_error'); $message .= $error ?: trans('texts.payment_error');
Session::flash('error', $message); Session::flash('error', $message);
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message)); Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
} }
} }

View File

@ -7,27 +7,33 @@ use URL;
use Input; use Input;
use Utils; use Utils;
use Request; use Request;
use Response;
use Session; use Session;
use Datatable; use Datatable;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Document;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository; use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Events\InvoiceInvitationWasViewed; use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService; use App\Services\PaymentService;
use Barracuda\ArchiveStream\ZipArchive;
class PublicClientController extends BaseController class PublicClientController extends BaseController
{ {
private $invoiceRepo; private $invoiceRepo;
private $paymentRepo; private $paymentRepo;
private $documentRepo;
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService) public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService)
{ {
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->activityRepo = $activityRepo; $this->activityRepo = $activityRepo;
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService; $this->paymentService = $paymentService;
} }
@ -117,8 +123,9 @@ class PublicClientController extends BaseController
'showApprove' => $showApprove, 'showApprove' => $showApprove,
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(), 'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(), 'invoice' => $invoice->hidePrivateFields(),
@ -133,6 +140,15 @@ class PublicClientController extends BaseController
'phantomjs' => Input::has('phantomjs'), 'phantomjs' => Input::has('phantomjs'),
); );
if($account->isPro() && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
if(count($zipDocs) > 1){
$data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}");
$data['documentsZipSize'] = $size;
}
}
return View::make('invoices.view', $data); return View::make('invoices.view', $data);
} }
@ -196,7 +212,7 @@ class PublicClientController extends BaseController
$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) { if (!$account->enable_client_portal || !$account->enable_client_portal_dashboard) {
return $this->returnError(); return $this->returnError();
} }
@ -205,6 +221,7 @@ class PublicClientController extends BaseController
'account' => $account, 'account' => $account,
'client' => $client, 'client' => $client,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
]; ];
@ -245,13 +262,20 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
@ -278,12 +302,17 @@ class PublicClientController extends BaseController
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
@ -302,7 +331,7 @@ class PublicClientController extends BaseController
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch')); $payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments) return Datatable::query($payments)
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })->toHtml() ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml() : $model->invoice_number; })
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; }) ->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); }) ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
@ -315,13 +344,19 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
@ -342,6 +377,44 @@ 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'));
} }
public function documentIndex()
{
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.documents'),
'entityType' => ENTITY_DOCUMENT,
'columns' => Utils::trans(['invoice_number', 'name', 'document_date', 'document_size']),
];
return response()->view('public_list', $data);
}
public function documentDatatable()
{
if (!$invitation = $this->getInvitation()) {
return false;
}
return $this->documentRepo->getClientDatatable($invitation->contact_id, ENTITY_DOCUMENT, Input::get('sSearch'));
}
private function returnError($error = false) private function returnError($error = false)
{ {
return response()->view('error', [ return response()->view('error', [
@ -373,4 +446,147 @@ class PublicClientController extends BaseController
return $invitation; return $invitation;
} }
public function getDocumentVFSJS($publicId, $name){
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->first();
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
protected function canCreateZip(){
return function_exists('gmp_init');
}
protected function getInvoiceZipDocuments($invoice, &$size=0){
$documents = $invoice->documents;
foreach($invoice->expenses as $expense){
$documents = $documents->merge($expense->documents);
}
$documents = $documents->sortBy('size');
$size = 0;
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
$toZip = array();
foreach($documents as $document){
if($size + $document->size > $maxSize)break;
if(!empty($toZip[$document->name])){
// This name is taken
if($toZip[$document->name]->hash != $document->hash){
// 2 different files with the same name
$nameInfo = pathinfo($document->name);
for($i = 1;; $i++){
$name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];
if(empty($toZip[$name])){
$toZip[$name] = $document;
$size += $document->size;
break;
} else if ($toZip[$name]->hash == $document->hash){
// We're not adding this after all
break;
}
}
}
}
else{
$toZip[$document->name] = $document;
$size += $document->size;
}
}
return $toZip;
}
public function getInvoiceDocumentsZip($invitationKey){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$invoice = $invitation->invoice;
$toZip = $this->getInvoiceZipDocuments($invoice);
if(!count($toZip)){
return Response::view('error', array('error'=>'No documents small enough'), 404);
}
$zip = new ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip');
return Response::stream(function() use ($toZip, $zip) {
foreach($toZip as $name=>$document){
$fileStream = $document->getStream();
if($fileStream){
$zip->init_file_stream_transfer($name, $document->size, array('time'=>$document->created_at->timestamp));
while ($buffer = fread($fileStream, 256000))$zip->stream_file_part($buffer);
fclose($fileStream);
$zip->complete_file_stream();
}
else{
$zip->add_file($name, $document->getRaw());
}
}
$zip->finish();
}, 200);
}
public function getDocument($invitationKey, $publicId){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->firstOrFail();
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
return DocumentController::getDownloadResponse($document);
}
} }

View File

@ -7,7 +7,7 @@ use Response;
use App\Models\Invoice; use App\Models\Invoice;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Http\Controllers\BaseAPIController; use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\QuoteTransformer; use App\Ninja\Transformers\InvoiceTransformer;
class QuoteApiController extends BaseAPIController class QuoteApiController extends BaseAPIController
{ {
@ -53,7 +53,7 @@ class QuoteApiController extends BaseAPIController
$invoices = $invoices->orderBy('created_at', 'desc')->paginate(); $invoices = $invoices->orderBy('created_at', 'desc')->paginate();
$transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer')); $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate(); $paginator = $paginator->paginate();
$data = $this->createCollection($invoices, $transformer, 'quotes', $paginator); $data = $this->createCollection($invoices, $transformer, 'quotes', $paginator);

View File

@ -111,10 +111,27 @@ class QuoteController extends BaseController
private static function getViewModel() private static function getViewModel()
{ {
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
return [ return [
'entityType' => ENTITY_QUOTE, 'entityType' => ENTITY_QUOTE,
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')), 'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(),

View File

@ -105,7 +105,7 @@ class ReportController extends BaseController
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport)); $params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
if ($isExport) { if ($isExport) {
self::export($params['displayData'], $params['columns'], $params['reportTotals']); self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
} }
} }
if ($enableChart) { if ($enableChart) {
@ -158,8 +158,10 @@ class ReportController extends BaseController
} }
$records = DB::table($entityType.'s') $records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, '.$timeframe.' as '.$groupBy)) ->select(DB::raw('sum('.$entityType.'s.amount) as total, '.$timeframe.' as '.$groupBy))
->where('account_id', '=', Auth::user()->account_id) ->join('clients', 'clients.id', '=', $entityType.'s.client_id')
->where('clients.is_deleted', '=', false)
->where($entityType.'s.account_id', '=', Auth::user()->account_id)
->where($entityType.'s.is_deleted', '=', false) ->where($entityType.'s.is_deleted', '=', false)
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d')) ->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d')) ->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
@ -168,6 +170,9 @@ class ReportController extends BaseController
if ($entityType == ENTITY_INVOICE) { if ($entityType == ENTITY_INVOICE) {
$records->where('is_quote', '=', false) $records->where('is_quote', '=', false)
->where('is_recurring', '=', false); ->where('is_recurring', '=', false);
} elseif ($entityType == ENTITY_PAYMENT) {
$records->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->where('invoices.is_deleted', '=', false);
} }
$totals = $records->lists('total'); $totals = $records->lists('total');
@ -354,7 +359,7 @@ class ReportController extends BaseController
private function generateInvoiceReport($startDate, $endDate, $isExport) private function generateInvoiceReport($startDate, $endDate, $isExport)
{ {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance']; $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account; $account = Auth::user()->account;
$displayData = []; $displayData = [];
@ -379,19 +384,25 @@ class ReportController extends BaseController
}]); }]);
foreach ($clients->get() as $client) { foreach ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
foreach ($client->invoices as $invoice) { foreach ($client->invoices as $invoice) {
$payments = count($invoice->payments) ? $invoice->payments : [false];
foreach ($payments as $payment) {
$displayData[] = [ $displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link, $isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link, $isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date, $invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client), $account->formatMoney($invoice->amount, $client),
$account->formatMoney($invoice->getAmountPaid(), $client), $payment ? $payment->present()->payment_date : '',
$account->formatMoney($invoice->balance, $client), $payment ? $account->formatMoney($payment->amount, $client) : '',
$payment ? $payment->present()->method : '',
]; ];
if ($payment) {
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount);
}
}
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); $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); $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
} }
} }
@ -503,11 +514,14 @@ class ReportController extends BaseController
return $data; return $data;
} }
private function export($data, $columns, $totals) private function export($reportType, $data, $columns, $totals)
{ {
$output = fopen('php://output', 'w') or Utils::fatalError(); $output = fopen('php://output', 'w') or Utils::fatalError();
$reportType = trans("texts.{$reportType}s");
$date = date('Y-m-d');
header('Content-Type:application/csv'); header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv'); header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv");
Utils::exportData($output, $data, Utils::trans($columns)); Utils::exportData($output, $data, Utils::trans($columns));

View File

@ -77,7 +77,6 @@ class UserController extends BaseController
'user' => $user, 'user' => $user,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'users/'.$publicId, 'url' => 'users/'.$publicId,
'title' => trans('texts.edit_user'),
]; ];
return View::make('users.edit', $data); return View::make('users.edit', $data);
@ -120,7 +119,6 @@ class UserController extends BaseController
'user' => null, 'user' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'users', 'url' => 'users',
'title' => trans('texts.add_user'),
]; ];
return View::make('users.edit', $data); return View::make('users.edit', $data);

View File

@ -107,7 +107,7 @@ class VendorController extends BaseController
Utils::trackViewed($vendor->getDisplayName(), 'vendor'); Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [ $actionLinks = [
['label' => trans('texts.new_vendor'), 'url' => '/vendors/create/' . $vendor->public_id] ['label' => trans('texts.new_vendor'), 'url' => URL::to('/vendors/create/' . $vendor->public_id)]
]; ];
$data = array( $data = array(

View File

@ -42,7 +42,7 @@ class Authenticate {
// Does this account require portal passwords? // Does this account require portal passwords?
$account = Account::whereId($account_id)->first(); $account = Account::whereId($account_id)->first();
if(!$account->enable_portal_password || !$account->isPro()){ if($account && (!$account->enable_portal_password || !$account->isPro())){
$authenticated = true; $authenticated = true;
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier { class VerifyCsrfToken extends BaseVerifier {
private $openRoutes = [ private $openRoutes = [
'complete',
'signup/register', 'signup/register',
'api/v1/*', 'api/v1/*',
'api/v1/login', 'api/v1/login',

View File

@ -26,21 +26,4 @@ class CreateClientRequest extends Request
'contacts' => 'valid_contacts', 'contacts' => 'valid_contacts',
]; ];
} }
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['contact'])) {
$input['contacts'] = [$input['contact']];
unset($input['contact']);
$this->replace($input);
}
return $factory->make(
$this->input(),
$this->container->call([$this, 'rules']),
$this->messages()
);
}
} }

View File

@ -26,21 +26,4 @@ class CreateVendorRequest extends Request
'name' => 'required', 'name' => 'required',
]; ];
} }
/*
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['vendor_contact'])) {
$input['vendor_contacts'] = [$input['vendor_contact']];
unset($input['vendor_contact']);
$this->replace($input);
}
return $factory->make(
$this->input(), $this->container->call([$this, 'rules']), $this->messages()
);
}
*/
} }

View File

@ -42,17 +42,23 @@ Route::group(['middleware' => 'auth:client'], function() {
Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment'); Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex'); Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex'); Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/documents', 'PublicClientController@documentIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex'); Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard'); Route::get('client/dashboard', 'PublicClientController@dashboard');
Route::get('client/document/js/{public_id}/{filename}', 'PublicClientController@getDocumentVFSJS');
Route::get('client/document/{invitation_key}/{public_id}/{filename?}', 'PublicClientController@getDocument');
Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip');
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
}); });
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
Route::get('license', 'PaymentController@show_license_payment'); Route::get('license', 'PaymentController@show_license_payment');
Route::post('license', 'PaymentController@do_license_payment'); Route::post('license', 'PaymentController@do_license_payment');
@ -73,8 +79,8 @@ Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@po
Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); Route::get('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); Route::post('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
Route::get('/user/confirm/{code}', 'UserController@confirm'); Route::get('/user/confirm/{code}', 'UserController@confirm');
@ -83,8 +89,8 @@ Route::get('/user/confirm/{code}', 'UserController@confirm');
Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin')); Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin'));
Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin')); Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin'));
Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout')); Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout'));
Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail')); Route::get('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail')); Route::post('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset')); Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset'));
Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset')); Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset'));
@ -101,7 +107,6 @@ if (Utils::isReseller()) {
Route::group(['middleware' => 'auth:user'], function() { Route::group(['middleware' => 'auth:user'], function() {
Route::get('dashboard', 'DashboardController@index'); Route::get('dashboard', 'DashboardController@index');
Route::get('dashboard2', 'DashboardApiController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage'); Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('force_inline_pdf', 'UserController@forcePDFJS');
@ -133,6 +138,11 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::post('invoices/bulk', 'InvoiceController@bulk'); Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('document/{public_id}/{filename?}', 'DocumentController@get');
Route::get('document/js/{public_id}/{filename}', 'DocumentController@getVFSJS');
Route::get('document/preview/{public_id}/{filename?}', 'DocumentController@getPreview');
Route::post('document', 'DocumentController@postUpload');
Route::get('quotes/create/{client_id?}', 'QuoteController@create'); Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice'); Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit'); Route::get('quotes/{public_id}/edit', 'InvoiceController@edit');
@ -258,6 +268,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('expenses','ExpenseApiController'); Route::resource('expenses','ExpenseApiController');
Route::post('add_token', 'AccountApiController@addDeviceToken'); Route::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications'); Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
Route::get('dashboard', 'DashboardApiController@index');
// Vendor // Vendor
Route::resource('vendors', 'VendorApiController'); Route::resource('vendors', 'VendorApiController');
@ -267,7 +278,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
}); });
// Redirects for legacy links // Redirects for legacy links
/*
Route::get('/rocksteady', function() { Route::get('/rocksteady', function() {
return Redirect::to(NINJA_WEB_URL, 301); return Redirect::to(NINJA_WEB_URL, 301);
}); });
@ -295,7 +305,7 @@ Route::get('/compare-online-invoicing{sites?}', function() {
Route::get('/forgot_password', function() { Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301); return Redirect::to(NINJA_APP_URL.'/forgot', 301);
}); });
*/
if (!defined('CONTACT_EMAIL')) { if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address')); define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -310,6 +320,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_CLIENT', 'client'); define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact'); define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice'); define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEMS', 'invoice_items'); define('ENTITY_INVOICE_ITEMS', 'invoice_items');
define('ENTITY_INVITATION', 'invitation'); define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice'); define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
@ -425,6 +436,10 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_IFRAME_URL_LENGTH', 250); define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10); define('MAX_FAILED_LOGINS', 10);
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels
define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1);// Roboto define('DEFAULT_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto define('DEFAULT_BODY_FONT', 1);// Roboto
@ -520,6 +535,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_BITPAY', 42); define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43); define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47); define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2); define('EVENT_CREATE_INVOICE', 2);
@ -535,7 +551,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.5.0.4'); define('NINJA_VERSION', '2.5.1.3');
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');

View File

@ -247,7 +247,7 @@ class Utils
return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}"; return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
} }
public static function logError($error, $context = 'PHP') public static function logError($error, $context = 'PHP', $info = false)
{ {
if ($error instanceof Exception) { if ($error instanceof Exception) {
$error = self::getErrorString($error); $error = self::getErrorString($error);
@ -271,7 +271,11 @@ class Utils
'count' => Session::get('error_count', 0), 'count' => Session::get('error_count', 0),
]; ];
if ($info) {
Log::info($error."\n", $data);
} else {
Log::error($error."\n", $data); Log::error($error."\n", $data);
}
/* /*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message) Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
@ -620,8 +624,8 @@ class Utils
private static function getMonth($offset) private static function getMonth($offset)
{ {
$months = [ "January", "February", "March", "April", "May", "June", $months = [ "january", "february", "march", "april", "may", "june",
"July", "August", "September", "October", "November", "December", ]; "july", "august", "september", "october", "november", "december", ];
$month = intval(date('n')) - 1; $month = intval(date('n')) - 1;
@ -632,7 +636,7 @@ class Utils
$month += 12; $month += 12;
} }
return $months[$month]; return trans('texts.' . $months[$month]);
} }
private static function getQuarter($offset) private static function getQuarter($offset)

View File

@ -8,7 +8,9 @@ use Event;
use Cache; use Cache;
use App; use App;
use File; use File;
use App\Models\Document;
use App\Events\UserSettingsChanged; use App\Events\UserSettingsChanged;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
@ -384,26 +386,69 @@ class Account extends Eloquent
public function hasLogo() public function hasLogo()
{ {
return file_exists($this->getLogoFullPath()); if($this->logo == ''){
$this->calculateLogoDetails();
} }
public function getLogoPath() return !empty($this->logo);
{
$fileName = 'logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
} }
public function getLogoFullPath() public function getLogoDisk(){
{ return Storage::disk(env('LOGO_FILESYSTEM', 'logos'));
$fileName = public_path() . '/logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
} }
public function getLogoURL() protected function calculateLogoDetails(){
$disk = $this->getLogoDisk();
if($disk->exists($this->account_key.'.png')){
$this->logo = $this->account_key.'.png';
} else if($disk->exists($this->account_key.'.jpg')) {
$this->logo = $this->account_key.'.jpg';
}
if(!empty($this->logo)){
$image = imagecreatefromstring($disk->get($this->logo));
$this->logo_width = imagesx($image);
$this->logo_height = imagesy($image);
$this->logo_size = $disk->size($this->logo);
} else {
$this->logo = null;
}
$this->save();
}
public function getLogoRaw(){
if(!$this->hasLogo()){
return null;
}
$disk = $this->getLogoDisk();
return $disk->get($this->logo);
}
public function getLogoURL($cachebuster = false)
{ {
return SITE_URL . '/' . $this->getLogoPath(); if(!$this->hasLogo()){
return null;
}
$disk = $this->getLogoDisk();
$adapter = $disk->getAdapter();
if($adapter instanceof \League\Flysystem\Adapter\Local) {
// Stored locally
$logo_url = str_replace(public_path(), url('/'), $adapter->applyPathPrefix($this->logo), $count);
if ($cachebuster) {
$logo_url .= '?no_cache='.time();
}
if($count == 1){
return str_replace(DIRECTORY_SEPARATOR, '/', $logo_url);
}
}
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
} }
public function getToken($name) public function getToken($name)
@ -419,24 +464,20 @@ class Account extends Eloquent
public function getLogoWidth() public function getLogoWidth()
{ {
$path = $this->getLogoFullPath(); if(!$this->hasLogo()){
if (!file_exists($path)) { return null;
return 0;
} }
list($width, $height) = getimagesize($path);
return $width; return $this->logo_width;
} }
public function getLogoHeight() public function getLogoHeight()
{ {
$path = $this->getLogoFullPath(); if(!$this->hasLogo()){
if (!file_exists($path)) { return null;
return 0;
} }
list($width, $height) = getimagesize($path);
return $height; return $this->logo_height;
} }
public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null) public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null)
@ -815,12 +856,11 @@ class Account extends Eloquent
public function getLogoSize() public function getLogoSize()
{ {
if (!$this->hasLogo()) { if(!$this->hasLogo()){
return 0; return null;
} }
$filename = $this->getLogoFullPath(); return round($this->logo_size / 1000);
return round(File::size($filename) / 1000);
} }
public function isLogoTooLarge() public function isLogoTooLarge()
@ -948,7 +988,7 @@ class Account extends Eloquent
// Add line breaks if HTML isn't already being used // Add line breaks if HTML isn't already being used
return strip_tags($this->email_footer) == $this->email_footer ? nl2br($this->email_footer) : $this->email_footer; return strip_tags($this->email_footer) == $this->email_footer ? nl2br($this->email_footer) : $this->email_footer;
} else { } else {
return "<p>" . trans('texts.email_signature') . "\n<br>\$account</ p>"; return "<p>" . trans('texts.email_signature') . "\n<br>\$account</p>";
} }
} }

View File

@ -146,7 +146,7 @@ class Client extends EntityModel
public function addContact($data, $isPrimary = false) public function addContact($data, $isPrimary = false)
{ {
$publicId = isset($data['public_id']) ? $data['public_id'] : false; $publicId = isset($data['public_id']) ? $data['public_id'] : (isset($data['id']) ? $data['id'] : false);
if ($publicId && $publicId != '-1') { if ($publicId && $publicId != '-1') {
$contact = Contact::scope($publicId)->firstOrFail(); $contact = Contact::scope($publicId)->firstOrFail();

263
app/Models/Document.php Normal file
View File

@ -0,0 +1,263 @@
<?php namespace App\Models;
use Illuminate\Support\Facades\Storage;
use DB;
use Auth;
class Document extends EntityModel
{
public static $extraExtensions = array(
'jpg' => 'jpeg',
'tif' => 'tiff',
);
public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts
'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain',
'application/zip', 'application/msword',
'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml',
'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint',
);
public static $types = array(
'png' => array(
'mime' => 'image/png',
),
'ai' => array(
'mime' => 'application/postscript',
),
'svg' => array(
'mime' => 'image/svg+xml',
),
'jpeg' => array(
'mime' => 'image/jpeg',
),
'tiff' => array(
'mime' => 'image/tiff',
),
'pdf' => array(
'mime' => 'application/pdf',
),
'gif' => array(
'mime' => 'image/gif',
),
'psd' => array(
'mime' => 'image/vnd.adobe.photoshop',
),
'txt' => array(
'mime' => 'text/plain',
),
'zip' => array(
'mime' => 'application/zip',
),
'doc' => array(
'mime' => 'application/msword',
),
'xls' => array(
'mime' => 'application/vnd.ms-excel',
),
'ppt' => array(
'mime' => 'application/vnd.ms-powerpoint',
),
'xlsx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
),
'docx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
),
'pptx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
),
);
public function fill(array $attributes)
{
parent::fill($attributes);
if(empty($this->attributes['disk'])){
$this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents');
}
return $this;
}
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function expense()
{
return $this->belongsTo('App\Models\Expense')->withTrashed();
}
public function invoice()
{
return $this->belongsTo('App\Models\Invoice')->withTrashed();
}
public function getDisk(){
return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents'));
}
public function setDiskAttribute($value)
{
$this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents');
}
public function getDirectUrl(){
return static::getDirectFileUrl($this->path, $this->getDisk());
}
public function getDirectPreviewUrl(){
return $this->preview?static::getDirectFileUrl($this->preview, $this->getDisk(), true):null;
}
public static function getDirectFileUrl($path, $disk, $prioritizeSpeed = false){
$adapter = $disk->getAdapter();
$fullPath = $adapter->applyPathPrefix($path);
if($adapter instanceof \League\Flysystem\AwsS3v3\AwsS3Adapter) {
$client = $adapter->getClient();
$command = $client->getCommand('GetObject', [
'Bucket' => $adapter->getBucket(),
'Key' => $fullPath
]);
return (string) $client->createPresignedRequest($command, '+10 minutes')->getUri();
} else if (!$prioritizeSpeed // Rackspace temp URLs are slow, so we don't use them for previews
&& $adapter instanceof \League\Flysystem\Rackspace\RackspaceAdapter) {
$secret = env('RACKSPACE_TEMP_URL_SECRET');
if($secret){
$object = $adapter->getContainer()->getObject($fullPath);
if(env('RACKSPACE_TEMP_URL_SECRET_SET')){
// Go ahead and set the secret too
$object->getService()->getAccount()->setTempUrlSecret($secret);
}
$url = $object->getUrl();
$expiry = strtotime('+10 minutes');
$urlPath = urldecode($url->getPath());
$body = sprintf("%s\n%d\n%s", 'GET', $expiry, $urlPath);
$hash = hash_hmac('sha1', $body, $secret);
return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry);
}
}
return null;
}
public function getRaw(){
$disk = $this->getDisk();
return $disk->get($this->path);
}
public function getStream(){
$disk = $this->getDisk();
return $disk->readStream($this->path);
}
public function getRawPreview(){
$disk = $this->getDisk();
return $disk->get($this->preview);
}
public function getUrl(){
return url('document/'.$this->public_id.'/'.$this->name);
}
public function getClientUrl($invitation){
return url('client/document/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name);
}
public function isPDFEmbeddable(){
return $this->type == 'jpeg' || $this->type == 'png' || $this->preview;
}
public function getVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null;
return url('document/js/'.$this->public_id.'/'.$this->name.'.js');
}
public function getClientVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null;
return url('client/document/js/'.$this->public_id.'/'.$this->name.'.js');
}
public function getPreviewUrl(){
return $this->preview?url('document/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null;
}
public function toArray()
{
$array = parent::toArray();
if(empty($this->visible) || in_array('url', $this->visible))$array['url'] = $this->getUrl();
if(empty($this->visible) || in_array('preview_url', $this->visible))$array['preview_url'] = $this->getPreviewUrl();
return $array;
}
public function cloneDocument(){
$document = Document::createNew($this);
$document->path = $this->path;
$document->preview = $this->preview;
$document->name = $this->name;
$document->type = $this->type;
$document->disk = $this->disk;
$document->hash = $this->hash;
$document->size = $this->size;
$document->width = $this->width;
$document->height = $this->height;
return $document;
}
public static function canCreate(){
return true;
}
public static function canViewItem($document){
if(Auth::user()->hasPermission('view_all'))return true;
if($document->expense){
if($document->expense->invoice)return $document->expense->invoice->canView();
return $document->expense->canView();
}
if($document->invoice)return $document->invoice->canView();
return Auth::user()->id == $item->user_id;
}
}
Document::deleted(function ($document) {
$same_path_count = DB::table('documents')
->where('documents.account_id', '=', $document->account_id)
->where('documents.path', '=', $document->path)
->where('documents.disk', '=', $document->disk)
->count();
if(!$same_path_count){
$document->getDisk()->delete($document->path);
}
if($document->preview){
$same_preview_count = DB::table('documents')
->where('documents.account_id', '=', $document->account_id)
->where('documents.preview', '=', $document->preview)
->where('documents.disk', '=', $document->disk)
->count();
if(!$same_preview_count){
$document->getDisk()->delete($document->preview);
}
}
});

View File

@ -24,9 +24,14 @@ class EntityModel extends Eloquent
Utils::fatalError(); Utils::fatalError();
} }
if(method_exists($className, 'withTrashed')){
$lastEntity = $className::withTrashed() $lastEntity = $className::withTrashed()
->scope(false, $entity->account_id) ->scope(false, $entity->account_id);
->orderBy('public_id', 'DESC') } else {
$lastEntity = $className::scope(false, $entity->account_id);
}
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first(); ->first();
if ($lastEntity) { if ($lastEntity) {

View File

@ -53,6 +53,11 @@ class Expense extends EntityModel
return $this->belongsTo('App\Models\Invoice')->withTrashed(); return $this->belongsTo('App\Models\Invoice')->withTrashed();
} }
public function documents()
{
return $this->hasMany('App\Models\Document')->orderBy('id');
}
public function getName() public function getName()
{ {
if($this->expense_number) if($this->expense_number)
@ -80,6 +85,20 @@ class Expense extends EntityModel
{ {
return $this->invoice_currency_id != $this->expense_currency_id; return $this->invoice_currency_id != $this->expense_currency_id;
} }
public function convertedAmount()
{
return round($this->amount * $this->exchange_rate, 2);
}
public function toArray()
{
$array = parent::toArray();
if(empty($this->visible) || in_array('converted_amount', $this->visible))$array['converted_amount'] = $this->convertedAmount();
return $array;
}
} }
Expense::creating(function ($expense) { Expense::creating(function ($expense) {

View File

@ -29,7 +29,9 @@ class Invitation extends EntityModel
return $this->belongsTo('App\Models\Account'); return $this->belongsTo('App\Models\Account');
} }
public function getLink($type = 'view') // If we're getting the link for PhantomJS to generate the PDF
// we need to make sure it's served from our site
public function getLink($type = 'view', $forceOnsite = false)
{ {
if (!$this->account) { if (!$this->account) {
$this->load('account'); $this->load('account');
@ -39,8 +41,8 @@ class Invitation extends EntityModel
$iframe_url = $this->account->iframe_url; $iframe_url = $this->account->iframe_url;
if ($this->account->isPro()) { if ($this->account->isPro()) {
if ($iframe_url) { if ($iframe_url && !$forceOnsite) {
return "{$iframe_url}/?{$this->invitation_key}"; return "{$iframe_url}?{$this->invitation_key}";
} elseif ($this->account->subdomain) { } elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain); $url = Utils::replaceSubdomain($url, $this->account->subdomain);
} }

View File

@ -2,6 +2,7 @@
use Utils; use Utils;
use DateTime; use DateTime;
use URL;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
use App\Models\BalanceAffecting; use App\Models\BalanceAffecting;
@ -24,6 +25,13 @@ class Invoice extends EntityModel implements BalanceAffecting
protected $presenter = 'App\Ninja\Presenters\InvoicePresenter'; protected $presenter = 'App\Ninja\Presenters\InvoicePresenter';
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
protected $fillable = [
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
];
protected $casts = [ protected $casts = [
'is_recurring' => 'boolean', 'is_recurring' => 'boolean',
'has_tasks' => 'boolean', 'has_tasks' => 'boolean',
@ -175,6 +183,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\InvoiceItem')->orderBy('id'); return $this->hasMany('App\Models\InvoiceItem')->orderBy('id');
} }
public function documents()
{
return $this->hasMany('App\Models\Document')->orderBy('id');
}
public function invoice_status() public function invoice_status()
{ {
return $this->belongsTo('App\Models\InvoiceStatus'); return $this->belongsTo('App\Models\InvoiceStatus');
@ -385,9 +398,13 @@ class Invoice extends EntityModel implements BalanceAffecting
'amount', 'amount',
'balance', 'balance',
'invoice_items', 'invoice_items',
'documents',
'expenses',
'client', 'client',
'tax_name', 'tax_name1',
'tax_rate', 'tax_rate1',
'tax_name2',
'tax_rate2',
'account', 'account',
'invoice_design', 'invoice_design',
'invoice_design_id', 'invoice_design_id',
@ -457,6 +474,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'custom_invoice_text_label2', 'custom_invoice_text_label2',
'custom_invoice_item_label1', 'custom_invoice_item_label1',
'custom_invoice_item_label2', 'custom_invoice_item_label2',
'invoice_embed_documents'
]); ]);
foreach ($this->invoice_items as $invoiceItem) { foreach ($this->invoice_items as $invoiceItem) {
@ -467,8 +485,10 @@ class Invoice extends EntityModel implements BalanceAffecting
'custom_value2', 'custom_value2',
'cost', 'cost',
'qty', 'qty',
'tax_name', 'tax_name1',
'tax_rate', 'tax_rate1',
'tax_name2',
'tax_rate2',
]); ]);
} }
@ -481,6 +501,26 @@ class Invoice extends EntityModel implements BalanceAffecting
]); ]);
} }
foreach ($this->documents as $document) {
$document->setVisible([
'public_id',
'name',
]);
}
foreach ($this->expenses as $expense) {
$expense->setVisible([
'documents',
]);
foreach ($expense->documents as $document) {
$document->setVisible([
'public_id',
'name',
]);
}
}
return $this; return $this;
} }
@ -749,7 +789,7 @@ class Invoice extends EntityModel implements BalanceAffecting
} }
$invitation = $this->invitations[0]; $invitation = $this->invitations[0];
$link = $invitation->getLink(); $link = $invitation->getLink('view', true);
$key = env('PHANTOMJS_CLOUD_KEY'); $key = env('PHANTOMJS_CLOUD_KEY');
$curl = curl_init(); $curl = curl_init();
@ -814,52 +854,78 @@ class Invoice extends EntityModel implements BalanceAffecting
return $total; return $total;
} }
// if $calculatePaid is true we'll loop through each payment to
// determine the sum, otherwise we'll use the cached paid_to_date amount
public function getTaxes($calculatePaid = false) public function getTaxes($calculatePaid = false)
{ {
$taxes = []; $taxes = [];
$taxable = $this->getTaxable(); $taxable = $this->getTaxable();
$paidAmount = $this->getAmountPaid($calculatePaid);
if ($this->tax_rate && $this->tax_name) { if ($this->tax_name1) {
$taxAmount = $taxable * ($this->tax_rate / 100); $invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
$taxAmount = round($taxAmount, 2); $invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
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)
];
} }
if ($this->tax_name2) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
$invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
} }
foreach ($this->invoice_items as $invoiceItem) { foreach ($this->invoice_items as $invoiceItem) {
if ( ! $invoiceItem->tax_rate || ! $invoiceItem->tax_name) { $itemTaxAmount = $this->getItemTaxable($invoiceItem, $taxable);
continue;
if ($invoiceItem->tax_name1) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate1 / 100), 2);
$itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
} }
$taxAmount = $this->getItemTaxable($invoiceItem, $taxable); if ($invoiceItem->tax_name2) {
$taxAmount = $taxable * ($invoiceItem->tax_rate / 100); $itemTaxAmount = round($taxable * ($invoiceItem->tax_rate2 / 100), 2);
$taxAmount = round($taxAmount, 2); $itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
}
}
if ($taxAmount) { return $taxes;
$key = $invoiceItem->tax_name.$invoiceItem->tax_rate; }
private function calculateTax(&$taxes, $name, $rate, $amount, $paid)
{
if ( ! $amount) {
return;
}
$amount = round($amount, 2);
$paid = round($paid, 2);
$key = $rate . ' ' . $name;
if ( ! isset($taxes[$key])) { if ( ! isset($taxes[$key])) {
$taxes[$key] = [ $taxes[$key] = [
'name' => $name,
'rate' => $rate+0,
'amount' => 0, 'amount' => 0,
'paid' => 0 'paid' => 0
]; ];
} }
$taxes[$key]['amount'] += $taxAmount; $taxes[$key]['amount'] += $amount;
$taxes[$key]['paid'] += $this->amount && $taxAmount ? round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) : 0; $taxes[$key]['paid'] += $paid;
$taxes[$key]['name'] = $invoiceItem->tax_name;
$taxes[$key]['rate'] = $invoiceItem->tax_rate;
}
} }
return $taxes; public function hasDocuments(){
if(count($this->documents))return true;
return $this->hasExpenseDocuments();
}
public function hasExpenseDocuments(){
foreach($this->expenses as $expense){
if(count($expense->documents))return true;
}
return false;
} }
} }

View File

@ -7,6 +7,13 @@ class InvoiceItem extends EntityModel
use SoftDeletes; use SoftDeletes;
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
protected $fillable = [
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
];
public function invoice() public function invoice()
{ {
return $this->belongsTo('App\Models\Invoice'); return $this->belongsTo('App\Models\Invoice');

View File

@ -1,5 +1,6 @@
<?php namespace App\Models; <?php namespace App\Models;
use Auth;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class TaxRate extends EntityModel class TaxRate extends EntityModel

View File

@ -1,6 +1,7 @@
<?php namespace App\Ninja\Mailers; <?php namespace App\Ninja\Mailers;
use Form; use Form;
use HTML;
use Utils; use Utils;
use Event; use Event;
use URL; use URL;
@ -28,6 +29,7 @@ class ContactMailer extends Mailer
'invoice', 'invoice',
'quote', 'quote',
'password', 'password',
'documents',
'viewLink', 'viewLink',
'viewButton', 'viewButton',
'paymentLink', 'paymentLink',
@ -60,8 +62,31 @@ class ContactMailer extends Mailer
$pdfString = $invoice->getPDFString(); $pdfString = $invoice->getPDFString();
} }
$documentStrings = array();
if ($account->document_email_attachment && $invoice->hasDocuments()) {
$documents = $invoice->documents;
foreach($invoice->expenses as $expense){
$documents = $documents->merge($expense->documents);
}
$documents = $documents->sortBy('size');
$size = 0;
$maxSize = MAX_EMAIL_DOCUMENTS_SIZE * 1000;
foreach($documents as $document){
$size += $document->size;
if($size > $maxSize)break;
$documentStrings[] = array(
'name' => $document->name,
'data' => $document->getRaw(),
);
}
}
foreach ($invoice->invitations as $invitation) { foreach ($invoice->invitations as $invitation) {
$response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString); $response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString, $documentStrings);
if ($response === true) { if ($response === true) {
$sent = true; $sent = true;
} }
@ -80,7 +105,7 @@ class ContactMailer extends Mailer
return $response; return $response;
} }
private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString) private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString, $documentStrings)
{ {
$client = $invoice->client; $client = $invoice->client;
$account = $invoice->account; $account = $invoice->account;
@ -127,6 +152,7 @@ class ContactMailer extends Mailer
'account' => $account, 'account' => $account,
'client' => $client, 'client' => $client,
'invoice' => $invoice, 'invoice' => $invoice,
'documents' => $documentStrings,
]; ];
if ($account->attatchPDF()) { if ($account->attatchPDF()) {
@ -263,6 +289,20 @@ class ContactMailer extends Mailer
$invitation = $data['invitation']; $invitation = $data['invitation'];
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false; $passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$documentsHTML = '';
if($account->isPro() && $invoice->hasDocuments()){
$documentsHTML .= trans('texts.email_documents_header').'<ul>';
foreach($invoice->documents as $document){
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
}
foreach($invoice->expenses as $expense){
foreach($expense->documents as $document){
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
}
}
$documentsHTML .= '</ul>';
}
$variables = [ $variables = [
'$footer' => $account->getEmailFooter(), '$footer' => $account->getEmailFooter(),
@ -285,6 +325,7 @@ class ContactMailer extends Mailer
'$customClient2' => $account->custom_client_label2, '$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1, '$customInvoice1' => $account->custom_invoice_text_label1,
'$customInvoice2' => $account->custom_invoice_text_label2, '$customInvoice2' => $account->custom_invoice_text_label2,
'$documents' => $documentsHTML,
]; ];
// Add variables for available payment types // Add variables for available payment types

View File

@ -44,6 +44,13 @@ class Mailer
if (!empty($data['pdfString']) && !empty($data['pdfFileName'])) { if (!empty($data['pdfString']) && !empty($data['pdfFileName'])) {
$message->attachData($data['pdfString'], $data['pdfFileName']); $message->attachData($data['pdfString'], $data['pdfFileName']);
} }
// Attach documents to the email
if(!empty($data['documents'])){
foreach($data['documents'] as $document){
$message->attachData($document['data'], $document['name']);
}
}
}); });
return $this->handleSuccess($response, $data); return $this->handleSuccess($response, $data);
@ -81,7 +88,7 @@ class Mailer
$emailError = $exception->getMessage(); $emailError = $exception->getMessage();
} }
Utils::logError("Email Error: $emailError"); //Utils::logError("Email Error: $emailError");
if (isset($data['invitation'])) { if (isset($data['invitation'])) {
$invitation = $data['invitation']; $invitation = $data['invitation'];

View File

@ -16,14 +16,9 @@ class ExpensePresenter extends Presenter {
return Utils::fromSqlDate($this->entity->expense_date); return Utils::fromSqlDate($this->entity->expense_date);
} }
public function converted_amount()
{
return round($this->entity->amount * $this->entity->exchange_rate, 2);
}
public function invoiced_amount() public function invoiced_amount()
{ {
return $this->entity->invoice_id ? $this->converted_amount() : 0; return $this->entity->invoice_id ? $this->entity->convertedAmount() : 0;
} }
public function link() public function link()

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;
@ -24,4 +25,14 @@ class PaymentPresenter extends Presenter {
} }
} }
public function url()
{
return URL::to('/payments/' . $this->entity->public_id . '/edit');
}
public function link()
{
return link_to('/payments/' . $this->entity->public_id . '/edit', $this->entity->getDisplayName());
}
} }

View File

@ -475,7 +475,7 @@ class AccountRepository
$item->account_id = $user->account->id; $item->account_id = $user->account->id;
$item->account_name = $user->account->getDisplayName(); $item->account_name = $user->account->getDisplayName();
$item->pro_plan_paid = $user->account->pro_plan_paid; $item->pro_plan_paid = $user->account->pro_plan_paid;
$item->logo_path = $user->account->hasLogo() ? $user->account->getLogoPath() : null; $item->logo_url = $user->account->hasLogo() ? $user->account->getLogoUrl() : null;
$data[] = $item; $data[] = $item;
} }

View File

@ -20,6 +20,10 @@ class BaseRepository
public function archive($entity) public function archive($entity)
{ {
if ($entity->trashed()) {
return;
}
$entity->delete(); $entity->delete();
$className = $this->getEventClass($entity, 'Archived'); $className = $this->getEventClass($entity, 'Archived');
@ -31,6 +35,10 @@ class BaseRepository
public function restore($entity) public function restore($entity)
{ {
if ( ! $entity->trashed()) {
return;
}
$fromDeleted = false; $fromDeleted = false;
$entity->restore(); $entity->restore();
@ -49,6 +57,10 @@ class BaseRepository
public function delete($entity) public function delete($entity)
{ {
if ($entity->is_deleted) {
return;
}
$entity->is_deleted = true; $entity->is_deleted = true;
$entity->save(); $entity->save();

View File

@ -100,6 +100,11 @@ class ClientRepository extends BaseRepository
$contacts = isset($data['contact']) ? [$data['contact']] : $data['contacts']; $contacts = isset($data['contact']) ? [$data['contact']] : $data['contacts'];
$contactIds = []; $contactIds = [];
// If the primary is set ensure it's listed first
usort($contacts, function ($left, $right) {
return (isset($right['is_primary']) ? $right['is_primary'] : 0) - (isset($left['is_primary']) ? $left['is_primary'] : 0);
});
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
$contact = $client->addContact($contact, $first); $contact = $client->addContact($contact, $first);
$contactIds[] = $contact->public_id; $contactIds[] = $contact->public_id;

View File

@ -0,0 +1,228 @@
<?php namespace app\Ninja\Repositories;
use DB;
use Utils;
use Response;
use App\Models\Document;
use App\Ninja\Repositories\BaseRepository;
use Intervention\Image\ImageManager;
use Session;
use Form;
class DocumentRepository extends BaseRepository
{
// Expenses
public function getClassName()
{
return 'App\Models\Document';
}
public function all()
{
return Document::scope()
->with('user')
->get();
}
public function find()
{
$accountid = \Auth::user()->account_id;
$query = DB::table('clients')
->join('accounts', 'accounts.id', '=', 'clients.account_id')
->leftjoin('clients', 'clients.id', '=', 'clients.client_id')
/*->leftJoin('expenses', 'expenses.id', '=', 'clients.expense_id')
->leftJoin('invoices', 'invoices.id', '=', 'clients.invoice_id')*/
->where('documents.account_id', '=', $accountid)
/*->where('vendors.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)*/
->select(
'documents.account_id',
'documents.path',
'documents.deleted_at',
'documents.size',
'documents.width',
'documents.height',
'documents.id',
'documents.is_deleted',
'documents.public_id',
'documents.invoice_id',
'documents.expense_id',
'documents.user_id',
'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'expenses.public_id as expense_public_id',
'expenses.user_id as expense_user_id'
);
return $query;
}
public function upload($uploaded, &$doc_array=null)
{
$extension = strtolower($uploaded->getClientOriginalExtension());
if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){
$documentType = Document::$extraExtensions[$extension];
}
else{
$documentType = $extension;
}
if(empty(Document::$types[$documentType])){
return 'Unsupported file type';
}
$documentTypeData = Document::$types[$documentType];
$filePath = $uploaded->path();
$name = $uploaded->getClientOriginalName();
$size = filesize($filePath);
if($size/1000 > MAX_DOCUMENT_SIZE){
return 'File too large';
}
$hash = sha1_file($filePath);
$filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType;
$document = Document::createNew();
$disk = $document->getDisk();
if(!$disk->exists($filename)){// Have we already stored the same file
$stream = fopen($filePath, 'r');
$disk->getDriver()->putStream($filename, $stream, ['mimetype'=>$documentTypeData['mime']]);
fclose($stream);
}
// This is an image; check if we need to create a preview
if(in_array($documentType, array('jpeg','png','gif','bmp','tiff','psd'))){
$makePreview = false;
$imageSize = getimagesize($filePath);
$width = $imageSize[0];
$height = $imageSize[1];
$imgManagerConfig = array();
if(in_array($documentType, array('gif','bmp','tiff','psd'))){
// Needs to be converted
$makePreview = true;
} else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){
$makePreview = true;
}
if(in_array($documentType,array('bmp','tiff','psd'))){
if(!class_exists('Imagick')){
// Cant't read this
$makePreview = false;
} else {
$imgManagerConfig['driver'] = 'imagick';
}
}
if($makePreview){
$previewType = 'jpeg';
if(in_array($documentType, array('png','gif','tiff','psd'))){
// Has transparency
$previewType = 'png';
}
$document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType.'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType;
if(!$disk->exists($document->preview)){
// We haven't created a preview yet
$imgManager = new ImageManager($imgManagerConfig);
$img = $imgManager->make($filePath);
if($width <= DOCUMENT_PREVIEW_SIZE && $height <= DOCUMENT_PREVIEW_SIZE){
$previewWidth = $width;
$previewHeight = $height;
} else if($width > $height) {
$previewWidth = DOCUMENT_PREVIEW_SIZE;
$previewHeight = $height * DOCUMENT_PREVIEW_SIZE / $width;
} else {
$previewHeight = DOCUMENT_PREVIEW_SIZE;
$previewWidth = $width * DOCUMENT_PREVIEW_SIZE / $height;
}
$img->resize($previewWidth, $previewHeight);
$previewContent = (string) $img->encode($previewType);
$disk->put($document->preview, $previewContent);
$base64 = base64_encode($previewContent);
}
else{
$base64 = base64_encode($disk->get($document->preview));
}
}else{
$base64 = base64_encode(file_get_contents($filePath));
}
}
$document->path = $filename;
$document->type = $documentType;
$document->size = $size;
$document->hash = $hash;
$document->name = substr($name, -255);
if(!empty($imageSize)){
$document->width = $imageSize[0];
$document->height = $imageSize[1];
}
$document->save();
$doc_array = $document->toArray();
if(!empty($base64)){
$mime = Document::$types[!empty($previewType)?$previewType:$documentType]['mime'];
$doc_array['base64'] = 'data:'.$mime.';base64,'.$base64;
}
return $document;
}
public function getClientDatatable($contactId, $entityType, $search)
{
$query = DB::table('invitations')
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('documents', 'documents.invoice_id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
// This needs to be a setting to also hide the activity on the dashboard page
//->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT)
->select(
'invitations.invitation_key',
'invoices.invoice_number',
'documents.name',
'documents.public_id',
'documents.created_at',
'documents.size'
);
$table = \Datatable::query($query)
->addColumn('invoice_number', function ($model) {
return link_to(
'/view/'.$model->invitation_key,
$model->invoice_number
)->toHtml();
})
->addColumn('name', function ($model) {
return link_to(
'/client/document/'.$model->invitation_key.'/'.$model->public_id.'/'.$model->name,
$model->name,
['target'=>'_blank']
)->toHtml();
})
->addColumn('document_date', function ($model) {
return Utils::fromSqlDate($model->created_at);
})
->addColumn('document_size', function ($model) {
return Form::human_filesize($model->size);
});
return $table->make();
}
}

View File

@ -4,17 +4,25 @@ use DB;
use Utils; use Utils;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Vendor; use App\Models\Vendor;
use App\Models\Document;
use App\Ninja\Repositories\BaseRepository; use App\Ninja\Repositories\BaseRepository;
use Session; use Session;
class ExpenseRepository extends BaseRepository class ExpenseRepository extends BaseRepository
{ {
protected $documentRepo;
// Expenses // Expenses
public function getClassName() public function getClassName()
{ {
return 'App\Models\Expense'; return 'App\Models\Expense';
} }
public function __construct(DocumentRepository $documentRepo)
{
$this->documentRepo = $documentRepo;
}
public function all() public function all()
{ {
return Expense::scope() return Expense::scope()
@ -113,7 +121,7 @@ class ExpenseRepository extends BaseRepository
return $query; return $query;
} }
public function save($input) public function save($input, $checkSubPermissions=false)
{ {
$publicId = isset($input['public_id']) ? $input['public_id'] : false; $publicId = isset($input['public_id']) ? $input['public_id'] : false;
@ -147,6 +155,43 @@ class ExpenseRepository extends BaseRepository
$expense->save(); $expense->save();
// Documents
$document_ids = !empty($input['document_ids'])?array_map('intval', $input['document_ids']):array();;
foreach ($document_ids as $document_id){
$document = Document::scope($document_id)->first();
if($document && !$checkSubPermissions || $document->canEdit()){
$document->invoice_id = null;
$document->expense_id = $expense->id;
$document->save();
}
}
if(!empty($input['documents']) && Document::canCreate()){
// Fallback upload
$doc_errors = array();
foreach($input['documents'] as $upload){
$result = $this->documentRepo->upload($upload);
if(is_string($result)){
$doc_errors[] = $result;
}
else{
$result->expense_id = $expense->id;
$result->save();
$document_ids[] = $result->public_id;
}
}
if(!empty($doc_errors)){
Session::flash('error', implode('<br>',array_map('htmlentities',$doc_errors)));
}
}
foreach ($expense->documents as $document){
if(!in_array($document->public_id, $document_ids)){
// Not checking permissions; deleting a document is just editing the invoice
$document->delete();
}
}
return $expense; return $expense;
} }

View File

@ -2,24 +2,29 @@
use DB; use DB;
use Utils; use Utils;
use Session;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Product; use App\Models\Product;
use App\Models\Task; use App\Models\Task;
use App\Models\Document;
use App\Models\Expense; use App\Models\Expense;
use App\Services\PaymentService; use App\Services\PaymentService;
use App\Ninja\Repositories\BaseRepository; use App\Ninja\Repositories\BaseRepository;
class InvoiceRepository extends BaseRepository class InvoiceRepository extends BaseRepository
{ {
protected $documentRepo;
public function getClassName() public function getClassName()
{ {
return 'App\Models\Invoice'; return 'App\Models\Invoice';
} }
public function __construct(PaymentService $paymentService) public function __construct(PaymentService $paymentService, DocumentRepository $documentRepo)
{ {
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService; $this->paymentService = $paymentService;
} }
@ -216,6 +221,8 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::scope($publicId)->firstOrFail(); $invoice = Invoice::scope($publicId)->firstOrFail();
} }
$invoice->fill($data);
if ((isset($data['set_default_terms']) && $data['set_default_terms']) if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) { || (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) { if (isset($data['set_default_terms']) && $data['set_default_terms']) {
@ -292,12 +299,10 @@ class InvoiceRepository extends BaseRepository
$invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id; $invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id;
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) { // provide backwards compatability
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']); if (isset($data['tax_name']) && isset($data['tax_rate'])) {
$invoice->tax_name = trim($data['tax_name']); $data['tax_name1'] = $data['tax_name'];
} else { $data['tax_rate1'] = $data['tax_rate'];
$invoice->tax_rate = 0;
$invoice->tax_name = '';
} }
$total = 0; $total = 0;
@ -318,10 +323,8 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2); $invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2); $invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
$lineTotal = $invoiceItemCost * $invoiceItemQty; $lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) { if ($invoice->discount > 0) {
@ -332,6 +335,12 @@ class InvoiceRepository extends BaseRepository
} }
} }
if (isset($item['tax_rate1']) && Utils::parseFloat($item['tax_rate1']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']);
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
}
if (isset($item['tax_rate2']) && Utils::parseFloat($item['tax_rate2']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']);
$itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2); $itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2);
} }
} }
@ -373,8 +382,9 @@ class InvoiceRepository extends BaseRepository
$total += $invoice->custom_value2; $total += $invoice->custom_value2;
} }
$total += $total * $invoice->tax_rate / 100; $taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2);
$total = round($total, 2); $taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2);
$total = round($total + $taxAmount1 + $taxAmount2, 2);
$total += $itemTax; $total += $itemTax;
// custom fields not charged taxes // custom fields not charged taxes
@ -398,6 +408,53 @@ class InvoiceRepository extends BaseRepository
$invoice->invoice_items()->forceDelete(); $invoice->invoice_items()->forceDelete();
} }
$document_ids = !empty($data['document_ids'])?array_map('intval', $data['document_ids']):array();;
foreach ($document_ids as $document_id){
$document = Document::scope($document_id)->first();
if($document && !$checkSubPermissions || $document->canEdit()){
if($document->invoice_id && $document->invoice_id != $invoice->id){
// From a clone
$document = $document->cloneDocument();
$document_ids[] = $document->public_id;// Don't remove this document
}
$document->invoice_id = $invoice->id;
$document->expense_id = null;
$document->save();
}
}
if(!empty($data['documents']) && Document::canCreate()){
// Fallback upload
$doc_errors = array();
foreach($data['documents'] as $upload){
$result = $this->documentRepo->upload($upload);
if(is_string($result)){
$doc_errors[] = $result;
}
else{
$result->invoice_id = $invoice->id;
$result->save();
$document_ids[] = $result->public_id;
}
}
if(!empty($doc_errors)){
Session::flash('error', implode('<br>',array_map('htmlentities',$doc_errors)));
}
}
foreach ($invoice->documents as $document){
if(!in_array($document->public_id, $document_ids)){
// Removed
// Not checking permissions; deleting a document is just editing the invoice
if($document->invoice_id == $invoice->id){
// Make sure the document isn't on a clone
$document->delete();
}
}
}
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) { if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) {
@ -450,7 +507,7 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes'])); $invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']); $invoiceItem->cost = Utils::parseFloat($item['cost']);
$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'])) { if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1']; $invoiceItem->custom_value1 = $item['custom_value1'];
@ -459,11 +516,14 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->custom_value2 = $item['custom_value2']; $invoiceItem->custom_value2 = $item['custom_value2'];
} }
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) { // provide backwards compatability
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']); if (isset($item['tax_name']) && isset($item['tax_rate'])) {
$invoiceItem['tax_name'] = trim($item['tax_name']); $item['tax_name1'] = $item['tax_name'];
$item['tax_rate1'] = $item['tax_rate'];
} }
$invoiceItem->fill($item);
$invoice->invoice_items()->save($invoiceItem); $invoice->invoice_items()->save($invoiceItem);
} }
@ -494,14 +554,13 @@ class InvoiceRepository extends BaseRepository
} }
} }
$clone->invoice_number = $invoiceNumber ?: $account->getNextInvoiceNumber($clone); $clone->invoice_number = $invoiceNumber ?: $account->getNextInvoiceNumber($clone);
$clone->invoice_date = date_create()->format('Y-m-d');
foreach ([ foreach ([
'client_id', 'client_id',
'discount', 'discount',
'is_amount_discount', 'is_amount_discount',
'invoice_date',
'po_number', 'po_number',
'due_date',
'is_recurring', 'is_recurring',
'frequency_id', 'frequency_id',
'start_date', 'start_date',
@ -510,8 +569,10 @@ class InvoiceRepository extends BaseRepository
'invoice_footer', 'invoice_footer',
'public_notes', 'public_notes',
'invoice_design_id', 'invoice_design_id',
'tax_name', 'tax_name1',
'tax_rate', 'tax_rate1',
'tax_name2',
'tax_rate2',
'amount', 'amount',
'is_quote', 'is_quote',
'custom_value1', 'custom_value1',
@ -545,14 +606,22 @@ class InvoiceRepository extends BaseRepository
'notes', 'notes',
'cost', 'cost',
'qty', 'qty',
'tax_name', 'tax_name1',
'tax_rate', ] as $field) { 'tax_rate1',
'tax_name2',
'tax_rate2',
] as $field) {
$cloneItem->$field = $item->$field; $cloneItem->$field = $item->$field;
} }
$clone->invoice_items()->save($cloneItem); $clone->invoice_items()->save($cloneItem);
} }
foreach ($invoice->documents as $document) {
$cloneDocument = $document->cloneDocument();
$invoice->documents()->save($cloneDocument);
}
foreach ($invoice->invitations as $invitation) { foreach ($invoice->invitations as $invitation) {
$cloneInvitation = Invitation::createNew($invoice); $cloneInvitation = Invitation::createNew($invoice);
$cloneInvitation->contact_id = $invitation->contact_id; $cloneInvitation->contact_id = $invitation->contact_id;
@ -581,7 +650,7 @@ class InvoiceRepository extends BaseRepository
return false; return false;
} }
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country'); $invoice->load('user', 'invoice_items', 'documents', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
$client = $invoice->client; $client = $invoice->client;
if (!$client || $client->is_deleted) { if (!$client || $client->is_deleted) {
@ -632,8 +701,10 @@ class InvoiceRepository extends BaseRepository
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes); $invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms); $invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer); $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name; $invoice->tax_name1 = $recurInvoice->tax_name1;
$invoice->tax_rate = $recurInvoice->tax_rate; $invoice->tax_rate1 = $recurInvoice->tax_rate1;
$invoice->tax_name2 = $recurInvoice->tax_name2;
$invoice->tax_rate2 = $recurInvoice->tax_rate2;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id; $invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1 ?: 0; $invoice->custom_value1 = $recurInvoice->custom_value1 ?: 0;
$invoice->custom_value2 = $recurInvoice->custom_value2 ?: 0; $invoice->custom_value2 = $recurInvoice->custom_value2 ?: 0;
@ -652,11 +723,18 @@ class InvoiceRepository extends BaseRepository
$item->cost = $recurItem->cost; $item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes); $item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key); $item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name; $item->tax_name1 = $recurItem->tax_name1;
$item->tax_rate = $recurItem->tax_rate; $item->tax_rate1 = $recurItem->tax_rate1;
$item->tax_name2 = $recurItem->tax_name2;
$item->tax_rate2 = $recurItem->tax_rate2;
$invoice->invoice_items()->save($item); $invoice->invoice_items()->save($item);
} }
foreach ($recurInvoice->documents as $recurDocument) {
$document = $recurDocument->cloneDocument();
$invoice->documents()->save($document);
}
foreach ($recurInvoice->invitations as $recurInvitation) { foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation); $invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id; $invitation->contact_id = $recurInvitation->contact_id;

View File

@ -19,8 +19,10 @@ class InvoiceItemTransformer extends EntityTransformer
'notes' => $item->notes, 'notes' => $item->notes,
'cost' => (float) $item->cost, 'cost' => (float) $item->cost,
'qty' => (float) $item->qty, 'qty' => (float) $item->qty,
'tax_name' => $item->tax_name, 'tax_name1' => $item->tax_name1 ? $item->tax_name1 : '',
'tax_rate' => (float) $item->tax_rate 'tax_rate1' => (float) $item->tax_rate1,
'tax_name2' => $item->tax_name2 ? $item->tax_name1 : '',
'tax_rate2' => (float) $item->tax_rate2,
]; ];
} }
} }

View File

@ -87,8 +87,10 @@ class InvoiceTransformer extends EntityTransformer
'end_date' => $invoice->end_date, 'end_date' => $invoice->end_date,
'last_sent_date' => $invoice->last_sent_date, 'last_sent_date' => $invoice->last_sent_date,
'recurring_invoice_id' => (int) $invoice->recurring_invoice_id, 'recurring_invoice_id' => (int) $invoice->recurring_invoice_id,
'tax_name' => $invoice->tax_name, 'tax_name1' => $invoice->tax_name1 ? $invoice->tax_name1 : '',
'tax_rate' => (float) $invoice->tax_rate, 'tax_rate1' => (float) $invoice->tax_rate1,
'tax_name2' => $invoice->tax_name2 ? $invoice->tax_name2 : '',
'tax_rate2' => (float) $invoice->tax_rate2,
'amount' => (float) $invoice->amount, 'amount' => (float) $invoice->amount,
'balance' => (float) $invoice->balance, 'balance' => (float) $invoice->balance,
'is_amount_discount' => (bool) $invoice->is_amount_discount, 'is_amount_discount' => (bool) $invoice->is_amount_discount,

View File

@ -22,8 +22,15 @@ class AppServiceProvider extends ServiceProvider {
*/ */
public function boot() public function boot()
{ {
Form::macro('image_data', function($imagePath) { Form::macro('image_data', function($image, $contents = false) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath)); if(!$contents){
$contents = file_get_contents($image);
}
else{
$contents = $image;
}
return 'data:image/jpeg;base64,' . base64_encode($contents);
}); });
Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
@ -152,6 +159,13 @@ class AppServiceProvider extends ServiceProvider {
return $str . '</ol>'; return $str . '</ol>';
}); });
Form::macro('human_filesize', function($bytes, $decimals = 1) {
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
$factor = floor((strlen($bytes) - 1) / 3);
if($factor == 0)$decimals=0;// There aren't fractional bytes
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
});
Validator::extend('positive', function($attribute, $value, $parameters) { Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0; return Utils::parseFloat($value) >= 0;
}); });

View File

@ -28,7 +28,7 @@ class ExpenseService extends BaseService
return $this->expenseRepo; return $this->expenseRepo;
} }
public function save($data) public function save($data, $checkSubPermissions=false)
{ {
if (isset($data['client_id']) && $data['client_id']) { if (isset($data['client_id']) && $data['client_id']) {
$data['client_id'] = Client::getPrivateId($data['client_id']); $data['client_id'] = Client::getPrivateId($data['client_id']);
@ -38,7 +38,7 @@ class ExpenseService extends BaseService
$data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']); $data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']);
} }
return $this->expenseRepo->save($data); return $this->expenseRepo->save($data, $checkSubPermissions);
} }
public function getDatatable($search) public function getDatatable($search)

View File

@ -74,6 +74,7 @@ class PaymentService extends BaseService
if ($input) { if ($input) {
$data = self::convertInputForOmnipay($input); $data = self::convertInputForOmnipay($input);
$data['email'] = $invitation->contact->email;
Session::put($key, $data); Session::put($key, $data);
} elseif (Session::get($key)) { } elseif (Session::get($key)) {
$data = Session::get($key); $data = Session::get($key);

View File

@ -58,4 +58,11 @@ if (strstr($_SERVER['HTTP_USER_AGENT'], 'PhantomJS') && Utils::isNinjaDev()) {
} }
*/ */
// Write info messages to a separate file
$app->configureMonologUsing(function($monolog) {
$monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-info.log', Monolog\Logger::INFO, false));
$monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-warning.log', Monolog\Logger::WARNING, false));
$monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-error.log', Monolog\Logger::ERROR, false));
});
return $app; return $app;

View File

@ -26,7 +26,8 @@
"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" "fuse.js": "~2.0.2",
"dropzone": "~4.3.0"
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

View File

@ -1,7 +1,10 @@
{ {
"name": "hillelcoren/invoice-ninja", "name": "hillelcoren/invoice-ninja",
"description": "An open-source invoicing site built with Laravel", "description": "An open-source invoicing site built with Laravel",
"keywords": ["invoice", "laravel"], "keywords": [
"invoice",
"laravel"
],
"license": "Attribution Assurance License", "license": "Attribution Assurance License",
"authors": [ "authors": [
{ {
@ -15,7 +18,7 @@
"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.2.22", "laravel/framework": "5.2.*",
"laravelcollective/html": "5.2.*", "laravelcollective/html": "5.2.*",
"laravelcollective/bus": "5.2.*", "laravelcollective/bus": "5.2.*",
"symfony/css-selector": "~3.0", "symfony/css-selector": "~3.0",
@ -38,7 +41,7 @@
"alfaproject/omnipay-skrill": "dev-master", "alfaproject/omnipay-skrill": "dev-master",
"omnipay/bitpay": "dev-master", "omnipay/bitpay": "dev-master",
"guzzlehttp/guzzle": "~6.0", "guzzlehttp/guzzle": "~6.0",
"wildbit/laravel-postmark-provider": "2.0", "wildbit/laravel-postmark-provider": "3.0",
"Dwolla/omnipay-dwolla": "dev-master", "Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0", "laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master", "simshaun/recurr": "dev-master",
@ -66,7 +69,10 @@
"maatwebsite/excel": "~2.0", "maatwebsite/excel": "~2.0",
"ezyang/htmlpurifier": "~v4.7", "ezyang/htmlpurifier": "~v4.7",
"cerdic/css-tidy": "~v1.5", "cerdic/css-tidy": "~v1.5",
"asgrim/ofxparser": "^1.1" "asgrim/ofxparser": "^1.1",
"league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-rackspace": "~1.0",
"barracudanetworks/archivestream-php": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",

457
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "a33dce96f4ded3fb269a6d9dcbf24b27", "hash": "2ab7ab9013e31d8a2f0dcf43b31beefa",
"content-hash": "f73a83c64422ef3560da4adb988850ae", "content-hash": "188fba7fcc31b702098d5417bc0e63e2",
"packages": [ "packages": [
{ {
"name": "agmscode/omnipay-agms", "name": "agmscode/omnipay-agms",
@ -123,12 +123,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/formers/former.git", "url": "https://github.com/formers/former.git",
"reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683" "reference": "d97f907741323b390f43954a90a227921ecc6b96"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/formers/former/zipball/e196c4336db77be97131f6a3b3c3b69b3a22b683", "url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96",
"reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683", "reference": "d97f907741323b390f43954a90a227921ecc6b96",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -174,7 +174,7 @@
"foundation", "foundation",
"laravel" "laravel"
], ],
"time": "2016-03-02 17:21:21" "time": "2016-03-16 01:43:45"
}, },
{ {
"name": "anahkiasen/html-object", "name": "anahkiasen/html-object",
@ -321,6 +321,126 @@
], ],
"time": "2015-12-11 11:08:57" "time": "2015-12-11 11:08:57"
}, },
{
"name": "aws/aws-sdk-php",
"version": "3.17.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "f8c0cc9357e10896a5c57104f2c79d1b727d97d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f8c0cc9357e10896a5c57104f2c79d1b727d97d0",
"reference": "f8c0cc9357e10896a5c57104f2c79d1b727d97d0",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1",
"guzzlehttp/promises": "~1.0",
"guzzlehttp/psr7": "~1.0",
"mtdowling/jmespath.php": "~2.2",
"php": ">=5.5"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-json": "*",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"ext-spl": "*",
"nette/neon": "^2.3",
"phpunit/phpunit": "~4.0|~5.0",
"psr/cache": "^1.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
"doctrine/cache": "To use the DoctrineCacheAdapter",
"ext-curl": "To send requests using cURL",
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Aws\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"keywords": [
"amazon",
"aws",
"cloud",
"dynamodb",
"ec2",
"glacier",
"s3",
"sdk"
],
"time": "2016-03-22 19:19:22"
},
{
"name": "barracudanetworks/archivestream-php",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/barracudanetworks/ArchiveStream-php.git",
"reference": "9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barracudanetworks/ArchiveStream-php/zipball/9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6",
"reference": "9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6",
"shasum": ""
},
"require": {
"ext-gmp": "*",
"ext-mbstring": "*",
"php": ">=5.1.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Barracuda\\ArchiveStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A library for dynamically streaming dynamic tar or zip files without the need to have the complete file stored on the server.",
"homepage": "https://github.com/barracudanetworks/ArchiveStream-php",
"keywords": [
"archive",
"php",
"stream",
"tar",
"zip"
],
"time": "2016-01-07 06:02:26"
},
{ {
"name": "barryvdh/laravel-debugbar", "name": "barryvdh/laravel-debugbar",
"version": "v2.2.0", "version": "v2.2.0",
@ -880,12 +1000,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/delatbabel/omnipay-fatzebra.git", "url": "https://github.com/delatbabel/omnipay-fatzebra.git",
"reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc" "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/d0a56a8704357d91457672741a48a4cb6c7ecd53",
"reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -929,7 +1049,7 @@
"payment", "payment",
"paystream" "paystream"
], ],
"time": "2015-02-15 11:27:23" "time": "2016-03-21 09:21:14"
}, },
{ {
"name": "dercoder/omnipay-ecopayz", "name": "dercoder/omnipay-ecopayz",
@ -1039,12 +1159,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/descubraomundo/omnipay-pagarme.git", "url": "https://github.com/descubraomundo/omnipay-pagarme.git",
"reference": "528953568929b57189de16fa7431eaab75d61840" "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/528953568929b57189de16fa7431eaab75d61840", "url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/8571396139eb1fb1a7011450714a5e8d8d604d8c",
"reference": "528953568929b57189de16fa7431eaab75d61840", "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1081,7 +1201,7 @@
"pay", "pay",
"payment" "payment"
], ],
"time": "2015-10-27 19:17:20" "time": "2016-03-18 19:37:37"
}, },
{ {
"name": "dioscouri/omnipay-cybersource", "name": "dioscouri/omnipay-cybersource",
@ -1938,16 +2058,16 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "6.1.1", "version": "6.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" "reference": "d094e337976dff9d8e2424e8485872194e768662"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", "reference": "d094e337976dff9d8e2424e8485872194e768662",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1963,7 +2083,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "6.1-dev" "dev-master": "6.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -1996,7 +2116,7 @@
"rest", "rest",
"web service" "web service"
], ],
"time": "2015-11-23 00:47:50" "time": "2016-03-21 20:02:09"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
@ -2603,12 +2723,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/labs7in0/omnipay-wechat.git", "url": "https://github.com/labs7in0/omnipay-wechat.git",
"reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a" "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e",
"reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2644,7 +2764,7 @@
"purchase", "purchase",
"wechat" "wechat"
], ],
"time": "2015-11-16 11:04:21" "time": "2016-03-18 09:59:11"
}, },
{ {
"name": "laracasts/presenter", "name": "laracasts/presenter",
@ -2694,16 +2814,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v5.2.22", "version": "v5.2.24",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4" "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/aec1b7cb9ec0bac0107361a3730cac9b6f945ef4", "url": "https://api.github.com/repos/laravel/framework/zipball/396297a5fd3c70c2fc1af68f09ee574a2380175c",
"reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4", "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2716,7 +2836,7 @@
"monolog/monolog": "~1.11", "monolog/monolog": "~1.11",
"mtdowling/cron-expression": "~1.0", "mtdowling/cron-expression": "~1.0",
"nesbot/carbon": "~1.20", "nesbot/carbon": "~1.20",
"paragonie/random_compat": "~1.1", "paragonie/random_compat": "~1.4",
"php": ">=5.5.9", "php": ">=5.5.9",
"psy/psysh": "0.7.*", "psy/psysh": "0.7.*",
"swiftmailer/swiftmailer": "~5.1", "swiftmailer/swiftmailer": "~5.1",
@ -2818,7 +2938,7 @@
"framework", "framework",
"laravel" "laravel"
], ],
"time": "2016-02-27 22:09:19" "time": "2016-03-22 13:45:19"
}, },
{ {
"name": "laravel/socialite", "name": "laravel/socialite",
@ -3056,6 +3176,100 @@
], ],
"time": "2016-03-14 21:54:11" "time": "2016-03-14 21:54:11"
}, },
{
"name": "league/flysystem-aws-s3-v3",
"version": "1.0.9",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
"reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/595e24678bf78f8107ebc9355d8376ae0eb712c6",
"reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6",
"shasum": ""
},
"require": {
"aws/aws-sdk-php": "^3.0.0",
"league/flysystem": "~1.0",
"php": ">=5.5.0"
},
"require-dev": {
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
"phpspec/phpspec": "^2.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\AwsS3v3\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
}
],
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
"time": "2015-11-19 08:44:16"
},
{
"name": "league/flysystem-rackspace",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-rackspace.git",
"reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-rackspace/zipball/ba877e837f5dce60e78a0555de37eb9bfc7dd6b9",
"reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9",
"shasum": ""
},
"require": {
"league/flysystem": "~1.0",
"php": ">=5.4.0",
"rackspace/php-opencloud": "~1.16"
},
"require-dev": {
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\Rackspace\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
}
],
"description": "Flysystem adapter for Rackspace",
"time": "2016-03-11 12:13:42"
},
{ {
"name": "league/fractal", "name": "league/fractal",
"version": "0.13.0", "version": "0.13.0",
@ -3585,6 +3799,33 @@
], ],
"time": "2015-07-14 19:53:54" "time": "2015-07-14 19:53:54"
}, },
{
"name": "mikemccabe/json-patch-php",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/mikemccabe/json-patch-php.git",
"reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikemccabe/json-patch-php/zipball/b3af30a6aec7f6467c773cd49b2d974a70f7c0d4",
"reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"mikemccabe\\JsonPatch\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"description": "Produce and apply json-patch objects",
"time": "2015-01-05 21:19:54"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "1.18.1", "version": "1.18.1",
@ -3707,6 +3948,61 @@
], ],
"time": "2016-01-26 21:23:30" "time": "2016-01-26 21:23:30"
}, },
{
"name": "mtdowling/jmespath.php",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
"reference": "192f93e43c2c97acde7694993ab171b3de284093"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093",
"reference": "192f93e43c2c97acde7694993ab171b3de284093",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"bin": [
"bin/jp.php"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"JmesPath\\": "src/"
},
"files": [
"src/JmesPath.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Declaratively specify how to extract elements from a JSON document",
"keywords": [
"json",
"jsonpath"
],
"time": "2016-01-05 18:25:05"
},
{ {
"name": "nesbot/carbon", "name": "nesbot/carbon",
"version": "1.21.0", "version": "1.21.0",
@ -3815,7 +4111,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/31394ce58d5999b6f49b321cb3547747837c1297", "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/b27d2823d052f5c227eeb29324bb564cfdb8f9af",
"reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"shasum": "" "shasum": ""
}, },
@ -4328,16 +4624,16 @@
}, },
{ {
"name": "omnipay/eway", "name": "omnipay/eway",
"version": "v2.2.0", "version": "v2.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/omnipay-eway.git", "url": "https://github.com/thephpleague/omnipay-eway.git",
"reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16" "reference": "1c953630f7097bfdeed200b17a847015a4df5607"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/0dcf28596f0382fbfc3ee229e98e60798675ed16", "url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/1c953630f7097bfdeed200b17a847015a4df5607",
"reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16", "reference": "1c953630f7097bfdeed200b17a847015a4df5607",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4381,7 +4677,7 @@
"pay", "pay",
"payment" "payment"
], ],
"time": "2015-03-30 00:28:33" "time": "2016-03-22 01:11:02"
}, },
{ {
"name": "omnipay/firstdata", "name": "omnipay/firstdata",
@ -5536,16 +5832,16 @@
}, },
{ {
"name": "paragonie/random_compat", "name": "paragonie/random_compat",
"version": "v1.2.2", "version": "v1.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/paragonie/random_compat.git", "url": "https://github.com/paragonie/random_compat.git",
"reference": "b3313b618f4edd76523572531d5d7e22fe747430" "reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/b3313b618f4edd76523572531d5d7e22fe747430", "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
"reference": "b3313b618f4edd76523572531d5d7e22fe747430", "reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5580,7 +5876,7 @@
"pseudorandom", "pseudorandom",
"random" "random"
], ],
"time": "2016-03-11 19:54:08" "time": "2016-03-18 20:34:03"
}, },
{ {
"name": "patricktalmadge/bootstrapper", "name": "patricktalmadge/bootstrapper",
@ -5906,6 +6202,63 @@
], ],
"time": "2016-03-09 05:03:14" "time": "2016-03-09 05:03:14"
}, },
{
"name": "rackspace/php-opencloud",
"version": "v1.16.0",
"source": {
"type": "git",
"url": "https://github.com/rackspace/php-opencloud.git",
"reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rackspace/php-opencloud/zipball/d6b71feed7f9e7a4b52e0240a79f06473ba69c8c",
"reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c",
"shasum": ""
},
"require": {
"guzzle/guzzle": "~3.8",
"mikemccabe/json-patch-php": "~0.1",
"php": ">=5.4",
"psr/log": "~1.0"
},
"require-dev": {
"apigen/apigen": "~4.0",
"fabpot/php-cs-fixer": "1.0.*@dev",
"jakub-onderka/php-parallel-lint": "0.*",
"phpspec/prophecy": "~1.4",
"phpunit/phpunit": "4.3.*",
"satooshi/php-coveralls": "0.6.*@dev"
},
"type": "library",
"autoload": {
"psr-0": {
"OpenCloud": [
"lib/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Jamie Hannaford",
"email": "jamie.hannaford@rackspace.com",
"homepage": "https://github.com/jamiehannaford"
}
],
"description": "PHP SDK for Rackspace/OpenStack APIs",
"keywords": [
"Openstack",
"nova",
"opencloud",
"rackspace",
"swift"
],
"time": "2016-01-29 10:34:57"
},
{ {
"name": "samvaughton/omnipay-barclays-epdq", "name": "samvaughton/omnipay-barclays-epdq",
"version": "2.2.0", "version": "2.2.0",
@ -7585,20 +7938,20 @@
}, },
{ {
"name": "wildbit/laravel-postmark-provider", "name": "wildbit/laravel-postmark-provider",
"version": "2.0.0", "version": "3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/wildbit/laravel-postmark-provider.git", "url": "https://github.com/wildbit/laravel-postmark-provider.git",
"reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5" "reference": "b80815602f618abe24030ea6d3f117da49a72885"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/79a7e8bde66b2bd6f314829b00ee08616847ebc5", "url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/b80815602f618abe24030ea6d3f117da49a72885",
"reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5", "reference": "b80815602f618abe24030ea6d3f117da49a72885",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/mail": "~5.0", "illuminate/mail": "~5.2",
"wildbit/swiftmailer-postmark": "~2.0" "wildbit/swiftmailer-postmark": "~2.0"
}, },
"type": "library", "type": "library",
@ -7612,7 +7965,7 @@
"MIT" "MIT"
], ],
"description": "An officially supported mail provider to send mail from Laravel through Postmark, see instructions for integrating it here: https://github.com/wildbit/laravel-postmark-provider/blob/master/README.md", "description": "An officially supported mail provider to send mail from Laravel through Postmark, see instructions for integrating it here: https://github.com/wildbit/laravel-postmark-provider/blob/master/README.md",
"time": "2015-11-10 14:43:06" "time": "2016-02-10 14:15:58"
}, },
{ {
"name": "wildbit/swiftmailer-postmark", "name": "wildbit/swiftmailer-postmark",
@ -8475,16 +8828,16 @@
}, },
{ {
"name": "phpspec/phpspec", "name": "phpspec/phpspec",
"version": "2.4.1", "version": "2.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/phpspec.git", "url": "https://github.com/phpspec/phpspec.git",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" "reference": "385ecb015e97c13818074f1517928b24d4a26067"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", "reference": "385ecb015e97c13818074f1517928b24d4a26067",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8549,7 +8902,7 @@
"testing", "testing",
"tests" "tests"
], ],
"time": "2016-01-01 10:17:54" "time": "2016-03-20 20:34:32"
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",

View File

@ -48,22 +48,32 @@ return [
'root' => storage_path().'/app', 'root' => storage_path().'/app',
], ],
'logos' => [
'driver' => 'local',
'root' => env('LOGO_PATH', public_path().'/logo'),
],
'documents' => [
'driver' => 'local',
'root' => storage_path().'/documents',
],
's3' => [ 's3' => [
'driver' => 's3', 'driver' => 's3',
'key' => 'your-key', 'key' => env('S3_KEY', ''),
'secret' => 'your-secret', 'secret' => env('S3_SECRET', ''),
'region' => 'your-region', 'region' => env('S3_REGION', 'us-east-1'),
'bucket' => 'your-bucket', 'bucket' => env('S3_BUCKET', ''),
], ],
'rackspace' => [ 'rackspace' => [
'driver' => 'rackspace', 'driver' => 'rackspace',
'username' => 'your-username', 'username' => env('RACKSPACE_USERNAME', ''),
'key' => 'your-key', 'key' => env('RACKSPACE_KEY', ''),
'container' => 'your-container', 'container' => env('RACKSPACE_CONTAINER', ''),
'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', 'endpoint' => env('RACKSPACE_ENDPOINT', 'https://identity.api.rackspacecloud.com/v2.0/'),
'region' => 'IAD', 'region' => env('RACKSPACE_REGION', 'IAD'),
'url_type' => 'publicURL' 'url_type' => env('RACKSPACE_URL_TYPE', 'publicURL')
], ],
], ],

View File

@ -21,6 +21,13 @@ return array(
*/ */
'doc-route' => 'docs', 'doc-route' => 'docs',
/*
|--------------------------------------------------------------------------
| Relative path to access swagger ui.
|--------------------------------------------------------------------------
*/
'api-docs-route' => 'api-docs',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Absolute path to directory containing the swagger annotations are stored. | Absolute path to directory containing the swagger annotations are stored.

View File

@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddDocuments extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table) {
$table->string('logo')->nullable()->default(null);
$table->unsignedInteger('logo_width');
$table->unsignedInteger('logo_height');
$table->unsignedInteger('logo_size');
$table->boolean('invoice_embed_documents')->default(1);
$table->boolean('document_email_attachment')->default(1);
});
DB::table('accounts')->update(array('logo' => ''));
Schema::dropIfExists('documents');
Schema::create('documents', function($t)
{
$t->increments('id');
$t->unsignedInteger('public_id')->nullable();
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('invoice_id')->nullable();
$t->unsignedInteger('expense_id')->nullable();
$t->string('path');
$t->string('preview');
$t->string('name');
$t->string('type');
$t->string('disk');
$t->string('hash', 40);
$t->unsignedInteger('size');
$t->unsignedInteger('width')->nullable();
$t->unsignedInteger('height')->nullable();
$t->timestamps();
$t->foreign('account_id')->references('id')->on('accounts');
$t->foreign('user_id')->references('id')->on('users');
$t->foreign('invoice_id')->references('id')->on('invoices');
$t->foreign('expense_id')->references('id')->on('expenses');
$t->unique( array('account_id','public_id') );
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table) {
$table->dropColumn('logo');
$table->dropColumn('logo_width');
$table->dropColumn('logo_height');
$table->dropColumn('logo_size');
$table->dropColumn('invoice_embed_documents');
});
Schema::dropIfExists('documents');
}
}

View File

@ -0,0 +1,68 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SupportMultipleTaxRates extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function($table) {
$table->decimal('tax_rate', 13, 3)->change();
});
Schema::table('invoice_items', function($table) {
$table->decimal('tax_rate', 13, 3)->change();
});
Schema::table('invoices', function($table) {
$table->renameColumn('tax_rate', 'tax_rate1');
$table->renameColumn('tax_name', 'tax_name1');
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3);
});
Schema::table('invoice_items', function($table) {
$table->renameColumn('tax_rate', 'tax_rate1');
$table->renameColumn('tax_name', 'tax_name1');
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3);
});
Schema::table('accounts', function($table) {
$table->boolean('enable_client_portal_dashboard')->default(true);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function($table) {
$table->decimal('tax_rate1', 13, 2)->change();
$table->renameColumn('tax_rate1', 'tax_rate');
$table->renameColumn('tax_name1', 'tax_name');
$table->dropColumn('tax_name2');
$table->dropColumn('tax_rate2');
});
Schema::table('invoice_items', function($table) {
$table->decimal('tax_rate1', 13, 2)->change();
$table->renameColumn('tax_rate1', 'tax_rate');
$table->renameColumn('tax_name1', 'tax_name');
$table->dropColumn('tax_name2');
$table->dropColumn('tax_rate2');
});
Schema::table('accounts', function($table) {
$table->dropColumn('enable_client_portal_dashboard');
});
}
}

View File

@ -55,6 +55,7 @@ class CurrenciesSeeder extends Seeder
['name' => 'Saudi Riyal', 'code' => 'SAR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Saudi Riyal', 'code' => 'SAR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Costa Rican Colón', 'code' => 'CRC', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
]; ];
foreach ($currencies as $currency) { foreach ($currencies as $currency) {

View File

@ -0,0 +1,23 @@
<?php
class UpdateSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->command->info('Running UpdateSeeder...');
$this->call('PaymentLibrariesSeeder');
$this->call('FontsSeeder');
$this->call('BanksSeeder');
$this->call('InvoiceStatusSeeder');
$this->call('CurrenciesSeeder');
$this->call('DateFormatsSeeder');
$this->call('InvoiceDesignsSeeder');
$this->call('PaymentTermsSeeder');
}
}

View File

@ -14,6 +14,6 @@
RewriteRule ^ index.php [L] RewriteRule ^ index.php [L]
# In case of running InvoiceNinja in a Subdomain like invoiceninja.example.com, # In case of running InvoiceNinja in a Subdomain like invoiceninja.example.com,
# you have to enablel the following line: # you have to enable the following line:
# RewriteBase / # RewriteBase /
</IfModule> </IfModule>

File diff suppressed because one or more lines are too long

56
public/css/built.css vendored

File diff suppressed because one or more lines are too long

View File

@ -790,14 +790,24 @@ html {
overflow-y: scroll; overflow-y: scroll;
} }
@media screen and (min-width: 700px) {
.navbar-header { .navbar-header {
padding-top: 16px; padding-top: 4px;
padding-bottom: 16px; padding-bottom: 4px;
} }
.navbar li a { .navbar li a {
padding: 31px 20px 31px 20px; padding-top: 18px;
} font-weight: 500;
font-size: 15px;
font-weight: bold;
padding-left: 20px;
padding-right: 20px;
}
.navbar {
x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
} }
#footer { #footer {

View File

@ -7,14 +7,24 @@ html {
overflow-y: scroll; overflow-y: scroll;
} }
@media screen and (min-width: 700px) {
.navbar-header { .navbar-header {
padding-top: 16px; padding-top: 4px;
padding-bottom: 16px; padding-bottom: 4px;
} }
.navbar li a { .navbar li a {
padding: 31px 20px 31px 20px; padding-top: 18px;
} font-weight: 500;
font-size: 15px;
font-weight: bold;
padding-left: 20px;
padding-right: 20px;
}
.navbar {
x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
} }
#footer { #footer {

54
public/css/style.css vendored
View File

@ -402,6 +402,21 @@ font-weight: bold;
filter: none; filter: none;
} }
.navbar,
ul.dropdown-menu,
.twitter-typeahead .tt-menu {
x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
}
.panel-default,
canvas {
border: 1px solid;
border-color: #e5e6e9 #dfe0e4 #d0d1d5;
border-radius: 3px;
}
.navbar .active > a { .navbar .active > a {
background-color: #09334f !important; background-color: #09334f !important;
background-image: none; background-image: none;
@ -1052,3 +1067,42 @@ td.right {
div.panel-body div.panel-body { div.panel-body div.panel-body {
padding-bottom: 0px; padding-bottom: 0px;
} }
/* Attached Documents */
#document-upload {
border:1px solid #ebe7e7;
background:#f9f9f9 !important;
border-radius:3px;
padding:20px;
}
.invoice-table #document-upload{
width:500px;
}
#document-upload .dropzone{
background:none;
border:none;
padding:0;
}
.dropzone .dz-preview.dz-image-preview{
background:none;
}
.dropzone .dz-preview .dz-image{
border-radius:5px!important;
}
.dropzone .dz-preview.dz-image-preview .dz-image img{
object-fit: cover;
width: 100%;
height: 100%;
}
.dropzone .fallback-doc{
display:none;
}
.dropzone.dz-browser-not-supported .fallback-doc{
display:block;
}

View File

@ -1,5 +1,3 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2012 Mozilla Foundation /* Copyright 2012 Mozilla Foundation
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -25,9 +23,10 @@ if (typeof PDFJS === 'undefined') {
} }
// Checking if the typed arrays are supported // Checking if the typed arrays are supported
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
(function checkTypedArrayCompatibility() { (function checkTypedArrayCompatibility() {
if (typeof Uint8Array !== 'undefined') { if (typeof Uint8Array !== 'undefined') {
// some mobile versions do not support subarray (e.g. safari 5 / iOS) // Support: iOS<6.0
if (typeof Uint8Array.prototype.subarray === 'undefined') { if (typeof Uint8Array.prototype.subarray === 'undefined') {
Uint8Array.prototype.subarray = function subarray(start, end) { Uint8Array.prototype.subarray = function subarray(start, end) {
return new Uint8Array(this.slice(start, end)); return new Uint8Array(this.slice(start, end));
@ -37,10 +36,10 @@ if (typeof PDFJS === 'undefined') {
}; };
} }
// some mobile version might not support Float64Array // Support: Android<4.1
if (typeof Float64Array === 'undefined') if (typeof Float64Array === 'undefined') {
window.Float64Array = Float32Array; window.Float64Array = Float32Array;
}
return; return;
} }
@ -49,23 +48,26 @@ if (typeof PDFJS === 'undefined') {
} }
function setArrayOffset(array, offset) { function setArrayOffset(array, offset) {
if (arguments.length < 2) if (arguments.length < 2) {
offset = 0; offset = 0;
for (var i = 0, n = array.length; i < n; ++i, ++offset) }
for (var i = 0, n = array.length; i < n; ++i, ++offset) {
this[offset] = array[i] & 0xFF; this[offset] = array[i] & 0xFF;
} }
}
function TypedArray(arg1) { function TypedArray(arg1) {
var result; var result, i, n;
if (typeof arg1 === 'number') { if (typeof arg1 === 'number') {
result = []; result = [];
for (var i = 0; i < arg1; ++i) for (i = 0; i < arg1; ++i) {
result[i] = 0; result[i] = 0;
}
} else if ('slice' in arg1) { } else if ('slice' in arg1) {
result = arg1.slice(0); result = arg1.slice(0);
} else { } else {
result = []; result = [];
for (var i = 0, n = arg1.length; i < n; ++i) { for (i = 0, n = arg1.length; i < n; ++i) {
result[i] = arg1[i]; result[i] = arg1[i];
} }
} }
@ -75,13 +77,14 @@ if (typeof PDFJS === 'undefined') {
result.byteLength = result.length; result.byteLength = result.length;
result.set = setArrayOffset; result.set = setArrayOffset;
if (typeof arg1 === 'object' && arg1.buffer) if (typeof arg1 === 'object' && arg1.buffer) {
result.buffer = arg1.buffer; result.buffer = arg1.buffer;
}
return result; return result;
} }
window.Uint8Array = TypedArray; window.Uint8Array = TypedArray;
window.Int8Array = TypedArray;
// we don't need support for set, byteLength for 32-bit array // we don't need support for set, byteLength for 32-bit array
// so we can use the TypedArray as well // so we can use the TypedArray as well
@ -93,25 +96,15 @@ if (typeof PDFJS === 'undefined') {
})(); })();
// URL = URL || webkitURL // URL = URL || webkitURL
// Support: Safari<7, Android 4.2+
(function normalizeURLObject() { (function normalizeURLObject() {
if (!window.URL) { if (!window.URL) {
window.URL = window.webkitURL; window.URL = window.webkitURL;
} }
})(); })();
// Object.create() ? // Object.defineProperty()?
(function checkObjectCreateCompatibility() { // Support: Android<4.0, Safari<5.1
if (typeof Object.create !== 'undefined')
return;
Object.create = function objectCreate(proto) {
function Constructor() {}
Constructor.prototype = proto;
return new Constructor();
};
})();
// Object.defineProperty() ?
(function checkObjectDefinePropertyCompatibility() { (function checkObjectDefinePropertyCompatibility() {
if (typeof Object.defineProperty !== 'undefined') { if (typeof Object.defineProperty !== 'undefined') {
var definePropertyPossible = true; var definePropertyPossible = true;
@ -127,15 +120,19 @@ if (typeof PDFJS === 'undefined') {
} catch (e) { } catch (e) {
definePropertyPossible = false; definePropertyPossible = false;
} }
if (definePropertyPossible) return; if (definePropertyPossible) {
return;
}
} }
Object.defineProperty = function objectDefineProperty(obj, name, def) { Object.defineProperty = function objectDefineProperty(obj, name, def) {
delete obj[name]; delete obj[name];
if ('get' in def) if ('get' in def) {
obj.__defineGetter__(name, def['get']); obj.__defineGetter__(name, def['get']);
if ('set' in def) }
if ('set' in def) {
obj.__defineSetter__(name, def['set']); obj.__defineSetter__(name, def['set']);
}
if ('value' in def) { if ('value' in def) {
obj.__defineSetter__(name, function objectDefinePropertySetter(value) { obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
this.__defineGetter__(name, function objectDefinePropertyGetter() { this.__defineGetter__(name, function objectDefinePropertyGetter() {
@ -148,105 +145,77 @@ if (typeof PDFJS === 'undefined') {
}; };
})(); })();
// Object.keys() ?
(function checkObjectKeysCompatibility() {
if (typeof Object.keys !== 'undefined')
return;
Object.keys = function objectKeys(obj) { // No XMLHttpRequest#response?
var result = []; // Support: IE<11, Android <4.0
for (var i in obj) {
if (obj.hasOwnProperty(i))
result.push(i);
}
return result;
};
})();
// No readAsArrayBuffer ?
(function checkFileReaderReadAsArrayBuffer() {
if (typeof FileReader === 'undefined')
return; // FileReader is not implemented
var frPrototype = FileReader.prototype;
// Older versions of Firefox might not have readAsArrayBuffer
if ('readAsArrayBuffer' in frPrototype)
return; // readAsArrayBuffer is implemented
Object.defineProperty(frPrototype, 'readAsArrayBuffer', {
value: function fileReaderReadAsArrayBuffer(blob) {
var fileReader = new FileReader();
var originalReader = this;
fileReader.onload = function fileReaderOnload(evt) {
var data = evt.target.result;
var buffer = new ArrayBuffer(data.length);
var uint8Array = new Uint8Array(buffer);
for (var i = 0, ii = data.length; i < ii; i++)
uint8Array[i] = data.charCodeAt(i);
Object.defineProperty(originalReader, 'result', {
value: buffer,
enumerable: true,
writable: false,
configurable: true
});
var event = document.createEvent('HTMLEvents');
event.initEvent('load', false, false);
originalReader.dispatchEvent(event);
};
fileReader.readAsBinaryString(blob);
}
});
})();
// No XMLHttpRequest.response ?
(function checkXMLHttpRequestResponseCompatibility() { (function checkXMLHttpRequestResponseCompatibility() {
var xhrPrototype = XMLHttpRequest.prototype; var xhrPrototype = XMLHttpRequest.prototype;
if (!('overrideMimeType' in xhrPrototype)) { var xhr = new XMLHttpRequest();
if (!('overrideMimeType' in xhr)) {
// IE10 might have response, but not overrideMimeType // IE10 might have response, but not overrideMimeType
// Support: IE10
Object.defineProperty(xhrPrototype, 'overrideMimeType', { Object.defineProperty(xhrPrototype, 'overrideMimeType', {
value: function xmlHttpRequestOverrideMimeType(mimeType) {} value: function xmlHttpRequestOverrideMimeType(mimeType) {}
}); });
} }
if ('response' in xhrPrototype || if ('responseType' in xhr) {
'mozResponseArrayBuffer' in xhrPrototype ||
'mozResponse' in xhrPrototype ||
'responseArrayBuffer' in xhrPrototype)
return; return;
// IE9 ? }
// The worker will be using XHR, so we can save time and disable worker.
PDFJS.disableWorker = true;
Object.defineProperty(xhrPrototype, 'responseType', {
get: function xmlHttpRequestGetResponseType() {
return this._responseType || 'text';
},
set: function xmlHttpRequestSetResponseType(value) {
if (value === 'text' || value === 'arraybuffer') {
this._responseType = value;
if (value === 'arraybuffer' &&
typeof this.overrideMimeType === 'function') {
this.overrideMimeType('text/plain; charset=x-user-defined');
}
}
}
});
// Support: IE9
if (typeof VBArray !== 'undefined') { if (typeof VBArray !== 'undefined') {
Object.defineProperty(xhrPrototype, 'response', { Object.defineProperty(xhrPrototype, 'response', {
get: function xmlHttpRequestResponseGet() { get: function xmlHttpRequestResponseGet() {
if (this.responseType === 'arraybuffer') {
return new Uint8Array(new VBArray(this.responseBody).toArray()); return new Uint8Array(new VBArray(this.responseBody).toArray());
} else {
return this.responseText;
}
} }
}); });
return; return;
} }
// other browsers Object.defineProperty(xhrPrototype, 'response', {
function responseTypeSetter() { get: function xmlHttpRequestResponseGet() {
// will be only called to set "arraybuffer" if (this.responseType !== 'arraybuffer') {
this.overrideMimeType('text/plain; charset=x-user-defined'); return this.responseText;
} }
if (typeof xhrPrototype.overrideMimeType === 'function') {
Object.defineProperty(xhrPrototype, 'responseType',
{ set: responseTypeSetter });
}
function responseGetter() {
var text = this.responseText; var text = this.responseText;
var i, n = text.length; var i, n = text.length;
var result = new Uint8Array(n); var result = new Uint8Array(n);
for (i = 0; i < n; ++i) for (i = 0; i < n; ++i) {
result[i] = text.charCodeAt(i) & 0xFF; result[i] = text.charCodeAt(i) & 0xFF;
return result;
} }
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter }); return result.buffer;
}
});
})(); })();
// window.btoa (base64 encode function) ? // window.btoa (base64 encode function) ?
// Support: IE<10
(function checkWindowBtoaCompatibility() { (function checkWindowBtoaCompatibility() {
if ('btoa' in window) if ('btoa' in window) {
return; return;
}
var digits = var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
@ -268,17 +237,21 @@ if (typeof PDFJS === 'undefined') {
}; };
})(); })();
// window.atob (base64 encode function) ? // window.atob (base64 encode function)?
// Support: IE<10
(function checkWindowAtobCompatibility() { (function checkWindowAtobCompatibility() {
if ('atob' in window) if ('atob' in window) {
return; return;
}
// https://github.com/davidchambers/Base64.js // https://github.com/davidchambers/Base64.js
var digits = var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
window.atob = function (input) { window.atob = function (input) {
input = input.replace(/=+$/, ''); input = input.replace(/=+$/, '');
if (input.length % 4 == 1) throw new Error('bad atob input'); if (input.length % 4 === 1) {
throw new Error('bad atob input');
}
for ( for (
// initialize result and counters // initialize result and counters
var bc = 0, bs, buffer, idx = 0, output = ''; var bc = 0, bs, buffer, idx = 0, output = '';
@ -298,15 +271,17 @@ if (typeof PDFJS === 'undefined') {
}; };
})(); })();
// Function.prototype.bind ? // Function.prototype.bind?
// Support: Android<4.0, iOS<6.0
(function checkFunctionPrototypeBindCompatibility() { (function checkFunctionPrototypeBindCompatibility() {
if (typeof Function.prototype.bind !== 'undefined') if (typeof Function.prototype.bind !== 'undefined') {
return; return;
}
Function.prototype.bind = function functionPrototypeBind(obj) { Function.prototype.bind = function functionPrototypeBind(obj) {
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1); var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
var bound = function functionPrototypeBindBound() { var bound = function functionPrototypeBindBound() {
var args = Array.prototype.concat.apply(headArgs, arguments); var args = headArgs.concat(Array.prototype.slice.call(arguments));
return fn.apply(obj, args); return fn.apply(obj, args);
}; };
return bound; return bound;
@ -314,23 +289,29 @@ if (typeof PDFJS === 'undefined') {
})(); })();
// HTMLElement dataset property // HTMLElement dataset property
// Support: IE<11, Safari<5.1, Android<4.0
(function checkDatasetProperty() { (function checkDatasetProperty() {
var div = document.createElement('div'); var div = document.createElement('div');
if ('dataset' in div) if ('dataset' in div) {
return; // dataset property exists return; // dataset property exists
}
Object.defineProperty(HTMLElement.prototype, 'dataset', { Object.defineProperty(HTMLElement.prototype, 'dataset', {
get: function() { get: function() {
if (this._dataset) if (this._dataset) {
return this._dataset; return this._dataset;
}
var dataset = {}; var dataset = {};
for (var j = 0, jj = this.attributes.length; j < jj; j++) { for (var j = 0, jj = this.attributes.length; j < jj; j++) {
var attribute = this.attributes[j]; var attribute = this.attributes[j];
if (attribute.name.substring(0, 5) != 'data-') if (attribute.name.substring(0, 5) !== 'data-') {
continue; continue;
}
var key = attribute.name.substring(5).replace(/\-([a-z])/g, var key = attribute.name.substring(5).replace(/\-([a-z])/g,
function(all, ch) { return ch.toUpperCase(); }); function(all, ch) {
return ch.toUpperCase();
});
dataset[key] = attribute.value; dataset[key] = attribute.value;
} }
@ -346,20 +327,26 @@ if (typeof PDFJS === 'undefined') {
})(); })();
// HTMLElement classList property // HTMLElement classList property
// Support: IE<10, Android<4.0, iOS<5.0
(function checkClassListProperty() { (function checkClassListProperty() {
var div = document.createElement('div'); var div = document.createElement('div');
if ('classList' in div) if ('classList' in div) {
return; // classList property exists return; // classList property exists
}
function changeList(element, itemName, add, remove) { function changeList(element, itemName, add, remove) {
var s = element.className || ''; var s = element.className || '';
var list = s.split(/\s+/g); var list = s.split(/\s+/g);
if (list[0] === '') list.shift(); if (list[0] === '') {
list.shift();
}
var index = list.indexOf(itemName); var index = list.indexOf(itemName);
if (index < 0 && add) if (index < 0 && add) {
list.push(itemName); list.push(itemName);
if (index >= 0 && remove) }
if (index >= 0 && remove) {
list.splice(index, 1); list.splice(index, 1);
}
element.className = list.join(' '); element.className = list.join(' ');
return (index >= 0); return (index >= 0);
} }
@ -381,8 +368,9 @@ if (typeof PDFJS === 'undefined') {
Object.defineProperty(HTMLElement.prototype, 'classList', { Object.defineProperty(HTMLElement.prototype, 'classList', {
get: function() { get: function() {
if (this._classList) if (this._classList) {
return this._classList; return this._classList;
}
var classList = Object.create(classListPrototype, { var classList = Object.create(classListPrototype, {
element: { element: {
@ -403,6 +391,9 @@ if (typeof PDFJS === 'undefined') {
})(); })();
// Check console compatibility // Check console compatibility
// In older IE versions the console object is not available
// unless console is open.
// Support: IE<10
(function checkConsoleCompatibility() { (function checkConsoleCompatibility() {
if (!('console' in window)) { if (!('console' in window)) {
window.console = { window.console = {
@ -425,6 +416,7 @@ if (typeof PDFJS === 'undefined') {
})(); })();
// Check onclick compatibility in Opera // Check onclick compatibility in Opera
// Support: Opera<15
(function checkOnClickCompatibility() { (function checkOnClickCompatibility() {
// workaround for reported Opera bug DSK-354448: // workaround for reported Opera bug DSK-354448:
// onclick fires on disabled buttons with opaque content // onclick fires on disabled buttons with opaque content
@ -436,30 +428,34 @@ if (typeof PDFJS === 'undefined') {
function isDisabled(node) { function isDisabled(node) {
return node.disabled || (node.parentNode && isDisabled(node.parentNode)); return node.disabled || (node.parentNode && isDisabled(node.parentNode));
} }
if (navigator.userAgent.indexOf('Opera') != -1) { if (navigator.userAgent.indexOf('Opera') !== -1) {
// use browser detection since we cannot feature-check this bug // use browser detection since we cannot feature-check this bug
document.addEventListener('click', ignoreIfTargetDisabled, true); document.addEventListener('click', ignoreIfTargetDisabled, true);
} }
})(); })();
// Checks if possible to use URL.createObjectURL()
// Support: IE
(function checkOnBlobSupport() {
// sometimes IE loosing the data created with createObjectURL(), see #3977
if (navigator.userAgent.indexOf('Trident') >= 0) {
PDFJS.disableCreateObjectURL = true;
}
})();
// Checks if navigator.language is supported // Checks if navigator.language is supported
(function checkNavigatorLanguage() { (function checkNavigatorLanguage() {
if ('language' in navigator) if ('language' in navigator) {
return; return;
Object.defineProperty(navigator, 'language', { }
get: function navigatorLanguage() { PDFJS.locale = navigator.userLanguage || 'en-US';
var language = navigator.userLanguage || 'en-US';
return language.substring(0, 2).toLowerCase() +
language.substring(2).toUpperCase();
},
enumerable: true
});
})(); })();
(function checkRangeRequests() { (function checkRangeRequests() {
// Safari has issues with cached range requests see: // Safari has issues with cached range requests see:
// https://github.com/mozilla/pdf.js/issues/3260 // https://github.com/mozilla/pdf.js/issues/3260
// Last tested with version 6.0.4. // Last tested with version 6.0.4.
// Support: Safari 6.0+
var isSafari = Object.prototype.toString.call( var isSafari = Object.prototype.toString.call(
window.HTMLElement).indexOf('Constructor') > 0; window.HTMLElement).indexOf('Constructor') > 0;
@ -467,17 +463,131 @@ if (typeof PDFJS === 'undefined') {
// https://github.com/mozilla/pdf.js/issues/3381. // https://github.com/mozilla/pdf.js/issues/3381.
// Make sure that we only match webkit-based Android browsers, // Make sure that we only match webkit-based Android browsers,
// since Firefox/Fennec works as expected. // since Firefox/Fennec works as expected.
// Support: Android<3.0
var regex = /Android\s[0-2][^\d]/; var regex = /Android\s[0-2][^\d]/;
var isOldAndroid = regex.test(navigator.userAgent); var isOldAndroid = regex.test(navigator.userAgent);
if (isSafari || isOldAndroid) { // Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent);
if (isSafari || isOldAndroid || isChromeWithRangeBug) {
PDFJS.disableRange = true; PDFJS.disableRange = true;
PDFJS.disableStream = true;
} }
})(); })();
// Check if the browser supports manipulation of the history. // Check if the browser supports manipulation of the history.
// Support: IE<10, Android<4.2
(function checkHistoryManipulation() { (function checkHistoryManipulation() {
if (!window.history.pushState) { // Android 2.x has so buggy pushState support that it was removed in
// Android 3.0 and restored as late as in Android 4.2.
// Support: Android 2.x
if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
PDFJS.disableHistory = true; PDFJS.disableHistory = true;
} }
})(); })();
// Support: IE<11, Chrome<21, Android<4.4, Safari<6
(function checkSetPresenceInImageData() {
// IE < 11 will use window.CanvasPixelArray which lacks set function.
if (window.CanvasPixelArray) {
if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
window.CanvasPixelArray.prototype.set = function(arr) {
for (var i = 0, ii = this.length; i < ii; i++) {
this[i] = arr[i];
}
};
}
} else {
// Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
// Because we cannot feature detect it, we rely on user agent parsing.
var polyfill = false, versionMatch;
if (navigator.userAgent.indexOf('Chrom') >= 0) {
versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
// Chrome < 21 lacks the set function.
polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
} else if (navigator.userAgent.indexOf('Android') >= 0) {
// Android < 4.4 lacks the set function.
// Android >= 4.4 will contain Chrome in the user agent,
// thus pass the Chrome check above and not reach this block.
polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
} else if (navigator.userAgent.indexOf('Safari') >= 0) {
versionMatch = navigator.userAgent.
match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
// Safari < 6 lacks the set function.
polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
}
if (polyfill) {
var contextPrototype = window.CanvasRenderingContext2D.prototype;
var createImageData = contextPrototype.createImageData;
contextPrototype.createImageData = function(w, h) {
var imageData = createImageData.call(this, w, h);
imageData.data.set = function(arr) {
for (var i = 0, ii = this.length; i < ii; i++) {
this[i] = arr[i];
}
};
return imageData;
};
// this closure will be kept referenced, so clear its vars
contextPrototype = null;
}
}
})();
// Support: IE<10, Android<4.0, iOS
(function checkRequestAnimationFrame() {
function fakeRequestAnimationFrame(callback) {
window.setTimeout(callback, 20);
}
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
if (isIOS) {
// requestAnimationFrame on iOS is broken, replacing with fake one.
window.requestAnimationFrame = fakeRequestAnimationFrame;
return;
}
if ('requestAnimationFrame' in window) {
return;
}
window.requestAnimationFrame =
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
fakeRequestAnimationFrame;
})();
(function checkCanvasSizeLimitation() {
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
var isAndroid = /Android/g.test(navigator.userAgent);
if (isIOS || isAndroid) {
// 5MP
PDFJS.maxCanvasPixels = 5242880;
}
})();
// Disable fullscreen support for certain problematic configurations.
// Support: IE11+ (when embedded).
(function checkFullscreenSupport() {
var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 &&
window.parent !== window);
if (isEmbeddedIE) {
PDFJS.disableFullscreen = true;
}
})();
// Provides document.currentScript support
// Support: IE, Chrome<29.
(function checkCurrentScript() {
if ('currentScript' in document) {
return;
}
Object.defineProperty(document, 'currentScript', {
get: function () {
var scripts = document.getElementsByTagName('script');
return scripts[scripts.length - 1];
},
enumerable: true,
configurable: true
});
})();

View File

@ -109,11 +109,12 @@ function GetPdfMake(invoice, javascript, callback) {
function addFont(font){ function addFont(font){
if(window.ninjaFontVfs[font.folder]){ if(window.ninjaFontVfs[font.folder]){
folder = 'fonts/'+font.folder;
pdfMake.fonts[font.name] = { pdfMake.fonts[font.name] = {
normal: font.folder+'/'+font.normal, normal: folder+'/'+font.normal,
italics: font.folder+'/'+font.italics, italics: folder+'/'+font.italics,
bold: font.folder+'/'+font.bold, bold: folder+'/'+font.bold,
bolditalics: font.folder+'/'+font.bolditalics bolditalics: folder+'/'+font.bolditalics
} }
} }
} }
@ -144,6 +145,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
'invoiceDetailsHeight': (NINJA.invoiceDetails(invoice).length * 16) + 16, 'invoiceDetailsHeight': (NINJA.invoiceDetails(invoice).length * 16) + 16,
'invoiceLineItems': NINJA.invoiceLines(invoice), 'invoiceLineItems': NINJA.invoiceLines(invoice),
'invoiceLineItemColumns': NINJA.invoiceColumns(invoice), 'invoiceLineItemColumns': NINJA.invoiceColumns(invoice),
'invoiceDocuments' : NINJA.invoiceDocuments(invoice),
'quantityWidth': NINJA.quantityWidth(invoice), 'quantityWidth': NINJA.quantityWidth(invoice),
'taxWidth': NINJA.taxWidth(invoice), 'taxWidth': NINJA.taxWidth(invoice),
'clientDetails': NINJA.clientDetails(invoice), 'clientDetails': NINJA.clientDetails(invoice),
@ -348,13 +350,15 @@ NINJA.invoiceLines = function(invoice) {
var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : ''; var qty = NINJA.parseFloat(item.qty) ? roundToTwo(NINJA.parseFloat(item.qty)) + '' : '';
var notes = item.notes; var notes = item.notes;
var productKey = item.product_key; var productKey = item.product_key;
var tax = ''; var tax1 = '';
var tax2 = '';
if (showItemTaxes) { if (showItemTaxes) {
if (item.tax && parseFloat(item.tax.rate)) { if (item.tax_name1) {
tax = parseFloat(item.tax.rate); tax1 = parseFloat(item.tax_rate1);
} else if (item.tax_rate && parseFloat(item.tax_rate)) { }
tax = parseFloat(item.tax_rate); if (item.tax_name2) {
tax2 = parseFloat(item.tax_rate2);
} }
} }
@ -391,7 +395,17 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["quantity", rowStyle], text:qty || ' '}); row.push({style:["quantity", rowStyle], text:qty || ' '});
} }
if (showItemTaxes) { if (showItemTaxes) {
row.push({style:["tax", rowStyle], text:tax ? (tax.toString() + '%') : ' '}); var str = ' ';
if (tax1) {
str += tax1.toString() + '%';
}
if (tax2) {
if (tax1) {
str += ' ';
}
str += tax2.toString() + '%';
}
row.push({style:["tax", rowStyle], text:str});
} }
row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '}); row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '});
@ -401,6 +415,39 @@ NINJA.invoiceLines = function(invoice) {
return NINJA.prepareDataTable(grid, 'invoiceItems'); return NINJA.prepareDataTable(grid, 'invoiceItems');
} }
NINJA.invoiceDocuments = function(invoice) {
if(!invoice.account.invoice_embed_documents)return[];
var stack = [];
var stackItem = null;
var j = 0;
for (var i = 0; i < invoice.documents.length; i++)addDoc(invoice.documents[i]);
if(invoice.expenses){
for (var i = 0; i < invoice.expenses.length; i++) {
var expense = invoice.expenses[i];
for (var i = 0; i < expense.documents.length; i++)addDoc(expense.documents[i]);
}
}
function addDoc(document){
var path = document.base64;
if(!path)path = 'docs/'+document.public_id+'/'+document.name;
if(path && (window.pdfMake.vfs[path] || document.base64)){
// Only embed if we actually have an image for it
if(j%3==0){
stackItem = {columns:[]};
stack.push(stackItem);
}
stackItem.columns.push({stack:[{image:path,style:'invoiceDocument',fit:[150,150]}], width:175})
j++;
}
}
return stack.length?{stack:stack}:[];
}
NINJA.subtotals = function(invoice, hideBalance) NINJA.subtotals = function(invoice, hideBalance)
{ {
if (!invoice) { if (!invoice) {
@ -430,9 +477,13 @@ NINJA.subtotals = function(invoice, hideBalance)
} }
} }
if (invoice.tax && invoice.tax.name || invoice.tax_name) { if (invoice.tax_amount1) {
var taxStr = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%'; var taxStr = invoice.tax_name1 + ' ' + (invoice.tax_rate1*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount, invoice)}]); data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount1, invoice)}]);
}
if (invoice.tax_amount2) {
var taxStr = invoice.tax_name2 + ' ' + (invoice.tax_rate2*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount2, invoice)}]);
} }
if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') { if (NINJA.parseFloat(invoice.custom_value1) && invoice.custom_taxes1 != '1') {

View File

@ -591,6 +591,11 @@ function calculateAmounts(invoice) {
var taxes = {}; var taxes = {};
invoice.has_product_key = false; invoice.has_product_key = false;
// Bold designs currently breaks w/o the product column
if (invoice.invoice_design_id == 2) {
invoice.has_product_key = true;
}
// 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++) {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
@ -603,8 +608,10 @@ function calculateAmounts(invoice) {
for (var i=0; i<invoice.invoice_items.length; i++) { for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i]; var item = invoice.invoice_items[i];
var taxRate = 0; var taxRate1 = 0;
var taxName = ''; var taxName1 = '';
var taxRate2 = 0;
var taxName2 = '';
if (item.product_key) { if (item.product_key) {
invoice.has_product_key = true; invoice.has_product_key = true;
@ -612,13 +619,14 @@ function calculateAmounts(invoice) {
invoice.has_product_key = true; invoice.has_product_key = true;
} }
// the object structure differs if it's read from the db or created by knockoutJS if (item.tax_rate1 && parseFloat(item.tax_rate1)) {
if (item.tax && parseFloat(item.tax.rate)) { taxRate1 = parseFloat(item.tax_rate1);
taxRate = parseFloat(item.tax.rate); taxName1 = item.tax_name1;
taxName = item.tax.name; }
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
taxRate = parseFloat(item.tax_rate); if (item.tax_rate2 && parseFloat(item.tax_rate2)) {
taxName = item.tax_name; taxRate2 = parseFloat(item.tax_rate2);
taxName2 = item.tax_name2;
} }
// calculate line item tax // calculate line item tax
@ -630,18 +638,28 @@ function calculateAmounts(invoice) {
lineTotal -= roundToTwo(lineTotal * (invoice.discount/100)); lineTotal -= roundToTwo(lineTotal * (invoice.discount/100));
} }
} }
var taxAmount = roundToTwo(lineTotal * taxRate / 100);
if (taxRate) { var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
var key = taxName + taxRate; if (taxAmount1) {
var key = taxName1 + taxRate1;
if (taxes.hasOwnProperty(key)) { if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount; taxes[key].amount += taxAmount1;
} else { } else {
taxes[key] = {name: taxName, rate:taxRate, amount:taxAmount}; taxes[key] = {name: taxName1, rate:taxRate1, amount:taxAmount1};
} }
} }
if ((item.tax && item.tax.name) || item.tax_name) { var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
if (taxAmount2) {
var key = taxName2 + taxRate2;
if (taxes.hasOwnProperty(key)) {
taxes[key].amount += taxAmount2;
} else {
taxes[key] = {name: taxName2, rate:taxRate2, amount:taxAmount2};
}
}
if (item.tax_name1 || item.tax_name2) {
hasTaxes = true; hasTaxes = true;
} }
} }
@ -666,17 +684,17 @@ function calculateAmounts(invoice) {
total += roundToTwo(invoice.custom_value2); total += roundToTwo(invoice.custom_value2);
} }
var tax = 0; taxRate1 = 0;
if (invoice.tax && parseFloat(invoice.tax.rate)) { taxRate2 = 0;
tax = parseFloat(invoice.tax.rate); if (invoice.tax_rate1 && parseFloat(invoice.tax_rate1)) {
} else if (invoice.tax_rate && parseFloat(invoice.tax_rate)) { taxRate1 = parseFloat(invoice.tax_rate1);
tax = parseFloat(invoice.tax_rate);
} }
if (invoice.tax_rate2 && parseFloat(invoice.tax_rate2)) {
if (tax) { taxRate2 = parseFloat(invoice.tax_rate2);
var tax = roundToTwo(total * (tax/100));
total = parseFloat(total) + parseFloat(tax);
} }
taxAmount1 = roundToTwo(total * (taxRate1/100));
taxAmount2 = roundToTwo(total * (taxRate2/100));
total = total + taxAmount1 + taxAmount2;
for (var key in taxes) { for (var key in taxes) {
if (taxes.hasOwnProperty(key)) { if (taxes.hasOwnProperty(key)) {
@ -694,7 +712,8 @@ function calculateAmounts(invoice) {
invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance)); invoice.total_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
invoice.discount_amount = discount; invoice.discount_amount = discount;
invoice.tax_amount = tax; invoice.tax_amount1 = taxAmount1;
invoice.tax_amount2 = taxAmount2;
invoice.item_taxes = taxes; invoice.item_taxes = taxes;
if (NINJA.parseFloat(invoice.partial)) { if (NINJA.parseFloat(invoice.partial)) {
@ -706,16 +725,6 @@ function calculateAmounts(invoice) {
return invoice; return invoice;
} }
function getInvoiceTaxRate(invoice) {
var tax = 0;
if (invoice.tax && parseFloat(invoice.tax.rate)) {
tax = parseFloat(invoice.tax.rate);
} else if (invoice.tax_rate && parseFloat(invoice.tax_rate)) {
tax = parseFloat(invoice.tax_rate);
}
return tax;
}
// http://stackoverflow.com/questions/11941876/correctly-suppressing-warnings-in-datatables // http://stackoverflow.com/questions/11941876/correctly-suppressing-warnings-in-datatables
window.alert = (function() { window.alert = (function() {
var nativeAlert = window.alert; var nativeAlert = window.alert;

View File

@ -3,7 +3,12 @@ if(window.ninjaFontVfs)ninjaLoadFontVfs();
function ninjaLoadFontVfs(){ function ninjaLoadFontVfs(){
jQuery.each(window.ninjaFontVfs, function(font, files){ jQuery.each(window.ninjaFontVfs, function(font, files){
jQuery.each(files, function(filename, file){ jQuery.each(files, function(filename, file){
window.pdfMake.vfs[font+'/'+filename] = file; window.pdfMake.vfs['fonts/'+font+'/'+filename] = file;
}); });
}) })
} }
function ninjaAddVFSDoc(name,content){
window.pdfMake.vfs['docs/'+name] = content;
if(window.refreshPDF)refreshPDF(true);
jQuery(document).trigger('ninjaVFSDocAdded');
}

File diff suppressed because one or more lines are too long

View File

@ -8,10 +8,6 @@
[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=develop)](https://travis-ci.org/invoiceninja/invoiceninja) [![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=develop)](https://travis-ci.org/invoiceninja/invoiceninja)
[![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Note: we've recently updated this branch to Laravel 5.2. If you're upgrading here are some things to note
* Make sure to run composer install
* If there are any strings with spaces in your .env file you'll need to enclose them in quotes to prevent error class log not found.
### Affiliates Programs ### Affiliates Programs
* Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) * Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/)
* White-label reseller (you pay us): 10% of revenue with a $100 sign up fee * White-label reseller (you pay us): 10% of revenue with a $100 sign up fee
@ -52,7 +48,7 @@ Note: we've recently updated this branch to Laravel 5.2. If you're upgrading her
* [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/) * [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/)
* [User Guide](https://www.invoiceninja.com/app-user-guide/) * [User Guide](https://www.invoiceninja.com/app-user-guide/)
* [Developer Guide](https://www.invoiceninja.com/knowledgebase/developer-guide/) * [Developer Guide](https://www.invoiceninja.com/knowledgebase/developer-guide/)
* [API Documentation](https://www.invoiceninja.com/knowledgebase/api-documentation/) * [API Documentation](https://www.invoiceninja.com/api-documentation/)
* [Support Forum](https://www.invoiceninja.com/forums/forum/support/) * [Support Forum](https://www.invoiceninja.com/forums/forum/support/)
* [Feature Roadmap](https://trello.com/b/63BbiVVe/) * [Feature Roadmap](https://trello.com/b/63BbiVVe/)

View File

@ -1131,4 +1131,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1132,4 +1132,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1049,8 +1049,6 @@ $LANG = array(
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', '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' => '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\'.', '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.',
// Client Passwords // Client Passwords
'enable_portal_password'=>'Password protect invoices', 'enable_portal_password'=>'Password protect invoices',
@ -1068,7 +1066,7 @@ $LANG = array(
// User Permissions // User Permissions
'owner' => 'Owner', 'owner' => 'Owner',
'administrator' => 'Administrator', 'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', 'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.', 'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.', 'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.',
@ -1078,6 +1076,56 @@ $LANG = array(
'restore_vendor' => 'Restore Vendor', 'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor', 'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense', 'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
// Documents
'documents_header' => 'Documents:',
'email_documents_header' => 'Documents:',
'email_documents_example_1' => 'Widgets Receipt.pdf',
'email_documents_example_2' => 'Final Deliverable.zip',
'invoice_documents' => 'Documents',
'expense_documents' => 'Attached Documents',
'invoice_embed_documents' => 'Embed Documents',
'invoice_embed_documents_help' => 'Include attached images in the invoice.',
'document_email_attachment' => 'Attach Documents',
'download_documents' => 'Download Documents (:size)',
'documents_from_expenses' => 'From Expenses:',
'dropzone' => array(// See http://www.dropzonejs.com/#config-dictDefaultMessage
'DefaultMessage' => 'Drop files or click to upload',
'FallbackMessage' => 'Your browser does not support drag\'n\'drop file uploads.',
'FallbackText' => 'Please use the fallback form below to upload your files like in the olden days.',
'FileTooBig' => 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.',
'InvalidFileType' => 'You can\'t upload files of this type.',
'ResponseError' => 'Server responded with {{statusCode}} code.',
'CancelUpload' => 'Cancel upload',
'CancelUploadConfirmation' => 'Are you sure you want to cancel this upload?',
'RemoveFile' => 'Remove file',
),
'documents' => 'Documents',
'document_date' => 'Document Date',
'document_size' => 'Size',
'enable_client_portal' => 'Client Portal',
'enable_client_portal_help' => 'Show/hide the client portal.',
'enable_client_portal_dashboard' => 'Dashboard',
'enable_client_portal_dashboard_help' => 'Show/hide the dashboard page in the client portal.',
); );

View File

@ -1108,4 +1108,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1128,4 +1128,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1123,4 +1123,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1121,4 +1121,73 @@ return array(
'overdue' => 'En souffrance', 'overdue' => 'En souffrance',
'white_label_text' => 'Achetez une licence sans pub d\'un an à $'.WHITE_LABEL_PRICE.' pour retirer le logo de Invoice Ninja du portail client et supporter notre projet.', 'white_label_text' => 'Achetez une licence sans pub d\'un an à $'.WHITE_LABEL_PRICE.' pour retirer le logo de Invoice Ninja du portail client et supporter notre projet.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -30,7 +30,7 @@ return array(
'invoice' => 'Fattura', 'invoice' => 'Fattura',
'client' => 'Cliente', 'client' => 'Cliente',
'invoice_date' => 'Data Fattura', 'invoice_date' => 'Data Fattura',
'due_date' => 'Scadenza Fattura', 'due_date' => 'Scadenza',
'invoice_number' => 'Numero Fattura', 'invoice_number' => 'Numero Fattura',
'invoice_number_short' => 'Fattura #', /* Fattura N° */ 'invoice_number_short' => 'Fattura #', /* Fattura N° */
'po_number' => 'Numero d\'ordine d\'acquisto', 'po_number' => 'Numero d\'ordine d\'acquisto',
@ -45,8 +45,8 @@ return array(
'quantity' => 'Quantità', 'quantity' => 'Quantità',
'line_total' => 'Totale Riga', 'line_total' => 'Totale Riga',
'subtotal' => 'Subtotale', 'subtotal' => 'Subtotale',
'paid_to_date' => 'Pagato in Data', 'paid_to_date' => 'Pagato a oggi',
'balance_due' => 'Saldo Dovuto', 'balance_due' => 'Totale',
'invoice_design_id' => 'Stile', 'invoice_design_id' => 'Stile',
'terms' => 'Condizioni', 'terms' => 'Condizioni',
'your_invoice' => 'Tua Fattura', 'your_invoice' => 'Tua Fattura',
@ -67,7 +67,7 @@ return array(
'clone_invoice' => 'Duplica Fattura', 'clone_invoice' => 'Duplica Fattura',
'archive_invoice' => 'Archivia Fattura', 'archive_invoice' => 'Archivia Fattura',
'delete_invoice' => 'Elimina Fattura', 'delete_invoice' => 'Elimina Fattura',
'email_invoice' => 'Manda Fattura', /* Spedisci Fattura */ 'email_invoice' => 'Invia Fattura', /* Spedisci Fattura */
'enter_payment' => 'Inserisci Pagamento', 'enter_payment' => 'Inserisci Pagamento',
'tax_rates' => 'Aliquote Fiscali', /* ^^Unsure^^ */ 'tax_rates' => 'Aliquote Fiscali', /* ^^Unsure^^ */
'rate' => 'Aliquota', /* ^^Unsure^^ */ 'rate' => 'Aliquota', /* ^^Unsure^^ */
@ -104,12 +104,12 @@ return array(
// recurring invoices // recurring invoices
'recurring_invoices' => 'Fatture ricorrenti', 'recurring_invoices' => 'Fatture ricorrenti',
'recurring_help' => '<p>Invia automaticamente al cliente le stesse fatture settimanalmente, bimestralmente, mensilmente, trimestralmente o annualmente. </p> 'recurring_help' => '<p>Invia automaticamente al cliente le stesse fatture settimanalmente, bimestralmente, mensilmente, trimestralmente o annualmente. </p>
<p>Usa :MESE, :TRIMESRE o :ANNO per date dinamiche. Funziona anche con la matematica di base, ad esempio :MESE-1.</p> <p>Usa :MONTH, :QUARTER o :YEAR per date dinamiche. Funziona anche con la matematica di base, ad esempio :MONTH-1.</p>
<p>Esempi di variabili di fattura dinamiche:</p> <p>Esempi di variabili di fattura dinamiche:</p>
<ul> <ul>
<li>"Iscrizione palestra per il mese di :MESE" => "Iscrizione palestra per il mese di Luglio"</li> <li>"Iscrizione palestra per il mese di :MONTH" => "Iscrizione palestra per il mese di Luglio"</li>
<li>":ANNO+1 iscrizione annuale" => "Anno d\'iscrizione 2015"</li> <li>":YEAR+1 iscrizione annuale" => "Anno d\'iscrizione 2015"</li>
<li>"Pagamento fermo a :TRIMESTRE+1" => "Pagamento fermo al 2° trimestre"</li> <li>"Pagamento fermo a :QUARTER+1" => "Pagamento fermo al 2° trimestre"</li>
</ul>', /* ^^Variables translated in case you'll need it for front end^^ */ </ul>', /* ^^Variables translated in case you'll need it for front end^^ */
// dashboard // dashboard
@ -118,7 +118,7 @@ return array(
'billed_clients' => 'Clienti fatturati', 'billed_clients' => 'Clienti fatturati',
'active_client' => 'cliente attivo', 'active_client' => 'cliente attivo',
'active_clients' => 'clienti attivi', 'active_clients' => 'clienti attivi',
'invoices_past_due' => 'Fatture Insolute', /* Insoluti */ 'invoices_past_due' => 'Fatture Scadute', /* Insoluti */
'upcoming_invoices' => 'Prossime fatture', 'upcoming_invoices' => 'Prossime fatture',
'average_invoice' => 'Fattura media', 'average_invoice' => 'Fattura media',
@ -140,7 +140,7 @@ return array(
'contact' => 'Contatto', 'contact' => 'Contatto',
'date_created' => 'Data di Creazione', 'date_created' => 'Data di Creazione',
'last_login' => 'Ultimo Accesso', 'last_login' => 'Ultimo Accesso',
'balance' => 'Saldo', 'balance' => 'Bilancio',
'action' => 'Azione', 'action' => 'Azione',
'status' => 'Stato', 'status' => 'Stato',
'invoice_total' => 'Totale Fattura', 'invoice_total' => 'Totale Fattura',
@ -169,7 +169,7 @@ return array(
'activity' => 'Attività', 'activity' => 'Attività',
'date' => 'Data', 'date' => 'Data',
'message' => 'Messaggio', 'message' => 'Messaggio',
'adjustment' => 'Correzione', 'adjustment' => 'Variazione',
'are_you_sure' => 'Sei sicuro?', 'are_you_sure' => 'Sei sicuro?',
// payment pages // payment pages
@ -337,7 +337,7 @@ return array(
'archived_product' => 'Prodotto archiviato con successo', 'archived_product' => 'Prodotto archiviato con successo',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan', 'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings', 'advanced_settings' => 'Impostazioni Avanzate',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan', 'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design', 'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors', 'specify_colors' => 'Specify colors',
@ -456,11 +456,11 @@ return array(
'sent' => 'sent', 'sent' => 'sent',
'timesheets' => 'Timesheets', 'timesheets' => 'Timesheets',
'payment_title' => 'Enter Your Billing Address and Credit Card information', 'payment_title' => 'Inserisci il tuo indirizzo di fatturazione e i dati della tua carta di credito',
'payment_cvv' => '*This is the 3-4 digit number onthe back of your card', 'payment_cvv' => '*This is the 3-4 digit number onthe back of your card',
'payment_footer1' => '*Billing address must match address associated with credit card.', 'payment_footer1' => '*L\'indirizzo di fatturazione deve corrispondere all\'indirizzo associato alla carta di credito.',
'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'vat_number' => 'Vat Number', 'vat_number' => 'Partita IVA',
'id_number' => 'ID Number', 'id_number' => 'ID Number',
'white_label_link' => 'White label', 'white_label_link' => 'White label',
@ -503,30 +503,30 @@ return array(
'payment_email' => 'Payment Email', 'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email', 'quote_email' => 'Quote Email',
'reset_all' => 'Reset All', 'reset_all' => 'Reset All',
'approve' => 'Approve', 'approve' => 'Approva',
'token_billing_type_id' => 'Token Billing', 'token_billing_type_id' => 'Token Billing',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
'token_billing_1' => 'Disabled', 'token_billing_1' => 'Disabilitato',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected', 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
'token_billing_3' => 'Opt-out - checkbox is shown and selected', 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
'token_billing_4' => 'Always', 'token_billing_4' => 'Sempre',
'token_billing_checkbox' => 'Store credit card details', 'token_billing_checkbox' => 'Salva dettagli carta di credito',
'view_in_stripe' => 'View in Stripe', 'view_in_stripe' => 'Vedi transazione in Stripe',
'use_card_on_file' => 'Use card on file', 'use_card_on_file' => 'Carta di credito salvata',
'edit_payment_details' => 'Edit payment details', 'edit_payment_details' => 'Modifica dettagli pagamento',
'token_billing' => 'Save card details', 'token_billing' => 'Salva carta di credito',
'token_billing_secure' => 'The data is stored securely by :stripe_link', 'token_billing_secure' => 'I dati sono memorizzati su piattaforma sicura mediante :stripe_link',
'support' => 'Support', 'support' => 'Support',
'contact_information' => 'Contact information', 'contact_information' => 'Informazioni di contatto',
'256_encryption' => '256-Bit Encryption', '256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due', 'amount_due' => 'Saldo dovuto',
'billing_address' => 'Billing address', 'billing_address' => 'Indirizzo di fatturazione',
'billing_method' => 'Billing method', 'billing_method' => 'Metodo di pagamento',
'order_overview' => 'Order overview', 'order_overview' => 'Riepilogo ordine',
'match_address' => '*Address must match address associated with credit card.', 'match_address' => '*L\'indirizzo deve corrispondere con quello associato alla carta di credito.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', 'click_once' => '*Per favore clicca "PAGA ADESSO" solo una volta - la transazione può impiegare sino a 1 minuto per essere completata.',
'default_invoice_footer' => 'Set default invoice footer', 'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer', 'invoice_footer' => 'Invoice footer',
@ -550,7 +550,7 @@ return array(
'created_gateway' => 'Successfully created gateway', 'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway', 'deleted_gateway' => 'Successfully deleted gateway',
'pay_with_paypal' => 'PayPal', 'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card', 'pay_with_card' => 'Carta di credito',
'change_password' => 'Change password', 'change_password' => 'Change password',
'current_password' => 'Current password', 'current_password' => 'Current password',
@ -579,12 +579,12 @@ return array(
'confirmation_resent' => 'The confirmation email was resent', 'confirmation_resent' => 'The confirmation email was resent',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.', 'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.',
'payment_type_credit_card' => 'Credit card', 'payment_type_credit_card' => 'Carta di credito',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin', 'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base', 'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial', 'partial' => 'Partial',
'partial_remaining' => ':partial of :balance', 'partial_remaining' => ':partial di :balance',
'more_fields' => 'More Fields', 'more_fields' => 'More Fields',
'less_fields' => 'Less Fields', 'less_fields' => 'Less Fields',
@ -614,40 +614,40 @@ return array(
'export' => 'Export', 'export' => 'Export',
'documentation' => 'Documentation', 'documentation' => 'Documentation',
'zapier' => 'Zapier', 'zapier' => 'Zapier',
'recurring' => 'Recurring', 'recurring' => 'Ricorrenti',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Ultima fattura inviata :date',
'processed_updates' => 'Successfully completed update', 'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks', 'tasks' => 'Task',
'new_task' => 'New Task', 'new_task' => 'Nuovo Task',
'start_time' => 'Start Time', 'start_time' => 'Tempo di inizio',
'created_task' => 'Successfully created task', 'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task', 'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task', 'edit_task' => 'Modifica il Task',
'archive_task' => 'Archive Task', 'archive_task' => 'Archivia il Task',
'restore_task' => 'Restore Task', 'restore_task' => 'Ripristina il Task',
'delete_task' => 'Delete Task', 'delete_task' => 'Cancella il Task',
'stop_task' => 'Stop Task', 'stop_task' => 'Ferma il Task',
'time' => 'Time', 'time' => 'Tempo',
'start' => 'Start', 'start' => 'Inizia',
'stop' => 'Stop', 'stop' => 'Ferma',
'now' => 'Now', 'now' => 'Adesso',
'timer' => 'Timer', 'timer' => 'Timer',
'manual' => 'Manual', 'manual' => 'Manuale',
'date_and_time' => 'Date & Time', 'date_and_time' => 'Data e ora',
'second' => 'second', 'second' => 'secondo',
'seconds' => 'seconds', 'seconds' => 'secondi',
'minute' => 'minute', 'minute' => 'minuto',
'minutes' => 'minutes', 'minutes' => 'minuti',
'hour' => 'hour', 'hour' => 'ora',
'hours' => 'hours', 'hours' => 'ore',
'task_details' => 'Task Details', 'task_details' => 'Dettagli Task',
'duration' => 'Duration', 'duration' => 'Durata',
'end_time' => 'End Time', 'end_time' => 'Tempo di fine',
'end' => 'End', 'end' => 'Fine',
'invoiced' => 'Invoiced', 'invoiced' => 'Fatturato',
'logged' => 'Logged', 'logged' => 'Loggato',
'running' => 'Running', 'running' => 'In corso',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients', 'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first', 'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced', 'task_error_invoiced' => 'Tasks have already been invoiced',
@ -656,9 +656,9 @@ return array(
'archived_tasks' => 'Successfully archived :count tasks', 'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task', 'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks', 'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task', 'create_task' => 'Crea Task',
'stopped_task' => 'Successfully stopped task', 'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task', 'invoice_task' => 'Fattura il Task',
'invoice_labels' => 'Invoice Labels', 'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix', 'prefix' => 'Prefix',
'counter' => 'Counter', 'counter' => 'Counter',
@ -666,7 +666,7 @@ return array(
'payment_type_dwolla' => 'Dwolla', 'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.', 'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total', 'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions', 'more_actions' => 'Altre Azioni',
'pro_plan_title' => 'NINJA PRO', 'pro_plan_title' => 'NINJA PRO',
@ -680,12 +680,12 @@ return array(
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering', 'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails', 'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'resume' => 'Resume', 'resume' => 'Riprendi',
'break_duration' => 'Break', 'break_duration' => 'Interrompi',
'edit_details' => 'Edit Details', 'edit_details' => 'Modifica dettagli',
'work' => 'Work', 'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone', 'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here', 'click_here' => 'clicca qui',
'email_receipt' => 'Email payment receipt to the client', 'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client',
@ -736,8 +736,8 @@ return array(
'total_revenue' => 'Total Revenue', 'total_revenue' => 'Total Revenue',
'current_user' => 'Current User', 'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice', 'new_recurring_invoice' => 'Nuova Fattura Ricorrente',
'recurring_invoice' => 'Recurring Invoice', 'recurring_invoice' => 'Fattura ricorrente',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date', 'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice', 'created_by_invoice' => 'Created by :invoice',
'primary_user' => 'Primary User', 'primary_user' => 'Primary User',
@ -746,17 +746,17 @@ return array(
<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.name</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' => 'Scadenza fattura',
'quote_due_date' => 'Valid Until', 'quote_due_date' => 'Validità preventivo',
'valid_until' => 'Valid Until', 'valid_until' => 'Valido fino a',
'reset_terms' => 'Reset terms', 'reset_terms' => 'Reset terms',
'reset_footer' => 'Reset footer', 'reset_footer' => 'Reset footer',
'invoices_sent' => ':count invoice sent|:count invoices sent', 'invoices_sent' => ':count invoice sent|:count invoices sent',
'status_draft' => 'Draft', 'status_draft' => 'Bozza',
'status_sent' => 'Sent', 'status_sent' => 'Spedito',
'status_viewed' => 'Viewed', 'status_viewed' => 'Visto',
'status_partial' => 'Partial', 'status_partial' => 'Parziale',
'status_paid' => 'Paid', 'status_paid' => 'Pagato',
'show_line_item_tax' => 'Display <b>line item taxes</b> inline', 'show_line_item_tax' => 'Display <b>line item taxes</b> inline',
'iframe_url' => 'Website', 'iframe_url' => 'Website',
@ -785,15 +785,15 @@ return array(
'page_expire' => 'This page will expire soon, :click_here to keep working', 'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes', 'upcoming_quotes' => 'Upcoming Quotes',
'expired_quotes' => 'Expired Quotes', 'expired_quotes' => 'Preventivi Scaduti',
'sign_up_using' => 'Sign up using', 'sign_up_using' => 'Sign up using',
'invalid_credentials' => 'These credentials do not match our records', 'invalid_credentials' => 'Queste credenziali non corrispondono alle nostre registrazioni',
'show_all_options' => 'Show all options', 'show_all_options' => 'Mostra tutte le opzioni',
'user_details' => 'User Details', 'user_details' => 'Dettagli Utente',
'oneclick_login' => 'One-Click Login', 'oneclick_login' => 'One-Click Login',
'disable' => 'Disable', 'disable' => 'Disabilita',
'invoice_quote_number' => 'Invoice and Quote Numbers', 'invoice_quote_number' => 'Numerazione Fatture e Preventivi',
'invoice_charges' => 'Invoice Charges', 'invoice_charges' => 'Invoice Charges',
'invitation_status' => [ 'invitation_status' => [
@ -807,10 +807,10 @@ return array(
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice', 'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link', 'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced', 'total_invoiced' => 'Fatturato totale',
'open_balance' => 'Open Balance', 'open_balance' => 'Da saldare',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.', 'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
'basic_settings' => 'Basic Settings', 'basic_settings' => 'Impostazioni Base',
'pro' => 'Pro', 'pro' => 'Pro',
'gateways' => 'Payment Gateways', 'gateways' => 'Payment Gateways',
@ -821,7 +821,7 @@ return array(
'oneclick_login_help' => 'Connect an account to login without a password', 'oneclick_login_help' => 'Connect an account to login without a password',
'referral_code_help' => 'Earn money by sharing our app online', 'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe', 'enable_with_stripe' => 'Abilita | Richiede Stripe',
'tax_settings' => 'Tax Settings', 'tax_settings' => 'Tax Settings',
'create_tax_rate' => 'Add Tax Rate', 'create_tax_rate' => 'Add Tax Rate',
'updated_tax_rate' => 'Successfully updated tax rate', 'updated_tax_rate' => 'Successfully updated tax rate',
@ -845,8 +845,8 @@ return array(
'activity_1' => ':user created client :client', 'activity_1' => ':user created client :client',
'activity_2' => ':user archived client :client', 'activity_2' => ':user archived client :client',
'activity_3' => ':user deleted client :client', 'activity_3' => ':user deleted client :client',
'activity_4' => ':user created invoice :invoice', 'activity_4' => ':user ha creato la fattura :invoice',
'activity_5' => ':user updated invoice :invoice', 'activity_5' => ':user ha aggiornato la fattura :invoice',
'activity_6' => ':user emailed invoice :invoice to :contact', 'activity_6' => ':user emailed invoice :invoice to :contact',
'activity_7' => ':contact viewed invoice :invoice', 'activity_7' => ':contact viewed invoice :invoice',
'activity_8' => ':user archived invoice :invoice', 'activity_8' => ':user archived invoice :invoice',
@ -883,7 +883,7 @@ return array(
'quote_footer' => 'Quote Footer', 'quote_footer' => 'Quote Footer',
'free' => 'Free', 'free' => 'Free',
'quote_is_approved' => 'This quote is approved', 'quote_is_approved' => 'Questo preventivo è stato approvato.',
'apply_credit' => 'Apply Credit', 'apply_credit' => 'Apply Credit',
'system_settings' => 'System Settings', 'system_settings' => 'System Settings',
'archive_token' => 'Archive Token', 'archive_token' => 'Archive Token',
@ -944,7 +944,7 @@ return array(
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'email_design' => 'Email Design', 'email_design' => 'Email Design',
'due_by' => 'Due by :date', 'due_by' => 'Scadenza :date',
'enable_email_markup' => 'Enable Markup', 'enable_email_markup' => 'Enable Markup',
'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.',
'template_help_title' => 'Templates Help', 'template_help_title' => 'Templates Help',
@ -986,10 +986,10 @@ return array(
'white_label_purchase_link' => 'Purchase a white label license', 'white_label_purchase_link' => 'Purchase a white label license',
// Expense / vendor // Expense / vendor
'expense' => 'Expense', 'expense' => 'Spesa',
'expenses' => 'Expenses', 'expenses' => 'Spese',
'new_expense' => 'Enter Expense', 'new_expense' => 'Inserisci Spesa',
'enter_expense' => 'Enter Expense', 'enter_expense' => 'Inserisci Spesa',
'vendors' => 'Vendors', 'vendors' => 'Vendors',
'new_vendor' => 'New Vendor', 'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net', 'payment_terms_net' => 'Net',
@ -1077,11 +1077,11 @@ return array(
'quote_message_button' => 'To view your quote for :amount, click the button below.', 'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.', 'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit', 'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts', 'bank_accounts' => 'Conti corrente',
'add_bank_account' => 'Add Bank Account', 'add_bank_account' => 'Nuovo conto corrente',
'setup_account' => 'Setup Account', 'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses', 'import_expenses' => 'Importa Spese',
'bank_id' => 'bank', 'bank_id' => 'banca',
'integration_type' => 'Integration Type', 'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account', 'updated_bank_account' => 'Successfully updated bank account',
'edit_bank_account' => 'Edit Bank Account', 'edit_bank_account' => 'Edit Bank Account',
@ -1126,4 +1126,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Da versare (parziale)',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1052,6 +1052,51 @@ $LANG = array(
'enable_client_portal' => 'ダッシュボード', 'enable_client_portal' => 'ダッシュボード',
'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );
return $LANG; return $LANG;

View File

@ -1133,4 +1133,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1131,4 +1131,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -10,15 +10,15 @@ return array(
'address' => 'Adres', 'address' => 'Adres',
'address1' => 'Straat', 'address1' => 'Straat',
'address2' => 'Bus/Suite', 'address2' => 'Bus/Suite',
'city' => 'Gemeente', 'city' => 'Plaats',
'state' => 'Staat/Provincie', 'state' => 'Staat/Provincie',
'postal_code' => 'Postcode', 'postal_code' => 'Postcode',
'country_id' => 'Land', 'country_id' => 'Land',
'contacts' => 'Contacten', 'contacts' => 'Contactpersonen',
'first_name' => 'Voornaam', 'first_name' => 'Voornaam',
'last_name' => 'Achternaam', 'last_name' => 'Achternaam',
'phone' => 'Telefoon', 'phone' => 'Telefoon',
'email' => 'E-mail', 'email' => 'E-mailadres',
'additional_info' => 'Extra informatie', 'additional_info' => 'Extra informatie',
'payment_terms' => 'Betalingsvoorwaarden', 'payment_terms' => 'Betalingsvoorwaarden',
'currency_id' => 'Munteenheid', 'currency_id' => 'Munteenheid',
@ -35,7 +35,7 @@ return array(
'invoice_number_short' => 'Factuur #', 'invoice_number_short' => 'Factuur #',
'po_number' => 'Bestelnummer', 'po_number' => 'Bestelnummer',
'po_number_short' => 'Bestel #', 'po_number_short' => 'Bestel #',
'frequency_id' => 'Hoe vaak', 'frequency_id' => 'Frequentie',
'discount' => 'Korting', 'discount' => 'Korting',
'taxes' => 'Belastingen', 'taxes' => 'Belastingen',
'tax' => 'Belasting', 'tax' => 'Belasting',
@ -52,9 +52,9 @@ return array(
'your_invoice' => 'Jouw factuur', 'your_invoice' => 'Jouw factuur',
'remove_contact' => 'Verwijder contact', 'remove_contact' => 'Verwijder contact',
'add_contact' => 'Voeg contact toe', 'add_contact' => 'Contact toevoegen',
'create_new_client' => 'Maak nieuwe klant', 'create_new_client' => 'Maak nieuwe klant',
'edit_client_details' => 'Pas klantdetails aan', 'edit_client_details' => 'Klantdetails aanpassen',
'enable' => 'Activeer', 'enable' => 'Activeer',
'learn_more' => 'Meer te weten komen', 'learn_more' => 'Meer te weten komen',
'manage_rates' => 'Beheer prijzen', 'manage_rates' => 'Beheer prijzen',
@ -63,12 +63,12 @@ return array(
'save_as_default_terms' => 'Opslaan als standaard voorwaarden', 'save_as_default_terms' => 'Opslaan als standaard voorwaarden',
'download_pdf' => 'Download PDF', 'download_pdf' => 'Download PDF',
'pay_now' => 'Betaal nu', 'pay_now' => 'Betaal nu',
'save_invoice' => 'Sla factuur op', 'save_invoice' => 'Factuur opslaan',
'clone_invoice' => 'Kopieer factuur', 'clone_invoice' => 'Kopieer factuur',
'archive_invoice' => 'Archiveer factuur', 'archive_invoice' => 'Archiveer factuur',
'delete_invoice' => 'Verwijder factuur', 'delete_invoice' => 'Verwijder factuur',
'email_invoice' => 'E-mail factuur', 'email_invoice' => 'E-mail factuur',
'enter_payment' => 'Betaling ingeven', 'enter_payment' => 'Betaling invoeren',
'tax_rates' => 'BTW-tarief', 'tax_rates' => 'BTW-tarief',
'rate' => 'Tarief', 'rate' => 'Tarief',
'settings' => 'Instellingen', 'settings' => 'Instellingen',
@ -176,21 +176,21 @@ return array(
'amount' => 'Bedrag', 'amount' => 'Bedrag',
// account/company pages // account/company pages
'work_email' => 'E-mail', 'work_email' => 'E-mailadres',
'language_id' => 'Taal', 'language_id' => 'Taal',
'timezone_id' => 'Tijdszone', 'timezone_id' => 'Tijdszone',
'date_format_id' => 'Datum formaat', 'date_format_id' => 'Datum formaat',
'datetime_format_id' => 'Datum/Tijd formaat', 'datetime_format_id' => 'Datum/Tijd formaat',
'users' => 'Gebruikers', 'users' => 'Gebruikers',
'localization' => 'Localisatie', 'localization' => 'Localisatie',
'remove_logo' => 'Verwijder logo', 'remove_logo' => 'Logo verwijderen',
'logo_help' => 'Ondersteund: JPEG, GIF en PNG', 'logo_help' => 'Ondersteund: JPEG, GIF en PNG',
'payment_gateway' => 'Betalingsmiddel', 'payment_gateway' => 'Betalingsmiddel',
'gateway_id' => 'Leverancier', 'gateway_id' => 'Leverancier',
'email_notifications' => 'E-mailmeldingen', 'email_notifications' => 'E-mailmeldingen',
'email_sent' => 'E-mail me wanneer een factuur is <b>verzonden</b>', 'email_sent' => 'E-mail mij wanneer een factuur is <b>verzonden</b>',
'email_viewed' => 'E-mail me wanneer een factuur is <b>bekeken</b>', 'email_viewed' => 'E-mail mij wanneer een factuur is <b>bekeken</b>',
'email_paid' => 'E-mail me wanneer een factuur is <b>betaald</b>', 'email_paid' => 'E-mail mij wanneer een factuur is <b>betaald</b>',
'site_updates' => 'Site Aanpassingen', 'site_updates' => 'Site Aanpassingen',
'custom_messages' => 'Aangepaste berichten', 'custom_messages' => 'Aangepaste berichten',
'default_invoice_terms' => 'Stel standaard factuurvoorwaarden in', 'default_invoice_terms' => 'Stel standaard factuurvoorwaarden in',
@ -310,10 +310,10 @@ return array(
'close' => 'Sluiten', 'close' => 'Sluiten',
'pro_plan_product' => 'Pro Plan', 'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'Één jaar abbonnement op het InvoiceNinja Pro Plan.', 'pro_plan_description' => 'Eén jaar abbonnement op het InvoiceNinja Pro Plan.',
'pro_plan_success' => 'Bedankt voor het aanmelden! Zodra uw factuur betaald is zal uw Pro Plan lidmaatschap beginnen.', 'pro_plan_success' => 'Bedankt voor het aanmelden! Zodra uw factuur betaald is zal uw Pro Plan lidmaatschap beginnen.',
'unsaved_changes' => 'U hebt niet bewaarde wijzigingen', 'unsaved_changes' => 'U hebt niet opgeslagen wijzigingen',
'custom_fields' => 'Aangepaste velden', 'custom_fields' => 'Aangepaste velden',
'company_fields' => 'Velden Bedrijf', 'company_fields' => 'Velden Bedrijf',
'client_fields' => 'Velden Klant', 'client_fields' => 'Velden Klant',
@ -366,7 +366,7 @@ return array(
'archive_quote' => 'Archiveer offerte', 'archive_quote' => 'Archiveer offerte',
'delete_quote' => 'Verwijder offerte', 'delete_quote' => 'Verwijder offerte',
'save_quote' => 'Bewaar offerte', 'save_quote' => 'Bewaar offerte',
'email_quote' => 'Email offerte', 'email_quote' => 'E-mail offerte',
'clone_quote' => 'Kloon offerte', 'clone_quote' => 'Kloon offerte',
'convert_to_invoice' => 'Zet om naar factuur', 'convert_to_invoice' => 'Zet om naar factuur',
'view_invoice' => 'Bekijk factuur', 'view_invoice' => 'Bekijk factuur',
@ -404,10 +404,10 @@ return array(
'charge_taxes' => 'BTW berekenen', 'charge_taxes' => 'BTW berekenen',
'user_management' => 'Gebruikersbeheer', 'user_management' => 'Gebruikersbeheer',
'add_user' => 'Nieuwe gebruiker', 'add_user' => 'Nieuwe gebruiker',
'send_invite' => 'Verstuur uitnodiging', 'send_invite' => 'Uitnodiging versturen',
'sent_invite' => 'Uitnodiging succesvol verzonden', 'sent_invite' => 'Uitnodiging succesvol verzonden',
'updated_user' => 'Gebruiker succesvol aangepast', 'updated_user' => 'Gebruiker succesvol aangepast',
'invitation_message' => 'U bent uigenodigd door :invitor. ', 'invitation_message' => 'U bent uitgenodigd door :invitor. ',
'register_to_add_user' => 'Meld u aan om een gebruiker toe te voegen', 'register_to_add_user' => 'Meld u aan om een gebruiker toe te voegen',
'user_state' => 'Status', 'user_state' => 'Status',
'edit_user' => 'Bewerk gebruiker', 'edit_user' => 'Bewerk gebruiker',
@ -417,11 +417,11 @@ return array(
'deleted_user' => 'Gebruiker succesvol verwijderd', 'deleted_user' => 'Gebruiker succesvol verwijderd',
'limit_users' => 'Sorry, dit zou de limiet van '.MAX_NUM_USERS.' gebruikers overschrijden', 'limit_users' => 'Sorry, dit zou de limiet van '.MAX_NUM_USERS.' gebruikers overschrijden',
'confirm_email_invoice' => 'Weet u zeker dat u deze factuur wilt mailen?', 'confirm_email_invoice' => 'Weet u zeker dat u deze factuur wilt e-mailen?',
'confirm_email_quote' => 'Weet u zeker dat u deze offerte wilt mailen?', 'confirm_email_quote' => 'Weet u zeker dat u deze offerte wilt e-mailen?',
'confirm_recurring_email_invoice' => 'Terugkeren (herhalen) staat aan, weet u zeker dat u deze factuur wilt mailen?', 'confirm_recurring_email_invoice' => 'Terugkeren (herhalen) staat aan, weet u zeker dat u deze factuur wilt e-mailen?',
'cancel_account' => 'Zeg Account Op', 'cancel_account' => 'Account opzeggen',
'cancel_account_message' => 'Waarschuwing: Dit zal al uw data verwijderen. Er is geen manier om dit ongedaan te maken', 'cancel_account_message' => 'Waarschuwing: Dit zal al uw data verwijderen. Er is geen manier om dit ongedaan te maken',
'go_back' => 'Ga Terug', 'go_back' => 'Ga Terug',
@ -457,10 +457,10 @@ return array(
'sent' => 'verzonden', 'sent' => 'verzonden',
'timesheets' => 'Timesheets', 'timesheets' => 'Timesheets',
'payment_title' => 'Geef uw betalingsadres en kredietkaartgegevens op', 'payment_title' => 'Geef uw betalingsadres en creditcardgegevens op',
'payment_cvv' => '*Dit is de code van 3-4 tekens op de achterkant van uw kaart', 'payment_cvv' => '*Dit is de code van 3 of 4 tekens op de achterkant van uw kaart',
'payment_footer1' => '*Betalingsadres moet overeenkomen met het adres dat aan uw kaart gekoppeld is.', 'payment_footer1' => '*Betalingsadres moet overeenkomen met het adres dat aan uw kaart gekoppeld is.',
'payment_footer2' => '*Klik alstublieft slechts 1 keer op "PAY NOW" - verwerking kan tot 1 minuut duren.', 'payment_footer2' => '*Klik alstublieft slechts &eacute;&eacute;n keer op "PAY NOW" - verwerking kan tot 1 minuut duren.',
'vat_number' => 'BTW-nummer', 'vat_number' => 'BTW-nummer',
'id_number' => 'Identificatienummer', 'id_number' => 'Identificatienummer',
@ -498,20 +498,20 @@ return array(
'restore_user' => 'Herstel gebruiker', 'restore_user' => 'Herstel gebruiker',
'restored_user' => 'Gebruiker succesvol hersteld', 'restored_user' => 'Gebruiker succesvol hersteld',
'show_deleted_users' => 'Toon verwijderde gebruikers', 'show_deleted_users' => 'Toon verwijderde gebruikers',
'email_templates' => 'Emailsjablonen', 'email_templates' => 'E-mailsjablonen',
'invoice_email' => 'Factuuremail', 'invoice_email' => 'Factuur-e-mail',
'payment_email' => 'Betalingsemail', 'payment_email' => 'Betalings-e-mail',
'quote_email' => 'Offerte-email', 'quote_email' => 'Offerte-e-mail',
'reset_all' => 'Reset alles', 'reset_all' => 'Reset alles',
'approve' => 'Goedkeuren', 'approve' => 'Goedkeuren',
'token_billing_type_id' => 'Betalingstoken', 'token_billing_type_id' => 'Betalingstoken',
'token_billing_help' => 'Stelt u in staat om kredietkaart gegevens bij uw gateway op te slaan en ze later te gebruiken.', 'token_billing_help' => 'Stelt u in staat om creditcard gegevens bij uw gateway op te slaan en ze later te gebruiken.',
'token_billing_1' => 'Inactief', 'token_billing_1' => 'Inactief',
'token_billing_2' => 'Opt-in - checkbox is getoond maar niet geselecteerd', 'token_billing_2' => 'Opt-in - checkbox is getoond maar niet geselecteerd',
'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd', 'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd',
'token_billing_4' => 'Altijd', 'token_billing_4' => 'Altijd',
'token_billing_checkbox' => 'Sla kredietkaart gegevens op', 'token_billing_checkbox' => 'Sla carditcard gegevens op',
'view_in_stripe' => 'In Stripe bekijken', 'view_in_stripe' => 'In Stripe bekijken',
'use_card_on_file' => 'Gebruik opgeslagen kaart', 'use_card_on_file' => 'Gebruik opgeslagen kaart',
'edit_payment_details' => 'Betalingsdetails aanpassen', 'edit_payment_details' => 'Betalingsdetails aanpassen',
@ -525,7 +525,7 @@ return array(
'billing_address' => 'Factuuradres', 'billing_address' => 'Factuuradres',
'billing_method' => 'Betaalmethode', 'billing_method' => 'Betaalmethode',
'order_overview' => 'Orderoverzicht', 'order_overview' => 'Orderoverzicht',
'match_address' => '*Addres moet overeenkomen met adres van kredietkaart.', 'match_address' => '*Adres moet overeenkomen met adres van creditcard.',
'click_once' => '*Klik alstublieft maar &eacute;&eacute;n keer; het kan een minuut duren om de betaling te verwerken.', 'click_once' => '*Klik alstublieft maar &eacute;&eacute;n keer; het kan een minuut duren om de betaling te verwerken.',
'default_invoice_footer' => 'Stel standaard factuurfooter in', 'default_invoice_footer' => 'Stel standaard factuurfooter in',
@ -550,7 +550,7 @@ return array(
'created_gateway' => 'Gateway succesvol aangemaakt', 'created_gateway' => 'Gateway succesvol aangemaakt',
'deleted_gateway' => 'Gateway succesvol verwijderd', 'deleted_gateway' => 'Gateway succesvol verwijderd',
'pay_with_paypal' => 'PayPal', 'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Kredietkaart', 'pay_with_card' => 'Creditcard',
'change_password' => 'Verander wachtwoord', 'change_password' => 'Verander wachtwoord',
'current_password' => 'Huidig wachtwoord', 'current_password' => 'Huidig wachtwoord',
@ -572,17 +572,17 @@ return array(
'set_password' => 'Stel wachtwoord in', 'set_password' => 'Stel wachtwoord in',
'converted' => 'Omgezet', 'converted' => 'Omgezet',
'email_approved' => 'Email me wanneer een offerte is <b>goedgekeurd</b>', 'email_approved' => 'Email mij wanneer een offerte is <b>goedgekeurd</b>',
'notification_quote_approved_subject' => 'Offerte :invoice is goedgekeurd door :client', 'notification_quote_approved_subject' => 'Offerte :invoice is goedgekeurd door :client',
'notification_quote_approved' => ':client heeft offerte :invoice goedgekeurd voor :amount.', 'notification_quote_approved' => ':client heeft offerte :invoice goedgekeurd voor :amount.',
'resend_confirmation' => 'Verstuurd bevestingsmail opnieuw', 'resend_confirmation' => 'Verstuurd bevestingsmail opnieuw',
'confirmation_resent' => 'De bevestigingsmail is opnieuw verstuurd', 'confirmation_resent' => 'De bevestigingsmail is opnieuw verstuurd',
'gateway_help_42' => ':link om te registreren voor BitPay.<br/>Opmerking: gebruik een Legacy API Key, niet een API token.', 'gateway_help_42' => ':link om te registreren voor BitPay.<br/>Opmerking: gebruik een Legacy API Key, niet een API token.',
'payment_type_credit_card' => 'Kredietkaart', 'payment_type_credit_card' => 'Creditcard',
'payment_type_paypal' => 'PayPal', 'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin', 'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Kennis databank', 'knowledge_base' => 'Kennisbank',
'partial' => 'Gedeeld', 'partial' => 'Gedeeld',
'partial_remaining' => ':partial / :balance', 'partial_remaining' => ':partial / :balance',
@ -591,11 +591,11 @@ return array(
'client_name' => 'Klantnaam', 'client_name' => 'Klantnaam',
'pdf_settings' => 'PDF-instellingen', 'pdf_settings' => 'PDF-instellingen',
'product_settings' => 'Productinstellingen', 'product_settings' => 'Productinstellingen',
'auto_wrap' => 'Automatisch lijn afbreken', 'auto_wrap' => 'Automatisch regel afbreken',
'duplicate_post' => 'Opgelet: de volgende pagina is twee keer doorgestuurd. De tweede verzending is genegeerd.', 'duplicate_post' => 'Opgelet: de volgende pagina is twee keer doorgestuurd. De tweede verzending is genegeerd.',
'view_documentation' => 'Bekijk documentatie', 'view_documentation' => 'Bekijk documentatie',
'app_title' => 'Gratis Open-Source Online Facturatie', 'app_title' => 'Gratis Open-Source Online Facturatie',
'app_description' => 'Invoice Ninja is een gratis, open-source oplossing voor het aanmkaen en versturen van facturen aan klanten. Met Invoice Ninja, kun je gemakkelijk mooie facturen aanmaken en verzenden van om het even welk toestel met internettoegang. Je klanten kunnen je facturen afdrukken, downloaden als pdf bestanden en je zelfs online betalen vanuit het systeem.', 'app_description' => 'Invoice Ninja is een gratis, open-source oplossing voor het maken en versturen van facturen aan klanten. Met Invoice Ninja, kun je gemakkelijk mooie facturen maken en verzenden vanaf elk apparaat met internettoegang. Je klanten kunnen je facturen afdrukken, downloaden als pdf bestand en je zelfs online betalen vanuit het systeem.',
'rows' => 'rijen', 'rows' => 'rijen',
'www' => 'www', 'www' => 'www',
@ -635,7 +635,7 @@ return array(
'timer' => 'Timer', 'timer' => 'Timer',
'manual' => 'Manueel', 'manual' => 'Manueel',
'date_and_time' => 'Datum en tijd', 'date_and_time' => 'Datum en tijd',
'second' => 'second', 'second' => 'seconde',
'seconds' => 'seconden', 'seconds' => 'seconden',
'minute' => 'minuut', 'minute' => 'minuut',
'minutes' => 'minuten', 'minutes' => 'minuten',
@ -670,9 +670,9 @@ return array(
'pro_plan_title' => 'NINJA PRO', 'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Nu upgraden!', 'pro_plan_call_to_action' => 'Nu upgraden!',
'pro_plan_feature1' => 'Maak ongelimiteerd klanten aan', 'pro_plan_feature1' => 'Ongelimiteerd klanten aanmaken',
'pro_plan_feature2' => 'Toegang tot 10 mooie factuur ontwerpen', 'pro_plan_feature2' => 'Toegang tot 10 mooie factuur ontwerpen',
'pro_plan_feature3' => 'Aangepaste URLs - "YourBrand.InvoiceNinja.com"', 'pro_plan_feature3' => 'Aangepaste URLs - "UwMerk.InvoiceNinja.com"',
'pro_plan_feature4' => 'Verwijder "Aangemaakt door Invoice Ninja"', 'pro_plan_feature4' => 'Verwijder "Aangemaakt door Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user toegang & Activeit Tracking', 'pro_plan_feature5' => 'Multi-user toegang & Activeit Tracking',
'pro_plan_feature6' => 'Maak offertes & Pro-forma facturen aan', 'pro_plan_feature6' => 'Maak offertes & Pro-forma facturen aan',
@ -696,14 +696,14 @@ return array(
'login' => 'Login', 'login' => 'Login',
'or' => 'of', 'or' => 'of',
'email_error' => 'Er was een probleem om de email te verzenden', 'email_error' => 'Er was een probleem met versturen van de e-mail',
'confirm_recurring_timing' => 'Opmerking: emails worden aan het begin van het uur verzonden.', 'confirm_recurring_timing' => 'Opmerking: e-mails worden aan het begin van het uur verzonden.',
'old_browser' => 'Gebruik a.u.b. een <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">nieuwere browser</a>', 'old_browser' => 'Gebruik a.u.b. een <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">moderne browser</a>',
'payment_terms_help' => 'Stel de standaard factuurvervaldatum in', 'payment_terms_help' => 'Stel de standaard factuurvervaldatum in',
'unlink_account' => 'Koppel account los', 'unlink_account' => 'Koppel account los',
'unlink' => 'Koppel los', 'unlink' => 'Koppel los',
'show_address' => 'Toon Adres', 'show_address' => 'Toon Adres',
'show_address_help' => 'Verplicht de klant om zijn factuur adres op te geven', 'show_address_help' => 'Verplicht de klant om zijn factuuradres op te geven',
'update_address' => 'Adres aanpassen', 'update_address' => 'Adres aanpassen',
'update_address_help' => 'Pas het adres van de klant aan met de ingevulde gegevens', 'update_address_help' => 'Pas het adres van de klant aan met de ingevulde gegevens',
'times' => 'Tijden', 'times' => 'Tijden',
@ -718,7 +718,7 @@ return array(
'font_size' => 'Tekstgrootte', 'font_size' => 'Tekstgrootte',
'primary_color' => 'Primaire kleur', 'primary_color' => 'Primaire kleur',
'secondary_color' => 'Secundaire kleur', 'secondary_color' => 'Secundaire kleur',
'customize_design' => 'Pas design aan', 'customize_design' => 'Pas ontwerp aan',
'content' => 'Inhoud', 'content' => 'Inhoud',
'styles' => 'Stijlen', 'styles' => 'Stijlen',
@ -740,9 +740,9 @@ return array(
'created_by_invoice' => 'Aangemaakt door :invoice', 'created_by_invoice' => 'Aangemaakt door :invoice',
'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 factuurontwerpen 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>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 puntnotatie om een "dochter eigenschap" te gebruiken. Bijvoorbeeld: om de naam van een klant te tonen gebruik je <code>$client.name</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, stel 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',
'quote_due_date' => 'Geldig tot', 'quote_due_date' => 'Geldig tot',
@ -763,11 +763,11 @@ return array(
'iframe_url_help2' => 'U kunt de functionaliteit testen door te klikken op \'Bekijk als ontvanger\' bij een factuur.', 'iframe_url_help2' => 'U kunt de functionaliteit testen door te klikken op \'Bekijk als ontvanger\' bij een factuur.',
'auto_bill' => 'Automatische incasso', 'auto_bill' => 'Automatische incasso',
'military_time' => '24 uurs tijd', 'military_time' => '24-uurs klok',
'last_sent' => 'Laatst verstuurd', 'last_sent' => 'Laatst verstuurd',
'reminder_emails' => 'Herinneringse-mails', 'reminder_emails' => 'Herinnerings-e-mails',
'templates_and_reminders' => 'Templates en herinneringen', 'templates_and_reminders' => 'Sjablonen en herinneringen',
'subject' => 'Onderwerp', 'subject' => 'Onderwerp',
'body' => 'Tekst', 'body' => 'Tekst',
'first_reminder' => 'Eerste herinnering', 'first_reminder' => 'Eerste herinnering',
@ -787,17 +787,17 @@ return array(
'expired_quotes' => 'Verlopen offertes', 'expired_quotes' => 'Verlopen offertes',
'sign_up_using' => 'Meld u aan met', 'sign_up_using' => 'Meld u aan met',
'invalid_credentials' => 'Deze credentials zijn niet bij ons bekend', 'invalid_credentials' => 'Deze inloggegevens zijn niet bij ons bekend',
'show_all_options' => 'Alle opties tonen', 'show_all_options' => 'Alle opties tonen',
'user_details' => 'Gebruiker gegevens', 'user_details' => 'Gebruiker gegevens',
'oneclick_login' => 'One-Click Login', 'oneclick_login' => 'One-Click Login',
'disable' => 'Uitzetten', 'disable' => 'Uitzetten',
'invoice_quote_number' => 'Factuur en offerte nummers', 'invoice_quote_number' => 'Factuur- en offertenummers',
'invoice_charges' => 'Facturatie kosten', 'invoice_charges' => 'Facturatiekosten',
'invitation_status' => [ 'invitation_status' => [
'sent' => 'Email verstuurd', 'sent' => 'E-mail verstuurd',
'opened' => 'Email geopend', 'opened' => 'E-mail geopend',
'viewed' => 'Factuur bekeken', 'viewed' => 'Factuur bekeken',
], ],
'notification_invoice_bounced' => 'We konden factuur :invoice niet afleveren bij :contact.', 'notification_invoice_bounced' => 'We konden factuur :invoice niet afleveren bij :contact.',
@ -808,7 +808,7 @@ return array(
'custom_invoice_link' => 'Eigen factuurlink', 'custom_invoice_link' => 'Eigen factuurlink',
'total_invoiced' => 'Totaal gefactureerd', 'total_invoiced' => 'Totaal gefactureerd',
'open_balance' => 'Openstaand bedrag', 'open_balance' => 'Openstaand bedrag',
'verify_email' => 'Klik alstublieft op de link in de accountbevestigingse-mail om uw e-mailadres te bevestigen.', 'verify_email' => 'Klik alstublieft op de link in de accountbevestigings-e-mail om uw e-mailadres te bevestigen.',
'basic_settings' => 'Basisinstellingen', 'basic_settings' => 'Basisinstellingen',
'pro' => 'Pro', 'pro' => 'Pro',
'gateways' => 'Betalingsverwerkers', 'gateways' => 'Betalingsverwerkers',
@ -817,7 +817,7 @@ return array(
'next_send_on' => 'Verstuur volgende: :date', 'next_send_on' => 'Verstuur volgende: :date',
'no_longer_running' => 'Deze factuur is niet ingepland', 'no_longer_running' => 'Deze factuur is niet ingepland',
'general_settings' => 'Algemene instellingen', 'general_settings' => 'Algemene instellingen',
'customize' => 'Pas aan', 'customize' => 'Aanpassen',
'oneclick_login_help' => 'Verbind een account om zonder wachtwoord in te kunnen loggen', 'oneclick_login_help' => 'Verbind een account om zonder wachtwoord in te kunnen loggen',
'referral_code_help' => 'Verdien geld door onze applicatie online te delen', 'referral_code_help' => 'Verdien geld door onze applicatie online te delen',
@ -842,39 +842,39 @@ return array(
'quote_counter' => 'Offerteteller', 'quote_counter' => 'Offerteteller',
'type' => 'Type', 'type' => 'Type',
'activity_1' => ':user created client :client', 'activity_1' => ':user heeft klant :client aangemaakt',
'activity_2' => ':user archived client :client', 'activity_2' => ':user heeft klant :client gearchiveerd',
'activity_3' => ':user deleted client :client', 'activity_3' => ':user heeft klant :client verwijderd',
'activity_4' => ':user created invoice :invoice', 'activity_4' => ':user heeft factuur :invoice aangemaakt',
'activity_5' => ':user updated invoice :invoice', 'activity_5' => ':user heeft factuur :invoice bijgewerkt',
'activity_6' => ':user emailed invoice :invoice to :contact', 'activity_6' => ':user heeft factuur :invoice verstuurd naar :contact',
'activity_7' => ':contact viewed invoice :invoice', 'activity_7' => ':contact heeft factuur :invoice bekeken',
'activity_8' => ':user archived invoice :invoice', 'activity_8' => ':user heeft factuur :invoice gearchiveerd',
'activity_9' => ':user deleted invoice :invoice', 'activity_9' => ':user heeft factuur :invoice verwijderd',
'activity_10' => ':contact entered payment :payment for :invoice', 'activity_10' => ':contact heeft betaling :payment ingevoerd voor factuur :invoice',
'activity_11' => ':user updated payment :payment', 'activity_11' => ':user heeft betaling :payment bijgewerkt',
'activity_12' => ':user archived payment :payment', 'activity_12' => ':user heeft betaling :payment gearchiveerd',
'activity_13' => ':user deleted payment :payment', 'activity_13' => ':user heeft betaling :payment verwijderd',
'activity_14' => ':user entered :credit credit', 'activity_14' => ':user heeft :credit krediet ingevoerd',
'activity_15' => ':user updated :credit credit', 'activity_15' => ':user heeft :credit krediet bijgewerkt',
'activity_16' => ':user archived :credit credit', 'activity_16' => ':user heeft :credit krediet gearchiveerd',
'activity_17' => ':user deleted :credit credit', 'activity_17' => ':user heeft :credit krediet verwijderd',
'activity_18' => ':user created quote :quote', 'activity_18' => ':user heeft offerte :quote aangemaakt',
'activity_19' => ':user updated quote :quote', 'activity_19' => ':user heeft offerte :quote bijgewerkt',
'activity_20' => ':user emailed quote :quote to :contact', 'activity_20' => ':user heeft offerte :quote verstuurd naar :contact',
'activity_21' => ':contact viewed quote :quote', 'activity_21' => ':contact heeft offerte :quote bekeken',
'activity_22' => ':user archived quote :quote', 'activity_22' => ':user heeft offerte :quote gearchiveerd',
'activity_23' => ':user deleted quote :quote', 'activity_23' => ':user heeft offerte :quote verwijderd',
'activity_24' => ':user restored quote :quote', 'activity_24' => ':user heeft offerte :quote hersteld',
'activity_25' => ':user restored invoice :invoice', 'activity_25' => ':user heeft factuur :invoice hersteld',
'activity_26' => ':user restored client :client', 'activity_26' => ':user heeft klant :client hersteld',
'activity_27' => ':user restored payment :payment', 'activity_27' => ':user heeft betaling :payment hersteld',
'activity_28' => ':user restored :credit credit', 'activity_28' => ':user heeft :credit krediet hersteld',
'activity_29' => ':contact approved quote :quote', 'activity_29' => ':contact heeft offerte :quote goedgekeurd',
'payment' => 'Betaling', 'payment' => 'Betaling',
'system' => 'Systeem', 'system' => 'Systeem',
'signature' => 'Emailondertekening', 'signature' => 'E-mailhandtekening',
'default_messages' => 'Standaardberichten', 'default_messages' => 'Standaardberichten',
'quote_terms' => 'Offertevoorwaarden', 'quote_terms' => 'Offertevoorwaarden',
'default_quote_terms' => 'Standaard offertevoorwaarden', 'default_quote_terms' => 'Standaard offertevoorwaarden',
@ -884,7 +884,7 @@ return array(
'free' => 'Gratis', 'free' => 'Gratis',
'quote_is_approved' => 'Deze offerte is geaccordeerd', 'quote_is_approved' => 'Deze offerte is geaccordeerd',
'apply_credit' => 'Apply Credit', 'apply_credit' => 'Krediet gebruiken',
'system_settings' => 'Systeeminstellingen', 'system_settings' => 'Systeeminstellingen',
'archive_token' => 'Archiveer token', 'archive_token' => 'Archiveer token',
'archived_token' => 'Token succesvol gearchiveerd', 'archived_token' => 'Token succesvol gearchiveerd',
@ -910,220 +910,286 @@ return array(
'country' => 'Land', 'country' => 'Land',
'include' => 'Voeg in', 'include' => 'Voeg in',
'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', 'logo_too_large' => 'Je logo is :size groot, voor betere PDF prestaties raden we je aan om een afbeelding kleiner dan 200KB te uploaden.',
'import_freshbooks' => 'Import From FreshBooks', 'import_freshbooks' => 'Importeren van FreshBooks',
'import_data' => 'Importeer data', 'import_data' => 'Importeer data',
'source' => 'Bron', 'source' => 'Bron',
'csv' => 'CSV', 'csv' => 'CSV',
'client_file' => 'Klantbestand', 'client_file' => 'Klantenbestand',
'invoice_file' => 'Factuurbestand', 'invoice_file' => 'Factuurbestand',
'task_file' => 'Urenbestand', 'task_file' => 'Urenbestand',
'no_mapper' => 'No valid mapping for file', 'no_mapper' => 'Geen geldige mapping voor bestand',
'invalid_csv_header' => 'Invalid CSV Header', 'invalid_csv_header' => 'Ongeldige CSV kop',
'email_errors' => [ 'email_errors' => [
'inactive_client' => 'Emails can not be sent to inactive clients', 'inactive_client' => 'E-mails kunnen niet worden verstuurd naar inactieve klanten',
'inactive_contact' => 'Emails can not be sent to inactive contacts', 'inactive_contact' => 'E-mails kunnen niet worden verstuurd naar inactieve contactpersonen',
'inactive_invoice' => 'Emails can not be sent to inactive invoices', 'inactive_invoice' => 'E-mails kunnen niet worden verstuurd naar inactieve facturen',
'user_unregistered' => 'Please register your account to send emails', 'user_unregistered' => 'Registreer een account om e-mails te kunnen versturen',
'user_unconfirmed' => 'Please confirm your account to send emails', 'user_unconfirmed' => 'Bevestig uw account om e-mails te kunnen versturen',
'invalid_contact_email' => 'Invalid contact email', 'invalid_contact_email' => 'Ongeldig e-mailadres van contactpersoon',
], ],
'client_portal' => 'Klantportaal', 'client_portal' => 'Klantenportaal',
'admin' => 'Admin', 'admin' => 'Admin',
'disabled' => 'Uitgeschakeld', 'disabled' => 'Uitgeschakeld',
'show_archived_users' => 'Toon gearchiveerde gebruikers', 'show_archived_users' => 'Toon gearchiveerde gebruikers',
'notes' => 'Notities', 'notes' => 'Notities',
'invoice_will_create' => 'klant zal worden aangemaakt', 'invoice_will_create' => 'klant zal worden aangemaakt',
'invoices_will_create' => 'factuur zal worden aangemaakt', 'invoices_will_create' => 'factuur zal worden aangemaakt',
'failed_to_import' => 'The following records failed to import, they either already exist or are missing required fields.', 'failed_to_import' => 'De volgende regels konden niet worden ge&iuml;mporteerd, ze bestaan al of missen verplichte velden.',
'publishable_key' => 'Publishable Key', 'publishable_key' => 'Publishable Key',
'secret_key' => 'Secret Key', 'secret_key' => 'Secret Key',
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'email_design' => 'Email Design', 'email_design' => 'E-mail Ontwerp',
'due_by' => 'Due by :date', 'due_by' => 'Vervaldatum :date',
'enable_email_markup' => 'Enable Markup', 'enable_email_markup' => 'Opmaak inschakelen',
'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', 'enable_email_markup_help' => 'Maak het gemakkelijker voor uw klanten om te betalen door scherma.org opmaak toe te voegen aan uw e-mails.',
'template_help_title' => 'Templates Help', 'template_help_title' => 'Hulp bij sjablonen',
'template_help_1' => 'Available variables:', 'template_help_1' => 'Beschikbare variabelen:',
'email_design_id' => 'Email Style', 'email_design_id' => 'E-mail stijl',
'email_design_help' => 'Make your emails look more professional with HTML layouts', 'email_design_help' => 'Geef uw e-mails een professionele uitstraling met HTML ontwerpen',
'plain' => 'Plain', 'plain' => 'Platte tekst',
'light' => 'Light', 'light' => 'Licht',
'dark' => 'Dark', 'dark' => 'Donker',
'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', 'industry_help' => 'Wordt gebruikt om een vergelijking te kunnen maken met de gemiddelden van andere bedrijven uit dezelfde sector en van dezelfde grootte.',
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'subdomain_help' => 'Pas het factuur link subdomein aan of toon de factuur op uw eigen website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'invoice_number_help' => 'Kies een voorvoegsel of gebruik een patroon om het factuurnummer dynamisch te genereren.',
'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', 'quote_number_help' => 'Kies een voorvoegsel of gebruik een patroon om het offertenummer dynamisch te genereren.',
'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' => 'Plaatst een tekstveld op de klanten aanmaak-/bewerkpagina en toont het gekozen label op de PDF.',
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_account_fields_helps' => 'Plaatst een tekstveld op de bedrijven aanmaak-/bewerkpagina en toont het gekozen label op de 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' => 'Plaatst een tekstveld op de factuur aanmaak-/bewerkpagina en toont het gekozen label op de 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' => 'Plaatst een tekstveld op de factuur aanmaak-/bewerkpagina en verwerkt de facturatiekosten in het subtotaal.',
'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.', 'color_help' => 'Opmerking: de primaire kleur wordt ook gebruikt in het klantenportaal en in aangepaste e-mailontwerpen.',
'token_expired' => 'Validation token was expired. Please try again.', 'token_expired' => 'De validatie token is verlopen. Probeer het opnieuw.',
'invoice_link' => 'Invoice Link', 'invoice_link' => 'Factuur Link',
'button_confirmation_message' => 'Click to confirm your email address.', 'button_confirmation_message' => 'Klik om uw e-mailadres te bevestigen.',
'confirm' => 'Confirm', 'confirm' => 'Bevestigen',
'email_preferences' => 'Email Preferences', 'email_preferences' => 'E-mailvoorkeuren',
'created_invoices' => 'Successfully created :count invoice(s)', 'created_invoices' => ':count facturen succesvol aangemaakt', //TODO: Implement pluralization?
'next_invoice_number' => 'The next invoice number is :number.', 'next_invoice_number' => 'Het volgende factuurnummer is :number.',
'next_quote_number' => 'The next quote number is :number.', 'next_quote_number' => 'Het volgende offertenummer is :number.',
'days_before' => 'days before', 'days_before' => 'dagen voor',
'days_after' => 'days after', 'days_after' => 'dagen na',
'field_due_date' => 'due date', 'field_due_date' => 'vervaldatum',
'field_invoice_date' => 'invoice date', 'field_invoice_date' => 'factuurdatum',
'schedule' => 'Schedule', 'schedule' => 'Schema',
'email_designs' => 'Email Designs', 'email_designs' => 'E-mail Ontwerpen',
'assigned_when_sent' => 'Assigned when sent', 'assigned_when_sent' => 'Toegwezen zodra verzonden',
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', 'white_label_custom_css' => ':link voor $'.WHITE_LABEL_PRICE.' om eigen opmaak te gebruiken en ons project te ondersteunen.',
'white_label_purchase_link' => 'Purchase a white label license', 'white_label_purchase_link' => 'Koop een whitelabel licentie',
// Expense / vendor // Expense / vendor
'expense' => 'Expense', 'expense' => 'Uitgave',
'expenses' => 'Expenses', 'expenses' => 'Uitgaven',
'new_expense' => 'Enter Expense', 'new_expense' => 'Nieuwe uitgave',
'enter_expense' => 'Enter Expense', 'enter_expense' => 'Uitgave invoeren',
'vendors' => 'Vendors', 'vendors' => 'Verkopers',
'new_vendor' => 'New Vendor', 'new_vendor' => 'Nieuwe verkoper',
'payment_terms_net' => 'Net', 'payment_terms_net' => 'Betaaltermijn',
'vendor' => 'Vendor', 'vendor' => 'Verkoper',
'edit_vendor' => 'Edit Vendor', 'edit_vendor' => 'Bewerk verkoper',
'archive_vendor' => 'Archive Vendor', 'archive_vendor' => 'Archiveer verkoper',
'delete_vendor' => 'Delete Vendor', 'delete_vendor' => 'Verwijder verkoper',
'view_vendor' => 'View Vendor', 'view_vendor' => 'Bekijk verkoper',
'deleted_expense' => 'Successfully deleted expense', 'deleted_expense' => 'Uitgave succesvol verwijderd',
'archived_expense' => 'Successfully archived expense', 'archived_expense' => 'Uitgave succesvol gearchiveerd',
'deleted_expenses' => 'Successfully deleted expenses', 'deleted_expenses' => 'Uitgaven succesvol verwijderd',
'archived_expenses' => 'Successfully archived expenses', 'archived_expenses' => 'Uitgaven succesvol gearchiveerd',
// Expenses // Expenses
'expense_amount' => 'Expense Amount', 'expense_amount' => 'Uitgave bedrag',
'expense_balance' => 'Expense Balance', 'expense_balance' => 'Uitgave saldo',
'expense_date' => 'Expense Date', 'expense_date' => 'Uitgave datum',
'expense_should_be_invoiced' => 'Should this expense be invoiced?', 'expense_should_be_invoiced' => 'Moet deze uitgave worden gefactureerd?',
'public_notes' => 'Public Notes', 'public_notes' => 'Publieke opmerkingen',
'invoice_amount' => 'Invoice Amount', 'invoice_amount' => 'Factuurbedrag',
'exchange_rate' => 'Exchange Rate', 'exchange_rate' => 'Wisselkoers',
'yes' => 'Yes', 'yes' => 'Ja',
'no' => 'No', 'no' => 'Nee',
'should_be_invoiced' => 'Should be invoiced', 'should_be_invoiced' => 'Moet worden gefactureerd',
'view_expense' => 'View expense # :expense', 'view_expense' => 'Bekijk uitgave #:expense',
'edit_expense' => 'Edit Expense', 'edit_expense' => 'Bewerk uitgave',
'archive_expense' => 'Archive Expense', 'archive_expense' => 'Archiveer uitgave',
'delete_expense' => 'Delete Expense', 'delete_expense' => 'Verwijder uitgave',
'view_expense_num' => 'Expense # :expense', 'view_expense_num' => 'Uitgave #:expense',
'updated_expense' => 'Successfully updated expense', 'updated_expense' => 'Uitgave succesvol bijgewerkt',
'created_expense' => 'Successfully created expense', 'created_expense' => 'Uitgave succesvol aangemaakt',
'enter_expense' => 'Enter Expense', 'enter_expense' => 'Uitgave invoeren',
'view' => 'View', 'view' => 'Bekijken',
'restore_expense' => 'Restore Expense', 'restore_expense' => 'Herstel uitgave',
'invoice_expense' => 'Invoice Expense', 'invoice_expense' => 'Factuur uitgave',
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients', 'expense_error_multiple_clients' => 'De uitgaven kunnen niet bij verschillende klanten horen',
'expense_error_invoiced' => 'Expense has already been invoiced', 'expense_error_invoiced' => 'Uitgave is al gefactureerd',
'convert_currency' => 'Convert currency', 'convert_currency' => 'Valuta omrekenen',
// Payment terms // Payment terms
'num_days' => 'Number of days', 'num_days' => 'Aantal dagen',
'create_payment_term' => 'Create Payment Term', 'create_payment_term' => 'Betalingstermijn aanmaken',
'edit_payment_terms' => 'Edit Payment Term', 'edit_payment_terms' => 'Bewerk betalingstermijnen',
'edit_payment_term' => 'Edit Payment Term', 'edit_payment_term' => 'Bewerk betalingstermijn',
'archive_payment_term' => 'Archive Payment Term', 'archive_payment_term' => 'Archiveer betalingstermijn',
// recurring due dates // recurring due dates
'recurring_due_dates' => 'Recurring Invoice Due Dates', 'recurring_due_dates' => 'Vervaldatums van terugkerende facturen',
'recurring_due_date_help' => '<p>Automatically sets a due date for the invoice.</p> 'recurring_due_date_help' => '<p>Stelt automatisch een vervaldatum in voor de factuur.</p>
<p>Invoices on a monthly or yearly cycle set to be due on or before the day they are created will be due the next month. Invoices set to be due on the 29th or 30th in months that don\'t have that day will be due the last day of the month.</p> <p>Facturen die maandelijks of jaarlijks terugkeren en ingesteld zijn om te vervallen op of voor de datum waarop ze gemaakt zijn zullen de volgende maand vervallen. Facturen die ingesteld zijn te vervallen op de 29e of 30e van een maand die deze dag niet heeft zullen vervallen op de laatste dag van die maand.</p>
<p>Invoices on a weekly cycle set to be due on the day of the week they are created will be due the next week.</p> <p>Facturen die wekelijks terugkeren en ingesteld zijn om te vervallen op de dag van de week dat ze gemaakt zijn zullen de volgende week vervallen.</p>
<p>For example:</p> <p>Bijvoorbeeld:</p>
<ul> <ul>
<li>Today is the 15th, due date is 1st of the month. The due date should likely be the 1st of the next month.</li> <li>Vandaag is het de 15e, de vervaldatum is ingesteld op de eerste dag van de maand. De vervaldatum zal de eerste dag van de volgende maand zijn.</li>
<li>Today is the 15th, due date is the last day of the month. The due date will be the last day of the this month. <li>Vandaag is het de 15e, de vervaldatum is ingesteld op de laatste dag van de maand. De vervaldatum zal de laatste dag van deze maand zijn.</li>
</li> <li>Vandaag is het de 15e, de vervaldatum is ingesteld op de 15e dag van de maand. De vervaldatum zal de 15e dag van de <strong>volgende</strong> maand zijn.</li>
<li>Today is the 15th, due date is the 15th day of the month. The due date will be the 15th day of <strong>next</strong> month. <li>Vandaag is het vrijdag, de vervaldatum is ingesteld op de 1e vrijdag erna. De vervaldatum zal volgende week vrijdag zijn, niet vandaag.</li>
</li>
<li>Today is the Friday, due date is the 1st Friday after. The due date will be next Friday, not today.
</li>
</ul>', </ul>',
'due' => 'Due', 'due' => 'Vervaldatum',
'next_due_on' => 'Due Next: :date', 'next_due_on' => 'Vervaldatum volgende: :date',
'use_client_terms' => 'Use client terms', 'use_client_terms' => 'Gebruik betalingsvoorwaarden klant',
'day_of_month' => ':ordinal day of month', 'day_of_month' => ':ordinal dag van de maand',
'last_day_of_month' => 'Last day of month', 'last_day_of_month' => 'Laatste dag van de maand',
'day_of_week_after' => ':ordinal :day after', 'day_of_week_after' => ':ordinal :day erna',
'sunday' => 'Sunday', 'sunday' => 'Zondag',
'monday' => 'Monday', 'monday' => 'Maandag',
'tuesday' => 'Tuesday', 'tuesday' => 'Dinsdag',
'wednesday' => 'Wednesday', 'wednesday' => 'Woensdag',
'thursday' => 'Thursday', 'thursday' => 'Donderdag',
'friday' => 'Friday', 'friday' => 'Vrijdag',
'saturday' => 'Saturday', 'saturday' => 'Zaterdag',
// Fonts // Fonts
'header_font_id' => 'Header Font', 'header_font_id' => 'Header lettertype',
'body_font_id' => 'Body Font', 'body_font_id' => 'Body lettertype',
'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.', 'color_font_help' => 'Opmerking: de primaire kleuren en lettertypen wordt ook gebruikt in het klantenportaal en in aangepaste e-mailontwerpen.',
'live_preview' => 'Live Preview', 'live_preview' => 'Live Voorbeeld',
'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.', 'invalid_mail_config' => 'Kon de e-mail niet verzenden, controleer of de e-mailinstellingen kloppen.',
'invoice_message_button' => 'To view your invoice for :amount, click the button below.', 'invoice_message_button' => 'Klik op de onderstaande link om uw factuur van :amount te bekijken.',
'quote_message_button' => 'To view your quote for :amount, click the button below.', 'quote_message_button' => 'Klik op de onderstaande link om uw offerte van :amount te bekijken.',
'payment_message_button' => 'Thank you for your payment of :amount.', 'payment_message_button' => 'Bedankt voor uw betaling van :amount.',
'payment_type_direct_debit' => 'Direct Debit', 'payment_type_direct_debit' => 'Automatisch incasso',
'bank_accounts' => 'Bank Accounts', 'bank_accounts' => 'Bankrekeningen',
'add_bank_account' => 'Add Bank Account', 'add_bank_account' => 'Bankrekening toevoegen',
'setup_account' => 'Setup Account', 'setup_account' => 'Rekening instellen',
'import_expenses' => 'Import Expenses', 'import_expenses' => 'Uitgaven importeren',
'bank_id' => 'bank', 'bank_id' => 'Bank',
'integration_type' => 'Integration Type', 'integration_type' => 'Integratie Type',
'updated_bank_account' => 'Successfully updated bank account', 'updated_bank_account' => 'Bankrekening succesvol bijgewerkt',
'edit_bank_account' => 'Edit Bank Account', 'edit_bank_account' => 'Bewerk bankrekening',
'archive_bank_account' => 'Archive Bank Account', 'archive_bank_account' => 'Archiveer bankrekening',
'archived_bank_account' => 'Successfully archived bank account', 'archived_bank_account' => 'Bankrekening succesvol gearchiveerd',
'created_bank_account' => 'Successfully created bank account', 'created_bank_account' => 'Bankrekening succesvol toegevoegd',
'validate_bank_account' => 'Validate Bank Account', 'validate_bank_account' => 'Bankrekening valideren',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>', 'bank_accounts_help' => 'Koppel een bankrekening om automatisch uitgaven en leveranciers te importeren. Ondersteund American Express en <a href="'.OFX_HOME_URL.'" target="_blank">400+ banken uit de VS.</a>',
'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.', 'bank_password_help' => 'Opmerking: uw wachtwoord wordt beveiligd verstuurd en wordt nooit op onze servers opgeslagen.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.', 'bank_password_warning' => 'Waarschuwing: uw wachtwoord wordt mogelijk als leesbare tekst verzonden, overweeg HTTPS in te schakelen.',
'username' => 'Username', 'username' => 'Gebruikersnaam',
'account_number' => 'Account Number', 'account_number' => 'Rekeningnummer',
'account_name' => 'Account Name', 'account_name' => 'Rekeninghouder',
'bank_account_error' => 'Failed to retreive account details, please check your credentials.', 'bank_account_error' => 'Het ophalen van rekeninggegevens is mislukt, controleer uw inloggegevens.',
'status_approved' => 'Approved', 'status_approved' => 'Goedgekeurd',
'quote_settings' => 'Quote Settings', 'quote_settings' => 'Offerte instellingen',
'auto_convert_quote' => 'Auto convert quote', 'auto_convert_quote' => 'Offerte automatisch omzetten',
'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.', 'auto_convert_quote_help' => 'Zet een offerte automatisch om in een factuur zodra deze door een klant wordt goedgekeurd.',
'validate' => 'Validate', 'validate' => 'Valideren',
'info' => 'Info', 'info' => 'Informatie',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)', 'imported_expenses' => 'Er zijn succesvol :count_vendors leverancier(s) en :count_expenses uitgaven aangemaakt.',
'iframe_url_help3' => 'Note: if you plan on accepting credit cards details we strongly recommend enabling HTTPS on your site.', 'iframe_url_help3' => 'Opmerking: als u van plan bent om creditcard betalingen te accepteren raden wij u dringend aan om HTTPS in te schakelen op uw website.',
'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.', 'expense_error_multiple_currencies' => 'De uitgaven kunnen geen verschillende munteenheden hebben.',
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.', 'expense_error_mismatch_currencies' => 'De munteenheid van de klant komt niet overeen met de munteenheid van de uitgave.',
'trello_roadmap' => 'Trello Roadmap', 'trello_roadmap' => 'Trello Roadmap',
'header_footer' => 'Header/Footer', 'header_footer' => 'Header/Footer',
'first_page' => 'first page', 'first_page' => 'eerste pagina',
'all_pages' => 'all pages', 'all_pages' => 'alle pagina\'s',
'last_page' => 'last page', 'last_page' => 'laatste pagina',
'all_pages_header' => 'Show header on', 'all_pages_header' => 'Toon header op',
'all_pages_footer' => 'Show footer on', 'all_pages_footer' => 'Toon footer op',
'invoice_currency' => 'Invoice Currency', 'invoice_currency' => 'Factuur valuta',
'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.', 'enable_https' => 'We raden u dringend aan om HTTPS te gebruiken om creditcard informatie digitaal te accepteren.',
'quote_issued_to' => 'Quote issued to', 'quote_issued_to' => 'Offerte uitgeschreven voor',
'show_currency_code' => 'Currency Code', 'show_currency_code' => 'Valutacode',
'trial_message' => 'Your account will receive a free two week trial of our pro plan.', 'trial_message' => 'Uw account zal een gratis twee weken durende probeerversie van ons pro plan krijgen.',
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.', 'trial_footer' => 'Uw gratis probeerversie duurt nog :count dagen, :link om direct te upgraden.',
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.', 'trial_footer_last_day' => 'Dit is de laatste dag van uw gratis probeerversie, :link om direct te upgraden.',
'trial_call_to_action' => 'Start Free Trial', 'trial_call_to_action' => 'Start gratis probeerversie',
'trial_success' => 'Successfully enabled two week free pro plan trial', 'trial_success' => 'De gratis twee weken durende probeerversie van het pro plan is succesvol geactiveerd.',
'overdue' => 'Overdue', 'overdue' => 'Verlopen',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Koop een &eacute;&eacute;n jaar geldige white label licentie van $'.WHITE_LABEL_PRICE.' om de Invoice Ninja logo\'s in het klantenportaal te verbergen en ons project te ondersteunen.',
'navigation' => 'Navigatie',
'list_invoices' => 'Toon Facturen',
'list_clients' => 'Toon Klanten',
'list_quotes' => 'Toon Offertes',
'list_tasks' => 'Toon Taken',
'list_expenses' => 'Toon Uitgaven',
'list_recurring_invoices' => 'Toon Terugkerende Facturen',
'list_payments' => 'Toon Betalingen',
'list_credits' => 'Toon Kredieten',
'tax_name' => 'Belasting naam',
'report_settings' => 'Rapport instellingen',
'search_hotkey' => 'Snelkoppeling is /',
'new_user' => 'Nieuwe Gebruiker',
'new_product' => 'Nieuw Product',
'new_tax_rate' => 'Nieuw BTW-tarief',
'invoiced_amount' => 'Gefactureerd bedrag',
'invoice_item_fields' => 'Factuurregels',
'custom_invoice_item_fields_help' => 'Voeg een veld toe bij het aanmaken van een factuurregel en toon het label met de waarde op de PDF.',
'recurring_invoice_number' => 'Nummer terugkerende factuur',
'recurring_invoice_number_prefix_help' => 'Kies een voorvoegsel voor het factuurnummer van terugkerende facturen. De standaard is: \'R\'.',
'enable_client_portal' => 'Dashboard',
'enable_client_portal_help' => 'Toon/verberg de dashboard pagina in het klantenportaal.',
// Client Passwords
'enable_portal_password'=>'Facturen beveiligen met een wachtwoord',
'enable_portal_password_help'=>'Geeft u de mogelijkheid om een wachtwoord in te stellen voor elke contactpersoon. Als er een wachtwoord is ingesteld moet de contactpersoon het wachtwoord invoeren voordat deze facturen kan bekijken.',
'send_portal_password'=>'Wachtwoord automatisch genereren',
'send_portal_password_help'=>'Als er geen wachtwoord is ingesteld zal deze automatisch worden gegenereerd en verzonden bij de eerste factuur.',
'expired' => 'Verlopen',
'invalid_card_number' => 'Het creditcardnummer is niet geldig.',
'invalid_expiry' => 'De verloopdatum is niet geldig.',
'invalid_cvv' => 'Het CVV-nummer is niet geldig.',
'cost' => 'Kosten',
'create_invoice_for_sample' => 'Opmerking: maak uw eerste factuur om hier een voorbeeld te zien.',
// User Permissions
'owner' => 'Eigenaar',
'administrator' => 'Beheerder',
'administrator_help' => 'Geef gebruiker de toestemming om andere gebruikers te beheren, instellingen te wijzigen en alle regels te bewerken.',
'user_create_all' => 'Aanmaken van klanten, facturen, enz.',
'user_view_all' => 'Bekijken van klanten, facturen, enz.',
'user_edit_all' => 'Bewerken van alle klanten, facturen, enz.',
'gateway_help_20' => ':link om aan te melden voor Sage Pay.',
'gateway_help_21' => ':link om aan te melden voor Sage Pay.',
'partial_due' => 'Gedeeltelijke vervaldatum',
'restore_vendor' => 'Leverancier herstellen',
'restored_vendor' => 'Leverancier succesvol hersteld',
'restored_expense' => 'Uitgave succesvol hersteld',
'permissions' => 'Rechten',
'create_all_help' => 'Gebruiker toestemming geven om nieuwe regels aan te maken en te bewerken',
'view_all_help' => 'Gebruiker toestemming geven om regels te bekijken die hij niet heeft gemaakt',
'edit_all_help' => 'Gebruiker toestemming geven om regels te bewerken die hij niet heeft gemaakt',
'view_payment' => 'Betaling bekijken',
'january' => 'januari',
'february' => 'februari',
'march' => 'maart',
'april' => 'april',
'may' => 'mei',
'june' => 'juni',
'july' => 'juli',
'august' => 'augustus',
'september' => 'september',
'october' => 'oktober',
'november' => 'november',
'december' => 'december',
); );

View File

@ -17,14 +17,14 @@ return array(
"active_url" => ":attribute is geen geldige URL.", "active_url" => ":attribute is geen geldige URL.",
"after" => ":attribute moet een datum na :date zijn.", "after" => ":attribute moet een datum na :date zijn.",
"alpha" => ":attribute mag alleen letters bevatten.", "alpha" => ":attribute mag alleen letters bevatten.",
"alpha_dash" => ":attribute mag alleen letters, nummers, onderstreep(_) en strepen(-) bevatten.", "alpha_dash" => ":attribute mag alleen letters, nummers, lage streep (_) en liggende streep (-) bevatten.",
"alpha_num" => ":attribute mag alleen letters en nummers bevatten.", "alpha_num" => ":attribute mag alleen letters en nummers bevatten.",
"array" => ":attribute moet geselecteerde elementen bevatten.", "array" => ":attribute moet geselecteerde elementen bevatten.",
"before" => ":attribute moet een datum voor :date zijn.", "before" => ":attribute moet een datum voor :date zijn.",
"between" => array( "between" => array(
"numeric" => ":attribute moet tussen :min en :max zijn.", "numeric" => ":attribute moet tussen :min en :max zijn.",
"file" => ":attribute moet tussen :min en :max kilobytes zijn.", "file" => ":attribute moet tussen :min en :max kilobytes zijn.",
"string" => ":attribute moet tussen :min en :max karakters zijn.", "string" => ":attribute moet tussen :min en :max tekens zijn.",
"array" => ":attribute moet tussen :min en :max items bevatten.", "array" => ":attribute moet tussen :min en :max items bevatten.",
), ),
"confirmed" => ":attribute bevestiging komt niet overeen.", "confirmed" => ":attribute bevestiging komt niet overeen.",
@ -47,14 +47,14 @@ return array(
"max" => array( "max" => array(
"numeric" => ":attribute moet minder dan :max zijn.", "numeric" => ":attribute moet minder dan :max zijn.",
"file" => ":attribute moet minder dan :max kilobytes zijn.", "file" => ":attribute moet minder dan :max kilobytes zijn.",
"string" => ":attribute moet minder dan :max karakters zijn.", "string" => ":attribute moet minder dan :max tekens zijn.",
"array" => ":attribute mag maximaal :max items bevatten.", "array" => ":attribute mag maximaal :max items bevatten.",
), ),
"mimes" => ":attribute moet een bestand zijn van het bestandstype :values.", "mimes" => ":attribute moet een bestand zijn van het bestandstype :values.",
"min" => array( "min" => array(
"numeric" => ":attribute moet minimaal :min zijn.", "numeric" => ":attribute moet minimaal :min zijn.",
"file" => ":attribute moet minimaal :min kilobytes zijn.", "file" => ":attribute moet minimaal :min kilobytes zijn.",
"string" => ":attribute moet minimaal :min karakters zijn.", "string" => ":attribute moet minimaal :min tekens zijn.",
"array" => ":attribute moet minimaal :min items bevatten.", "array" => ":attribute moet minimaal :min items bevatten.",
), ),
"not_in" => "Het geselecteerde :attribute is ongeldig.", "not_in" => "Het geselecteerde :attribute is ongeldig.",
@ -70,7 +70,7 @@ return array(
"size" => array( "size" => array(
"numeric" => ":attribute moet :size zijn.", "numeric" => ":attribute moet :size zijn.",
"file" => ":attribute moet :size kilobyte zijn.", "file" => ":attribute moet :size kilobyte zijn.",
"string" => ":attribute moet :size karakters lang zijn.", "string" => ":attribute moet :size tekens lang zijn.",
"array" => ":attribute moet :size items bevatten.", "array" => ":attribute moet :size items bevatten.",
), ),
"unique" => ":attribute is al in gebruik.", "unique" => ":attribute is al in gebruik.",
@ -81,7 +81,7 @@ return array(
"notmasked" => "De waarden zijn verborgen", "notmasked" => "De waarden zijn verborgen",
"less_than" => 'Het :attribute moet minder zijn dan :value', "less_than" => 'Het :attribute moet minder zijn dan :value',
"has_counter" => 'De waarde moet {$counter} bevatten', "has_counter" => 'De waarde moet {$counter} bevatten',
"valid_contacts" => "Alle contacten moeten een e-mailadres of een naam hebben", "valid_contacts" => "Alle contactpersonen moeten een e-mailadres of een naam hebben",
"valid_invoice_items" => "De factuur overschrijd het maximale aantal", "valid_invoice_items" => "De factuur overschrijd het maximale aantal",
/* /*

View File

@ -1123,4 +1123,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -1128,4 +1128,73 @@ return array(
'overdue' => 'Overdue', 'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'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.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
); );

View File

@ -6,7 +6,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS])
{!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} {!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!}
{!! Former::populate($account) !!} {!! Former::populateField('token_billing_type_id', $account->token_billing_type_id) !!}
<div class="panel panel-default"> <div class="panel panel-default">
@ -63,7 +63,7 @@
@foreach ($gateway->fields as $field => $details) @foreach ($gateway->fields as $field => $details)
@if ($details && !$accountGateway) @if ($details && !$accountGateway && !is_array($details))
{!! Former::populateField($gateway->id.'_'.$field, $details) !!} {!! Former::populateField($gateway->id.'_'.$field, $details) !!}
@endif @endif

View File

@ -5,7 +5,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_API_TOKENS, 'advanced' => true]) @include('accounts.nav', ['selected' => ACCOUNT_API_TOKENS, 'advanced' => true])
<div class="pull-right"> <div class="pull-right">
{!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/knowledgebase/api-documentation/')->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-sign')) !!} {!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/api-documentation/')->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-sign')) !!}
@if (Utils::isNinja()) @if (Utils::isNinja())
{!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!} {!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!}
@endif @endif
@ -51,4 +51,9 @@
</script> </script>
@if (Utils::isNinja() && !Utils::isReseller())
<p>&nbsp;</p>
<script src="https://zapier.com/zapbook/embed/widget.js?services=invoice-ninja&container=false&limit=6"></script>
@endif
@stop @stop

View File

@ -13,6 +13,7 @@
->addClass('warn-on-exit') !!} ->addClass('warn-on-exit') !!}
{!! Former::populateField('enable_client_portal', intval($account->enable_client_portal)) !!} {!! Former::populateField('enable_client_portal', intval($account->enable_client_portal)) !!}
{!! Former::populateField('enable_client_portal_dashboard', intval($account->enable_client_portal_dashboard)) !!}
{!! Former::populateField('client_view_css', $client_view_css) !!} {!! Former::populateField('client_view_css', $client_view_css) !!}
{!! Former::populateField('enable_portal_password', intval($enable_portal_password)) !!} {!! Former::populateField('enable_portal_password', intval($enable_portal_password)) !!}
{!! Former::populateField('send_portal_password', intval($send_portal_password)) !!} {!! Former::populateField('send_portal_password', intval($send_portal_password)) !!}
@ -28,6 +29,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_CLIENT_PORTAL]) @include('accounts.nav', ['selected' => ACCOUNT_CLIENT_PORTAL])
<div class="row"> <div class="row">
<div class="col-md-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.client_portal') !!}</h3> <h3 class="panel-title">{!! trans('texts.client_portal') !!}</h3>
@ -38,6 +40,11 @@
->text(trans('texts.enable')) ->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_help')) !!} ->help(trans('texts.enable_client_portal_help')) !!}
</div> </div>
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_client_portal_dashboard')
->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_dashboard_help')) !!}
</div>
<div class="col-md-10 col-md-offset-1"> <div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_portal_password') {!! Former::checkbox('enable_portal_password')
->text(trans('texts.enable_portal_password')) ->text(trans('texts.enable_portal_password'))
@ -67,6 +74,7 @@
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!} ->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>

View File

@ -51,8 +51,8 @@
<div class="form-group"> <div class="form-group">
<div class="col-lg-4 col-sm-4"></div> <div class="col-lg-4 col-sm-4"></div>
<div class="col-lg-8 col-sm-8"> <div class="col-lg-8 col-sm-8">
<a href="/{{ $account->getLogoPath().'?no_cache='.time() }}" target="_blank"> <a href="{{ $account->getLogoUrl(true) }}" target="_blank">
{!! HTML::image($account->getLogoPath().'?no_cache='.time(), 'Logo', ['max-width' => 200]) !!} {!! HTML::image($account->getLogoUrl(true), 'Logo', ['style' => 'max-width:300px']) !!}
</a> &nbsp; </a> &nbsp;
<a href="#" onclick="deleteLogo()">{{ trans('texts.remove_logo') }}</a> <a href="#" onclick="deleteLogo()">{{ trans('texts.remove_logo') }}</a>
</div> </div>

View File

@ -19,6 +19,7 @@
])->addClass('warn-on-exit') !!} ])->addClass('warn-on-exit') !!}
{{ Former::populate($account) }} {{ Former::populate($account) }}
{{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }} {{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }}
{{ Former::populateField('document_email_attachment', intval($account->document_email_attachment)) }}
{{ Former::populateField('enable_email_markup', intval($account->enable_email_markup)) }} {{ Former::populateField('enable_email_markup', intval($account->enable_email_markup)) }}
<div class="panel panel-default"> <div class="panel panel-default">
@ -27,6 +28,7 @@
</div> </div>
<div class="panel-body form-padding-right"> <div class="panel-body form-padding-right">
{!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!} {!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!}
{!! Former::checkbox('document_email_attachment')->text(trans('texts.enable')) !!}
&nbsp; &nbsp;
@ -50,7 +52,7 @@
->help(trans('texts.subdomain_help')) !!} ->help(trans('texts.subdomain_help')) !!}
{!! Former::text('iframe_url') {!! Former::text('iframe_url')
->placeholder('http://www.example.com/invoice') ->placeholder('https://www.example.com/invoice')
->appendIcon('question-sign') ->appendIcon('question-sign')
->addGroupClass('iframe_url') ->addGroupClass('iframe_url')
->label(' ') ->label(' ')

View File

@ -48,6 +48,7 @@
function getPDFString(cb) { function getPDFString(cb) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!}; invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
invoice.account.hide_quantity = $('#hide_quantity').is(":checked"); invoice.account.hide_quantity = $('#hide_quantity').is(":checked");
invoice.account.invoice_embed_documents = $('#invoice_embed_documents').is(":checked");
invoice.account.hide_paid_to_date = $('#hide_paid_to_date').is(":checked"); invoice.account.hide_paid_to_date = $('#hide_paid_to_date').is(":checked");
invoice.invoice_design_id = $('#invoice_design_id').val(); invoice.invoice_design_id = $('#invoice_design_id').val();
@ -207,6 +208,7 @@
{!! Former::checkbox('hide_quantity')->text(trans('texts.hide_quantity_help')) !!} {!! Former::checkbox('hide_quantity')->text(trans('texts.hide_quantity_help')) !!}
{!! Former::checkbox('hide_paid_to_date')->text(trans('texts.hide_paid_to_date_help')) !!} {!! Former::checkbox('hide_paid_to_date')->text(trans('texts.hide_paid_to_date_help')) !!}
{!! Former::checkbox('invoice_embed_documents')->text(trans('texts.invoice_embed_documents_help')) !!}
</div> </div>
</div> </div>

View File

@ -12,6 +12,8 @@
<div class="row"> <div class="row">
<div class="col-md-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.localization') !!}</h3> <h3 class="panel-title">{!! trans('texts.localization') !!}</h3>
@ -34,6 +36,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<center> <center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} {!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}

View File

@ -6,7 +6,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_SYSTEM_SETTINGS]) @include('accounts.nav', ['selected' => ACCOUNT_SYSTEM_SETTINGS])
<div class="row"> <div class="row">
<div class="col-md-12">
{!! Former::open('/update_setup') {!! Former::open('/update_setup')
->addClass('warn-on-exit') ->addClass('warn-on-exit')
->autocomplete('off') ->autocomplete('off')
@ -23,6 +23,7 @@
@include('partials.system_settings') @include('partials.system_settings')
</div> </div>
</div>
<center> <center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} {!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}

View File

@ -201,6 +201,13 @@
var keys = {!! json_encode(\App\Ninja\Mailers\ContactMailer::$variableFields) !!}; var keys = {!! json_encode(\App\Ninja\Mailers\ContactMailer::$variableFields) !!};
var passwordHtml = "{!! $account->isPro() && $account->enable_portal_password && $account->send_portal_password?'<p>'.trans('texts.password').': 6h2NWNdw6<p>':'' !!}"; var passwordHtml = "{!! $account->isPro() && $account->enable_portal_password && $account->send_portal_password?'<p>'.trans('texts.password').': 6h2NWNdw6<p>':'' !!}";
@if ($account->isPro())
var documentsHtml = "{!! trans('texts.email_documents_header').'<ul><li><a>'.trans('texts.email_documents_example_1').'</a></li><li><a>'.trans('texts.email_documents_example_2').'</a></li></ul>' !!}";
@else
var documentsHtml = "";
@endif
var vals = [ var vals = [
{!! json_encode($emailFooter) !!}, {!! json_encode($emailFooter) !!},
"{{ $account->getDisplayName() }}", "{{ $account->getDisplayName() }}",
@ -213,6 +220,7 @@
"0001", "0001",
"0001", "0001",
passwordHtml, passwordHtml,
documentsHtml,
"{{ URL::to('/view/...') }}$password", "{{ URL::to('/view/...') }}$password",
'{!! Form::flatButton('view_invoice', '#0b4d78') !!}$password', '{!! Form::flatButton('view_invoice', '#0b4d78') !!}$password',
"{{ URL::to('/payment/...') }}$password", "{{ URL::to('/payment/...') }}$password",

View File

@ -105,7 +105,7 @@
@endif @endif
<p class="link"> <p class="link">
{!! link_to('/forgot', trans('texts.forgot_password')) !!} {!! link_to('/recover_password', trans('texts.recover_password')) !!}
{!! link_to(NINJA_WEB_URL.'/knowledgebase/', trans('texts.knowledge_base'), ['target' => '_blank', 'class' => 'pull-right']) !!} {!! link_to(NINJA_WEB_URL.'/knowledgebase/', trans('texts.knowledge_base'), ['target' => '_blank', 'class' => 'pull-right']) !!}
</p> </p>

View File

@ -53,7 +53,7 @@
@section('body') @section('body')
<div class="container"> <div class="container">
{!! Former::open('forgot')->rules(['email' => 'required|email'])->addClass('form-signin') !!} {!! Former::open('recover_password')->rules(['email' => 'required|email'])->addClass('form-signin') !!}
<div class="modal-header"> <div class="modal-header">
<img src="{{ asset('images/icon-login.png') }}" /> <img src="{{ asset('images/icon-login.png') }}" />
<h4>Invoice Ninja | {{ trans('texts.password_recovery') }}</h4></div> <h4>Invoice Ninja | {{ trans('texts.password_recovery') }}</h4></div>

Some files were not shown because too many files have changed in this diff Show More