From bd817a3700a4cf36a5c8a6959ca633d27d2c6cc8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 21:28:41 +0300 Subject: [PATCH 01/51] Try running TaxRates test earlier --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f66f7aa29f7..cbfc3331d574 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,6 +66,7 @@ before_script: script: - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php @@ -76,7 +77,6 @@ script: - php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php @@ -96,4 +96,4 @@ after_script: notifications: email: on_success: never - on_failure: change \ No newline at end of file + on_failure: change From 85184f9a5b57bb6b0ed3255b6af1a7d2c23cdff7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 21:42:22 +0300 Subject: [PATCH 02/51] check document completed upload before user submitted form --- app/Ninja/Repositories/ExpenseRepository.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index 2d782298a2e7..2007ec6e8613 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -148,11 +148,14 @@ class ExpenseRepository extends BaseRepository // 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 && Auth::user()->can('edit', $document)){ - $document->invoice_id = null; - $document->expense_id = $expense->id; - $document->save(); + // check document completed upload before user submitted form + if ($document_id) { + $document = Document::scope($document_id)->first(); + if($document && Auth::user()->can('edit', $document)){ + $document->invoice_id = null; + $document->expense_id = $expense->id; + $document->save(); + } } } From 44fb6682e484b2cf27287cd2548af4987d631ba0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 22:01:15 +0300 Subject: [PATCH 03/51] Prevent saving expense before documents have uploaded --- resources/views/expenses/edit.blade.php | 24 ++++++++++++++++++++++-- resources/views/invoices/edit.blade.php | 7 ++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 873da5451ea0..aadfacc52617 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -15,7 +15,10 @@ @section('content') - {!! Former::open($url)->addClass('warn-on-exit main-form')->method($method) !!} + {!! Former::open($url) + ->addClass('warn-on-exit main-form') + ->onsubmit('return onFormSubmit(event)') + ->method($method) !!}
{!! Former::text('action') !!}
@@ -154,6 +157,15 @@ clientMap[client.public_id] = client; } + function onFormSubmit(event) { + if (window.countUploadingDocuments > 0) { + alert("{!! trans('texts.wait_for_upload') !!}"); + return false; + } + + return true; + } + function onClientChange() { var clientId = $('select#client_id').val(); var client = clientMap[clientId]; @@ -240,6 +252,7 @@ dropzone.on("addedfile",handleDocumentAdded); dropzone.on("removedfile",handleDocumentRemoved); dropzone.on("success",handleDocumentUploaded); + dropzone.on("canceled",handleDocumentCanceled); for (var i=0; iaccount->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ // open document when clicked @@ -373,6 +387,7 @@ if(file.mock)return; file.index = model.documents().length; model.addDocument({name:file.name, size:file.size, type:file.type}); + window.countUploadingDocuments++; } function handleDocumentRemoved(file){ @@ -382,11 +397,16 @@ function handleDocumentUploaded(file, response){ file.public_id = response.document.public_id model.documents()[file.index].update(response.document); - + window.countUploadingDocuments--; if(response.document.preview_url){ dropzone.emit('thumbnail', file, response.document.preview_url); } } + + function handleDocumentCanceled() + { + window.countUploadingDocuments--; + } @endif diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 235266c64a54..d17a3ba25c62 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1025,6 +1025,7 @@ dropzone.on("addedfile",handleDocumentAdded); dropzone.on("removedfile",handleDocumentRemoved); dropzone.on("success",handleDocumentUploaded); + dropzone.on("canceled",handleDocumentCanceled); for (var i=0; i From a2c8a3e53459e0b0cb40b3219513a9a12d74719b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 15:44:11 +0300 Subject: [PATCH 04/51] Test PHP 5.6.21 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cbfc3331d574..251caaf71c44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ sudo: true php: - 5.5.9 + - 5.6.21 # - 5.6 # - 7.0 # - hhvm From 84736dac9d3a523516e97a598130218acc17034b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 15:46:43 +0300 Subject: [PATCH 05/51] Test PHP 5.6.21 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 251caaf71c44..460a176f2c09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: true php: - 5.5.9 - - 5.6.21 + - 5.6.16 # - 5.6 # - 7.0 # - hhvm From 29bccd650d5764e30a88f49d04df521f7adaa4ad Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 15:50:23 +0300 Subject: [PATCH 06/51] Fix permission issue with quotes --- app/Http/Controllers/QuoteController.php | 12 ++++++------ app/Models/EntityModel.php | 13 +++++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index a8ea0beaa476..58807ba4a9eb 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -113,16 +113,16 @@ class QuoteController extends BaseController $rates = TaxRate::scope()->orderBy('name')->get(); $options = []; $defaultTax = false; - + foreach ($rates as $rate) { - $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; - + $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, @@ -130,7 +130,7 @@ class QuoteController extends BaseController 'taxRateOptions' => $options, 'defaultTax' => $defaultTax, 'countries' => Cache::get('countries'), - 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), + 'clients' => Client::scope()->viewable()->with('contacts', 'country')->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'currencies' => Cache::get('currencies'), 'sizes' => Cache::get('sizes'), diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 95e85e6acef4..4b724d3953ee 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -30,7 +30,7 @@ class EntityModel extends Eloquent } else { $lastEntity = $className::scope(false, $entity->account_id); } - + $lastEntity = $lastEntity->orderBy('public_id', 'DESC') ->first(); @@ -86,6 +86,15 @@ class EntityModel extends Eloquent return $query; } + public function scopeViewable($query) + { + if (Auth::check() && ! Auth::user()->hasPermission('view_all')) { + $query->where($this->getEntityType(). 's.user_id', '=', Auth::user()->id); + } + + return $query; + } + public function scopeWithArchived($query) { return $query->withTrashed()->where('is_deleted', '=', false); @@ -110,7 +119,7 @@ class EntityModel extends Eloquent { return 'App\\Ninja\\Transformers\\' . ucwords(Utils::toCamelCase($entityType)) . 'Transformer'; } - + public function setNullValues() { foreach ($this->fillable as $field) { From 5e4fab9f5bceb12bdd77503ed6b638500f8700c6 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 16:03:32 +0300 Subject: [PATCH 07/51] Updated PHP test version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 460a176f2c09..dd7541da820e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: true php: - 5.5.9 - - 5.6.16 + - 5.6 # - 5.6 # - 7.0 # - hhvm From c55d6510bf3031a2b3e3e80981f1a3797b259680 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 16:04:53 +0300 Subject: [PATCH 08/51] Show permissions if not enabled --- resources/lang/en/texts.php | 3 ++- resources/views/users/edit.blade.php | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 481de829b614..e70250130638 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1311,7 +1311,8 @@ $LANG = array( 'new_start_date' => 'New start date', 'security' => 'Security', 'see_whats_new' => 'See what\'s new in v:version', - 'wait_for_upload' => 'Please wait for the document upload to complete.' + 'wait_for_upload' => 'Please wait for the document upload to complete.', + 'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.' ); diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 94b74664a7e3..0a572fd9ec42 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_USER_MANAGEMENT]) @@ -31,13 +31,20 @@ -@if (Utils::hasFeature(FEATURE_USER_PERMISSIONS))

{!! trans('texts.permissions') !!}

+ @if ( ! Utils::hasFeature(FEATURE_USER_PERMISSIONS)) +
{{ trans('texts.upgrade_for_permissions') }}
+ + @endif {!! Former::checkbox('is_admin') ->label(' ') @@ -61,12 +68,11 @@ ->id('permissions_edit_all') ->text(trans('texts.user_edit_all')) ->help(trans('texts.edit_all_help')) !!} - -
-
-@endif - {!! Former::actions( + + + + {!! Former::actions( Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/user_management'))->appendIcon(Icon::create('remove-circle'))->large(), Button::success(trans($user && $user->confirmed ? 'texts.save' : 'texts.send_invite'))->submit()->large()->appendIcon(Icon::create($user && $user->confirmed ? 'floppy-disk' : 'send')) )!!} @@ -88,4 +94,4 @@ if(!viewChecked)$('#permissions_edit_all').prop('checked',false) } fixCheckboxes(); -@stop \ No newline at end of file +@stop From dd9c95a1a851189772e29a04d850d63c35ddfe4b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 18:21:43 +0300 Subject: [PATCH 09/51] Trying to fix zend_mm_heap corrupted --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index dd7541da820e..0518d00953f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ before_install: # set GitHub token and update composer - if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi; - composer self-update && composer -V + - export USE_ZEND_ALLOC=0 install: # install Composer dependencies From 0da3741b567e6492f63e91e13eeeb1dbaf23e6cb Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 18:28:13 +0300 Subject: [PATCH 10/51] Fixed date formatting in document list --- app/Ninja/Repositories/DocumentRepository.php | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 094b4848ebe0..a2278b1843ae 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -61,31 +61,31 @@ class DocumentRepository extends BaseRepository { $extension = strtolower($uploaded->getClientOriginalExtension()); if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ - $documentType = 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 @@ -93,7 +93,7 @@ class DocumentRepository extends BaseRepository $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; @@ -105,32 +105,32 @@ class DocumentRepository extends BaseRepository // Needs to be converted $makePreview = true; } else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){ - $makePreview = true; + $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; @@ -141,9 +141,9 @@ class DocumentRepository extends BaseRepository $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); @@ -153,23 +153,23 @@ class DocumentRepository extends BaseRepository } }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; @@ -177,10 +177,10 @@ class DocumentRepository extends BaseRepository 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') @@ -192,7 +192,7 @@ class DocumentRepository extends BaseRepository ->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) + //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( 'invitations.invitation_key', 'invoices.invoice_number', @@ -205,22 +205,22 @@ class DocumentRepository extends BaseRepository $table = \Datatable::query($query) ->addColumn('invoice_number', function ($model) { return link_to( - '/view/'.$model->invitation_key, + '/view/'.$model->invitation_key, $model->invoice_number - )->toHtml(); + )->toHtml(); }) ->addColumn('name', function ($model) { return link_to( - '/client/documents/'.$model->invitation_key.'/'.$model->public_id.'/'.$model->name, + '/client/documents/'.$model->invitation_key.'/'.$model->public_id.'/'.$model->name, $model->name, ['target'=>'_blank'] - )->toHtml(); + )->toHtml(); }) ->addColumn('document_date', function ($model) { - return Utils::fromSqlDate($model->created_at); + return Utils::dateToString($model->created_at); }) ->addColumn('document_size', function ($model) { - return Form::human_filesize($model->size); + return Form::human_filesize($model->size); }); return $table->make(); From 2043621ac9105d244dff85f921adfd746ea40d29 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 19:02:53 +0300 Subject: [PATCH 11/51] Fix for deleting documents --- app/Http/Controllers/DocumentController.php | 44 ++++++++++++--------- app/Http/Requests/UpdateDocumentRequest.php | 26 ++++++++++++ app/Http/routes.php | 1 + resources/views/expenses/edit.blade.php | 8 ++++ resources/views/invoices/edit.blade.php | 8 ++++ 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 app/Http/Requests/UpdateDocumentRequest.php diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index d597ba004474..e4ea605d57a0 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -14,6 +14,7 @@ use App\Ninja\Repositories\DocumentRepository; use App\Http\Requests\DocumentRequest; use App\Http\Requests\CreateDocumentRequest; +use App\Http\Requests\UpdateDocumentRequest; class DocumentController extends BaseController { @@ -26,20 +27,20 @@ class DocumentController extends BaseController $this->documentRepo = $documentRepo; } - + public function get(DocumentRequest $request) { return static::getDownloadResponse($request->entity()); } - + 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'], @@ -54,59 +55,59 @@ class DocumentController extends BaseController $response = Response::make($document->getRaw(), 200); $response->header('content-type', Document::$types[$document->type]['mime']); } - + return $response; } - + public function getPreview(DocumentRequest $request) { $document = $request->entity(); - + 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(DocumentRequest $request, $publicId, $name) { $document = $request->entity(); - + if(substr($name, -3)=='.js'){ $name = substr($name, 0, -3); } - + 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(CreateDocumentRequest $request) { if (!Utils::hasFeature(FEATURE_DOCUMENTS)) { return; } - + $result = $this->documentRepo->upload(Input::all()['file'], $doc_array); - + if(is_string($result)){ return Response::json([ 'error' => $result, @@ -120,4 +121,11 @@ class DocumentController extends BaseController ], 200); } } + + public function delete(UpdateDocumentRequest $request) + { + $request->entity()->delete(); + + return RESULT_SUCCESS; + } } diff --git a/app/Http/Requests/UpdateDocumentRequest.php b/app/Http/Requests/UpdateDocumentRequest.php new file mode 100644 index 000000000000..3a950c9f3475 --- /dev/null +++ b/app/Http/Requests/UpdateDocumentRequest.php @@ -0,0 +1,26 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 84ec2552bb30..446179ae23d8 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -156,6 +156,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS'); Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview'); Route::post('document', 'DocumentController@postUpload'); + Route::delete('documents/{documents}', 'DocumentController@delete'); Route::get('quotes/create/{client_id?}', 'QuoteController@create'); Route::get('quotes/{invoices}/clone', 'InvoiceController@cloneInvoice'); diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index aadfacc52617..d8c5c62d36e1 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -243,6 +243,7 @@ }, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, + dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}", @foreach(['default_message', 'fallback_message', 'fallback_text', 'file_too_big', 'invalid_file_type', 'response_error', 'cancel_upload', 'cancel_upload_confirmation', 'remove_file'] as $key) "dict{{strval($key)}}":"{{trans('texts.dropzone_'.Utils::toClassCase($key))}}", @endforeach @@ -392,6 +393,13 @@ function handleDocumentRemoved(file){ model.removeDocument(file.public_id); + $.ajax({ + url: '{{ '/documents/' }}' + file.public_id, + type: 'DELETE', + success: function(result) { + // Do something with the result + } + }); } function handleDocumentUploaded(file, response){ diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index d17a3ba25c62..4f6940645ada 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1016,6 +1016,7 @@ }, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, + dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}", @foreach(['default_message', 'fallback_message', 'fallback_text', 'file_too_big', 'invalid_file_type', 'response_error', 'cancel_upload', 'cancel_upload_confirmation', 'remove_file'] as $key) "dict{{Utils::toClassCase($key)}}":"{{trans('texts.dropzone_'.$key)}}", @endforeach @@ -1459,6 +1460,13 @@ function handleDocumentRemoved(file){ model.invoice().removeDocument(file.public_id); refreshPDF(true); + $.ajax({ + url: '{{ '/documents/' }}' + file.public_id, + type: 'DELETE', + success: function(result) { + // Do something with the result + } + }); } function handleDocumentUploaded(file, response){ From f233582ea8720cf28ae8df7aff44cfaa6bbb5a02 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 20:48:27 +0300 Subject: [PATCH 12/51] Ensure all files are deleted with the account --- app/Http/Controllers/AccountController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 49955e184b89..45057914f1e6 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -1245,6 +1245,10 @@ class AccountController extends BaseController $account = Auth::user()->account; \Log::info("Canceled Account: {$account->name} - {$user->email}"); + Document::scope()->each(function($item, $key) { + $item->delete(); + }); + $this->accountRepo->unlinkAccount($account); if ($account->company->accounts->count() == 1) { $account->company->forceDelete(); From e5afb28e4b7e8d2da6beaee93fd787fae4971f56 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 20:53:13 +0300 Subject: [PATCH 13/51] Testing travis change --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0518d00953f9..9eef15bf06b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_install: # set GitHub token and update composer - if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi; - composer self-update && composer -V - - export USE_ZEND_ALLOC=0 +# - export USE_ZEND_ALLOC=0 install: # install Composer dependencies From dadac05532c5d398961df69d70eb864b164deef4 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 26 May 2016 17:56:54 +0300 Subject: [PATCH 14/51] add support to multiple invoice types --- app/Console/Commands/CheckData.php | 12 +++---- app/Http/Controllers/AccountController.php | 2 +- app/Http/Controllers/ClientController.php | 2 +- .../Controllers/ClientPortalController.php | 2 +- .../Controllers/DashboardApiController.php | 10 +++--- app/Http/Controllers/DashboardController.php | 10 +++--- app/Http/Controllers/ExportController.php | 6 ++-- app/Http/Controllers/InvoiceController.php | 6 ++-- app/Http/Controllers/PaymentController.php | 4 +-- app/Http/Controllers/ReportController.php | 6 ++-- app/Http/routes.php | 3 ++ app/Models/Account.php | 34 +++++++++---------- app/Models/Invoice.php | 27 ++++++++++----- app/Ninja/Mailers/ContactMailer.php | 2 +- app/Ninja/Presenters/InvoicePresenter.php | 2 +- app/Ninja/Repositories/InvoiceRepository.php | 16 ++++----- app/Ninja/Transformers/InvoiceTransformer.php | 2 +- app/Services/InvoiceService.php | 4 +-- app/Services/PushService.php | 4 +-- ..._05_18_085739_add_invoice_type_support.php | 31 +++++++++++++++++ resources/views/dashboard.blade.php | 8 ++--- 21 files changed, 118 insertions(+), 75 deletions(-) create mode 100644 database/migrations/2016_05_18_085739_add_invoice_type_support.php diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 5c644c5b4bf4..795e241e6249 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -154,7 +154,7 @@ class CheckData extends Command { $clients->where('clients.id', '=', $this->option('client_id')); } else { $clients->where('invoices.is_deleted', '=', 0) - ->where('invoices.is_quote', '=', 0) + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.is_recurring', '=', 0) ->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999'); } @@ -184,7 +184,7 @@ class CheckData extends Command { if ($activity->invoice_id) { $invoice = DB::table('invoices') ->where('id', '=', $activity->invoice_id) - ->first(['invoices.amount', 'invoices.is_recurring', 'invoices.is_quote', 'invoices.deleted_at', 'invoices.id', 'invoices.is_deleted']); + ->first(['invoices.amount', 'invoices.is_recurring', 'invoices.invoice_type_id', 'invoices.deleted_at', 'invoices.id', 'invoices.is_deleted']); // Check if this invoice was once set as recurring invoice if ($invoice && !$invoice->is_recurring && DB::table('invoices') @@ -221,14 +221,14 @@ class CheckData extends Command { && $invoice->amount > 0; // **Fix for allowing converting a recurring invoice to a normal one without updating the balance** - if ($noAdjustment && !$invoice->is_quote && !$invoice->is_recurring) { - $this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}"); + if ($noAdjustment && $invoice->invoice_type_id == INVOICE_TYPE_STANDARD && !$invoice->is_recurring) { + $this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $foundProblem = true; $clientFix += $invoice->amount; $activityFix = $invoice->amount; // **Fix for updating balance when creating a quote or recurring invoice** - } elseif ($activity->adjustment != 0 && ($invoice->is_quote || $invoice->is_recurring)) { - $this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}"); + } elseif ($activity->adjustment != 0 && ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE || $invoice->is_recurring)) { + $this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $foundProblem = true; $clientFix -= $activity->adjustment; $activityFix = 0; diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 49955e184b89..6544333a3f03 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -590,8 +590,8 @@ class AccountController extends BaseController // sample invoice to help determine variables $invoice = Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->with('client', 'account') - ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->first(); diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index d2a0633e5c73..8a451113115e 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -136,7 +136,7 @@ class ClientController extends BaseController 'credit' => $client->getTotalCredit(), 'title' => trans('texts.view_client'), 'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0, - 'hasQuotes' => Invoice::scope()->where('is_quote', '=', true)->whereClientId($client->id)->count() > 0, + 'hasQuotes' => Invoice::scope()->invoiceType(INVOICE_TYPE_QUOTE)->whereClientId($client->id)->count() > 0, 'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0, 'gatewayLink' => $client->getGatewayLink($accountGateway), 'gateway' => $accountGateway diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index 56dd183b19fe..ba025e283f1b 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -62,7 +62,7 @@ class ClientPortalController extends BaseController if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { - if ($invoice->is_quote) { + if ($invoice->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteInvitationWasViewed($invoice, $invitation)); } else { event(new InvoiceInvitationWasViewed($invoice, $invitation)); diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 1170812b8356..e7e96a5ad91c 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -24,7 +24,7 @@ class DashboardApiController extends BaseAPIController ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) ->where('invoices.is_recurring', '=', false) - ->where('invoices.is_quote', '=', false); + ->where('invoices.invoice_type_id', '=', false); if(!$view_all){ $metrics = $metrics->where(function($query) use($user_id){ @@ -62,7 +62,7 @@ class DashboardApiController extends BaseAPIController ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) - ->where('invoices.is_quote', '=', false) + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.is_recurring', '=', false); if(!$view_all){ @@ -106,7 +106,7 @@ class DashboardApiController extends BaseAPIController $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']) + $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', 'invoice_type_id']) ->orderBy('invoices.due_date', 'asc') ->take(50) ->get(); @@ -131,7 +131,7 @@ class DashboardApiController extends BaseAPIController } $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']) + ->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', 'invoice_type_id']) ->get(); $payments = DB::table('payments') @@ -157,7 +157,7 @@ class DashboardApiController extends BaseAPIController $hasQuotes = false; foreach ([$upcoming, $pastDue] as $data) { foreach ($data as $invoice) { - if ($invoice->is_quote) { + if ($invoice->isType(INVOICE_TYPE_QUOTE)) { $hasQuotes = true; } } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 718d73c67d42..283a0984771c 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -26,7 +26,7 @@ class DashboardController extends BaseController ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) ->where('invoices.is_recurring', '=', false) - ->where('invoices.is_quote', '=', false); + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD); if(!$view_all){ $metrics = $metrics->where(function($query) use($user_id){ @@ -64,7 +64,7 @@ class DashboardController extends BaseController ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) - ->where('invoices.is_quote', '=', false) + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.is_recurring', '=', false); if(!$view_all){ @@ -121,7 +121,7 @@ class DashboardController extends BaseController $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']) + $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', 'invoice_type_id']) ->orderBy('invoices.due_date', 'asc') ->take(50) ->get(); @@ -147,7 +147,7 @@ class DashboardController extends BaseController } $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']) + ->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', 'invoice_type_id']) ->get(); $payments = DB::table('payments') @@ -173,7 +173,7 @@ class DashboardController extends BaseController $hasQuotes = false; foreach ([$upcoming, $pastDue] as $data) { foreach ($data as $invoice) { - if ($invoice->is_quote) { + if ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE) { $hasQuotes = true; } } diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index c7f30b03be29..244b6e8aee5f 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -134,23 +134,23 @@ class ExportController extends BaseController if ($request->input(ENTITY_INVOICE)) { $data['invoices'] = Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->with('user', 'client.contacts', 'invoice_status') ->withArchived() - ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->get(); $data['quotes'] = Invoice::scope() + ->invoiceType(INVOICE_TYPE_QUOTE) ->with('user', 'client.contacts', 'invoice_status') ->withArchived() - ->where('is_quote', '=', true) ->where('is_recurring', '=', false) ->get(); $data['recurringInvoices'] = Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->with('user', 'client.contacts', 'invoice_status', 'frequency') ->withArchived() - ->where('is_quote', '=', false) ->where('is_recurring', '=', true) ->get(); } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 9a0dadf494c4..62faaca695b5 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -567,9 +567,9 @@ class InvoiceController extends BaseController 'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY), 'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS), ]; - $invoice->is_quote = intval($invoice->is_quote); + $invoice->invoice_type_id = intval($invoice->invoice_type_id); - $activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE; + $activityTypeId = $invoice->isType(INVOICE_TYPE_QUOTE) ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE; $activities = Activity::scope(false, $invoice->account_id) ->where('activity_type_id', '=', $activityTypeId) ->where('invoice_id', '=', $invoice->id) @@ -589,7 +589,7 @@ class InvoiceController extends BaseController 'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY), 'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS), ]; - $backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote); + $backup->invoice_type_id = isset($backup->invoice_type_id) && intval($backup->invoice_type_id) == INVOICE_TYPE_QUOTE; $backup->account = $invoice->account->toArray(); $versionsJson[$activity->id] = $backup; diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 6a5742fabe2b..c0dd4a9f8550 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -77,8 +77,8 @@ class PaymentController extends BaseController public function create(PaymentRequest $request) { $invoices = Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->where('is_recurring', '=', false) - ->where('is_quote', '=', false) ->where('invoices.balance', '>', 0) ->with('client', 'invoice_status') ->orderBy('invoice_number')->get(); @@ -108,7 +108,7 @@ class PaymentController extends BaseController $data = array( 'client' => null, 'invoice' => null, - 'invoices' => Invoice::scope()->where('is_recurring', '=', false)->where('is_quote', '=', false) + 'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false) ->with('client', 'invoice_status')->orderBy('invoice_number')->get(), 'payment' => $payment, 'method' => 'PUT', diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 4f162f260d37..65d379eb3f68 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -168,7 +168,7 @@ class ReportController extends BaseController ->groupBy($groupBy); if ($entityType == ENTITY_INVOICE) { - $records->where('is_quote', '=', false) + $records->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('is_recurring', '=', false); } elseif ($entityType == ENTITY_PAYMENT) { $records->join('invoices', 'invoices.id', '=', 'payments.invoice_id') @@ -374,7 +374,7 @@ class ReportController extends BaseController $query->where('invoice_date', '>=', $startDate) ->where('invoice_date', '<=', $endDate) ->where('is_deleted', '=', false) - ->where('is_quote', '=', false) + ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('is_recurring', '=', false) ->with(['payments' => function($query) { $query->withTrashed() @@ -429,7 +429,7 @@ class ReportController extends BaseController ->with(['invoices' => function($query) use ($startDate, $endDate) { $query->where('invoice_date', '>=', $startDate) ->where('invoice_date', '<=', $endDate) - ->where('is_quote', '=', false) + ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('is_recurring', '=', false) ->withArchived(); }]); diff --git a/app/Http/routes.php b/app/Http/routes.php index 84ec2552bb30..4b1a8e10ad95 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -358,6 +358,9 @@ if (!defined('CONTACT_EMAIL')) { define('ENTITY_BANK_ACCOUNT', 'bank_account'); define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount'); + define('INVOICE_TYPE_STANDARD', 1); + define('INVOICE_TYPE_QUOTE', 2); + define('PERSON_CONTACT', 'contact'); define('PERSON_USER', 'user'); define('PERSON_VENDOR_CONTACT','vendorcontact'); diff --git a/app/Models/Account.php b/app/Models/Account.php index c489d21b180b..208ea19dcdf0 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -524,7 +524,7 @@ class Account extends Eloquent $invoice = Invoice::createNew(); $invoice->is_recurring = false; - $invoice->is_quote = false; + $invoice->invoice_type_id = INVOICE_TYPE_STANDARD; $invoice->invoice_date = Utils::today(); $invoice->start_date = Utils::today(); $invoice->invoice_design_id = $this->invoice_design_id; @@ -535,7 +535,7 @@ class Account extends Eloquent $invoice->is_recurring = true; } else { if ($entityType == ENTITY_QUOTE) { - $invoice->is_quote = true; + $invoice->invoice_type_id = INVOICE_TYPE_QUOTE; } if ($this->hasClientNumberPattern($invoice) && !$clientId) { @@ -553,34 +553,34 @@ class Account extends Eloquent return $invoice; } - public function getNumberPrefix($isQuote) + public function getNumberPrefix($invoice_type_id) { if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { return ''; } - return ($isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix) ?: ''; + return ($invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_prefix : $this->invoice_number_prefix) ?: ''; } - public function hasNumberPattern($isQuote) + public function hasNumberPattern($invoice_type_id) { if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { return false; } - return $isQuote ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false); + return $invoice_type_id == INVOICE_TYPE_QUOTE ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false); } public function hasClientNumberPattern($invoice) { - $pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern; + $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; return strstr($pattern, '$custom'); } public function getNumberPattern($invoice) { - $pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern; + $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; if (!$pattern) { return false; @@ -590,7 +590,7 @@ class Account extends Eloquent $replace = [date('Y')]; $search[] = '{$counter}'; - $replace[] = str_pad($this->getCounter($invoice->is_quote), $this->invoice_number_padding, '0', STR_PAD_LEFT); + $replace[] = str_pad($this->getCounter($invoice->invoice_type_id), $this->invoice_number_padding, '0', STR_PAD_LEFT); if (strstr($pattern, '{$userId}')) { $search[] = '{$userId}'; @@ -633,9 +633,9 @@ class Account extends Eloquent return str_replace($search, $replace, $pattern); } - public function getCounter($isQuote) + public function getCounter($invoice_type_id) { - return $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; + return $invoice_type_id == INVOICE_TYPE_QUOTE && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; } public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE) @@ -646,12 +646,12 @@ class Account extends Eloquent public function getNextInvoiceNumber($invoice) { - if ($this->hasNumberPattern($invoice->is_quote)) { + if ($this->hasNumberPattern($invoice->invoice_type_id)) { return $this->getNumberPattern($invoice); } - $counter = $this->getCounter($invoice->is_quote); - $prefix = $this->getNumberPrefix($invoice->is_quote); + $counter = $this->getCounter($invoice->invoice_type_id); + $prefix = $this->getNumberPrefix($invoice->invoice_type_id); $counterOffset = 0; // confirm the invoice number isn't already taken @@ -664,7 +664,7 @@ class Account extends Eloquent // update the invoice counter to be caught up if ($counterOffset > 1) { - if ($invoice->is_quote && !$this->share_counter) { + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { $this->quote_number_counter += $counterOffset - 1; } else { $this->invoice_number_counter += $counterOffset - 1; @@ -678,7 +678,7 @@ class Account extends Eloquent public function incrementCounter($invoice) { - if ($invoice->is_quote && !$this->share_counter) { + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { $this->quote_number_counter += 1; } else { $default = $this->invoice_number_counter; @@ -1102,7 +1102,7 @@ class Account extends Eloquent 'invoice_items', 'created_at', 'is_recurring', - 'is_quote', + 'invoice_type_id', ]); foreach ($invoice->invoice_items as $invoiceItem) { diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 02fdc73f99bd..f3717e9afa65 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -96,7 +96,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function affectsBalance() { - return !$this->is_quote && !$this->is_recurring; + return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring; } public function getAdjustment() @@ -139,7 +139,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function getAmountPaid($calculate = false) { - if ($this->is_quote || $this->is_recurring) { + if ($this->isType(INVOICE_TYPE_QUOTE) || $this->is_recurring) { return 0; } @@ -230,10 +230,19 @@ class Invoice extends EntityModel implements BalanceAffecting public function scopeInvoices($query) { - return $query->where('is_quote', '=', false) + return $query->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('is_recurring', '=', false); } + public function scopeInvoiceType($query, $typeId) + { + return $query->where('invoice_type_id', '=', $typeId); + } + + public function isType($typeId) { + return $this->invoice_type_id == $typeId; + } + public function markInvitationsSent($notify = false) { foreach ($this->invitations as $invitation) { @@ -256,7 +265,7 @@ class Invoice extends EntityModel implements BalanceAffecting return; } - if ($this->is_quote) { + if ($this->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteInvitationWasEmailed($invitation)); } else { event(new InvoiceInvitationWasEmailed($invitation)); @@ -292,7 +301,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function markApproved() { - if ($this->is_quote) { + if ($this->isType(INVOICE_TYPE_QUOTE)) { $this->invoice_status_id = INVOICE_STATUS_APPROVED; $this->save(); } @@ -341,7 +350,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function getEntityType() { - return $this->is_quote ? ENTITY_QUOTE : ENTITY_INVOICE; + return $this->isType(INVOICE_TYPE_QUOTE) ? ENTITY_QUOTE : ENTITY_INVOICE; } public function isSent() @@ -416,7 +425,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'invoice_design_id', 'invoice_fonts', 'features', - 'is_quote', + 'invoice_type_id', 'custom_value1', 'custom_value2', 'custom_taxes1', @@ -943,7 +952,7 @@ Invoice::creating(function ($invoice) { }); Invoice::created(function ($invoice) { - if ($invoice->is_quote) { + if ($invoice->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteWasCreated($invoice)); } else { event(new InvoiceWasCreated($invoice)); @@ -951,7 +960,7 @@ Invoice::created(function ($invoice) { }); Invoice::updating(function ($invoice) { - if ($invoice->is_quote) { + if ($invoice->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteWasUpdated($invoice)); } else { event(new InvoiceWasUpdated($invoice)); diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 224d48785aea..9d4b3c509111 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -96,7 +96,7 @@ class ContactMailer extends Mailer $account->loadLocalizationSettings(); if ($sent === true) { - if ($invoice->is_quote) { + if ($invoice->isType(INVOICE_TYPE_QUOTE)) { event(new QuoteWasEmailed($invoice)); } else { event(new InvoiceWasEmailed($invoice)); diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 827b171f4208..1c77ef1b09e7 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -19,7 +19,7 @@ class InvoicePresenter extends EntityPresenter { { if ($this->entity->partial > 0) { return 'partial_due'; - } elseif ($this->entity->is_quote) { + } elseif ($this->entity->isType(INVOICE_TYPE_QUOTE)) { return 'total'; } else { return 'balance_due'; diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 979304ed01a0..6a48c9a74921 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -32,9 +32,9 @@ class InvoiceRepository extends BaseRepository public function all() { return Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->with('user', 'client.contacts', 'invoice_status') ->withTrashed() - ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->get(); } @@ -106,7 +106,7 @@ class InvoiceRepository extends BaseRepository ->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id') ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('invoices.account_id', '=', $accountId) - ->where('invoices.is_quote', '=', false) + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('contacts.deleted_at', '=', null) ->where('invoices.is_recurring', '=', true) ->where('contacts.is_primary', '=', true) @@ -156,7 +156,7 @@ class InvoiceRepository extends BaseRepository ->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id') ->where('invitations.contact_id', '=', $contactId) ->where('invitations.deleted_at', '=', null) - ->where('invoices.is_quote', '=', false) + ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', true) @@ -203,7 +203,7 @@ class InvoiceRepository extends BaseRepository ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('invitations.contact_id', '=', $contactId) ->where('invitations.deleted_at', '=', null) - ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE) + ->where('invoices.invoice_type_id', '=', $entityType == ENTITY_QUOTE ? INVOICE_TYPE_QUOTE : INVOICE_TYPE_STANDARD) ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null) @@ -642,7 +642,7 @@ class InvoiceRepository extends BaseRepository 'tax_name2', 'tax_rate2', 'amount', - 'is_quote', + 'invoice_type_id', 'custom_value1', 'custom_value2', 'custom_taxes1', @@ -654,7 +654,7 @@ class InvoiceRepository extends BaseRepository } if ($quotePublicId) { - $clone->is_quote = false; + $clone->invoice_type_id = INVOICE_TYPE_STANDARD; $clone->quote_id = $quotePublicId; } @@ -838,9 +838,9 @@ class InvoiceRepository extends BaseRepository } $sql = implode(' OR ', $dates); - $invoices = Invoice::whereAccountId($account->id) + $invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD) + ->whereAccountId($account->id) ->where('balance', '>', 0) - ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->whereRaw('('.$sql.')') ->get(); diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index fc17127719a2..b961dbdfaeba 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -93,7 +93,7 @@ class InvoiceTransformer extends EntityTransformer 'terms' => $invoice->terms, 'public_notes' => $invoice->public_notes, 'is_deleted' => (bool) $invoice->is_deleted, - 'is_quote' => (bool) $invoice->is_quote, + 'invoice_type_id' => (int) $invoice->invoice_type_id, 'is_recurring' => (bool) $invoice->is_recurring, 'frequency_id' => (int) $invoice->frequency_id, 'start_date' => $invoice->start_date, diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index 950c8bb95fcb..71a8f91f0f68 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -94,7 +94,7 @@ class InvoiceService extends BaseService { $account = $quote->account; - if (!$quote->is_quote || $quote->quote_invoice_id) { + if (!$quote->isType(INVOICE_TYPE_QUOTE) || $quote->quote_invoice_id) { return null; } @@ -121,7 +121,7 @@ class InvoiceService extends BaseService { $datatable = new InvoiceDatatable( ! $clientPublicId, $clientPublicId); $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) - ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); + ->where('invoices.invoice_type_id', '=', $entityType == ENTITY_QUOTE ? INVOICE_TYPE_QUOTE : INVOICE_TYPE_STANDARD); if(!Utils::hasPermission('view_all')){ $query->where('invoices.user_id', '=', Auth::user()->id); diff --git a/app/Services/PushService.php b/app/Services/PushService.php index a96042b92419..2edd86af4be8 100644 --- a/app/Services/PushService.php +++ b/app/Services/PushService.php @@ -132,7 +132,7 @@ class PushService */ private function entitySentMessage($invoice) { - if($invoice->is_quote) + if($invoice->isType(INVOICE_TYPE_QUOTE)) return trans("texts.notification_quote_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); else return trans("texts.notification_invoice_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); @@ -163,7 +163,7 @@ class PushService */ private function entityViewedMessage($invoice) { - if($invoice->is_quote) + if($invoice->isType(INVOICE_TYPE_QUOTE)) return trans("texts.notification_quote_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); else return trans("texts.notification_invoice_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); diff --git a/database/migrations/2016_05_18_085739_add_invoice_type_support.php b/database/migrations/2016_05_18_085739_add_invoice_type_support.php new file mode 100644 index 000000000000..7b0b928d811b --- /dev/null +++ b/database/migrations/2016_05_18_085739_add_invoice_type_support.php @@ -0,0 +1,31 @@ +renameColumn('is_quote', 'invoice_type_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('invoices', function ($table) { + $table->renameColumn('invoice_type_id', 'is_quote'); + }); + } +} diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index bc1d2bb6e862..b661af7a40fc 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -150,7 +150,7 @@ @foreach ($upcoming as $invoice) - @if (!$invoice->is_quote) + @if ($invoice->invoice_type_id == INVOICE_TYPE_STANDARD) {!! \App\Models\Invoice::calcLink($invoice) !!} @can('viewByOwner', [ENTITY_CLIENT, $invoice->client_user_id]) @@ -185,7 +185,7 @@ @foreach ($pastDue as $invoice) - @if (!$invoice->is_quote) + @if ($invoice->invoice_type_id == INVOICE_TYPE_STANDARD) {!! \App\Models\Invoice::calcLink($invoice) !!} @can('viewByOwner', [ENTITY_CLIENT, $invoice->client_user_id]) @@ -224,7 +224,7 @@ @foreach ($upcoming as $invoice) - @if ($invoice->is_quote) + @if ($invoice->invoice_type_id == INVOICE_TYPE_STANDARD) {!! \App\Models\Invoice::calcLink($invoice) !!} {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} @@ -255,7 +255,7 @@ @foreach ($pastDue as $invoice) - @if ($invoice->is_quote) + @if ($invoice->invoice_type_id == INVOICE_TYPE_STANDARD) {!! \App\Models\Invoice::calcLink($invoice) !!} {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} From 0e75ddb5e66edc502957768d58ea7ca7c613cb08 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 12:26:02 +0300 Subject: [PATCH 15/51] Added setting to hide second tax rate --- app/Http/Controllers/AccountController.php | 6 +- app/Http/Controllers/InvoiceApiController.php | 11 +- app/Models/Account.php | 159 +++++++++--------- config/former.php | 4 +- resources/lang/en/texts.php | 3 +- resources/views/accounts/tax_rates.blade.php | 21 ++- resources/views/invoices/edit.blade.php | 22 ++- 7 files changed, 119 insertions(+), 107 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 45057914f1e6..8ad53a7d260c 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -790,11 +790,7 @@ class AccountController extends BaseController private function saveTaxRates() { $account = Auth::user()->account; - - $account->invoice_taxes = Input::get('invoice_taxes') ? true : false; - $account->invoice_item_taxes = Input::get('invoice_item_taxes') ? true : false; - $account->show_item_taxes = Input::get('show_item_taxes') ? true : false; - $account->default_tax_rate_id = Input::get('default_tax_rate_id'); + $account->fill(Input::all()); $account->save(); Session::flash('message', trans('texts.updated_settings')); diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 983e8fa14d71..520b5b09acac 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -134,6 +134,7 @@ class InvoiceApiController extends BaseAPIController 'city', 'state', 'postal_code', + 'country_id', 'private_notes', 'currency_code', ] as $field) { @@ -182,7 +183,7 @@ class InvoiceApiController extends BaseAPIController $invoice = Invoice::scope($invoice->public_id) ->with('client', 'invoice_items', 'invitations') ->first(); - + return $this->itemResponse($invoice); } @@ -269,7 +270,7 @@ class InvoiceApiController extends BaseAPIController $item[$key] = $val; } } - + return $item; } @@ -308,7 +309,7 @@ class InvoiceApiController extends BaseAPIController public function update(UpdateInvoiceAPIRequest $request, $publicId) { if ($request->action == ACTION_CONVERT) { - $quote = $request->entity(); + $quote = $request->entity(); $invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id); return $this->itemResponse($invoice); } elseif ($request->action) { @@ -322,7 +323,7 @@ class InvoiceApiController extends BaseAPIController $invoice = Invoice::scope($publicId) ->with('client', 'invoice_items', 'invitations') ->firstOrFail(); - + return $this->itemResponse($invoice); } @@ -351,7 +352,7 @@ class InvoiceApiController extends BaseAPIController public function destroy(UpdateInvoiceAPIRequest $request) { $invoice = $request->entity(); - + $this->invoiceRepo->delete($invoice); return $this->itemResponse($invoice); diff --git a/app/Models/Account.php b/app/Models/Account.php index c489d21b180b..2aa1b910b5b4 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -18,7 +18,7 @@ class Account extends Eloquent { use PresentableTrait; use SoftDeletes; - + public static $plan_prices = array( PLAN_PRO => array( PLAN_TERM_MONTHLY => PLAN_PRICE_PRO_MONTHLY, @@ -56,6 +56,11 @@ class Account extends Eloquent 'currency_id', 'language_id', 'military_time', + 'invoice_taxes', + 'invoice_item_taxes', + 'show_item_taxes', + 'default_tax_rate_id', + 'enable_second_tax_rate', ]; public static $basicSettings = [ @@ -401,7 +406,7 @@ class Account extends Eloquent return $gateway; } } - + return false; } @@ -421,27 +426,27 @@ class Account extends Eloquent 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_height = imagesy($image); $this->logo_size = $disk->size($this->logo); } else { $this->logo = null; @@ -453,33 +458,33 @@ class Account extends Eloquent if(!$this->hasLogo()){ return null; } - + $disk = $this->getLogoDisk(); return $disk->get($this->logo); } - + public function getLogoURL($cachebuster = false) { 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()); } @@ -529,7 +534,7 @@ class Account extends Eloquent $invoice->start_date = Utils::today(); $invoice->invoice_design_id = $this->invoice_design_id; $invoice->client_id = $clientId; - + if ($entityType === ENTITY_RECURRING_INVOICE) { $invoice->invoice_number = microtime(true); $invoice->is_recurring = true; @@ -544,7 +549,7 @@ class Account extends Eloquent $invoice->invoice_number = $this->getNextInvoiceNumber($invoice); } } - + if (!$clientId) { $invoice->client = Client::createNew(); $invoice->client->public_id = 0; @@ -574,7 +579,7 @@ class Account extends Eloquent public function hasClientNumberPattern($invoice) { $pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern; - + return strstr($pattern, '$custom'); } @@ -654,7 +659,7 @@ class Account extends Eloquent $prefix = $this->getNumberPrefix($invoice->is_quote); $counterOffset = 0; - // confirm the invoice number isn't already taken + // confirm the invoice number isn't already taken do { $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); @@ -690,7 +695,7 @@ class Account extends Eloquent $this->invoice_number_counter += 1; } } - + $this->save(); } @@ -713,7 +718,7 @@ class Account extends Eloquent $query->where('updated_at', '>=', $updatedAt); } }]); - } + } } public function loadLocalizationSettings($client = false) @@ -726,8 +731,8 @@ class Account extends Eloquent Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT); Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT); - $currencyId = ($client && $client->currency_id) ? $client->currency_id : $this->currency_id ?: DEFAULT_CURRENCY; - $locale = ($client && $client->language_id) ? $client->language->locale : ($this->language_id ? $this->Language->locale : DEFAULT_LOCALE); + $currencyId = ($client && $client->currency_id) ? $client->currency_id : $this->currency_id ?: DEFAULT_CURRENCY; + $locale = ($client && $client->language_id) ? $client->language->locale : ($this->language_id ? $this->Language->locale : DEFAULT_LOCALE); Session::put(SESSION_CURRENCY, $currencyId); Session::put(SESSION_LOCALE, $locale); @@ -810,7 +815,7 @@ class Account extends Eloquent if ( ! Utils::isNinja()) { return; } - + $this->company->trial_plan = $plan; $this->company->trial_started = date_create()->format('Y-m-d'); $this->company->save(); @@ -821,18 +826,18 @@ class Account extends Eloquent if (Utils::isNinjaDev()) { return true; } - + $planDetails = $this->getPlanDetails(); $selfHost = !Utils::isNinjaProd(); - + if (!$selfHost && function_exists('ninja_account_features')) { $result = ninja_account_features($this, $feature); - + if ($result != null) { return $result; } } - + switch ($feature) { // Pro case FEATURE_CUSTOMIZE_INVOICE_DESIGN: @@ -849,7 +854,7 @@ class Account extends Eloquent case FEATURE_CLIENT_PORTAL_PASSWORD: case FEATURE_CUSTOM_URL: return $selfHost || !empty($planDetails); - + // Pro; No trial allowed, unless they're trialing enterprise with an active pro plan case FEATURE_MORE_CLIENTS: return $selfHost || !empty($planDetails) && (!$planDetails['trial'] || !empty($this->getPlanDetails(false, false))); @@ -862,26 +867,26 @@ class Account extends Eloquent // Fallthrough case FEATURE_CLIENT_PORTAL_CSS: return !empty($planDetails);// A plan is required even for self-hosted users - + // Enterprise; No Trial allowed; grandfathered for old pro users case FEATURE_USERS:// Grandfathered for old Pro users if($planDetails && $planDetails['trial']) { // Do they have a non-trial plan? $planDetails = $this->getPlanDetails(false, false); } - + return $selfHost || !empty($planDetails) && ($planDetails['plan'] == PLAN_ENTERPRISE || $planDetails['started'] <= date_create(PRO_USERS_GRANDFATHER_DEADLINE)); - + // Enterprise; No Trial allowed case FEATURE_DOCUMENTS: case FEATURE_USER_PERMISSIONS: return $selfHost || !empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE && !$planDetails['trial']; - + default: return false; } } - + public function isPro(&$plan_details = null) { if (!Utils::isNinjaProd()) { @@ -893,7 +898,7 @@ class Account extends Eloquent } $plan_details = $this->getPlanDetails(); - + return !empty($plan_details); } @@ -908,36 +913,36 @@ class Account extends Eloquent } $plan_details = $this->getPlanDetails(); - + return $plan_details && $plan_details['plan'] == PLAN_ENTERPRISE; } - + public function getPlanDetails($include_inactive = false, $include_trial = true) { if (!$this->company) { return null; } - + $plan = $this->company->plan; $trial_plan = $this->company->trial_plan; - + if(!$plan && (!$trial_plan || !$include_trial)) { return null; - } - + } + $trial_active = false; if ($trial_plan && $include_trial) { $trial_started = DateTime::createFromFormat('Y-m-d', $this->company->trial_started); $trial_expires = clone $trial_started; $trial_expires->modify('+2 weeks'); - + if ($trial_expires >= date_create()) { $trial_active = true; } } - + $plan_active = false; - if ($plan) { + if ($plan) { if ($this->company->plan_expires == null) { $plan_active = true; $plan_expires = false; @@ -948,11 +953,11 @@ class Account extends Eloquent } } } - + if (!$include_inactive && !$plan_active && !$trial_active) { return null; } - + // Should we show plan details or trial details? if (($plan && !$trial_plan) || !$include_trial) { $use_plan = true; @@ -979,7 +984,7 @@ class Account extends Eloquent $use_plan = $plan_expires >= $trial_expires; } } - + if ($use_plan) { return array( 'trial' => false, @@ -1006,7 +1011,7 @@ class Account extends Eloquent if (!Utils::isNinjaProd()) { return false; } - + $plan_details = $this->getPlanDetails(); return $plan_details && $plan_details['trial']; @@ -1021,7 +1026,7 @@ class Account extends Eloquent return array(PLAN_PRO, PLAN_ENTERPRISE); } } - + if ($this->company->trial_plan == PLAN_PRO) { if ($plan) { return $plan != PLAN_PRO; @@ -1029,28 +1034,28 @@ class Account extends Eloquent return array(PLAN_ENTERPRISE); } } - + return false; } public function getCountTrialDaysLeft() { $planDetails = $this->getPlanDetails(true); - + if(!$planDetails || !$planDetails['trial']) { return 0; } - + $today = new DateTime('now'); $interval = $today->diff($planDetails['expires']); - + return $interval ? $interval->d : 0; } public function getRenewalDate() { $planDetails = $this->getPlanDetails(); - + if ($planDetails) { $date = $planDetails['expires']; $date = max($date, date_create()); @@ -1180,7 +1185,7 @@ class Account extends Eloquent $field = "email_template_{$entityType}"; $template = $this->$field; } - + if (!$template) { $template = $this->getDefaultEmailTemplate($entityType, $message); } @@ -1270,7 +1275,7 @@ class Account extends Eloquent { $url = SITE_URL; $iframe_url = $this->iframe_url; - + if ($iframe_url) { return "{$iframe_url}/?"; } else if ($this->subdomain) { @@ -1305,10 +1310,10 @@ class Account extends Eloquent if (!$entity) { return false; } - + // convert (for example) 'custom_invoice_label1' to 'invoice.custom_value1' $field = str_replace(['invoice_', 'label'], ['', 'value'], $field); - + return Utils::isEmpty($entity->$field) ? false : true; } @@ -1316,7 +1321,7 @@ class Account extends Eloquent { return $this->hasFeature(FEATURE_PDF_ATTACHMENT) && $this->pdf_email_attachment; } - + public function getEmailDesignId() { return $this->hasFeature(FEATURE_CUSTOM_EMAILS) ? $this->email_design_id : EMAIL_DESIGN_PLAIN; @@ -1324,11 +1329,11 @@ class Account extends Eloquent public function clientViewCSS(){ $css = ''; - + if ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) { $bodyFont = $this->getBodyFontCss(); $headerFont = $this->getHeaderFontCss(); - + $css = 'body{'.$bodyFont.'}'; if ($headerFont != $bodyFont) { $css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}'; @@ -1338,17 +1343,17 @@ class Account extends Eloquent // For self-hosted users, a white-label license is required for custom CSS $css .= $this->client_view_css; } - + return $css; } - + public function getFontsUrl($protocol = ''){ $bodyFont = $this->getHeaderFontId(); $headerFont = $this->getBodyFontId(); $bodyFontSettings = Utils::getFromCache($bodyFont, 'fonts'); $google_fonts = array($bodyFontSettings['google_font']); - + if($headerFont != $bodyFont){ $headerFontSettings = Utils::getFromCache($headerFont, 'fonts'); $google_fonts[] = $headerFontSettings['google_font']; @@ -1356,7 +1361,7 @@ class Account extends Eloquent return ($protocol?$protocol.':':'').'//fonts.googleapis.com/css?family='.implode('|',$google_fonts); } - + public function getHeaderFontId() { return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT; } @@ -1368,47 +1373,47 @@ class Account extends Eloquent public function getHeaderFontName(){ return Utils::getFromCache($this->getHeaderFontId(), 'fonts')['name']; } - + public function getBodyFontName(){ return Utils::getFromCache($this->getBodyFontId(), 'fonts')['name']; } - + public function getHeaderFontCss($include_weight = true){ $font_data = Utils::getFromCache($this->getHeaderFontId(), 'fonts'); $css = 'font-family:'.$font_data['css_stack'].';'; - + if($include_weight){ $css .= 'font-weight:'.$font_data['css_weight'].';'; } - + return $css; } - + public function getBodyFontCss($include_weight = true){ $font_data = Utils::getFromCache($this->getBodyFontId(), 'fonts'); $css = 'font-family:'.$font_data['css_stack'].';'; - + if($include_weight){ $css .= 'font-weight:'.$font_data['css_weight'].';'; } - + return $css; } - + public function getFonts(){ return array_unique(array($this->getHeaderFontId(), $this->getBodyFontId())); } - + public function getFontsData(){ $data = array(); - + foreach($this->getFonts() as $font){ $data[] = Utils::getFromCache($font, 'fonts'); } - + return $data; } - + public function getFontFolders(){ return array_map(function($item){return $item['folder'];}, $this->getFontsData()); } diff --git a/config/former.php b/config/former.php index b9c729e2f9ae..110fffe8ae2b 100644 --- a/config/former.php +++ b/config/former.php @@ -27,7 +27,7 @@ // Whether checkboxes should always be present in the POST data, // no matter if you checked them or not - 'push_checkboxes' => false, + 'push_checkboxes' => true, // The value a checkbox will have in the POST array if unchecked 'unchecked_value' => 0, @@ -181,4 +181,4 @@ ), -); \ No newline at end of file +); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index e70250130638..9bf85f0202ae 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1312,7 +1312,8 @@ $LANG = array( 'security' => 'Security', 'see_whats_new' => 'See what\'s new in v:version', 'wait_for_upload' => 'Please wait for the document upload to complete.', - 'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.' + 'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.', + 'enable_second_tax_rate' => 'Enable specifying a second tax rate', ); diff --git a/resources/views/accounts/tax_rates.blade.php b/resources/views/accounts/tax_rates.blade.php index 2079d72f2155..1a482dff8cad 100644 --- a/resources/views/accounts/tax_rates.blade.php +++ b/resources/views/accounts/tax_rates.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_TAX_RATES]) @@ -10,12 +10,13 @@ {{ Former::populateField('invoice_taxes', intval($account->invoice_taxes)) }} {{ Former::populateField('invoice_item_taxes', intval($account->invoice_item_taxes)) }} {{ Former::populateField('show_item_taxes', intval($account->show_item_taxes)) }} + {{ Former::populateField('enable_second_tax_rate', intval($account->enable_second_tax_rate)) }}

{!! trans('texts.tax_settings') !!}

-
+
{!! Former::checkbox('invoice_taxes') @@ -30,6 +31,10 @@ ->text(trans('texts.show_line_item_tax')) ->label(' ') !!} + {!! Former::checkbox('enable_second_tax_rate') + ->text(trans('texts.enable_second_tax_rate')) + ->label(' ') !!} +   {!! Former::select('default_tax_rate_id') @@ -51,22 +56,22 @@ @include('partials.bulk_form', ['entityType' => ENTITY_TAX_RATE]) - {!! Datatable::table() + {!! Datatable::table() ->addColumn( trans('texts.name'), trans('texts.rate'), trans('texts.action')) - ->setUrl(url('api/tax_rates/')) + ->setUrl(url('api/tax_rates/')) ->setOptions('sPaginationType', 'bootstrap') - ->setOptions('bFilter', false) - ->setOptions('bAutoWidth', false) + ->setOptions('bFilter', false) + ->setOptions('bAutoWidth', false) ->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]]) ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]]) ->render('datatable') !!} + -@stop \ No newline at end of file +@stop diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 4f6940645ada..7a744bee4601 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -243,7 +243,7 @@ @endif {{ $invoiceLabels['unit_cost'] }} {{ $invoiceLabels['quantity'] }} - {{ trans('texts.tax') }} + {{ trans('texts.tax') }} {{ trans('texts.line_total') }} @@ -288,16 +288,18 @@ ->addOption('', '') ->options($taxRateOptions) ->data_bind('value: tax1') - ->addClass('tax-select') + ->addClass($account->enable_second_tax_rate ? 'tax-select' : '') ->raw() !!} - {!! Former::select('') - ->addOption('', '') - ->options($taxRateOptions) - ->data_bind('value: tax2') - ->addClass('tax-select') - ->raw() !!} +
+ {!! Former::select('') + ->addOption('', '') + ->options($taxRateOptions) + ->data_bind('value: tax2') + ->addClass('tax-select') + ->raw() !!} +
@@ -438,17 +440,19 @@ ->id('taxRateSelect1') ->addOption('', '') ->options($taxRateOptions) - ->addClass('tax-select') + ->addClass($account->enable_second_tax_rate ? 'tax-select' : '') ->data_bind('value: tax1') ->raw() !!} +
{!! Former::select('') ->addOption('', '') ->options($taxRateOptions) ->addClass('tax-select') ->data_bind('value: tax2') ->raw() !!} +
From c254544b198f8f7fe24220194003ab4c2551d476 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 12:56:10 +0300 Subject: [PATCH 16/51] Added setting to hide second tax rate --- .../2014_05_17_175626_add_quotes.php | 6 ++-- ..._05_18_085739_add_invoice_type_support.php | 32 +++++++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/database/migrations/2014_05_17_175626_add_quotes.php b/database/migrations/2014_05_17_175626_add_quotes.php index 96ae916cafae..c332013d240d 100644 --- a/database/migrations/2014_05_17_175626_add_quotes.php +++ b/database/migrations/2014_05_17_175626_add_quotes.php @@ -14,9 +14,9 @@ class AddQuotes extends Migration { { Schema::table('invoices', function($table) { - $table->boolean('is_quote')->default(0); + $table->boolean('invoice_type_id')->default(0); $table->unsignedInteger('quote_id')->nullable(); - $table->unsignedInteger('quote_invoice_id')->nullable(); + $table->unsignedInteger('quote_invoice_id')->nullable(); }); } @@ -30,7 +30,7 @@ class AddQuotes extends Migration { Schema::table('invoices', function($table) { $table->dropColumn('is_quote'); - $table->dropColumn('quote_id'); + $table->dropColumn('invoice_type_id'); $table->dropColumn('quote_invoice_id'); }); } diff --git a/database/migrations/2016_05_18_085739_add_invoice_type_support.php b/database/migrations/2016_05_18_085739_add_invoice_type_support.php index 7b0b928d811b..faeb6ae0845d 100644 --- a/database/migrations/2016_05_18_085739_add_invoice_type_support.php +++ b/database/migrations/2016_05_18_085739_add_invoice_type_support.php @@ -12,9 +12,20 @@ class AddInvoiceTypeSupport extends Migration */ public function up() { - Schema::table('invoices', function ($table) { - $table->renameColumn('is_quote', 'invoice_type_id'); - }); + if (Schema::hasColumn('invoices', 'is_quote')) { + DB::update('update invoices set is_quote = is_quote + 1'); + + Schema::table('invoices', function ($table) { + $table->renameColumn('is_quote', 'invoice_type_id'); + }); + } + + Schema::table('accounts', function($table) + { + $table->boolean('enable_second_tax_rate')->default(false); + }); + + } /** @@ -24,8 +35,17 @@ class AddInvoiceTypeSupport extends Migration */ public function down() { - Schema::table('invoices', function ($table) { - $table->renameColumn('invoice_type_id', 'is_quote'); - }); + if (Schema::hasColumn('invoices', 'invoice_type_id')) { + DB::update('update invoices set invoice_type_id = invoice_type_id - 1'); + + Schema::table('invoices', function ($table) { + $table->renameColumn('invoice_type_id', 'is_quote'); + }); + } + + Schema::table('accounts', function($table) + { + $table->dropColumn('enable_second_tax_rate'); + }); } } From 59d0a1cb807cb42c21322723143af9af5efaa909 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 14:22:25 +0300 Subject: [PATCH 17/51] Fix to support mobile app --- .travis.yml | 2 +- app/Ninja/Transformers/InvoiceTransformer.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9eef15bf06b8..63de5f8ce3aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: true php: - 5.5.9 - - 5.6 +# - 5.6 # - 5.6 # - 7.0 # - hhvm diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index b961dbdfaeba..553fa604daae 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -34,7 +34,7 @@ class InvoiceTransformer extends EntityTransformer public function __construct($account = null, $serializer = null, $client = null) { parent::__construct($account, $serializer); - + $this->client = $client; } @@ -119,6 +119,7 @@ class InvoiceTransformer extends EntityTransformer 'quote_invoice_id' => (int) $invoice->quote_invoice_id, 'custom_text_value1' => $invoice->custom_text_value1, 'custom_text_value2' => $invoice->custom_text_value2, + 'is_quote' => (bool) $invoice->isType(INVOICE_TYPE_QUOTE), // Temp to support mobile app ]); } } From a9302d4d013d0d697541a83acf4422a97c3b8e49 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 15:34:44 +0300 Subject: [PATCH 18/51] Fixes for is_quote --- app/Models/Invoice.php | 60 +++++++++++--------- app/Ninja/Repositories/InvoiceRepository.php | 42 +++++++------- resources/views/invoices/view.blade.php | 20 +++---- 3 files changed, 63 insertions(+), 59 deletions(-) diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index f3717e9afa65..17b6292739c1 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -29,9 +29,9 @@ class Invoice extends EntityModel implements BalanceAffecting 'tax_name1', 'tax_rate1', 'tax_name2', - 'tax_rate2', + 'tax_rate2', ]; - + protected $casts = [ 'is_recurring' => 'boolean', 'has_tasks' => 'boolean', @@ -243,6 +243,10 @@ class Invoice extends EntityModel implements BalanceAffecting return $this->invoice_type_id == $typeId; } + public function isQuote() { + return $this->isType(INVOICE_TYPE_QUOTE); + } + public function markInvitationsSent($notify = false) { foreach ($this->invitations as $invitation) { @@ -524,12 +528,12 @@ class Invoice extends EntityModel implements BalanceAffecting 'name', ]); } - + foreach ($this->expenses as $expense) { $expense->setVisible([ 'documents', ]); - + foreach ($expense->documents as $document) { $document->setVisible([ 'public_id', @@ -588,12 +592,12 @@ class Invoice extends EntityModel implements BalanceAffecting return $schedule[1]->getStart(); } - + public function getDueDate($invoice_date = null){ if(!$this->is_recurring) { return $this->due_date ? $this->due_date : null; } - else{ + else{ $now = time(); if($invoice_date) { // If $invoice_date is specified, all calculations are based on that date @@ -607,7 +611,7 @@ class Invoice extends EntityModel implements BalanceAffecting $now = $invoice_date->getTimestamp(); } } - + if($this->due_date && $this->due_date != '0000-00-00'){ // This is a recurring invoice; we're using a custom format here. // The year is always 1998; January is 1st, 2nd, last day of the month. @@ -616,7 +620,7 @@ class Invoice extends EntityModel implements BalanceAffecting $monthVal = (int)date('n', $dueDateVal); $dayVal = (int)date('j', $dueDateVal); $dueDate = false; - + if($monthVal == 1) {// January; day of month $currentDay = (int)date('j', $now); $lastDayOfMonth = (int)date('t', $now); @@ -643,7 +647,7 @@ class Invoice extends EntityModel implements BalanceAffecting if($dueDay > $lastDayOfMonth){ // No later than the end of the month $dueDay = $lastDayOfMonth; - } + } } $dueDate = mktime(0, 0, 0, $dueMonth, $dueDay, $dueYear); @@ -672,7 +676,7 @@ class Invoice extends EntityModel implements BalanceAffecting return date('Y-m-d', strtotime('+'.$days.' day', $now)); } } - + // Couldn't calculate one return null; } @@ -690,11 +694,11 @@ class Invoice extends EntityModel implements BalanceAffecting $dateStart = $date->getStart(); $date = $this->account->formatDate($dateStart); $dueDate = $this->getDueDate($dateStart); - + if($dueDate) { $date .= ' (' . trans('texts.due') . ' ' . $this->account->formatDate($dueDate) . ')'; } - + $dates[] = $date; } @@ -808,16 +812,16 @@ class Invoice extends EntityModel implements BalanceAffecting $invitation = $this->invitations[0]; $link = $invitation->getLink('view', true); $key = env('PHANTOMJS_CLOUD_KEY'); - + if (Utils::isNinjaDev()) { $link = env('TEST_LINK'); } $url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D"; - + $pdfString = file_get_contents($url); $pdfString = strip_tags($pdfString); - + if ( ! $pdfString || strlen($pdfString) < 200) { Utils::logError("PhantomJSCloud - failed to create pdf: {$pdfString}"); return false; @@ -870,14 +874,14 @@ class Invoice extends EntityModel implements BalanceAffecting return $total; } - // if $calculatePaid is true we'll loop through each payment to + // 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(); $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; @@ -892,7 +896,7 @@ class Invoice extends EntityModel implements BalanceAffecting foreach ($this->invoice_items as $invoiceItem) { $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; @@ -905,20 +909,20 @@ class Invoice extends EntityModel implements BalanceAffecting $this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount); } } - + return $taxes; } - - private function calculateTax(&$taxes, $name, $rate, $amount, $paid) - { + + 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, @@ -929,14 +933,14 @@ class Invoice extends EntityModel implements BalanceAffecting } $taxes[$key]['amount'] += $amount; - $taxes[$key]['paid'] += $paid; + $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; diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 6a48c9a74921..290e22ed296c 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -210,7 +210,7 @@ class InvoiceRepository extends BaseRepository ->where('contacts.is_primary', '=', true) ->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) + //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), @@ -287,7 +287,7 @@ class InvoiceRepository extends BaseRepository $account->invoice_footer = trim($data['invoice_footer']); } $account->save(); - } + } if (isset($data['invoice_number']) && !$invoice->is_recurring) { $invoice->invoice_number = trim($data['invoice_number']); @@ -329,7 +329,7 @@ class InvoiceRepository extends BaseRepository if ($invoice->auto_bill < AUTO_BILL_OFF || $invoice->auto_bill > AUTO_BILL_ALWAYS ) { $invoice->auto_bill = AUTO_BILL_OFF; } - + if (isset($data['recurring_due_date'])) { $invoice->due_date = $data['recurring_due_date']; } elseif (isset($data['due_date'])) { @@ -351,7 +351,7 @@ class InvoiceRepository extends BaseRepository } else { $invoice->terms = ''; } - + $invoice->invoice_footer = (isset($data['invoice_footer']) && trim($data['invoice_footer'])) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : ''); $invoice->public_notes = isset($data['public_notes']) ? trim($data['public_notes']) : null; @@ -370,8 +370,8 @@ class InvoiceRepository extends BaseRepository // provide backwards compatability if (isset($data['tax_name']) && isset($data['tax_rate'])) { - $data['tax_name1'] = $data['tax_name']; - $data['tax_rate1'] = $data['tax_rate']; + $data['tax_name1'] = $data['tax_name']; + $data['tax_rate1'] = $data['tax_rate']; } $total = 0; @@ -405,11 +405,11 @@ class InvoiceRepository extends BaseRepository } if (isset($item['tax_rate1']) && Utils::parseFloat($item['tax_rate1']) > 0) { - $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']); + $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']); + $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']); $itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2); } } @@ -453,7 +453,7 @@ class InvoiceRepository extends BaseRepository $taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2); $taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2); - $total = round($total + $taxAmount1 + $taxAmount2, 2); + $total = round($total + $taxAmount1 + $taxAmount2, 2); $total += $itemTax; // custom fields not charged taxes @@ -476,24 +476,24 @@ 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 && Auth::user()->can('edit', $document)){ - + 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']) && Auth::user()->can('create', ENTITY_DOCUMENT)){ // Fallback upload $doc_errors = array(); @@ -512,7 +512,7 @@ class InvoiceRepository extends BaseRepository Session::flash('error', implode('
',array_map('htmlentities',$doc_errors))); } } - + foreach ($invoice->documents as $document){ if(!in_array($document->public_id, $document_ids)){ // Removed @@ -586,12 +586,12 @@ class InvoiceRepository extends BaseRepository // provide backwards compatability if (isset($item['tax_name']) && isset($item['tax_rate'])) { - $item['tax_name1'] = $item['tax_name']; - $item['tax_rate1'] = $item['tax_rate']; + $item['tax_name1'] = $item['tax_name']; + $item['tax_rate1'] = $item['tax_rate']; } $invoiceItem->fill($item); - + $invoice->invoice_items()->save($invoiceItem); } @@ -675,9 +675,9 @@ class InvoiceRepository extends BaseRepository 'cost', 'qty', 'tax_name1', - 'tax_rate1', + 'tax_rate1', 'tax_name2', - 'tax_rate2', + 'tax_rate2', ] as $field) { $cloneItem->$field = $item->$field; } @@ -686,7 +686,7 @@ class InvoiceRepository extends BaseRepository } foreach ($invoice->documents as $document) { - $cloneDocument = $document->cloneDocument(); + $cloneDocument = $document->cloneDocument(); $invoice->documents()->save($cloneDocument); } @@ -731,8 +731,8 @@ class InvoiceRepository extends BaseRepository public function findOpenInvoices($clientId) { return Invoice::scope() + ->invoiceType(INVOICE_TYPE_STANDARD) ->whereClientId($clientId) - ->whereIsQuote(false) ->whereIsRecurring(false) ->whereDeletedAt(null) ->whereHasTasks(true) diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index 5c32210f0c80..096b7b74f765 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -4,15 +4,15 @@ @parent @include('money_script') - + @foreach ($invoice->client->account->getFontFolders() as $font) @endforeach - + @@ -34,7 +32,7 @@ @foreach (\App\Services\ImportService::$entityTypes as $entityType) {!! Former::file("{$entityType}_file") - ->addGroupClass("{$entityType}-file") !!} + ->addGroupClass("import-file {$entityType}-file") !!} @endforeach {!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!} @@ -67,13 +65,17 @@ trans('texts.payments') => array('name' => ENTITY_PAYMENT, 'value' => 1), ])->check(ENTITY_CLIENT)->check(ENTITY_TASK)->check(ENTITY_INVOICE)->check(ENTITY_PAYMENT) !!} - {!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!} + {!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!}
{!! Former::close() !!} -@stop \ No newline at end of file +@stop diff --git a/resources/views/accounts/import_map.blade.php b/resources/views/accounts/import_map.blade.php index 12ac6766d693..5c3c76ac7c20 100644 --- a/resources/views/accounts/import_map.blade.php +++ b/resources/views/accounts/import_map.blade.php @@ -7,18 +7,16 @@ {!! Former::open('/import_csv')->addClass('warn-on-exit') !!} - @if (isset($data[ENTITY_CLIENT])) - @include('accounts.partials.map', $data[ENTITY_CLIENT]) - @endif + @foreach (App\Services\ImportService::$entityTypes as $entityType) + @if (isset($data[$entityType])) + @include('accounts.partials.map', $data[$entityType]) + @endif + @endforeach - @if (isset($data[ENTITY_INVOICE])) - @include('accounts.partials.map', $data[ENTITY_INVOICE]) - @endif - - {!! Former::actions( + {!! Former::actions( Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')), Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!} - + {!! Former::close() !!} -@stop \ No newline at end of file +@stop From e6a05509b19a94f0ce5be754a08632a540ed5204 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 31 May 2016 23:16:05 +0300 Subject: [PATCH 44/51] Enabled importing products --- app/Ninja/Import/CSV/ProductTransformer.php | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/Ninja/Import/CSV/ProductTransformer.php diff --git a/app/Ninja/Import/CSV/ProductTransformer.php b/app/Ninja/Import/CSV/ProductTransformer.php new file mode 100644 index 000000000000..248d3ed09cba --- /dev/null +++ b/app/Ninja/Import/CSV/ProductTransformer.php @@ -0,0 +1,22 @@ +product_key) || $this->hasProduct($data->product_key)) { + return false; + } + + return new Item($data, function ($data) { + return [ + 'product_key' => $this->getString($data, 'product_key'), + 'notes' => $this->getString($data, 'notes'), + 'cost' => $this->getNumber($data, 'cost'), + ]; + }); + } +} From 51d9b2b4271ce40559c3603c90996cd4436e26b0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 10:56:44 +0300 Subject: [PATCH 45/51] Prevent converting a qoute from incrementing the counter --- app/Models/Account.php | 62 ++++++++++---------- app/Ninja/Repositories/InvoiceRepository.php | 2 +- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index 5dd8a12c1851..61526c9095d4 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -652,30 +652,34 @@ class Account extends Eloquent public function getNextInvoiceNumber($invoice) { if ($this->hasNumberPattern($invoice->invoice_type_id)) { - return $this->getNumberPattern($invoice); + $number = $this->getNumberPattern($invoice); + } else { + $counter = $this->getCounter($invoice->invoice_type_id); + $prefix = $this->getNumberPrefix($invoice->invoice_type_id); + $counterOffset = 0; + + // confirm the invoice number isn't already taken + do { + $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + $counter++; + $counterOffset++; + } while ($check); + + // update the invoice counter to be caught up + if ($counterOffset > 1) { + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { + $this->quote_number_counter += $counterOffset - 1; + } else { + $this->invoice_number_counter += $counterOffset - 1; + } + + $this->save(); + } } - $counter = $this->getCounter($invoice->invoice_type_id); - $prefix = $this->getNumberPrefix($invoice->invoice_type_id); - $counterOffset = 0; - - // confirm the invoice number isn't already taken - do { - $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); - $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); - $counter++; - $counterOffset++; - } while ($check); - - // update the invoice counter to be caught up - if ($counterOffset > 1) { - if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { - $this->quote_number_counter += $counterOffset - 1; - } else { - $this->invoice_number_counter += $counterOffset - 1; - } - - $this->save(); + if ($invoice->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; } return $number; @@ -683,17 +687,15 @@ class Account extends Eloquent public function incrementCounter($invoice) { + // if they didn't use the counter don't increment it + if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice)) { + return; + } + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { $this->quote_number_counter += 1; } else { - $default = $this->invoice_number_counter; - $actual = Utils::parseInt($invoice->invoice_number); - - if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) { - $this->invoice_number_counter = $actual + 1; - } else { - $this->invoice_number_counter += 1; - } + $this->invoice_number_counter += 1; } $this->save(); diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index cb687430ebc5..c15e261848d0 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -741,7 +741,7 @@ class InvoiceRepository extends BaseRepository $invoice = Invoice::createNew($recurInvoice); $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice); + $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice); $invoice->amount = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount; $invoice->invoice_date = date_create()->format('Y-m-d'); From 17eb2a7a79cd0686cf8ddf99efb7fa5861f96d97 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 12:39:42 +0300 Subject: [PATCH 46/51] Support saving invoice_id and expense_id with the document --- .../Controllers/DocumentAPIController.php | 2 +- app/Http/Controllers/DocumentController.php | 2 +- app/Http/Requests/CreateDocumentRequest.php | 39 ++++----- app/Http/Requests/Request.php | 22 +++++- app/Models/Document.php | 79 ++++++++++--------- app/Ninja/Repositories/DocumentRepository.php | 10 ++- app/Providers/AppServiceProvider.php | 16 ++-- 7 files changed, 100 insertions(+), 70 deletions(-) diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index e4ca7c7fb5de..194c32e07d81 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -33,7 +33,7 @@ class DocumentAPIController extends BaseAPIController public function store(CreateDocumentRequest $request) { - $document = $this->documentRepo->upload($request->file); + $document = $this->documentRepo->upload($request->all()); return $this->itemResponse($document); } diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index f0871a2408f2..6f6b0bd0889d 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -102,7 +102,7 @@ class DocumentController extends BaseController public function postUpload(CreateDocumentRequest $request) { - $result = $this->documentRepo->upload($request->file, $doc_array); + $result = $this->documentRepo->upload($request->all(), $doc_array); if(is_string($result)){ return Response::json([ diff --git a/app/Http/Requests/CreateDocumentRequest.php b/app/Http/Requests/CreateDocumentRequest.php index d00576478191..5037ae3beb68 100644 --- a/app/Http/Requests/CreateDocumentRequest.php +++ b/app/Http/Requests/CreateDocumentRequest.php @@ -1,7 +1,15 @@ user()->can('create', ENTITY_DOCUMENT) && $this->user()->hasFeature(FEATURE_DOCUMENTS); + if ( ! $this->user()->hasFeature(FEATURE_DOCUMENTS)) { + return false; + } + + if ($this->invoice && $this->user()->cannot('edit', $this->invoice)) { + return false; + } + + if ($this->expense && $this->user()->cannot('edit', $this->expense)) { + return false; + } + + return $this->user()->can('create', ENTITY_DOCUMENT); } /** @@ -24,21 +44,4 @@ class CreateDocumentRequest extends DocumentRequest ]; } - /** - * Sanitize input before validation. - * - * @return array - */ - /* - public function sanitize() - { - $input = $this->all(); - - $input['phone'] = 'test123'; - - $this->replace($input); - - return $this->all(); - } - */ } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index b0f5a5d85a95..c704e409f373 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -5,6 +5,9 @@ use Illuminate\Foundation\Http\FormRequest; // https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366 abstract class Request extends FormRequest { + // populate in subclass to auto load record + protected $autoload = []; + /** * Validate the input. * @@ -25,11 +28,24 @@ abstract class Request extends FormRequest { */ protected function sanitizeInput() { - if (method_exists($this, 'sanitize')) - { - return $this->container->call([$this, 'sanitize']); + if (method_exists($this, 'sanitize')) { + $input = $this->container->call([$this, 'sanitize']); + } else { + $input = $this->all(); } + // autoload referenced entities + foreach ($this->autoload as $entityType) { + if ($id = $this->input("{$entityType}_public_id") ?: $this->input("{$entityType}_id")) { + $class = "App\\Models\\" . ucwords($entityType); + $entity = $class::scope($id)->firstOrFail(); + $input[$entityType] = $entity; + $input[$entityType . '_id'] = $entity->id; + } + } + + $this->replace($input); + return $this->all(); } } diff --git a/app/Models/Document.php b/app/Models/Document.php index 6d9c24857143..cc455fed0d79 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -6,20 +6,25 @@ use Auth; class Document extends EntityModel { + protected $fillable = [ + 'invoice_id', + 'expense_id', + ]; + 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/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', @@ -70,18 +75,18 @@ class Document extends EntityModel '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'); @@ -101,7 +106,7 @@ class Document extends EntityModel { return $this->belongsTo('App\Models\Invoice')->withTrashed(); } - + public function getDisk(){ return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents')); } @@ -110,19 +115,19 @@ class Document extends EntityModel { $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', [ @@ -136,12 +141,12 @@ class Document extends EntityModel $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()); @@ -150,64 +155,64 @@ class Document extends EntityModel 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('documents/'.$this->public_id.'/'.$this->name); } - + public function getClientUrl($invitation){ return url('client/documents/'.$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('documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getClientVFSJSUrl(){ if(!$this->isPDFEmbeddable())return null; return url('client/documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getPreviewUrl(){ return $this->preview?url('documents/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; @@ -219,7 +224,7 @@ class Document extends EntityModel $document->size = $this->size; $document->width = $this->width; $document->height = $this->height; - + return $document; } } @@ -230,11 +235,11 @@ Document::deleted(function ($document) { ->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) @@ -245,5 +250,5 @@ Document::deleted(function ($document) { $document->getDisk()->delete($document->preview); } } - -}); \ No newline at end of file + +}); diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index a2278b1843ae..0724144a7f09 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -57,8 +57,9 @@ class DocumentRepository extends BaseRepository return $query; } - public function upload($uploaded, &$doc_array=null) + public function upload($data, &$doc_array=null) { + $uploaded = $data['file']; $extension = strtolower($uploaded->getClientOriginalExtension()); if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ $documentType = Document::$extraExtensions[$extension]; @@ -81,12 +82,17 @@ class DocumentRepository extends BaseRepository return 'File too large'; } - + // don't allow a document to be linked to both an invoice and an expense + if (array_get($data, 'invoice_id') && array_get($data, 'expense_id')) { + unset($data['expense_id']); + } $hash = sha1_file($filePath); $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType; $document = Document::createNew(); + $document->fill($data); + $disk = $document->getDisk(); if(!$disk->exists($filename)){// Have we already stored the same file $stream = fopen($filePath, 'r'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e7587e48b11a..79e0f5dc032d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -29,8 +29,8 @@ class AppServiceProvider extends ServiceProvider { else{ $contents = $image; } - - return 'data:image/jpeg;base64,' . base64_encode($contents); + + return 'data:image/jpeg;base64,' . base64_encode($contents); }); Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { @@ -58,11 +58,11 @@ class AppServiceProvider extends ServiceProvider { $str = '
  • '.trans("texts.new_$type").'
  • '; - + if ($type == ENTITY_INVOICE) { if(!empty($items))$items[] = '
  • '; $items[] = '
  • '.trans("texts.recurring_invoices").'
  • '; @@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider { $items[] = '
  • '.trans("texts.vendors").'
  • '; if($user->can('create', ENTITY_VENDOR))$items[] = '
  • '.trans("texts.new_vendor").'
  • '; } - + if(!empty($items)){ $str.= ''; } @@ -157,14 +157,14 @@ class AppServiceProvider extends ServiceProvider { return $str . ''; }); - + 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; }); From 78bafbbf0971e9eef5814707452548b7203992f3 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 12:56:15 +0300 Subject: [PATCH 47/51] Support downloading PDF through the API --- app/Http/Controllers/InvoiceApiController.php | 13 +++++++++++++ app/Http/routes.php | 1 + 2 files changed, 14 insertions(+) diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 520b5b09acac..c508b790d4c4 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -358,4 +358,17 @@ class InvoiceApiController extends BaseAPIController return $this->itemResponse($invoice); } + public function download(InvoiceRequest $request) + { + $invoice = $request->entity(); + $pdfString = $invoice->getPDFString(); + + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($pdfString)); + header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"'); + header('Cache-Control: public, must-revalidate, max-age=0'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + + return $pdfString; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index e96aebb7ea54..f4e65d0af180 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -270,6 +270,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() //Route::get('quotes', 'QuoteApiController@index'); //Route::resource('quotes', 'QuoteApiController'); Route::get('invoices', 'InvoiceApiController@index'); + Route::get('download/{invoice_id}', 'InvoiceApiController@download'); Route::resource('invoices', 'InvoiceApiController'); Route::get('payments', 'PaymentApiController@index'); Route::resource('payments', 'PaymentApiController'); From 671b3ba02a2d2cbd4aefe19d3826751684939dc0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 1 Jun 2016 20:15:31 +1000 Subject: [PATCH 48/51] test --- app/Http/Controllers/DocumentAPIController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index 1845615cad0f..91ec1a87a165 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -34,6 +34,8 @@ class DocumentAPIController extends BaseAPIController public function store(CreateDocumentRequest $request) { + Log::info($request); + $document = $this->documentRepo->upload($request->all()); return $this->itemResponse($document); From 707334375659c4fcb0a4b5d8963a56008e359291 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 1 Jun 2016 21:35:53 +1000 Subject: [PATCH 49/51] Update DocumentTransformer.php --- app/Ninja/Transformers/DocumentTransformer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Ninja/Transformers/DocumentTransformer.php b/app/Ninja/Transformers/DocumentTransformer.php index 4cbfa0619193..95e4b281bec4 100644 --- a/app/Ninja/Transformers/DocumentTransformer.php +++ b/app/Ninja/Transformers/DocumentTransformer.php @@ -14,6 +14,7 @@ class DocumentTransformer extends EntityTransformer 'type' => $document->type, 'invoice_id' => isset($document->invoice->public_id) ? (int) $document->invoice->public_id : null, 'expense_id' => isset($document->expense->public_id) ? (int) $document->expense->public_id : null, + 'updated_at' => $this->getTimestamp($account->updated_at), ]); } -} \ No newline at end of file +} From 8b781746fea58b550a1c6f3c41f68ce2605f007d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 1 Jun 2016 21:36:27 +1000 Subject: [PATCH 50/51] Update DocumentTransformer.php --- app/Ninja/Transformers/DocumentTransformer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Ninja/Transformers/DocumentTransformer.php b/app/Ninja/Transformers/DocumentTransformer.php index 95e4b281bec4..4130bbd6c459 100644 --- a/app/Ninja/Transformers/DocumentTransformer.php +++ b/app/Ninja/Transformers/DocumentTransformer.php @@ -14,7 +14,7 @@ class DocumentTransformer extends EntityTransformer 'type' => $document->type, 'invoice_id' => isset($document->invoice->public_id) ? (int) $document->invoice->public_id : null, 'expense_id' => isset($document->expense->public_id) ? (int) $document->expense->public_id : null, - 'updated_at' => $this->getTimestamp($account->updated_at), + 'updated_at' => $this->getTimestamp($document->updated_at), ]); } } From 86a2dec504262d69ffa89d6b1e8add58432b7ebf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 1 Jun 2016 21:38:35 +1000 Subject: [PATCH 51/51] Bug Fixes --- app/Http/Controllers/DashboardApiController.php | 1 - app/Http/Controllers/DocumentAPIController.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 85d24ad7b329..6d69c53f2a01 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -2,7 +2,6 @@ use Auth; use DB; -use Illuminate\Support\Facades\Log; use View; use App\Models\Activity; diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index 91ec1a87a165..a1eef82c703d 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -5,7 +5,6 @@ use App\Models\Document; use App\Ninja\Repositories\DocumentRepository; use App\Http\Requests\DocumentRequest; use App\Http\Requests\CreateDocumentRequest; -use Illuminate\Support\Facades\Log; class DocumentAPIController extends BaseAPIController { @@ -34,7 +33,6 @@ class DocumentAPIController extends BaseAPIController public function store(CreateDocumentRequest $request) { - Log::info($request); $document = $this->documentRepo->upload($request->all());