1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-23 09:51:35 +02: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

@ -41,4 +41,30 @@ API_SECRET=password
#GOOGLE_CLIENT_ID=
#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 invoices;' 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
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.es.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/accounting/accounting.min.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/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/dropzone/dist/min/dropzone.min.css',
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',
@ -169,7 +171,7 @@ module.exports = function(grunt) {
'public/js/pdf_viewer.js',
'public/js/compatibility.js',
'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js',
'public/js/vfs.js',
],
dest: 'public/pdf.built.js',
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 = [
'App\Console\Commands\SendRecurringInvoices',
'App\Console\Commands\RemoveOrphanedDocuments',
'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices',

View File

@ -4,7 +4,11 @@ use Redirect;
use Utils;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
class Handler extends ExceptionHandler {
@ -14,7 +18,10 @@ class Handler extends ExceptionHandler {
* @var array
*/
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)
{
// don't show these errors in the logs
if ($e instanceof HttpResponseException) {
return false;
}
if (Utils::isNinja()) {
Utils::logError(Utils::getErrorString($e));
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
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {
$data = [
@ -70,5 +85,6 @@ class Handler extends ExceptionHandler {
} else {
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('/');
}
$config = "APP_ENV=production\n".
"APP_DEBUG={$app['debug']}\n".
"APP_URL={$app['url']}\n".
"APP_KEY={$app['key']}\n\n".
"DB_TYPE={$dbType}\n".
"DB_HOST={$database['type']['host']}\n".
"DB_DATABASE={$database['type']['database']}\n".
"DB_USERNAME={$database['type']['username']}\n".
"DB_PASSWORD={$database['type']['password']}\n\n".
"MAIL_DRIVER={$mail['driver']}\n".
"MAIL_PORT={$mail['port']}\n".
"MAIL_ENCRYPTION={$mail['encryption']}\n".
"MAIL_HOST={$mail['host']}\n".
"MAIL_USERNAME={$mail['username']}\n".
"MAIL_FROM_NAME={$mail['from']['name']}\n".
"MAIL_PASSWORD={$mail['password']}\n\n".
"PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
$_ENV['APP_ENV'] = 'production';
$_ENV['APP_DEBUG'] = $app['debug'];
$_ENV['APP_URL'] = $app['url'];
$_ENV['APP_KEY'] = $app['key'];
$_ENV['DB_TYPE'] = $dbType;
$_ENV['DB_HOST'] = $database['type']['host'];
$_ENV['DB_DATABASE'] = $database['type']['database'];
$_ENV['DB_USERNAME'] = $database['type']['username'];
$_ENV['DB_PASSWORD'] = $database['type']['password'];
$_ENV['MAIL_DRIVER'] = $mail['driver'];
$_ENV['MAIL_PORT'] = $mail['port'];
$_ENV['MAIL_ENCRYPTION'] = $mail['encryption'];
$_ENV['MAIL_HOST'] = $mail['host'];
$_ENV['MAIL_USERNAME'] = $mail['username'];
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_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
$fp = fopen(base_path()."/.env", 'w');
@ -166,6 +178,9 @@ class AppController extends BaseController
$config = '';
foreach ($_ENV as $key => $val) {
if (preg_match('/\s/',$val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n";
}
@ -311,4 +326,4 @@ class AppController extends BaseController
return json_encode($data);
}
}
}

View File

@ -112,10 +112,10 @@ class ClientController extends BaseController
$actionLinks = [];
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()) {
$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)){
@ -123,15 +123,15 @@ class ClientController extends BaseController
}
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()){
$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()){
$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(

View File

@ -11,7 +11,7 @@ class DashboardApiController extends BaseAPIController
{
$view_all = !Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients
$select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients,
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
@ -25,17 +25,17 @@ class DashboardApiController extends BaseAPIController
->where('invoices.is_deleted', '=', false)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false);
if(!$view_all){
$metrics = $metrics->where(function($query) use($user_id){
$query->where('invoices.user_id', '=', $user_id);
$query->orwhere(function($query) use($user_id){
$query->where('invoices.user_id', '=', null);
$query->where('invoices.user_id', '=', null);
$query->where('clients.user_id', '=', $user_id);
});
});
}
$metrics = $metrics->groupBy('accounts.id')
->first();
@ -45,11 +45,11 @@ class DashboardApiController extends BaseAPIController
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false);
if(!$view_all){
$paidToDate = $paidToDate->where('clients.user_id', '=', $user_id);
}
$paidToDate = $paidToDate->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
@ -64,11 +64,11 @@ class DashboardApiController extends BaseAPIController
->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false);
if(!$view_all){
$averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id);
}
$averageInvoice = $averageInvoice->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
@ -96,11 +96,11 @@ class DashboardApiController extends BaseAPIController
->where('invoices.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '<', date('Y-m-d'));
if(!$view_all){
$pastDue = $pastDue->where('invoices.user_id', '=', $user_id);
}
$pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->orderBy('invoices.due_date', 'asc')
->take(50)
@ -120,11 +120,11 @@ class DashboardApiController extends BaseAPIController
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '>=', date('Y-m-d'))
->orderBy('invoices.due_date', 'asc');
if(!$view_all){
$upcoming = $upcoming->where('invoices.user_id', '=', $user_id);
}
$upcoming = $upcoming->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->get();
@ -139,11 +139,11 @@ class DashboardApiController extends BaseAPIController
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true);
if(!$view_all){
$payments = $payments->where('payments.user_id', '=', $user_id);
}
$payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id'])
->orderBy('payments.payment_date', 'desc')
->take(50)
@ -158,20 +158,22 @@ class DashboardApiController extends BaseAPIController
}
}
$data = [
'paidToDate' => $paidToDate,
'balances' => $balances,
'averageInvoice' => $averageInvoice,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'payments' => $payments,
'title' => trans('texts.dashboard'),
'hasQuotes' => $hasQuotes,
];
return $this->response($data);
$data = [
'id' => 1,
'paidToDate' => $paidToDate[0]->value,
'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,
'activeClients' => $metrics ? $metrics->active_clients : 0,
];
return $this->response($data);
}
}

View File

@ -11,7 +11,7 @@ class DashboardController extends BaseController
{
public function index()
{
$view_all = !Auth::user()->hasPermission('view_all');
$view_all = Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients
@ -105,6 +105,7 @@ class DashboardController extends BaseController
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null)
@ -129,6 +130,7 @@ class DashboardController extends BaseController
->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->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)
{
$expense = Expense::scope($publicId)->firstOrFail();
$expense = Expense::scope($publicId)->with('documents')->firstOrFail();
if(!$this->checkEditPermission($expense, $response)){
return $response;
@ -146,12 +146,6 @@ class ExpenseController extends BaseController
$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);
}
@ -163,7 +157,14 @@ class ExpenseController extends BaseController
*/
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'));
@ -177,7 +178,14 @@ class ExpenseController extends BaseController
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'));
@ -195,8 +203,7 @@ class ExpenseController extends BaseController
$expenses = Expense::scope($ids)->with('client')->get();
$clientPublicId = 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
foreach ($expenses as $expense)
{
@ -220,19 +227,11 @@ class ExpenseController extends BaseController
Session::flash('error', trans('texts.expense_error_invoiced'));
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}")
->with('expenseCurrencyId', $currencyId)
->with('expenses', $data);
->with('expenses', $ids);
break;
default:

View File

@ -166,6 +166,7 @@ class InvoiceApiController extends BaseAPIController
'state',
'postal_code',
'private_notes',
'currency_code',
] as $field) {
if (isset($data[$field])) {
$clientData[$field] = $data[$field];
@ -258,10 +259,11 @@ class InvoiceApiController extends BaseAPIController
// initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)];
// 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_rate']);
unset($data['invoice_items'][0]['tax_name1']);
unset($data['invoice_items'][0]['tax_rate1']);
unset($data['invoice_items'][0]['tax_name2']);
unset($data['invoice_items'][0]['tax_rate2']);
} else {
foreach ($data['invoice_items'] as $index => $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\Account;
use App\Models\Product;
use App\Models\Expense;
use App\Models\TaxRate;
use App\Models\InvoiceDesign;
use App\Models\Activity;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Services\InvoiceService;
use App\Services\RecurringInvoiceService;
use App\Http\Requests\SaveInvoiceWithClientRequest;
@ -32,11 +34,12 @@ class InvoiceController extends BaseController
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $documentRepo;
protected $invoiceService;
protected $recurringInvoiceService;
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();
@ -89,7 +92,7 @@ class InvoiceController extends BaseController
{
$account = Auth::user()->account;
$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()
->firstOrFail();
@ -155,6 +158,14 @@ class InvoiceController extends BaseController
if (!$invoice->is_recurring && $invoice->balance > 0) {
$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) {
@ -183,7 +194,7 @@ class InvoiceController extends BaseController
'isRecurring' => $invoice->is_recurring,
'actions' => $actions,
'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel());
$data = array_merge($data, self::getViewModel($invoice));
if ($clone) {
$data['formIsChanged'] = true;
@ -203,6 +214,7 @@ class InvoiceController extends BaseController
$contact->email_error = $invitation->email_error;
$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_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();
}
}
@ -221,7 +233,7 @@ class InvoiceController extends BaseController
return $response;
}
$account = Auth::user()->account;
$account = Auth::user()->account;
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
$clientId = null;
@ -231,6 +243,11 @@ class InvoiceController extends BaseController
$invoice = $account->createInvoice($entityType, $clientId);
$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');
if(!Auth::user()->hasPermission('view_all')){
@ -245,7 +262,7 @@ class InvoiceController extends BaseController
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
];
$data = array_merge($data, self::getViewModel());
$data = array_merge($data, self::getViewModel($invoice));
return View::make('invoices.edit', $data);
}
@ -255,7 +272,7 @@ class InvoiceController extends BaseController
return self::create($clientPublicId, true);
}
private static function getViewModel()
private static function getViewModel($invoice)
{
$recurringHelp = '';
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 [
'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'),
'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'),
'languages' => Cache::get('languages'),
'sizes' => Cache::get('sizes'),
@ -342,7 +385,6 @@ class InvoiceController extends BaseController
'recurringDueDateHelp' => $recurringDueDateHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'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,
];
@ -356,6 +398,7 @@ class InvoiceController extends BaseController
public function store(SaveInvoiceWithClientRequest $request)
{
$data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
@ -396,6 +439,7 @@ class InvoiceController extends BaseController
public function update(SaveInvoiceWithClientRequest $request)
{
$data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
@ -526,7 +570,7 @@ class InvoiceController extends BaseController
public function invoiceHistory($publicId)
{
$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->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro();

View File

@ -460,6 +460,8 @@ class PaymentController extends BaseController
$ref = $response->getData()['m_payment_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
$ref = $response->getData()['signature'];
} elseif ($accountGateway->gateway_id == GATEWAY_CYBERSOURCE) {
$ref = $response->getData()['transaction_uuid'];
} else {
$ref = $response->getTransactionReference();
}
@ -551,7 +553,15 @@ class PaymentController extends BaseController
}
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_CHECKOUT_COM)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
@ -572,11 +582,9 @@ class PaymentController extends BaseController
} else {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to($invitation->getLink());
}
} catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to($invitation->getLink());
}
@ -642,6 +650,6 @@ class PaymentController extends BaseController
$message .= $error ?: trans('texts.payment_error');
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 Utils;
use Request;
use Response;
use Session;
use Datatable;
use App\Models\Gateway;
use App\Models\Invitation;
use App\Models\Document;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService;
use Barracuda\ArchiveStream\ZipArchive;
class PublicClientController extends BaseController
{
private $invoiceRepo;
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->paymentRepo = $paymentRepo;
$this->activityRepo = $activityRepo;
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService;
}
@ -117,8 +123,9 @@ class PublicClientController extends BaseController
'showApprove' => $showApprove,
'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(),
'hideDashboard' => !$account->enable_client_portal,
'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(),
@ -132,6 +139,15 @@ class PublicClientController extends BaseController
'checkoutComDebug' => $checkoutComDebug,
'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);
}
@ -196,7 +212,7 @@ class PublicClientController extends BaseController
$client = $invoice->client;
$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();
}
@ -205,6 +221,7 @@ class PublicClientController extends BaseController
'account' => $account,
'client' => $client,
'hideLogo' => $account->isWhiteLabel(),
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
];
@ -245,13 +262,20 @@ class PublicClientController extends BaseController
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,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'),
@ -278,12 +302,17 @@ class PublicClientController extends BaseController
return $this->returnError();
}
$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 = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT,
@ -302,7 +331,7 @@ class PublicClientController extends BaseController
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
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('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); })
@ -315,13 +344,19 @@ class PublicClientController extends BaseController
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,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'),
@ -342,6 +377,44 @@ class PublicClientController extends BaseController
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)
{
return response()->view('error', [
@ -372,5 +445,148 @@ class PublicClientController extends BaseController
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\Ninja\Repositories\InvoiceRepository;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\QuoteTransformer;
use App\Ninja\Transformers\InvoiceTransformer;
class QuoteApiController extends BaseAPIController
{
@ -53,7 +53,7 @@ class QuoteApiController extends BaseAPIController
$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();
$data = $this->createCollection($invoices, $transformer, 'quotes', $paginator);

View File

@ -111,10 +111,27 @@ class QuoteController extends BaseController
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 [
'entityType' => ENTITY_QUOTE,
'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->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));
if ($isExport) {
self::export($params['displayData'], $params['columns'], $params['reportTotals']);
self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
}
}
if ($enableChart) {
@ -158,8 +158,10 @@ class ReportController extends BaseController
}
$records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, '.$timeframe.' as '.$groupBy))
->where('account_id', '=', Auth::user()->account_id)
->select(DB::raw('sum('.$entityType.'s.amount) as total, '.$timeframe.' as '.$groupBy))
->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.'.$entityType.'_date', '>=', $startDate->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) {
$records->where('is_quote', '=', 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');
@ -354,8 +359,8 @@ class ReportController extends BaseController
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;
$displayData = [];
$reportTotals = [];
@ -379,19 +384,25 @@ class ReportController extends BaseController
}]);
foreach ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
foreach ($client->invoices as $invoice) {
$payments = count($invoice->payments) ? $invoice->payments : [false];
foreach ($payments as $payment) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment ? $payment->present()->payment_date : '',
$payment ? $account->formatMoney($payment->amount, $client) : '',
$payment ? $payment->present()->method : '',
];
if ($payment) {
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount);
}
}
foreach ($client->invoices as $invoice) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$account->formatMoney($invoice->getAmountPaid(), $client),
$account->formatMoney($invoice->balance, $client),
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $invoice->getAmountPaid());
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
}
}
@ -503,11 +514,14 @@ class ReportController extends BaseController
return $data;
}
private function export($data, $columns, $totals)
private function export($reportType, $data, $columns, $totals)
{
$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-Disposition:attachment;filename=ninja-report.csv');
header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv");
Utils::exportData($output, $data, Utils::trans($columns));

View File

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

View File

@ -107,7 +107,7 @@ class VendorController extends BaseController
Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$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(

View File

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

View File

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

View File

@ -26,21 +26,4 @@ class CreateClientRequest extends Request
'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',
];
}
/*
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('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_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/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/documents', 'PublicClientController@documentIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex');
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::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::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
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::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin'));
Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout'));
Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
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::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::get('dashboard', 'DashboardController@index');
Route::get('dashboard2', 'DashboardApiController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage');
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('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/{public_id}/clone', 'InvoiceController@cloneInvoice');
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::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
Route::get('dashboard', 'DashboardApiController@index');
// Vendor
Route::resource('vendors', 'VendorApiController');
@ -267,7 +278,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
});
// Redirects for legacy links
/*
Route::get('/rocksteady', function() {
return Redirect::to(NINJA_WEB_URL, 301);
});
@ -295,7 +305,7 @@ Route::get('/compare-online-invoicing{sites?}', function() {
Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301);
});
*/
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -310,6 +320,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEMS', 'invoice_items');
define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
@ -425,6 +436,10 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB
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_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto
@ -520,6 +535,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
@ -535,7 +551,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.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('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');
@ -708,4 +724,4 @@ if (Utils::isNinjaDev())
//ini_set('memory_limit','1024M');
//Auth::loginUsingId(1);
}
*/
*/

View File

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

View File

@ -8,7 +8,9 @@ use Event;
use Cache;
use App;
use File;
use App\Models\Document;
use App\Events\UserSettingsChanged;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
@ -384,26 +386,69 @@ class Account extends Eloquent
public function hasLogo()
{
return file_exists($this->getLogoFullPath());
if($this->logo == ''){
$this->calculateLogoDetails();
}
return !empty($this->logo);
}
public function getLogoDisk(){
return Storage::disk(env('LOGO_FILESYSTEM', 'logos'));
}
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 getLogoPath()
{
$fileName = 'logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
public function getLogoRaw(){
if(!$this->hasLogo()){
return null;
}
$disk = $this->getLogoDisk();
return $disk->get($this->logo);
}
public function getLogoFullPath()
public function getLogoURL($cachebuster = false)
{
$fileName = public_path() . '/logo/' . $this->account_key;
return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg';
}
public function getLogoURL()
{
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)
@ -419,24 +464,20 @@ class Account extends Eloquent
public function getLogoWidth()
{
$path = $this->getLogoFullPath();
if (!file_exists($path)) {
return 0;
if(!$this->hasLogo()){
return null;
}
list($width, $height) = getimagesize($path);
return $width;
return $this->logo_width;
}
public function getLogoHeight()
{
$path = $this->getLogoFullPath();
if (!file_exists($path)) {
return 0;
if(!$this->hasLogo()){
return null;
}
list($width, $height) = getimagesize($path);
return $height;
return $this->logo_height;
}
public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null)
@ -815,12 +856,11 @@ class Account extends Eloquent
public function getLogoSize()
{
if (!$this->hasLogo()) {
return 0;
if(!$this->hasLogo()){
return null;
}
$filename = $this->getLogoFullPath();
return round(File::size($filename) / 1000);
return round($this->logo_size / 1000);
}
public function isLogoTooLarge()
@ -948,7 +988,7 @@ class Account extends Eloquent
// 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;
} 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)
{
$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') {
$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();
}
$lastEntity = $className::withTrashed()
->scope(false, $entity->account_id)
->orderBy('public_id', 'DESC')
if(method_exists($className, 'withTrashed')){
$lastEntity = $className::withTrashed()
->scope(false, $entity->account_id);
} else {
$lastEntity = $className::scope(false, $entity->account_id);
}
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first();
if ($lastEntity) {

View File

@ -53,6 +53,11 @@ class Expense extends EntityModel
return $this->belongsTo('App\Models\Invoice')->withTrashed();
}
public function documents()
{
return $this->hasMany('App\Models\Document')->orderBy('id');
}
public function getName()
{
if($this->expense_number)
@ -80,6 +85,20 @@ class Expense extends EntityModel
{
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) {

View File

@ -29,7 +29,9 @@ class Invitation extends EntityModel
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) {
$this->load('account');
@ -39,8 +41,8 @@ class Invitation extends EntityModel
$iframe_url = $this->account->iframe_url;
if ($this->account->isPro()) {
if ($iframe_url) {
return "{$iframe_url}/?{$this->invitation_key}";
if ($iframe_url && !$forceOnsite) {
return "{$iframe_url}?{$this->invitation_key}";
} elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain);
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
<?php namespace App\Ninja\Mailers;
use Form;
use HTML;
use Utils;
use Event;
use URL;
@ -28,6 +29,7 @@ class ContactMailer extends Mailer
'invoice',
'quote',
'password',
'documents',
'viewLink',
'viewButton',
'paymentLink',
@ -59,9 +61,32 @@ class ContactMailer extends Mailer
if ($account->attatchPDF() && !$pdfString) {
$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) {
$response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString);
$response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString, $documentStrings);
if ($response === true) {
$sent = true;
}
@ -80,7 +105,7 @@ class ContactMailer extends Mailer
return $response;
}
private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString)
private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString, $documentStrings)
{
$client = $invoice->client;
$account = $invoice->account;
@ -127,6 +152,7 @@ class ContactMailer extends Mailer
'account' => $account,
'client' => $client,
'invoice' => $invoice,
'documents' => $documentStrings,
];
if ($account->attatchPDF()) {
@ -263,7 +289,21 @@ class ContactMailer extends Mailer
$invitation = $data['invitation'];
$invoice = $invitation->invoice;
$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 = [
'$footer' => $account->getEmailFooter(),
'$client' => $client->getDisplayName(),
@ -285,6 +325,7 @@ class ContactMailer extends Mailer
'$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1,
'$customInvoice2' => $account->custom_invoice_text_label2,
'$documents' => $documentsHTML,
];
// Add variables for available payment types

View File

@ -44,6 +44,13 @@ class Mailer
if (!empty($data['pdfString']) && !empty($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);
@ -81,7 +88,7 @@ class Mailer
$emailError = $exception->getMessage();
}
Utils::logError("Email Error: $emailError");
//Utils::logError("Email Error: $emailError");
if (isset($data['invitation'])) {
$invitation = $data['invitation'];

View File

@ -16,14 +16,9 @@ class ExpensePresenter extends Presenter {
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()
{
return $this->entity->invoice_id ? $this->converted_amount() : 0;
return $this->entity->invoice_id ? $this->entity->convertedAmount() : 0;
}
public function link()

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters;
use URL;
use Utils;
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_name = $user->account->getDisplayName();
$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;
}

View File

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

View File

@ -100,6 +100,11 @@ class ClientRepository extends BaseRepository
$contacts = isset($data['contact']) ? [$data['contact']] : $data['contacts'];
$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) {
$contact = $client->addContact($contact, $first);
$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 App\Models\Expense;
use App\Models\Vendor;
use App\Models\Document;
use App\Ninja\Repositories\BaseRepository;
use Session;
class ExpenseRepository extends BaseRepository
{
protected $documentRepo;
// Expenses
public function getClassName()
{
return 'App\Models\Expense';
}
public function __construct(DocumentRepository $documentRepo)
{
$this->documentRepo = $documentRepo;
}
public function all()
{
return Expense::scope()
@ -113,7 +121,7 @@ class ExpenseRepository extends BaseRepository
return $query;
}
public function save($input)
public function save($input, $checkSubPermissions=false)
{
$publicId = isset($input['public_id']) ? $input['public_id'] : false;
@ -144,9 +152,46 @@ class ExpenseRepository extends BaseRepository
$rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1;
$expense->exchange_rate = round($rate, 4);
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
$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;
}

View File

@ -2,24 +2,29 @@
use DB;
use Utils;
use Session;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
use App\Models\Product;
use App\Models\Task;
use App\Models\Document;
use App\Models\Expense;
use App\Services\PaymentService;
use App\Ninja\Repositories\BaseRepository;
class InvoiceRepository extends BaseRepository
{
protected $documentRepo;
public function getClassName()
{
return 'App\Models\Invoice';
}
public function __construct(PaymentService $paymentService)
public function __construct(PaymentService $paymentService, DocumentRepository $documentRepo)
{
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService;
}
@ -216,6 +221,8 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::scope($publicId)->firstOrFail();
}
$invoice->fill($data);
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
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;
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
$invoice->tax_name = trim($data['tax_name']);
} else {
$invoice->tax_rate = 0;
$invoice->tax_name = '';
// provide backwards compatability
if (isset($data['tax_name']) && isset($data['tax_rate'])) {
$data['tax_name1'] = $data['tax_name'];
$data['tax_rate1'] = $data['tax_rate'];
}
$total = 0;
@ -318,20 +323,24 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$lineTotal = $invoiceItemCost * $invoiceItemQty;
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$lineTotal -= round(($lineTotal/$total) * $invoice->discount, 2);
} else {
$lineTotal -= round($lineTotal * ($invoice->discount/100), 2);
}
}
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);
}
}
@ -373,8 +382,9 @@ class InvoiceRepository extends BaseRepository
$total += $invoice->custom_value2;
}
$total += $total * $invoice->tax_rate / 100;
$total = round($total, 2);
$taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2);
$taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2);
$total = round($total + $taxAmount1 + $taxAmount2, 2);
$total += $itemTax;
// custom fields not charged taxes
@ -397,6 +407,53 @@ class InvoiceRepository extends BaseRepository
if ($publicId) {
$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) {
$item = (array) $item;
@ -450,7 +507,7 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']);
$invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0;
//$invoiceItem->tax_rate = 0;
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
@ -459,11 +516,14 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->custom_value2 = $item['custom_value2'];
}
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']);
// provide backwards compatability
if (isset($item['tax_name']) && isset($item['tax_rate'])) {
$item['tax_name1'] = $item['tax_name'];
$item['tax_rate1'] = $item['tax_rate'];
}
$invoiceItem->fill($item);
$invoice->invoice_items()->save($invoiceItem);
}
@ -494,14 +554,13 @@ class InvoiceRepository extends BaseRepository
}
}
$clone->invoice_number = $invoiceNumber ?: $account->getNextInvoiceNumber($clone);
$clone->invoice_date = date_create()->format('Y-m-d');
foreach ([
'client_id',
'discount',
'is_amount_discount',
'invoice_date',
'po_number',
'due_date',
'is_recurring',
'frequency_id',
'start_date',
@ -510,8 +569,10 @@ class InvoiceRepository extends BaseRepository
'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',
'tax_rate',
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
'amount',
'is_quote',
'custom_value1',
@ -545,14 +606,22 @@ class InvoiceRepository extends BaseRepository
'notes',
'cost',
'qty',
'tax_name',
'tax_rate', ] as $field) {
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
] as $field) {
$cloneItem->$field = $item->$field;
}
$clone->invoice_items()->save($cloneItem);
}
foreach ($invoice->documents as $document) {
$cloneDocument = $document->cloneDocument();
$invoice->documents()->save($cloneDocument);
}
foreach ($invoice->invitations as $invitation) {
$cloneInvitation = Invitation::createNew($invoice);
$cloneInvitation->contact_id = $invitation->contact_id;
@ -581,7 +650,7 @@ class InvoiceRepository extends BaseRepository
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;
if (!$client || $client->is_deleted) {
@ -632,8 +701,10 @@ class InvoiceRepository extends BaseRepository
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->tax_name1 = $recurInvoice->tax_name1;
$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->custom_value1 = $recurInvoice->custom_value1 ?: 0;
$invoice->custom_value2 = $recurInvoice->custom_value2 ?: 0;
@ -652,11 +723,18 @@ class InvoiceRepository extends BaseRepository
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$item->tax_name1 = $recurItem->tax_name1;
$item->tax_rate1 = $recurItem->tax_rate1;
$item->tax_name2 = $recurItem->tax_name2;
$item->tax_rate2 = $recurItem->tax_rate2;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->documents as $recurDocument) {
$document = $recurDocument->cloneDocument();
$invoice->documents()->save($document);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;

View File

@ -19,8 +19,10 @@ class InvoiceItemTransformer extends EntityTransformer
'notes' => $item->notes,
'cost' => (float) $item->cost,
'qty' => (float) $item->qty,
'tax_name' => $item->tax_name,
'tax_rate' => (float) $item->tax_rate
'tax_name1' => $item->tax_name1 ? $item->tax_name1 : '',
'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,
'last_sent_date' => $invoice->last_sent_date,
'recurring_invoice_id' => (int) $invoice->recurring_invoice_id,
'tax_name' => $invoice->tax_name,
'tax_rate' => (float) $invoice->tax_rate,
'tax_name1' => $invoice->tax_name1 ? $invoice->tax_name1 : '',
'tax_rate1' => (float) $invoice->tax_rate1,
'tax_name2' => $invoice->tax_name2 ? $invoice->tax_name2 : '',
'tax_rate2' => (float) $invoice->tax_rate2,
'amount' => (float) $invoice->amount,
'balance' => (float) $invoice->balance,
'is_amount_discount' => (bool) $invoice->is_amount_discount,

View File

@ -22,8 +22,15 @@ class AppServiceProvider extends ServiceProvider {
*/
public function boot()
{
Form::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
Form::macro('image_data', function($image, $contents = false) {
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 = '') {
@ -152,6 +159,13 @@ class AppServiceProvider extends ServiceProvider {
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) {
return Utils::parseFloat($value) >= 0;
});

View File

@ -28,7 +28,7 @@ class ExpenseService extends BaseService
return $this->expenseRepo;
}
public function save($data)
public function save($data, $checkSubPermissions=false)
{
if (isset($data['client_id']) && $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']);
}
return $this->expenseRepo->save($data);
return $this->expenseRepo->save($data, $checkSubPermissions);
}
public function getDatatable($search)

View File

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

View File

@ -26,7 +26,8 @@
"quill": "~0.20.0",
"datetimepicker": "~2.4.5",
"stacktrace-js": "~1.0.1",
"fuse.js": "~2.0.2"
"fuse.js": "~2.0.2",
"dropzone": "~4.3.0"
},
"resolutions": {
"jquery": "~1.11"

View File

@ -1,119 +1,125 @@
{
"name": "hillelcoren/invoice-ninja",
"description": "An open-source invoicing site built with Laravel",
"keywords": ["invoice", "laravel"],
"license": "Attribution Assurance License",
"authors": [
{
"name": "Hillel Coren",
"email": "hillelcoren@gmail.com"
}
],
"require": {
"turbo124/laravel-push-notification": "dev-laravel5",
"name": "hillelcoren/invoice-ninja",
"description": "An open-source invoicing site built with Laravel",
"keywords": [
"invoice",
"laravel"
],
"license": "Attribution Assurance License",
"authors": [
{
"name": "Hillel Coren",
"email": "hillelcoren@gmail.com"
}
],
"require": {
"turbo124/laravel-push-notification": "dev-laravel5",
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"omnipay/gocardless": "dev-master",
"omnipay/stripe": "2.3.0",
"laravel/framework": "5.2.22",
"laravel/framework": "5.2.*",
"laravelcollective/html": "5.2.*",
"laravelcollective/bus": "5.2.*",
"symfony/css-selector": "~3.0",
"patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0",
"chumper/datatable": "dev-develop#04ef2bf",
"omnipay/omnipay": "~2.3.0",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
"barryvdh/laravel-ide-helper": "dev-master",
"doctrine/dbal": "2.5.x",
"jsanc623/phpbenchtime": "2.x",
"lokielse/omnipay-alipay": "dev-master",
"coatesap/omnipay-datacash": "~2.0",
"mfauveau/omnipay-pacnet": "~2.0",
"coatesap/omnipay-paymentsense": "2.0.0",
"coatesap/omnipay-realex": "~2.0",
"fruitcakestudio/omnipay-sisow": "~2.0",
"alfaproject/omnipay-skrill": "dev-master",
"omnipay/bitpay": "dev-master",
"guzzlehttp/guzzle": "~6.0",
"wildbit/laravel-postmark-provider": "2.0",
"Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master",
"league/fractal": "0.13.*",
"agmscode/omnipay-agms": "~1.0",
"samvaughton/omnipay-barclays-epdq": "~2.0",
"cardgate/omnipay-cardgate": "~2.0",
"fotografde/omnipay-checkoutcom": "~2.0",
"meebio/omnipay-creditcall": "dev-master",
"dioscouri/omnipay-cybersource": "dev-master",
"dercoder/omnipay-ecopayz": "~1.0",
"andreas22/omnipay-fasapay": "1.*",
"delatbabel/omnipay-fatzebra": "dev-master",
"vink/omnipay-komoju": "~1.0",
"incube8/omnipay-multicards": "dev-master",
"descubraomundo/omnipay-pagarme": "dev-master",
"dercoder/omnipay-paysafecard": "dev-master",
"softcommerce/omnipay-paytrace": "~1.0",
"meebio/omnipay-secure-trading": "dev-master",
"justinbusschau/omnipay-secpay": "~2.0",
"labs7in0/omnipay-wechat": "dev-master",
"collizo4sky/omnipay-wepay": "~1.0",
"laracasts/presenter": "dev-master",
"jlapp/swaggervel": "master-dev",
"maatwebsite/excel": "~2.0",
"ezyang/htmlpurifier": "~v4.7",
"cerdic/css-tidy": "~v1.5",
"asgrim/ofxparser": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1",
"codeception/codeception": "*",
"codeception/c3": "~2.0",
"fzaninotto/faker": "^1.5",
"patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0",
"chumper/datatable": "dev-develop#04ef2bf",
"omnipay/omnipay": "~2.3.0",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
"barryvdh/laravel-ide-helper": "dev-master",
"doctrine/dbal": "2.5.x",
"jsanc623/phpbenchtime": "2.x",
"lokielse/omnipay-alipay": "dev-master",
"coatesap/omnipay-datacash": "~2.0",
"mfauveau/omnipay-pacnet": "~2.0",
"coatesap/omnipay-paymentsense": "2.0.0",
"coatesap/omnipay-realex": "~2.0",
"fruitcakestudio/omnipay-sisow": "~2.0",
"alfaproject/omnipay-skrill": "dev-master",
"omnipay/bitpay": "dev-master",
"guzzlehttp/guzzle": "~6.0",
"wildbit/laravel-postmark-provider": "3.0",
"Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master",
"league/fractal": "0.13.*",
"agmscode/omnipay-agms": "~1.0",
"samvaughton/omnipay-barclays-epdq": "~2.0",
"cardgate/omnipay-cardgate": "~2.0",
"fotografde/omnipay-checkoutcom": "~2.0",
"meebio/omnipay-creditcall": "dev-master",
"dioscouri/omnipay-cybersource": "dev-master",
"dercoder/omnipay-ecopayz": "~1.0",
"andreas22/omnipay-fasapay": "1.*",
"delatbabel/omnipay-fatzebra": "dev-master",
"vink/omnipay-komoju": "~1.0",
"incube8/omnipay-multicards": "dev-master",
"descubraomundo/omnipay-pagarme": "dev-master",
"dercoder/omnipay-paysafecard": "dev-master",
"softcommerce/omnipay-paytrace": "~1.0",
"meebio/omnipay-secure-trading": "dev-master",
"justinbusschau/omnipay-secpay": "~2.0",
"labs7in0/omnipay-wechat": "dev-master",
"collizo4sky/omnipay-wepay": "~1.0",
"laracasts/presenter": "dev-master",
"jlapp/swaggervel": "master-dev",
"maatwebsite/excel": "~2.0",
"ezyang/htmlpurifier": "~v4.7",
"cerdic/css-tidy": "~v1.5",
"asgrim/ofxparser": "^1.1",
"league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-rackspace": "~1.0",
"barracudanetworks/archivestream-php": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1",
"codeception/codeception": "*",
"codeception/c3": "~2.0",
"fzaninotto/faker": "^1.5",
"symfony/dom-crawler": "~3.0"
},
"autoload": {
"classmap": [
"app/Console/Commands",
"app/Libraries",
"app/Http/Controllers",
"app/Models",
"app/Ninja",
"app/Ninja/Repositories",
"database"
],
"psr-4": {
"App\\": "app/"
},
},
"autoload": {
"classmap": [
"app/Console/Commands",
"app/Libraries",
"app/Http/Controllers",
"app/Models",
"app/Ninja",
"app/Ninja/Repositories",
"database"
],
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Libraries/lib_autolink.php",
"app/Libraries/OFX.php"
]
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
]
},
"scripts": {
"post-install-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-update-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-create-project-cmd": [
"php -r \"copy('.env.example', '.env');\"",
"php artisan key:generate"
]
},
"config": {
"preferred-install": "dist"
}
}
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
]
},
"scripts": {
"post-install-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-update-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-create-project-cmd": [
"php -r \"copy('.env.example', '.env');\"",
"php artisan key:generate"
]
},
"config": {
"preferred-install": "dist"
}
}

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",
"This file is @generated automatically"
],
"hash": "a33dce96f4ded3fb269a6d9dcbf24b27",
"content-hash": "f73a83c64422ef3560da4adb988850ae",
"hash": "2ab7ab9013e31d8a2f0dcf43b31beefa",
"content-hash": "188fba7fcc31b702098d5417bc0e63e2",
"packages": [
{
"name": "agmscode/omnipay-agms",
@ -123,12 +123,12 @@
"source": {
"type": "git",
"url": "https://github.com/formers/former.git",
"reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683"
"reference": "d97f907741323b390f43954a90a227921ecc6b96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/formers/former/zipball/e196c4336db77be97131f6a3b3c3b69b3a22b683",
"reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683",
"url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96",
"reference": "d97f907741323b390f43954a90a227921ecc6b96",
"shasum": ""
},
"require": {
@ -174,7 +174,7 @@
"foundation",
"laravel"
],
"time": "2016-03-02 17:21:21"
"time": "2016-03-16 01:43:45"
},
{
"name": "anahkiasen/html-object",
@ -321,6 +321,126 @@
],
"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",
"version": "v2.2.0",
@ -880,12 +1000,12 @@
"source": {
"type": "git",
"url": "https://github.com/delatbabel/omnipay-fatzebra.git",
"reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc"
"reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc",
"reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc",
"url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/d0a56a8704357d91457672741a48a4cb6c7ecd53",
"reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53",
"shasum": ""
},
"require": {
@ -929,7 +1049,7 @@
"payment",
"paystream"
],
"time": "2015-02-15 11:27:23"
"time": "2016-03-21 09:21:14"
},
{
"name": "dercoder/omnipay-ecopayz",
@ -1039,12 +1159,12 @@
"source": {
"type": "git",
"url": "https://github.com/descubraomundo/omnipay-pagarme.git",
"reference": "528953568929b57189de16fa7431eaab75d61840"
"reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/528953568929b57189de16fa7431eaab75d61840",
"reference": "528953568929b57189de16fa7431eaab75d61840",
"url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/8571396139eb1fb1a7011450714a5e8d8d604d8c",
"reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c",
"shasum": ""
},
"require": {
@ -1081,7 +1201,7 @@
"pay",
"payment"
],
"time": "2015-10-27 19:17:20"
"time": "2016-03-18 19:37:37"
},
{
"name": "dioscouri/omnipay-cybersource",
@ -1938,16 +2058,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.1.1",
"version": "6.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c"
"reference": "d094e337976dff9d8e2424e8485872194e768662"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
"reference": "d094e337976dff9d8e2424e8485872194e768662",
"shasum": ""
},
"require": {
@ -1963,7 +2083,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "6.2-dev"
}
},
"autoload": {
@ -1996,7 +2116,7 @@
"rest",
"web service"
],
"time": "2015-11-23 00:47:50"
"time": "2016-03-21 20:02:09"
},
{
"name": "guzzlehttp/promises",
@ -2603,12 +2723,12 @@
"source": {
"type": "git",
"url": "https://github.com/labs7in0/omnipay-wechat.git",
"reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a"
"reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a",
"reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a",
"url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e",
"reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e",
"shasum": ""
},
"require": {
@ -2644,7 +2764,7 @@
"purchase",
"wechat"
],
"time": "2015-11-16 11:04:21"
"time": "2016-03-18 09:59:11"
},
{
"name": "laracasts/presenter",
@ -2694,16 +2814,16 @@
},
{
"name": "laravel/framework",
"version": "v5.2.22",
"version": "v5.2.24",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4"
"reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/aec1b7cb9ec0bac0107361a3730cac9b6f945ef4",
"reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4",
"url": "https://api.github.com/repos/laravel/framework/zipball/396297a5fd3c70c2fc1af68f09ee574a2380175c",
"reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c",
"shasum": ""
},
"require": {
@ -2716,7 +2836,7 @@
"monolog/monolog": "~1.11",
"mtdowling/cron-expression": "~1.0",
"nesbot/carbon": "~1.20",
"paragonie/random_compat": "~1.1",
"paragonie/random_compat": "~1.4",
"php": ">=5.5.9",
"psy/psysh": "0.7.*",
"swiftmailer/swiftmailer": "~5.1",
@ -2818,7 +2938,7 @@
"framework",
"laravel"
],
"time": "2016-02-27 22:09:19"
"time": "2016-03-22 13:45:19"
},
{
"name": "laravel/socialite",
@ -3056,6 +3176,100 @@
],
"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",
"version": "0.13.0",
@ -3585,6 +3799,33 @@
],
"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",
"version": "1.18.1",
@ -3707,6 +3948,61 @@
],
"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",
"version": "1.21.0",
@ -3815,7 +4111,7 @@
},
"dist": {
"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",
"shasum": ""
},
@ -4328,16 +4624,16 @@
},
{
"name": "omnipay/eway",
"version": "v2.2.0",
"version": "v2.2.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-eway.git",
"reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16"
"reference": "1c953630f7097bfdeed200b17a847015a4df5607"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/0dcf28596f0382fbfc3ee229e98e60798675ed16",
"reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16",
"url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/1c953630f7097bfdeed200b17a847015a4df5607",
"reference": "1c953630f7097bfdeed200b17a847015a4df5607",
"shasum": ""
},
"require": {
@ -4381,7 +4677,7 @@
"pay",
"payment"
],
"time": "2015-03-30 00:28:33"
"time": "2016-03-22 01:11:02"
},
{
"name": "omnipay/firstdata",
@ -5536,16 +5832,16 @@
},
{
"name": "paragonie/random_compat",
"version": "v1.2.2",
"version": "v1.4.1",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "b3313b618f4edd76523572531d5d7e22fe747430"
"reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/b3313b618f4edd76523572531d5d7e22fe747430",
"reference": "b3313b618f4edd76523572531d5d7e22fe747430",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
"reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
"shasum": ""
},
"require": {
@ -5580,7 +5876,7 @@
"pseudorandom",
"random"
],
"time": "2016-03-11 19:54:08"
"time": "2016-03-18 20:34:03"
},
{
"name": "patricktalmadge/bootstrapper",
@ -5906,6 +6202,63 @@
],
"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",
"version": "2.2.0",
@ -7585,20 +7938,20 @@
},
{
"name": "wildbit/laravel-postmark-provider",
"version": "2.0.0",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wildbit/laravel-postmark-provider.git",
"reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5"
"reference": "b80815602f618abe24030ea6d3f117da49a72885"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/79a7e8bde66b2bd6f314829b00ee08616847ebc5",
"reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5",
"url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/b80815602f618abe24030ea6d3f117da49a72885",
"reference": "b80815602f618abe24030ea6d3f117da49a72885",
"shasum": ""
},
"require": {
"illuminate/mail": "~5.0",
"illuminate/mail": "~5.2",
"wildbit/swiftmailer-postmark": "~2.0"
},
"type": "library",
@ -7612,7 +7965,7 @@
"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",
"time": "2015-11-10 14:43:06"
"time": "2016-02-10 14:15:58"
},
{
"name": "wildbit/swiftmailer-postmark",
@ -8475,16 +8828,16 @@
},
{
"name": "phpspec/phpspec",
"version": "2.4.1",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/phpspec.git",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed"
"reference": "385ecb015e97c13818074f1517928b24d4a26067"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067",
"reference": "385ecb015e97c13818074f1517928b24d4a26067",
"shasum": ""
},
"require": {
@ -8549,7 +8902,7 @@
"testing",
"tests"
],
"time": "2016-01-01 10:17:54"
"time": "2016-03-20 20:34:32"
},
{
"name": "phpspec/prophecy",

View File

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

View File

@ -21,6 +21,13 @@ return array(
*/
'doc-route' => 'docs',
/*
|--------------------------------------------------------------------------
| Relative path to access swagger ui.
|--------------------------------------------------------------------------
*/
'api-docs-route' => 'api-docs',
/*
|--------------------------------------------------------------------------
| 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' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', '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) {

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]
# 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 /
</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;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
.navbar-header {
padding-top: 4px;
padding-bottom: 4px;
}
.navbar li a {
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 {

View File

@ -7,14 +7,24 @@ html {
overflow-y: scroll;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
.navbar-header {
padding-top: 4px;
padding-bottom: 4px;
}
.navbar li a {
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 {

54
public/css/style.css vendored
View File

@ -402,6 +402,21 @@ font-weight: bold;
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 {
background-color: #09334f !important;
background-image: none;
@ -1051,4 +1066,43 @@ td.right {
div.panel-body div.panel-body {
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
*
* 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
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
(function checkTypedArrayCompatibility() {
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') {
Uint8Array.prototype.subarray = function subarray(start, end) {
return new Uint8Array(this.slice(start, end));
@ -37,10 +36,10 @@ if (typeof PDFJS === 'undefined') {
};
}
// some mobile version might not support Float64Array
if (typeof Float64Array === 'undefined')
// Support: Android<4.1
if (typeof Float64Array === 'undefined') {
window.Float64Array = Float32Array;
}
return;
}
@ -49,23 +48,26 @@ if (typeof PDFJS === 'undefined') {
}
function setArrayOffset(array, offset) {
if (arguments.length < 2)
if (arguments.length < 2) {
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;
}
}
function TypedArray(arg1) {
var result;
var result, i, n;
if (typeof arg1 === 'number') {
result = [];
for (var i = 0; i < arg1; ++i)
for (i = 0; i < arg1; ++i) {
result[i] = 0;
}
} else if ('slice' in arg1) {
result = arg1.slice(0);
} else {
result = [];
for (var i = 0, n = arg1.length; i < n; ++i) {
for (i = 0, n = arg1.length; i < n; ++i) {
result[i] = arg1[i];
}
}
@ -75,13 +77,14 @@ if (typeof PDFJS === 'undefined') {
result.byteLength = result.length;
result.set = setArrayOffset;
if (typeof arg1 === 'object' && arg1.buffer)
if (typeof arg1 === 'object' && arg1.buffer) {
result.buffer = arg1.buffer;
}
return result;
}
window.Uint8Array = TypedArray;
window.Int8Array = TypedArray;
// we don't need support for set, byteLength for 32-bit array
// so we can use the TypedArray as well
@ -93,25 +96,15 @@ if (typeof PDFJS === 'undefined') {
})();
// URL = URL || webkitURL
// Support: Safari<7, Android 4.2+
(function normalizeURLObject() {
if (!window.URL) {
window.URL = window.webkitURL;
}
})();
// Object.create() ?
(function checkObjectCreateCompatibility() {
if (typeof Object.create !== 'undefined')
return;
Object.create = function objectCreate(proto) {
function Constructor() {}
Constructor.prototype = proto;
return new Constructor();
};
})();
// Object.defineProperty() ?
// Object.defineProperty()?
// Support: Android<4.0, Safari<5.1
(function checkObjectDefinePropertyCompatibility() {
if (typeof Object.defineProperty !== 'undefined') {
var definePropertyPossible = true;
@ -127,15 +120,19 @@ if (typeof PDFJS === 'undefined') {
} catch (e) {
definePropertyPossible = false;
}
if (definePropertyPossible) return;
if (definePropertyPossible) {
return;
}
}
Object.defineProperty = function objectDefineProperty(obj, name, def) {
delete obj[name];
if ('get' in def)
if ('get' in def) {
obj.__defineGetter__(name, def['get']);
if ('set' in def)
}
if ('set' in def) {
obj.__defineSetter__(name, def['set']);
}
if ('value' in def) {
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
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) {
var result = [];
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 ?
// No XMLHttpRequest#response?
// Support: IE<11, Android <4.0
(function checkXMLHttpRequestResponseCompatibility() {
var xhrPrototype = XMLHttpRequest.prototype;
if (!('overrideMimeType' in xhrPrototype)) {
var xhr = new XMLHttpRequest();
if (!('overrideMimeType' in xhr)) {
// IE10 might have response, but not overrideMimeType
// Support: IE10
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
});
}
if ('response' in xhrPrototype ||
'mozResponseArrayBuffer' in xhrPrototype ||
'mozResponse' in xhrPrototype ||
'responseArrayBuffer' in xhrPrototype)
if ('responseType' in xhr) {
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') {
Object.defineProperty(xhrPrototype, 'response', {
get: function xmlHttpRequestResponseGet() {
return new Uint8Array(new VBArray(this.responseBody).toArray());
if (this.responseType === 'arraybuffer') {
return new Uint8Array(new VBArray(this.responseBody).toArray());
} else {
return this.responseText;
}
}
});
return;
}
// other browsers
function responseTypeSetter() {
// will be only called to set "arraybuffer"
this.overrideMimeType('text/plain; charset=x-user-defined');
}
if (typeof xhrPrototype.overrideMimeType === 'function') {
Object.defineProperty(xhrPrototype, 'responseType',
{ set: responseTypeSetter });
}
function responseGetter() {
var text = this.responseText;
var i, n = text.length;
var result = new Uint8Array(n);
for (i = 0; i < n; ++i)
result[i] = text.charCodeAt(i) & 0xFF;
return result;
}
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
Object.defineProperty(xhrPrototype, 'response', {
get: function xmlHttpRequestResponseGet() {
if (this.responseType !== 'arraybuffer') {
return this.responseText;
}
var text = this.responseText;
var i, n = text.length;
var result = new Uint8Array(n);
for (i = 0; i < n; ++i) {
result[i] = text.charCodeAt(i) & 0xFF;
}
return result.buffer;
}
});
})();
// window.btoa (base64 encode function) ?
// Support: IE<10
(function checkWindowBtoaCompatibility() {
if ('btoa' in window)
if ('btoa' in window) {
return;
}
var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
@ -268,17 +237,21 @@ if (typeof PDFJS === 'undefined') {
};
})();
// window.atob (base64 encode function) ?
// window.atob (base64 encode function)?
// Support: IE<10
(function checkWindowAtobCompatibility() {
if ('atob' in window)
if ('atob' in window) {
return;
}
// https://github.com/davidchambers/Base64.js
var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
window.atob = function (input) {
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 (
// initialize result and counters
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() {
if (typeof Function.prototype.bind !== 'undefined')
if (typeof Function.prototype.bind !== 'undefined') {
return;
}
Function.prototype.bind = function functionPrototypeBind(obj) {
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
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 bound;
@ -314,23 +289,29 @@ if (typeof PDFJS === 'undefined') {
})();
// HTMLElement dataset property
// Support: IE<11, Safari<5.1, Android<4.0
(function checkDatasetProperty() {
var div = document.createElement('div');
if ('dataset' in div)
if ('dataset' in div) {
return; // dataset property exists
}
Object.defineProperty(HTMLElement.prototype, 'dataset', {
get: function() {
if (this._dataset)
if (this._dataset) {
return this._dataset;
}
var dataset = {};
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
var attribute = this.attributes[j];
if (attribute.name.substring(0, 5) != 'data-')
if (attribute.name.substring(0, 5) !== 'data-') {
continue;
}
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;
}
@ -346,20 +327,26 @@ if (typeof PDFJS === 'undefined') {
})();
// HTMLElement classList property
// Support: IE<10, Android<4.0, iOS<5.0
(function checkClassListProperty() {
var div = document.createElement('div');
if ('classList' in div)
if ('classList' in div) {
return; // classList property exists
}
function changeList(element, itemName, add, remove) {
var s = element.className || '';
var list = s.split(/\s+/g);
if (list[0] === '') list.shift();
if (list[0] === '') {
list.shift();
}
var index = list.indexOf(itemName);
if (index < 0 && add)
if (index < 0 && add) {
list.push(itemName);
if (index >= 0 && remove)
}
if (index >= 0 && remove) {
list.splice(index, 1);
}
element.className = list.join(' ');
return (index >= 0);
}
@ -381,8 +368,9 @@ if (typeof PDFJS === 'undefined') {
Object.defineProperty(HTMLElement.prototype, 'classList', {
get: function() {
if (this._classList)
if (this._classList) {
return this._classList;
}
var classList = Object.create(classListPrototype, {
element: {
@ -403,6 +391,9 @@ if (typeof PDFJS === 'undefined') {
})();
// Check console compatibility
// In older IE versions the console object is not available
// unless console is open.
// Support: IE<10
(function checkConsoleCompatibility() {
if (!('console' in window)) {
window.console = {
@ -425,6 +416,7 @@ if (typeof PDFJS === 'undefined') {
})();
// Check onclick compatibility in Opera
// Support: Opera<15
(function checkOnClickCompatibility() {
// workaround for reported Opera bug DSK-354448:
// onclick fires on disabled buttons with opaque content
@ -436,30 +428,34 @@ if (typeof PDFJS === 'undefined') {
function isDisabled(node) {
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
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
(function checkNavigatorLanguage() {
if ('language' in navigator)
if ('language' in navigator) {
return;
Object.defineProperty(navigator, 'language', {
get: function navigatorLanguage() {
var language = navigator.userLanguage || 'en-US';
return language.substring(0, 2).toLowerCase() +
language.substring(2).toUpperCase();
},
enumerable: true
});
}
PDFJS.locale = navigator.userLanguage || 'en-US';
})();
(function checkRangeRequests() {
// Safari has issues with cached range requests see:
// https://github.com/mozilla/pdf.js/issues/3260
// Last tested with version 6.0.4.
// Support: Safari 6.0+
var isSafari = Object.prototype.toString.call(
window.HTMLElement).indexOf('Constructor') > 0;
@ -467,17 +463,131 @@ if (typeof PDFJS === 'undefined') {
// https://github.com/mozilla/pdf.js/issues/3381.
// Make sure that we only match webkit-based Android browsers,
// since Firefox/Fennec works as expected.
// Support: Android<3.0
var regex = /Android\s[0-2][^\d]/;
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.disableStream = true;
}
})();
// Check if the browser supports manipulation of the history.
// Support: IE<10, Android<4.2
(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;
}
})();
// 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){
if(window.ninjaFontVfs[font.folder]){
folder = 'fonts/'+font.folder;
pdfMake.fonts[font.name] = {
normal: font.folder+'/'+font.normal,
italics: font.folder+'/'+font.italics,
bold: font.folder+'/'+font.bold,
bolditalics: font.folder+'/'+font.bolditalics
normal: folder+'/'+font.normal,
italics: folder+'/'+font.italics,
bold: folder+'/'+font.bold,
bolditalics: folder+'/'+font.bolditalics
}
}
}
@ -144,6 +145,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
'invoiceDetailsHeight': (NINJA.invoiceDetails(invoice).length * 16) + 16,
'invoiceLineItems': NINJA.invoiceLines(invoice),
'invoiceLineItemColumns': NINJA.invoiceColumns(invoice),
'invoiceDocuments' : NINJA.invoiceDocuments(invoice),
'quantityWidth': NINJA.quantityWidth(invoice),
'taxWidth': NINJA.taxWidth(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 notes = item.notes;
var productKey = item.product_key;
var tax = '';
var tax1 = '';
var tax2 = '';
if (showItemTaxes) {
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
if (item.tax_name1) {
tax1 = parseFloat(item.tax_rate1);
}
if (item.tax_name2) {
tax2 = parseFloat(item.tax_rate2);
}
}
@ -391,7 +395,17 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["quantity", rowStyle], text:qty || ' '});
}
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 || ' '});
@ -401,6 +415,39 @@ NINJA.invoiceLines = function(invoice) {
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)
{
if (!invoice) {
@ -424,15 +471,19 @@ NINJA.subtotals = function(invoice, hideBalance)
for (var key in invoice.item_taxes) {
if (invoice.item_taxes.hasOwnProperty(key)) {
var taxRate = invoice.item_taxes[key];
var taxRate = invoice.item_taxes[key];
var taxStr = taxRate.name + ' ' + (taxRate.rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(taxRate.amount, invoice)}]);
}
}
if (invoice.tax && invoice.tax.name || invoice.tax_name) {
var taxStr = invoice.tax_name + ' ' + (invoice.tax_rate*1).toString() + '%';
data.push([{text: taxStr}, {text: formatMoneyInvoice(invoice.tax_amount, invoice)}]);
if (invoice.tax_amount1) {
var taxStr = invoice.tax_name1 + ' ' + (invoice.tax_rate1*1).toString() + '%';
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') {

View File

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

View File

@ -3,7 +3,12 @@ if(window.ninjaFontVfs)ninjaLoadFontVfs();
function ninjaLoadFontVfs(){
jQuery.each(window.ninjaFontVfs, function(font, files){
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)
[![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
* 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
@ -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/)
* [User Guide](https://www.invoiceninja.com/app-user-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/)
* [Feature Roadmap](https://trello.com/b/63BbiVVe/)

View File

@ -1131,4 +1131,73 @@ return array(
'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.',
'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',
'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.',
'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',
@ -1068,7 +1066,7 @@ $LANG = array(
// User Permissions
'owner' => 'Owner',
'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_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
@ -1078,7 +1076,57 @@ $LANG = array(
'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',
// 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.',
);
return $LANG;

View File

@ -1108,4 +1108,73 @@ return array(
'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.',
'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',
'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',
'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',
'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',
'client' => 'Cliente',
'invoice_date' => 'Data Fattura',
'due_date' => 'Scadenza Fattura',
'due_date' => 'Scadenza',
'invoice_number' => 'Numero Fattura',
'invoice_number_short' => 'Fattura #', /* Fattura N° */
'po_number' => 'Numero d\'ordine d\'acquisto',
@ -45,8 +45,8 @@ return array(
'quantity' => 'Quantità',
'line_total' => 'Totale Riga',
'subtotal' => 'Subtotale',
'paid_to_date' => 'Pagato in Data',
'balance_due' => 'Saldo Dovuto',
'paid_to_date' => 'Pagato a oggi',
'balance_due' => 'Totale',
'invoice_design_id' => 'Stile',
'terms' => 'Condizioni',
'your_invoice' => 'Tua Fattura',
@ -67,7 +67,7 @@ return array(
'clone_invoice' => 'Duplica Fattura',
'archive_invoice' => 'Archivia Fattura',
'delete_invoice' => 'Elimina Fattura',
'email_invoice' => 'Manda Fattura', /* Spedisci Fattura */
'email_invoice' => 'Invia Fattura', /* Spedisci Fattura */
'enter_payment' => 'Inserisci Pagamento',
'tax_rates' => 'Aliquote Fiscali', /* ^^Unsure^^ */
'rate' => 'Aliquota', /* ^^Unsure^^ */
@ -104,12 +104,12 @@ return array(
// recurring invoices
'recurring_invoices' => 'Fatture ricorrenti',
'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>
<ul>
<li>"Iscrizione palestra per il mese di :MESE" => "Iscrizione palestra per il mese di Luglio"</li>
<li>":ANNO+1 iscrizione annuale" => "Anno d\'iscrizione 2015"</li>
<li>"Pagamento fermo a :TRIMESTRE+1" => "Pagamento fermo al 2° trimestre"</li>
<li>"Iscrizione palestra per il mese di :MONTH" => "Iscrizione palestra per il mese di Luglio"</li>
<li>":YEAR+1 iscrizione annuale" => "Anno d\'iscrizione 2015"</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^^ */
// dashboard
@ -118,7 +118,7 @@ return array(
'billed_clients' => 'Clienti fatturati',
'active_client' => 'cliente attivo',
'active_clients' => 'clienti attivi',
'invoices_past_due' => 'Fatture Insolute', /* Insoluti */
'invoices_past_due' => 'Fatture Scadute', /* Insoluti */
'upcoming_invoices' => 'Prossime fatture',
'average_invoice' => 'Fattura media',
@ -140,7 +140,7 @@ return array(
'contact' => 'Contatto',
'date_created' => 'Data di Creazione',
'last_login' => 'Ultimo Accesso',
'balance' => 'Saldo',
'balance' => 'Bilancio',
'action' => 'Azione',
'status' => 'Stato',
'invoice_total' => 'Totale Fattura',
@ -169,7 +169,7 @@ return array(
'activity' => 'Attività',
'date' => 'Data',
'message' => 'Messaggio',
'adjustment' => 'Correzione',
'adjustment' => 'Variazione',
'are_you_sure' => 'Sei sicuro?',
// payment pages
@ -337,7 +337,7 @@ return array(
'archived_product' => 'Prodotto archiviato con successo',
'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',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
@ -456,11 +456,11 @@ return array(
'sent' => 'sent',
'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_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.',
'vat_number' => 'Vat Number',
'vat_number' => 'Partita IVA',
'id_number' => 'ID Number',
'white_label_link' => 'White label',
@ -503,30 +503,30 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
'approve' => 'Approve',
'approve' => 'Approva',
'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_1' => 'Disabled',
'token_billing_1' => 'Disabilitato',
'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
'token_billing_3' => 'Opt-out - checkbox is shown and selected',
'token_billing_4' => 'Always',
'token_billing_checkbox' => 'Store credit card details',
'view_in_stripe' => 'View in Stripe',
'use_card_on_file' => 'Use card on file',
'edit_payment_details' => 'Edit payment details',
'token_billing' => 'Save card details',
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'token_billing_4' => 'Sempre',
'token_billing_checkbox' => 'Salva dettagli carta di credito',
'view_in_stripe' => 'Vedi transazione in Stripe',
'use_card_on_file' => 'Carta di credito salvata',
'edit_payment_details' => 'Modifica dettagli pagamento',
'token_billing' => 'Salva carta di credito',
'token_billing_secure' => 'I dati sono memorizzati su piattaforma sicura mediante :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'contact_information' => 'Informazioni di contatto',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'billing_address' => 'Billing address',
'billing_method' => 'Billing method',
'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'amount_due' => 'Saldo dovuto',
'billing_address' => 'Indirizzo di fatturazione',
'billing_method' => 'Metodo di pagamento',
'order_overview' => 'Riepilogo ordine',
'match_address' => '*L\'indirizzo deve corrispondere con quello associato alla carta di credito.',
'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',
'invoice_footer' => 'Invoice footer',
@ -550,7 +550,7 @@ return array(
'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit card',
'pay_with_card' => 'Carta di credito',
'change_password' => 'Change password',
'current_password' => 'Current password',
@ -579,12 +579,12 @@ return array(
'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.',
'payment_type_credit_card' => 'Credit card',
'payment_type_credit_card' => 'Carta di credito',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'partial_remaining' => ':partial di :balance',
'more_fields' => 'More Fields',
'less_fields' => 'Less Fields',
@ -614,40 +614,40 @@ return array(
'export' => 'Export',
'documentation' => 'Documentation',
'zapier' => 'Zapier',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'recurring' => 'Ricorrenti',
'last_invoice_sent' => 'Ultima fattura inviata :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'tasks' => 'Task',
'new_task' => 'Nuovo Task',
'start_time' => 'Tempo di inizio',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'edit_task' => 'Modifica il Task',
'archive_task' => 'Archivia il Task',
'restore_task' => 'Ripristina il Task',
'delete_task' => 'Cancella il Task',
'stop_task' => 'Ferma il Task',
'time' => 'Tempo',
'start' => 'Inizia',
'stop' => 'Ferma',
'now' => 'Adesso',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'manual' => 'Manuale',
'date_and_time' => 'Data e ora',
'second' => 'secondo',
'seconds' => 'secondi',
'minute' => 'minuto',
'minutes' => 'minuti',
'hour' => 'ora',
'hours' => 'ore',
'task_details' => 'Dettagli Task',
'duration' => 'Durata',
'end_time' => 'Tempo di fine',
'end' => 'Fine',
'invoiced' => 'Fatturato',
'logged' => 'Loggato',
'running' => 'In corso',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
@ -656,9 +656,9 @@ return array(
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'create_task' => 'Crea Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_task' => 'Fattura il Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
@ -666,7 +666,7 @@ return array(
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'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',
@ -680,12 +680,12 @@ return array(
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'resume' => 'Riprendi',
'break_duration' => 'Interrompi',
'edit_details' => 'Modifica dettagli',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'click_here' => 'clicca qui',
'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
@ -736,8 +736,8 @@ return array(
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
'new_recurring_invoice' => 'Nuova Fattura Ricorrente',
'recurring_invoice' => 'Fattura ricorrente',
'recurring_too_soon' => 'It\'s too soon to create the next recurring invoice, it\'s scheduled for :date',
'created_by_invoice' => 'Created by :invoice',
'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>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',
'quote_due_date' => 'Valid Until',
'valid_until' => 'Valid Until',
'invoice_due_date' => 'Scadenza fattura',
'quote_due_date' => 'Validità preventivo',
'valid_until' => 'Valido fino a',
'reset_terms' => 'Reset terms',
'reset_footer' => 'Reset footer',
'invoices_sent' => ':count invoice sent|:count invoices sent',
'status_draft' => 'Draft',
'status_sent' => 'Sent',
'status_viewed' => 'Viewed',
'status_partial' => 'Partial',
'status_paid' => 'Paid',
'status_draft' => 'Bozza',
'status_sent' => 'Spedito',
'status_viewed' => 'Visto',
'status_partial' => 'Parziale',
'status_paid' => 'Pagato',
'show_line_item_tax' => 'Display <b>line item taxes</b> inline',
'iframe_url' => 'Website',
@ -785,15 +785,15 @@ return array(
'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes',
'expired_quotes' => 'Expired Quotes',
'expired_quotes' => 'Preventivi Scaduti',
'sign_up_using' => 'Sign up using',
'invalid_credentials' => 'These credentials do not match our records',
'show_all_options' => 'Show all options',
'user_details' => 'User Details',
'invalid_credentials' => 'Queste credenziali non corrispondono alle nostre registrazioni',
'show_all_options' => 'Mostra tutte le opzioni',
'user_details' => 'Dettagli Utente',
'oneclick_login' => 'One-Click Login',
'disable' => 'Disable',
'invoice_quote_number' => 'Invoice and Quote Numbers',
'disable' => 'Disabilita',
'invoice_quote_number' => 'Numerazione Fatture e Preventivi',
'invoice_charges' => 'Invoice Charges',
'invitation_status' => [
@ -807,10 +807,10 @@ return array(
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'total_invoiced' => 'Fatturato totale',
'open_balance' => 'Da saldare',
'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',
'gateways' => 'Payment Gateways',
@ -821,7 +821,7 @@ return array(
'oneclick_login_help' => 'Connect an account to login without a password',
'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',
'create_tax_rate' => 'Add Tax Rate',
'updated_tax_rate' => 'Successfully updated tax rate',
@ -845,8 +845,8 @@ return array(
'activity_1' => ':user created client :client',
'activity_2' => ':user archived client :client',
'activity_3' => ':user deleted client :client',
'activity_4' => ':user created invoice :invoice',
'activity_5' => ':user updated invoice :invoice',
'activity_4' => ':user ha creato la fattura :invoice',
'activity_5' => ':user ha aggiornato la fattura :invoice',
'activity_6' => ':user emailed invoice :invoice to :contact',
'activity_7' => ':contact viewed invoice :invoice',
'activity_8' => ':user archived invoice :invoice',
@ -883,7 +883,7 @@ return array(
'quote_footer' => 'Quote Footer',
'free' => 'Free',
'quote_is_approved' => 'This quote is approved',
'quote_is_approved' => 'Questo preventivo è stato approvato.',
'apply_credit' => 'Apply Credit',
'system_settings' => 'System Settings',
'archive_token' => 'Archive Token',
@ -944,7 +944,7 @@ return array(
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'email_design' => 'Email Design',
'due_by' => 'Due by :date',
'due_by' => 'Scadenza :date',
'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.',
'template_help_title' => 'Templates Help',
@ -986,10 +986,10 @@ return array(
'white_label_purchase_link' => 'Purchase a white label license',
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'expense' => 'Spesa',
'expenses' => 'Spese',
'new_expense' => 'Inserisci Spesa',
'enter_expense' => 'Inserisci Spesa',
'vendors' => 'Vendors',
'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net',
@ -1077,11 +1077,11 @@ return array(
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'add_bank_account' => 'Add Bank Account',
'bank_accounts' => 'Conti corrente',
'add_bank_account' => 'Nuovo conto corrente',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
'bank_id' => 'bank',
'import_expenses' => 'Importa Spese',
'bank_id' => 'banca',
'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account',
'edit_bank_account' => 'Edit Bank Account',
@ -1126,4 +1126,73 @@ return array(
'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.',
);
'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

@ -1051,6 +1051,51 @@ $LANG = array(
'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' => 'ダッシュボード',
'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

@ -1133,4 +1133,73 @@ return array(
'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.',
'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',
'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',
'address1' => 'Straat',
'address2' => 'Bus/Suite',
'city' => 'Gemeente',
'city' => 'Plaats',
'state' => 'Staat/Provincie',
'postal_code' => 'Postcode',
'country_id' => 'Land',
'contacts' => 'Contacten',
'contacts' => 'Contactpersonen',
'first_name' => 'Voornaam',
'last_name' => 'Achternaam',
'phone' => 'Telefoon',
'email' => 'E-mail',
'email' => 'E-mailadres',
'additional_info' => 'Extra informatie',
'payment_terms' => 'Betalingsvoorwaarden',
'currency_id' => 'Munteenheid',
@ -35,7 +35,7 @@ return array(
'invoice_number_short' => 'Factuur #',
'po_number' => 'Bestelnummer',
'po_number_short' => 'Bestel #',
'frequency_id' => 'Hoe vaak',
'frequency_id' => 'Frequentie',
'discount' => 'Korting',
'taxes' => 'Belastingen',
'tax' => 'Belasting',
@ -52,9 +52,9 @@ return array(
'your_invoice' => 'Jouw factuur',
'remove_contact' => 'Verwijder contact',
'add_contact' => 'Voeg contact toe',
'add_contact' => 'Contact toevoegen',
'create_new_client' => 'Maak nieuwe klant',
'edit_client_details' => 'Pas klantdetails aan',
'edit_client_details' => 'Klantdetails aanpassen',
'enable' => 'Activeer',
'learn_more' => 'Meer te weten komen',
'manage_rates' => 'Beheer prijzen',
@ -63,12 +63,12 @@ return array(
'save_as_default_terms' => 'Opslaan als standaard voorwaarden',
'download_pdf' => 'Download PDF',
'pay_now' => 'Betaal nu',
'save_invoice' => 'Sla factuur op',
'save_invoice' => 'Factuur opslaan',
'clone_invoice' => 'Kopieer factuur',
'archive_invoice' => 'Archiveer factuur',
'delete_invoice' => 'Verwijder factuur',
'email_invoice' => 'E-mail factuur',
'enter_payment' => 'Betaling ingeven',
'enter_payment' => 'Betaling invoeren',
'tax_rates' => 'BTW-tarief',
'rate' => 'Tarief',
'settings' => 'Instellingen',
@ -176,21 +176,21 @@ return array(
'amount' => 'Bedrag',
// account/company pages
'work_email' => 'E-mail',
'work_email' => 'E-mailadres',
'language_id' => 'Taal',
'timezone_id' => 'Tijdszone',
'date_format_id' => 'Datum formaat',
'datetime_format_id' => 'Datum/Tijd formaat',
'users' => 'Gebruikers',
'localization' => 'Localisatie',
'remove_logo' => 'Verwijder logo',
'remove_logo' => 'Logo verwijderen',
'logo_help' => 'Ondersteund: JPEG, GIF en PNG',
'payment_gateway' => 'Betalingsmiddel',
'gateway_id' => 'Leverancier',
'email_notifications' => 'E-mailmeldingen',
'email_sent' => 'E-mail me wanneer een factuur is <b>verzonden</b>',
'email_viewed' => 'E-mail me wanneer een factuur is <b>bekeken</b>',
'email_paid' => 'E-mail me wanneer een factuur is <b>betaald</b>',
'email_sent' => 'E-mail mij wanneer een factuur is <b>verzonden</b>',
'email_viewed' => 'E-mail mij wanneer een factuur is <b>bekeken</b>',
'email_paid' => 'E-mail mij wanneer een factuur is <b>betaald</b>',
'site_updates' => 'Site Aanpassingen',
'custom_messages' => 'Aangepaste berichten',
'default_invoice_terms' => 'Stel standaard factuurvoorwaarden in',
@ -310,10 +310,10 @@ return array(
'close' => 'Sluiten',
'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.',
'unsaved_changes' => 'U hebt niet bewaarde wijzigingen',
'unsaved_changes' => 'U hebt niet opgeslagen wijzigingen',
'custom_fields' => 'Aangepaste velden',
'company_fields' => 'Velden Bedrijf',
'client_fields' => 'Velden Klant',
@ -366,7 +366,7 @@ return array(
'archive_quote' => 'Archiveer offerte',
'delete_quote' => 'Verwijder offerte',
'save_quote' => 'Bewaar offerte',
'email_quote' => 'Email offerte',
'email_quote' => 'E-mail offerte',
'clone_quote' => 'Kloon offerte',
'convert_to_invoice' => 'Zet om naar factuur',
'view_invoice' => 'Bekijk factuur',
@ -404,10 +404,10 @@ return array(
'charge_taxes' => 'BTW berekenen',
'user_management' => 'Gebruikersbeheer',
'add_user' => 'Nieuwe gebruiker',
'send_invite' => 'Verstuur uitnodiging',
'send_invite' => 'Uitnodiging versturen',
'sent_invite' => 'Uitnodiging succesvol verzonden',
'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',
'user_state' => 'Status',
'edit_user' => 'Bewerk gebruiker',
@ -417,11 +417,11 @@ return array(
'deleted_user' => 'Gebruiker succesvol verwijderd',
'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_quote' => 'Weet u zeker dat u deze offerte wilt mailen?',
'confirm_recurring_email_invoice' => 'Terugkeren (herhalen) staat aan, 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 e-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',
'go_back' => 'Ga Terug',
@ -457,10 +457,10 @@ return array(
'sent' => 'verzonden',
'timesheets' => 'Timesheets',
'payment_title' => 'Geef uw betalingsadres en kredietkaartgegevens op',
'payment_cvv' => '*Dit is de code van 3-4 tekens op de achterkant van uw kaart',
'payment_title' => 'Geef uw betalingsadres en creditcardgegevens op',
'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_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',
'id_number' => 'Identificatienummer',
@ -498,20 +498,20 @@ return array(
'restore_user' => 'Herstel gebruiker',
'restored_user' => 'Gebruiker succesvol hersteld',
'show_deleted_users' => 'Toon verwijderde gebruikers',
'email_templates' => 'Emailsjablonen',
'invoice_email' => 'Factuuremail',
'payment_email' => 'Betalingsemail',
'quote_email' => 'Offerte-email',
'email_templates' => 'E-mailsjablonen',
'invoice_email' => 'Factuur-e-mail',
'payment_email' => 'Betalings-e-mail',
'quote_email' => 'Offerte-e-mail',
'reset_all' => 'Reset alles',
'approve' => 'Goedkeuren',
'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_2' => 'Opt-in - checkbox is getoond maar niet geselecteerd',
'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd',
'token_billing_4' => 'Altijd',
'token_billing_checkbox' => 'Sla kredietkaart gegevens op',
'token_billing_checkbox' => 'Sla carditcard gegevens op',
'view_in_stripe' => 'In Stripe bekijken',
'use_card_on_file' => 'Gebruik opgeslagen kaart',
'edit_payment_details' => 'Betalingsdetails aanpassen',
@ -525,7 +525,7 @@ return array(
'billing_address' => 'Factuuradres',
'billing_method' => 'Betaalmethode',
'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.',
'default_invoice_footer' => 'Stel standaard factuurfooter in',
@ -550,7 +550,7 @@ return array(
'created_gateway' => 'Gateway succesvol aangemaakt',
'deleted_gateway' => 'Gateway succesvol verwijderd',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Kredietkaart',
'pay_with_card' => 'Creditcard',
'change_password' => 'Verander wachtwoord',
'current_password' => 'Huidig wachtwoord',
@ -572,17 +572,17 @@ return array(
'set_password' => 'Stel wachtwoord in',
'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' => ':client heeft offerte :invoice goedgekeurd voor :amount.',
'resend_confirmation' => 'Verstuurd bevestingsmail opnieuw',
'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.',
'payment_type_credit_card' => 'Kredietkaart',
'payment_type_credit_card' => 'Creditcard',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Kennis databank',
'knowledge_base' => 'Kennisbank',
'partial' => 'Gedeeld',
'partial_remaining' => ':partial / :balance',
@ -591,11 +591,11 @@ return array(
'client_name' => 'Klantnaam',
'pdf_settings' => 'PDF-instellingen',
'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.',
'view_documentation' => 'Bekijk documentatie',
'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',
'www' => 'www',
@ -635,7 +635,7 @@ return array(
'timer' => 'Timer',
'manual' => 'Manueel',
'date_and_time' => 'Datum en tijd',
'second' => 'second',
'second' => 'seconde',
'seconds' => 'seconden',
'minute' => 'minuut',
'minutes' => 'minuten',
@ -670,9 +670,9 @@ return array(
'pro_plan_title' => 'NINJA PRO',
'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_feature3' => 'Aangepaste URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature3' => 'Aangepaste URLs - "UwMerk.InvoiceNinja.com"',
'pro_plan_feature4' => 'Verwijder "Aangemaakt door Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user toegang & Activeit Tracking',
'pro_plan_feature6' => 'Maak offertes & Pro-forma facturen aan',
@ -696,14 +696,14 @@ return array(
'login' => 'Login',
'or' => 'of',
'email_error' => 'Er was een probleem om de email te verzenden',
'confirm_recurring_timing' => 'Opmerking: emails 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>',
'email_error' => 'Er was een probleem met versturen van de e-mail',
'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">moderne browser</a>',
'payment_terms_help' => 'Stel de standaard factuurvervaldatum in',
'unlink_account' => 'Koppel account los',
'unlink' => 'Koppel los',
'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_help' => 'Pas het adres van de klant aan met de ingevulde gegevens',
'times' => 'Tijden',
@ -718,7 +718,7 @@ return array(
'font_size' => 'Tekstgrootte',
'primary_color' => 'Primaire kleur',
'secondary_color' => 'Secundaire kleur',
'customize_design' => 'Pas design aan',
'customize_design' => 'Pas ontwerp aan',
'content' => 'Inhoud',
'styles' => 'Stijlen',
@ -740,9 +740,9 @@ return array(
'created_by_invoice' => 'Aangemaakt door :invoice',
'primary_user' => 'Primaire gebruiker',
'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>
<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>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>',
'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 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, 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',
'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.',
'auto_bill' => 'Automatische incasso',
'military_time' => '24 uurs tijd',
'military_time' => '24-uurs klok',
'last_sent' => 'Laatst verstuurd',
'reminder_emails' => 'Herinneringse-mails',
'templates_and_reminders' => 'Templates en herinneringen',
'reminder_emails' => 'Herinnerings-e-mails',
'templates_and_reminders' => 'Sjablonen en herinneringen',
'subject' => 'Onderwerp',
'body' => 'Tekst',
'first_reminder' => 'Eerste herinnering',
@ -787,17 +787,17 @@ return array(
'expired_quotes' => 'Verlopen offertes',
'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',
'user_details' => 'Gebruiker gegevens',
'oneclick_login' => 'One-Click Login',
'disable' => 'Uitzetten',
'invoice_quote_number' => 'Factuur en offerte nummers',
'invoice_charges' => 'Facturatie kosten',
'invoice_quote_number' => 'Factuur- en offertenummers',
'invoice_charges' => 'Facturatiekosten',
'invitation_status' => [
'sent' => 'Email verstuurd',
'opened' => 'Email geopend',
'sent' => 'E-mail verstuurd',
'opened' => 'E-mail geopend',
'viewed' => 'Factuur bekeken',
],
'notification_invoice_bounced' => 'We konden factuur :invoice niet afleveren bij :contact.',
@ -808,7 +808,7 @@ return array(
'custom_invoice_link' => 'Eigen factuurlink',
'total_invoiced' => 'Totaal gefactureerd',
'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',
'pro' => 'Pro',
'gateways' => 'Betalingsverwerkers',
@ -817,7 +817,7 @@ return array(
'next_send_on' => 'Verstuur volgende: :date',
'no_longer_running' => 'Deze factuur is niet ingepland',
'general_settings' => 'Algemene instellingen',
'customize' => 'Pas aan',
'customize' => 'Aanpassen',
'oneclick_login_help' => 'Verbind een account om zonder wachtwoord in te kunnen loggen',
'referral_code_help' => 'Verdien geld door onze applicatie online te delen',
@ -842,39 +842,39 @@ return array(
'quote_counter' => 'Offerteteller',
'type' => 'Type',
'activity_1' => ':user created client :client',
'activity_2' => ':user archived client :client',
'activity_3' => ':user deleted client :client',
'activity_4' => ':user created invoice :invoice',
'activity_5' => ':user updated invoice :invoice',
'activity_6' => ':user emailed invoice :invoice to :contact',
'activity_7' => ':contact viewed invoice :invoice',
'activity_8' => ':user archived invoice :invoice',
'activity_9' => ':user deleted invoice :invoice',
'activity_10' => ':contact entered payment :payment for :invoice',
'activity_11' => ':user updated payment :payment',
'activity_12' => ':user archived payment :payment',
'activity_13' => ':user deleted payment :payment',
'activity_14' => ':user entered :credit credit',
'activity_15' => ':user updated :credit credit',
'activity_16' => ':user archived :credit credit',
'activity_17' => ':user deleted :credit credit',
'activity_18' => ':user created quote :quote',
'activity_19' => ':user updated quote :quote',
'activity_20' => ':user emailed quote :quote to :contact',
'activity_21' => ':contact viewed quote :quote',
'activity_22' => ':user archived quote :quote',
'activity_23' => ':user deleted quote :quote',
'activity_24' => ':user restored quote :quote',
'activity_25' => ':user restored invoice :invoice',
'activity_26' => ':user restored client :client',
'activity_27' => ':user restored payment :payment',
'activity_28' => ':user restored :credit credit',
'activity_29' => ':contact approved quote :quote',
'activity_1' => ':user heeft klant :client aangemaakt',
'activity_2' => ':user heeft klant :client gearchiveerd',
'activity_3' => ':user heeft klant :client verwijderd',
'activity_4' => ':user heeft factuur :invoice aangemaakt',
'activity_5' => ':user heeft factuur :invoice bijgewerkt',
'activity_6' => ':user heeft factuur :invoice verstuurd naar :contact',
'activity_7' => ':contact heeft factuur :invoice bekeken',
'activity_8' => ':user heeft factuur :invoice gearchiveerd',
'activity_9' => ':user heeft factuur :invoice verwijderd',
'activity_10' => ':contact heeft betaling :payment ingevoerd voor factuur :invoice',
'activity_11' => ':user heeft betaling :payment bijgewerkt',
'activity_12' => ':user heeft betaling :payment gearchiveerd',
'activity_13' => ':user heeft betaling :payment verwijderd',
'activity_14' => ':user heeft :credit krediet ingevoerd',
'activity_15' => ':user heeft :credit krediet bijgewerkt',
'activity_16' => ':user heeft :credit krediet gearchiveerd',
'activity_17' => ':user heeft :credit krediet verwijderd',
'activity_18' => ':user heeft offerte :quote aangemaakt',
'activity_19' => ':user heeft offerte :quote bijgewerkt',
'activity_20' => ':user heeft offerte :quote verstuurd naar :contact',
'activity_21' => ':contact heeft offerte :quote bekeken',
'activity_22' => ':user heeft offerte :quote gearchiveerd',
'activity_23' => ':user heeft offerte :quote verwijderd',
'activity_24' => ':user heeft offerte :quote hersteld',
'activity_25' => ':user heeft factuur :invoice hersteld',
'activity_26' => ':user heeft klant :client hersteld',
'activity_27' => ':user heeft betaling :payment hersteld',
'activity_28' => ':user heeft :credit krediet hersteld',
'activity_29' => ':contact heeft offerte :quote goedgekeurd',
'payment' => 'Betaling',
'system' => 'Systeem',
'signature' => 'Emailondertekening',
'signature' => 'E-mailhandtekening',
'default_messages' => 'Standaardberichten',
'quote_terms' => 'Offertevoorwaarden',
'default_quote_terms' => 'Standaard offertevoorwaarden',
@ -884,7 +884,7 @@ return array(
'free' => 'Gratis',
'quote_is_approved' => 'Deze offerte is geaccordeerd',
'apply_credit' => 'Apply Credit',
'apply_credit' => 'Krediet gebruiken',
'system_settings' => 'Systeeminstellingen',
'archive_token' => 'Archiveer token',
'archived_token' => 'Token succesvol gearchiveerd',
@ -910,220 +910,286 @@ return array(
'country' => 'Land',
'include' => 'Voeg in',
'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB',
'import_freshbooks' => 'Import From FreshBooks',
'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' => 'Importeren van FreshBooks',
'import_data' => 'Importeer data',
'source' => 'Bron',
'csv' => 'CSV',
'client_file' => 'Klantbestand',
'client_file' => 'Klantenbestand',
'invoice_file' => 'Factuurbestand',
'task_file' => 'Urenbestand',
'no_mapper' => 'No valid mapping for file',
'invalid_csv_header' => 'Invalid CSV Header',
'no_mapper' => 'Geen geldige mapping voor bestand',
'invalid_csv_header' => 'Ongeldige CSV kop',
'email_errors' => [
'inactive_client' => 'Emails can not be sent to inactive clients',
'inactive_contact' => 'Emails can not be sent to inactive contacts',
'inactive_invoice' => 'Emails can not be sent to inactive invoices',
'user_unregistered' => 'Please register your account to send emails',
'user_unconfirmed' => 'Please confirm your account to send emails',
'invalid_contact_email' => 'Invalid contact email',
'inactive_client' => 'E-mails kunnen niet worden verstuurd naar inactieve klanten',
'inactive_contact' => 'E-mails kunnen niet worden verstuurd naar inactieve contactpersonen',
'inactive_invoice' => 'E-mails kunnen niet worden verstuurd naar inactieve facturen',
'user_unregistered' => 'Registreer een account om e-mails te kunnen versturen',
'user_unconfirmed' => 'Bevestig uw account om e-mails te kunnen versturen',
'invalid_contact_email' => 'Ongeldig e-mailadres van contactpersoon',
],
'client_portal' => 'Klantportaal',
'client_portal' => 'Klantenportaal',
'admin' => 'Admin',
'disabled' => 'Uitgeschakeld',
'show_archived_users' => 'Toon gearchiveerde gebruikers',
'notes' => 'Notities',
'invoice_will_create' => 'klant 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',
'secret_key' => 'Secret Key',
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'email_design' => 'Email Design',
'due_by' => 'Due by :date',
'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.',
'template_help_title' => 'Templates Help',
'template_help_1' => 'Available variables:',
'email_design_id' => 'Email Style',
'email_design_help' => 'Make your emails look more professional with HTML layouts',
'plain' => 'Plain',
'light' => 'Light',
'dark' => 'Dark',
'email_design' => 'E-mail Ontwerp',
'due_by' => 'Vervaldatum :date',
'enable_email_markup' => 'Opmaak inschakelen',
'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' => 'Hulp bij sjablonen',
'template_help_1' => 'Beschikbare variabelen:',
'email_design_id' => 'E-mail stijl',
'email_design_help' => 'Geef uw e-mails een professionele uitstraling met HTML ontwerpen',
'plain' => 'Platte tekst',
'light' => 'Licht',
'dark' => 'Donker',
'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.',
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.',
'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.',
'custom_client_fields_helps' => 'Add a text input to the client create/edit page and display the label and value on the PDF.',
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.',
'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.',
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.',
'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.',
'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' => 'Pas het factuur link subdomein aan of toon de factuur op uw eigen website.',
'invoice_number_help' => 'Kies een voorvoegsel of gebruik een patroon om het factuurnummer dynamisch te genereren.',
'quote_number_help' => 'Kies een voorvoegsel of gebruik een patroon om het offertenummer dynamisch te genereren.',
'custom_client_fields_helps' => 'Plaatst een tekstveld op de klanten aanmaak-/bewerkpagina en toont het gekozen label op de 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' => 'Plaatst een tekstveld op de factuur aanmaak-/bewerkpagina en toont het gekozen label op de PDF.',
'custom_invoice_charges_helps' => 'Plaatst een tekstveld op de factuur aanmaak-/bewerkpagina en verwerkt de facturatiekosten in het subtotaal.',
'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.',
'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.',
'confirm' => 'Confirm',
'email_preferences' => 'Email Preferences',
'created_invoices' => 'Successfully created :count invoice(s)',
'next_invoice_number' => 'The next invoice number is :number.',
'next_quote_number' => 'The next quote number is :number.',
'token_expired' => 'De validatie token is verlopen. Probeer het opnieuw.',
'invoice_link' => 'Factuur Link',
'button_confirmation_message' => 'Klik om uw e-mailadres te bevestigen.',
'confirm' => 'Bevestigen',
'email_preferences' => 'E-mailvoorkeuren',
'created_invoices' => ':count facturen succesvol aangemaakt', //TODO: Implement pluralization?
'next_invoice_number' => 'Het volgende factuurnummer is :number.',
'next_quote_number' => 'Het volgende offertenummer is :number.',
'days_before' => 'days before',
'days_after' => 'days after',
'field_due_date' => 'due date',
'field_invoice_date' => 'invoice date',
'schedule' => 'Schedule',
'email_designs' => 'Email Designs',
'assigned_when_sent' => 'Assigned when sent',
'days_before' => 'dagen voor',
'days_after' => 'dagen na',
'field_due_date' => 'vervaldatum',
'field_invoice_date' => 'factuurdatum',
'schedule' => 'Schema',
'email_designs' => 'E-mail Ontwerpen',
'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_purchase_link' => 'Purchase a white label license',
'white_label_custom_css' => ':link voor $'.WHITE_LABEL_PRICE.' om eigen opmaak te gebruiken en ons project te ondersteunen.',
'white_label_purchase_link' => 'Koop een whitelabel licentie',
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors',
'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net',
'vendor' => 'Vendor',
'edit_vendor' => 'Edit Vendor',
'archive_vendor' => 'Archive Vendor',
'delete_vendor' => 'Delete Vendor',
'view_vendor' => 'View Vendor',
'deleted_expense' => 'Successfully deleted expense',
'archived_expense' => 'Successfully archived expense',
'deleted_expenses' => 'Successfully deleted expenses',
'archived_expenses' => 'Successfully archived expenses',
'expense' => 'Uitgave',
'expenses' => 'Uitgaven',
'new_expense' => 'Nieuwe uitgave',
'enter_expense' => 'Uitgave invoeren',
'vendors' => 'Verkopers',
'new_vendor' => 'Nieuwe verkoper',
'payment_terms_net' => 'Betaaltermijn',
'vendor' => 'Verkoper',
'edit_vendor' => 'Bewerk verkoper',
'archive_vendor' => 'Archiveer verkoper',
'delete_vendor' => 'Verwijder verkoper',
'view_vendor' => 'Bekijk verkoper',
'deleted_expense' => 'Uitgave succesvol verwijderd',
'archived_expense' => 'Uitgave succesvol gearchiveerd',
'deleted_expenses' => 'Uitgaven succesvol verwijderd',
'archived_expenses' => 'Uitgaven succesvol gearchiveerd',
// Expenses
'expense_amount' => 'Expense Amount',
'expense_balance' => 'Expense Balance',
'expense_date' => 'Expense Date',
'expense_should_be_invoiced' => 'Should this expense be invoiced?',
'public_notes' => 'Public Notes',
'invoice_amount' => 'Invoice Amount',
'exchange_rate' => 'Exchange Rate',
'yes' => 'Yes',
'no' => 'No',
'should_be_invoiced' => 'Should be invoiced',
'view_expense' => 'View expense # :expense',
'edit_expense' => 'Edit Expense',
'archive_expense' => 'Archive Expense',
'delete_expense' => 'Delete Expense',
'view_expense_num' => 'Expense # :expense',
'updated_expense' => 'Successfully updated expense',
'created_expense' => 'Successfully created expense',
'enter_expense' => 'Enter Expense',
'view' => 'View',
'restore_expense' => 'Restore Expense',
'invoice_expense' => 'Invoice Expense',
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense has already been invoiced',
'convert_currency' => 'Convert currency',
'expense_amount' => 'Uitgave bedrag',
'expense_balance' => 'Uitgave saldo',
'expense_date' => 'Uitgave datum',
'expense_should_be_invoiced' => 'Moet deze uitgave worden gefactureerd?',
'public_notes' => 'Publieke opmerkingen',
'invoice_amount' => 'Factuurbedrag',
'exchange_rate' => 'Wisselkoers',
'yes' => 'Ja',
'no' => 'Nee',
'should_be_invoiced' => 'Moet worden gefactureerd',
'view_expense' => 'Bekijk uitgave #:expense',
'edit_expense' => 'Bewerk uitgave',
'archive_expense' => 'Archiveer uitgave',
'delete_expense' => 'Verwijder uitgave',
'view_expense_num' => 'Uitgave #:expense',
'updated_expense' => 'Uitgave succesvol bijgewerkt',
'created_expense' => 'Uitgave succesvol aangemaakt',
'enter_expense' => 'Uitgave invoeren',
'view' => 'Bekijken',
'restore_expense' => 'Herstel uitgave',
'invoice_expense' => 'Factuur uitgave',
'expense_error_multiple_clients' => 'De uitgaven kunnen niet bij verschillende klanten horen',
'expense_error_invoiced' => 'Uitgave is al gefactureerd',
'convert_currency' => 'Valuta omrekenen',
// Payment terms
'num_days' => 'Number of days',
'create_payment_term' => 'Create Payment Term',
'edit_payment_terms' => 'Edit Payment Term',
'edit_payment_term' => 'Edit Payment Term',
'archive_payment_term' => 'Archive Payment Term',
'num_days' => 'Aantal dagen',
'create_payment_term' => 'Betalingstermijn aanmaken',
'edit_payment_terms' => 'Bewerk betalingstermijnen',
'edit_payment_term' => 'Bewerk betalingstermijn',
'archive_payment_term' => 'Archiveer betalingstermijn',
// recurring due dates
'recurring_due_dates' => 'Recurring Invoice Due Dates',
'recurring_due_date_help' => '<p>Automatically sets a due date for the invoice.</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>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>For example:</p>
'recurring_due_dates' => 'Vervaldatums van terugkerende facturen',
'recurring_due_date_help' => '<p>Stelt automatisch een vervaldatum in voor de factuur.</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>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>Bijvoorbeeld:</p>
<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>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>
<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>
<li>Today is the Friday, due date is the 1st Friday after. The due date will be next Friday, not today.
</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>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>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>Vandaag is het vrijdag, de vervaldatum is ingesteld op de 1e vrijdag erna. De vervaldatum zal volgende week vrijdag zijn, niet vandaag.</li>
</ul>',
'due' => 'Due',
'next_due_on' => 'Due Next: :date',
'use_client_terms' => 'Use client terms',
'day_of_month' => ':ordinal day of month',
'last_day_of_month' => 'Last day of month',
'day_of_week_after' => ':ordinal :day after',
'sunday' => 'Sunday',
'monday' => 'Monday',
'tuesday' => 'Tuesday',
'wednesday' => 'Wednesday',
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
'due' => 'Vervaldatum',
'next_due_on' => 'Vervaldatum volgende: :date',
'use_client_terms' => 'Gebruik betalingsvoorwaarden klant',
'day_of_month' => ':ordinal dag van de maand',
'last_day_of_month' => 'Laatste dag van de maand',
'day_of_week_after' => ':ordinal :day erna',
'sunday' => 'Zondag',
'monday' => 'Maandag',
'tuesday' => 'Dinsdag',
'wednesday' => 'Woensdag',
'thursday' => 'Donderdag',
'friday' => 'Vrijdag',
'saturday' => 'Zaterdag',
// Fonts
'header_font_id' => 'Header Font',
'body_font_id' => 'Body Font',
'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.',
'header_font_id' => 'Header lettertype',
'body_font_id' => 'Body lettertype',
'color_font_help' => 'Opmerking: de primaire kleuren en lettertypen wordt ook gebruikt in het klantenportaal en in aangepaste e-mailontwerpen.',
'live_preview' => 'Live Preview',
'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.',
'live_preview' => 'Live Voorbeeld',
'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.',
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'add_bank_account' => 'Add Bank Account',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
'bank_id' => 'bank',
'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account',
'edit_bank_account' => 'Edit Bank Account',
'archive_bank_account' => 'Archive Bank Account',
'archived_bank_account' => 'Successfully archived bank account',
'created_bank_account' => 'Successfully created bank account',
'validate_bank_account' => 'Validate Bank Account',
'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_password_help' => 'Note: your password is transmitted securely and never stored on our servers.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.',
'username' => 'Username',
'account_number' => 'Account Number',
'account_name' => 'Account Name',
'bank_account_error' => 'Failed to retreive account details, please check your credentials.',
'status_approved' => 'Approved',
'quote_settings' => 'Quote Settings',
'auto_convert_quote' => 'Auto convert quote',
'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.',
'validate' => 'Validate',
'info' => 'Info',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)',
'invoice_message_button' => 'Klik op de onderstaande link om uw factuur van :amount te bekijken.',
'quote_message_button' => 'Klik op de onderstaande link om uw offerte van :amount te bekijken.',
'payment_message_button' => 'Bedankt voor uw betaling van :amount.',
'payment_type_direct_debit' => 'Automatisch incasso',
'bank_accounts' => 'Bankrekeningen',
'add_bank_account' => 'Bankrekening toevoegen',
'setup_account' => 'Rekening instellen',
'import_expenses' => 'Uitgaven importeren',
'bank_id' => 'Bank',
'integration_type' => 'Integratie Type',
'updated_bank_account' => 'Bankrekening succesvol bijgewerkt',
'edit_bank_account' => 'Bewerk bankrekening',
'archive_bank_account' => 'Archiveer bankrekening',
'archived_bank_account' => 'Bankrekening succesvol gearchiveerd',
'created_bank_account' => 'Bankrekening succesvol toegevoegd',
'validate_bank_account' => 'Bankrekening valideren',
'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' => 'Opmerking: uw wachtwoord wordt beveiligd verstuurd en wordt nooit op onze servers opgeslagen.',
'bank_password_warning' => 'Waarschuwing: uw wachtwoord wordt mogelijk als leesbare tekst verzonden, overweeg HTTPS in te schakelen.',
'username' => 'Gebruikersnaam',
'account_number' => 'Rekeningnummer',
'account_name' => 'Rekeninghouder',
'bank_account_error' => 'Het ophalen van rekeninggegevens is mislukt, controleer uw inloggegevens.',
'status_approved' => 'Goedgekeurd',
'quote_settings' => 'Offerte instellingen',
'auto_convert_quote' => 'Offerte automatisch omzetten',
'auto_convert_quote_help' => 'Zet een offerte automatisch om in een factuur zodra deze door een klant wordt goedgekeurd.',
'validate' => 'Valideren',
'info' => 'Informatie',
'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.',
'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.',
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.',
'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' => 'De uitgaven kunnen geen verschillende munteenheden hebben.',
'expense_error_mismatch_currencies' => 'De munteenheid van de klant komt niet overeen met de munteenheid van de uitgave.',
'trello_roadmap' => 'Trello Roadmap',
'header_footer' => 'Header/Footer',
'first_page' => 'first page',
'all_pages' => 'all pages',
'last_page' => 'last page',
'all_pages_header' => 'Show header on',
'all_pages_footer' => 'Show footer on',
'invoice_currency' => 'Invoice Currency',
'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.',
'quote_issued_to' => 'Quote issued to',
'show_currency_code' => 'Currency Code',
'trial_message' => 'Your account will receive a free two week trial of our pro plan.',
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.',
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.',
'trial_call_to_action' => 'Start Free Trial',
'trial_success' => 'Successfully enabled two week free pro plan trial',
'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.',
'first_page' => 'eerste pagina',
'all_pages' => 'alle pagina\'s',
'last_page' => 'laatste pagina',
'all_pages_header' => 'Toon header op',
'all_pages_footer' => 'Toon footer op',
'invoice_currency' => 'Factuur valuta',
'enable_https' => 'We raden u dringend aan om HTTPS te gebruiken om creditcard informatie digitaal te accepteren.',
'quote_issued_to' => 'Offerte uitgeschreven voor',
'show_currency_code' => 'Valutacode',
'trial_message' => 'Uw account zal een gratis twee weken durende probeerversie van ons pro plan krijgen.',
'trial_footer' => 'Uw gratis probeerversie duurt nog :count dagen, :link om direct te upgraden.',
'trial_footer_last_day' => 'Dit is de laatste dag van uw gratis probeerversie, :link om direct te upgraden.',
'trial_call_to_action' => 'Start gratis probeerversie',
'trial_success' => 'De gratis twee weken durende probeerversie van het pro plan is succesvol geactiveerd.',
'overdue' => 'Verlopen',
'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.",
"after" => ":attribute moet een datum na :date zijn.",
"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.",
"array" => ":attribute moet geselecteerde elementen bevatten.",
"before" => ":attribute moet een datum voor :date zijn.",
"between" => array(
"numeric" => ":attribute moet tussen :min en :max 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.",
),
"confirmed" => ":attribute bevestiging komt niet overeen.",
@ -47,14 +47,14 @@ return array(
"max" => array(
"numeric" => ":attribute moet minder dan :max 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.",
),
"mimes" => ":attribute moet een bestand zijn van het bestandstype :values.",
"min" => array(
"numeric" => ":attribute moet minimaal :min 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.",
),
"not_in" => "Het geselecteerde :attribute is ongeldig.",
@ -70,7 +70,7 @@ return array(
"size" => array(
"numeric" => ":attribute moet :size 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.",
),
"unique" => ":attribute is al in gebruik.",
@ -81,7 +81,7 @@ return array(
"notmasked" => "De waarden zijn verborgen",
"less_than" => 'Het :attribute moet minder zijn dan :value',
"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",
/*

View File

@ -1123,4 +1123,73 @@ return array(
'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.',
'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',
'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])
{!! 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">
@ -63,7 +63,7 @@
@foreach ($gateway->fields as $field => $details)
@if ($details && !$accountGateway)
@if ($details && !$accountGateway && !is_array($details))
{!! Former::populateField($gateway->id.'_'.$field, $details) !!}
@endif

View File

@ -5,7 +5,7 @@
@include('accounts.nav', ['selected' => ACCOUNT_API_TOKENS, 'advanced' => true])
<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())
{!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!}
@endif
@ -51,4 +51,9 @@
</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

View File

@ -13,6 +13,7 @@
->addClass('warn-on-exit') !!}
{!! 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('enable_portal_password', intval($enable_portal_password)) !!}
{!! Former::populateField('send_portal_password', intval($send_portal_password)) !!}
@ -28,45 +29,52 @@
@include('accounts.nav', ['selected' => ACCOUNT_CLIENT_PORTAL])
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.client_portal') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_client_portal')
->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_help')) !!}
</div>
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_portal_password')
->text(trans('texts.enable_portal_password'))
->help(trans('texts.enable_portal_password_help'))
->label('&nbsp;') !!}
</div>
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('send_portal_password')
->text(trans('texts.send_portal_password'))
->help(trans('texts.send_portal_password_help'))
->label('&nbsp;') !!}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::textarea('client_view_css')
->label(trans('texts.custom_css'))
->rows(10)
->raw()
->autofocus()
->maxlength(60000)
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
</div>
</div>
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.client_portal') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_client_portal')
->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_help')) !!}
</div>
<div 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">
{!! Former::checkbox('enable_portal_password')
->text(trans('texts.enable_portal_password'))
->help(trans('texts.enable_portal_password_help'))
->label('&nbsp;') !!}
</div>
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('send_portal_password')
->text(trans('texts.send_portal_password'))
->help(trans('texts.send_portal_password_help'))
->label('&nbsp;') !!}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::textarea('client_view_css')
->label(trans('texts.custom_css'))
->rows(10)
->raw()
->autofocus()
->maxlength(60000)
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
</div>
</div>
</div>
</div>
</div>

View File

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

View File

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

View File

@ -48,6 +48,7 @@
function getPDFString(cb) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
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.invoice_design_id = $('#invoice_design_id').val();
@ -207,6 +208,7 @@
{!! 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('invoice_embed_documents')->text(trans('texts.invoice_embed_documents_help')) !!}
</div>
</div>

View File

@ -11,26 +11,29 @@
@include('accounts.nav', ['selected' => ACCOUNT_LOCALIZATION])
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.localization') !!}</h3>
</div>
<div class="panel-body form-padding-right">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.localization') !!}</h3>
</div>
<div class="panel-body form-padding-right">
{!! Former::select('currency_id')->addOption('','')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::select('language_id')->addOption('','')
->fromQuery($languages, 'name', 'id') !!}
{!! Former::select('timezone_id')->addOption('','')
->fromQuery($timezones, 'location', 'id') !!}
{!! Former::select('date_format_id')->addOption('','')
->fromQuery($dateFormats, 'label', 'id') !!}
{!! Former::select('datetime_format_id')->addOption('','')
->fromQuery($datetimeFormats, 'label', 'id') !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
{{-- Former::checkbox('show_currency_code')->text(trans('texts.enable')) --}}
{!! Former::select('currency_id')->addOption('','')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::select('language_id')->addOption('','')
->fromQuery($languages, 'name', 'id') !!}
{!! Former::select('timezone_id')->addOption('','')
->fromQuery($timezones, 'location', 'id') !!}
{!! Former::select('date_format_id')->addOption('','')
->fromQuery($dateFormats, 'label', 'id') !!}
{!! Former::select('datetime_format_id')->addOption('','')
->fromQuery($datetimeFormats, 'label', 'id') !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
{{-- Former::checkbox('show_currency_code')->text(trans('texts.enable')) --}}
</div>
</div>
</div>
</div>

View File

@ -6,22 +6,23 @@
@include('accounts.nav', ['selected' => ACCOUNT_SYSTEM_SETTINGS])
<div class="row">
{!! Former::open('/update_setup')
->addClass('warn-on-exit')
->autocomplete('off')
->rules([
'app[url]' => 'required',
//'database[default]' => 'required',
'database[type][host]' => 'required',
'database[type][database]' => 'required',
'database[type][username]' => 'required',
'database[type][password]' => 'required',
]) !!}
<div class="col-md-12">
{!! Former::open('/update_setup')
->addClass('warn-on-exit')
->autocomplete('off')
->rules([
'app[url]' => 'required',
//'database[default]' => 'required',
'database[type][host]' => 'required',
'database[type][database]' => 'required',
'database[type][username]' => 'required',
'database[type][password]' => 'required',
]) !!}
@include('partials.system_settings')
@include('partials.system_settings')
</div>
</div>
<center>

View File

@ -201,6 +201,13 @@
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>':'' !!}";
@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 = [
{!! json_encode($emailFooter) !!},
"{{ $account->getDisplayName() }}",
@ -213,6 +220,7 @@
"0001",
"0001",
passwordHtml,
documentsHtml,
"{{ URL::to('/view/...') }}$password",
'{!! Form::flatButton('view_invoice', '#0b4d78') !!}$password',
"{{ URL::to('/payment/...') }}$password",

View File

@ -105,7 +105,7 @@
@endif
<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']) !!}
</p>

View File

@ -53,7 +53,7 @@
@section('body')
<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">
<img src="{{ asset('images/icon-login.png') }}" />
<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