From 67357e07f1035c8361c6af5d79633b9abc2df9b6 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 1 Sep 2021 17:05:00 -0700 Subject: [PATCH 1/4] Added API route for depreciations report RED FLAG: This will need to be updated for v6!!!! Signed-off-by: snipe --- routes/api.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/routes/api.php b/routes/api.php index 51032feaac..398af78bce 100644 --- a/routes/api.php +++ b/routes/api.php @@ -903,6 +903,14 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'auth:api'] [ 'as' => 'api.activity.index', 'uses' => 'ReportsController@index' ] ); + Route::get( + 'reports/depreciation', + [ + 'as' => 'api.depreciation-report.index', + 'uses' => 'AssetsController@index' + ] + ); + /*--- Kits API ---*/ Route::resource('kits', 'PredefinedKitsController', From 387018c44e4db28e3c659a3b454c9083a49ee1b0 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 1 Sep 2021 17:05:31 -0700 Subject: [PATCH 2/4] Updated depreciation report blade to use server-side API Signed-off-by: snipe --- .../views/reports/depreciation.blade.php | 170 +++--------------- 1 file changed, 22 insertions(+), 148 deletions(-) diff --git a/resources/views/reports/depreciation.blade.php b/resources/views/reports/depreciation.blade.php index 611a06887d..c1449ce139 100644 --- a/resources/views/reports/depreciation.blade.php +++ b/resources/views/reports/depreciation.blade.php @@ -16,156 +16,30 @@ @if (($depreciations) && ($depreciations->count() > 0)) -
+
- +
- - - - - - - @if ($snipeSettings->display_asset_name) - - @endif - - - - - - - - - - - - - - - - @foreach ($assets as $asset) - - - - - - @if ($snipeSettings->display_asset_name) - - @endif - - - - - - - - - - - @if ($asset->purchase_cost > 0) - - - - - @else - - - - - @endif - - @endforeach -
{{ trans('admin/companies/table.title') }}{{ trans('admin/categories/general.category_name') }}{{ trans('admin/hardware/table.asset_tag') }}{{ trans('admin/hardware/table.title') }}{{ trans('general.name') }}{{ trans('admin/hardware/table.serial') }}{{ trans('admin/depreciations/general.depreciation_name') }}{{ trans('admin/depreciations/general.number_of_months') }}{{ trans('admin/hardware/table.status') }}{{ trans('admin/hardware/table.checkoutto') }}{{ trans('admin/hardware/table.location') }}{{ trans('admin/hardware/table.purchase_date') }}{{ trans('admin/hardware/table.eol') }}{{ trans('admin/hardware/table.purchase_cost') }}{{ trans('admin/hardware/table.book_value') }}{{ trans('admin/hardware/table.monthly_depreciation') }}{{ trans('admin/hardware/table.diff') }}
{{ is_null($asset->company) ? '' : $asset->company->name }} - @if ($asset->model) - {{ $asset->model->category->name }} - @endif - - @if ($asset->deleted_at!='') - {{ $asset->asset_tag }} - @else - {{ $asset->asset_tag }} - @endif - {{ $asset->model->name }}{{ $asset->name }}{{ $asset->serial }} - @if ($asset->model->depreciation) - {{ $asset->model->depreciation->name }} - @endif - - @if ($asset->model->depreciation) - {{ $asset->model->depreciation->months }} - @endif - - {{ $asset->assetstatus->name }} - ({{ $asset->present()->statusMeta }}) - - @if (($asset->checkedOutToUser()) && ($asset->assigned)) - {{ $asset->assigned->getFullNameAttribute() }} - @else - - @if ($asset->assigned) - {{ $asset->assigned->name }} - @endif - @endif - - @if ($asset->location) - {{ $asset->location->name }} - @elseif ($asset->defaultloc) - {{ $asset->defaultloc->name }} - @endif - - {{ \Carbon\Carbon::parse($asset->purchase_date)->format('Y-m-d') }} - - @if ($asset->model->eol) {{ $asset->present()->eol_date() }} - @endif - - @if ($asset->location && $asset->location->currency) - {{ $asset->location->currency }} - @else - {{ $snipeSettings->default_currency }} - @endif - {{ \App\Helpers\Helper::formatCurrencyOutput($asset->purchase_cost) }} - - @if ($asset->location && $asset->location->currency) - {{ $asset->location->currency }} - @else - {{ $snipeSettings->default_currency }} - @endif - - {{ \App\Helpers\Helper::formatCurrencyOutput($asset->getDepreciatedValue()) }} - - @if ($asset->model->depreciation) - @if ($asset->location && $asset->location->currency) - {{ $asset->location->currency }} - @else - {{ $snipeSettings->default_currency }} - @endif - - {{ \App\Helpers\Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)) }} - @endif - - @if ($asset->location && $asset->location->currency) - {{ $asset->location->currency }} - @else - {{ $snipeSettings->default_currency }} - @endif - - -{{ \App\Helpers\Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())) }} -
@else From 2f25eb598b06704a515ab5f723adf5e5ca5eb31f Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 1 Sep 2021 17:33:39 -0700 Subject: [PATCH 3/4] Allow the Assets API controller to handle depreciation reports Signed-off-by: snipe --- app/Http/Controllers/Api/AssetsController.php | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 3f3709f526..7a333e7086 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -7,6 +7,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\AssetCheckoutRequest; use App\Http\Transformers\AssetsTransformer; +use App\Http\Transformers\DepreciationReportTransformer; use App\Http\Transformers\LicensesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Actionlog; @@ -29,6 +30,7 @@ use Slack; use Str; use TCPDF; use Validator; +use Route; /** @@ -49,10 +51,32 @@ class AssetsController extends Controller * @since [v4.0] * @return JsonResponse */ - public function index(Request $request, $audit = null) + public function index(Request $request, $audit = null) { - $this->authorize('index', Asset::class); + \Log::debug(Route::currentRouteName()); + + + /** + * This looks MAD janky (and it is), but the AssetsController@index does a LOT of heavy lifting throughout the + * app. This bit here just makes sure that someone without permission to view assets doesn't + * end up with priv escalations because they asked for a different endpoint. + * + * Since we never gave the specification for which transformer to use before, it should default + * gracefully to just use the AssetTransformer by default, which shouldn't break anything. + * + * It was either this mess, or repeating ALL of the searching and sorting and filtering code, + * which would have been far worse of a mess. *sad face* - snipe (Sept 1, 2021) + */ + if (Route::currentRouteName()=='api.depreciation-report.index') { + $transformer = 'App\Http\Transformers\DepreciationReportTransformer'; + $this->authorize('reports.view'); + } else { + $transformer = 'App\Http\Transformers\AssetsTransformer'; + $this->authorize('index', Asset::class); + } + + $settings = Setting::getSettings(); $allowed_columns = [ @@ -295,8 +319,12 @@ class AssetsController extends Controller $total = $assets->count(); $assets = $assets->skip($offset)->take($limit)->get(); - // dd($assets); - return (new AssetsTransformer)->transformAssets($assets, $total); + + /** + * Here we're just determining which Transformer (via $transformer) to use based on the + * variables we set earlier on in this method - we default to AssetsTransformer. + */ + return (new $transformer)->transformAssets($assets, $total); } From 94310e18b1a543c9b94bbcbd5a23ae3556befd2c Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 1 Sep 2021 17:33:59 -0700 Subject: [PATCH 4/4] Presenters and Transformers for Depreciation report Signed-off-by: snipe --- .../DepreciationReportTransformer.php | 119 ++++++ .../DepreciationReportPresenter.php | 393 ++++++++++++++++++ 2 files changed, 512 insertions(+) create mode 100644 app/Http/Transformers/DepreciationReportTransformer.php create mode 100644 app/Presenters/DepreciationReportPresenter.php diff --git a/app/Http/Transformers/DepreciationReportTransformer.php b/app/Http/Transformers/DepreciationReportTransformer.php new file mode 100644 index 0000000000..dfa5adc985 --- /dev/null +++ b/app/Http/Transformers/DepreciationReportTransformer.php @@ -0,0 +1,119 @@ +] + * @since [v5.2.0] + */ +class DepreciationReportTransformer +{ + public function transformAssets(Collection $assets, $total) + { + $array = array(); + foreach ($assets as $asset) { + $array[] = self::transformAsset($asset); + } + return (new DatatablesTransformer)->transformDatatables($array, $total); + } + + + public function transformAsset(Asset $asset) + { + + /** + * Set some default values here + */ + $purchase_cost = null; + $depreciated_value = null; + $monthly_depreciation = null; + $diff = null; + $checkout_target = null; + + /** + * If there is a location set and a currency set, use that for display + */ + if ($asset->location && $asset->location->currency) { + $purchase_cost_currency = $asset->location->currency; + } else { + $purchase_cost_currency = \App\Models\Setting::getSettings()->default_currency; + } + + /** + * If there is a NOT an empty purchase cost (meaning not null or '' but it *could* be zero), + * format the purchase cost. We coould do this inline in the transformer, but we need that value + * for the other calculations that come after, like diff, etc. + */ + if ($asset->purchase_cost!='') { + $purchase_cost = $purchase_cost_currency . ' ' . \App\Helpers\Helper::formatCurrencyOutput($asset->purchase_cost); + } + + + /** + * Override the previously set null values if there is a valid model and associated depreciation + */ + if (($asset->model) && ($asset->model->depreciation)) { + $depreciated_value = $purchase_cost_currency . ' ' . \App\Helpers\Helper::formatCurrencyOutput($asset->getDepreciatedValue()); + $monthly_depreciation = $purchase_cost_currency . ' ' .\App\Helpers\Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)); + $diff = $purchase_cost_currency . ' ' .\App\Helpers\Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())); + } + + if ($asset->assigned) { + $checkout_target = $asset->assigned->name; + if ($asset->checkedOutToUser()) { + $checkout_target = $asset->assigned->getFullNameAttribute(); + } + + } + + $array = [ + + 'company' => ($asset->company) ? e($asset->company->name) : null, + 'name' => e($asset->name), + 'asset_tag' => e($asset->asset_tag), + 'serial' => e($asset->serial), + 'model' => ($asset->model) ? e($asset->model->name) : null, + 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, + 'eol' => ($asset->purchase_date!='') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null , + 'status_label' => ($asset->assetstatus) ? e($asset->assetstatus->name) : null, + 'status' => ($asset->assetstatus) ? e($asset->present()->statusMeta) : null, + 'category' => (($asset->model) && ($asset->model->category)) ? e($asset->model->category->name) : null, + 'manufacturer' => (($asset->model) && ($asset->model->manufacturer)) ? e($asset->model->manufacturer->name) : null, + 'supplier' => ($asset->supplier) ? e($asset->supplier->name) : null, + 'notes' => ($asset->notes) ? e($asset->notes) : null, + 'order_number' => ($asset->order_number) ? e($asset->order_number) : null, + 'location' => ($asset->location) ? e($asset->location->name) : null, + 'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null, + 'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'), + 'purchase_cost' => $purchase_cost, + 'book_value' => $depreciated_value, + 'monthly_depreciation' => $monthly_depreciation, + 'checked_out_to' => $checkout_target, + 'diff' => $diff, + 'number_of_months' => ($asset->model && $asset->model->depreciation) ? e($asset->model->depreciation->months) : null, + 'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? e($asset->model->depreciation->name) : null, + + + + ]; + + return $array; + } + + public function transformAssetsDatatable($assets) + { + return (new DatatablesTransformer)->transformDatatables($assets); + } + + + +} diff --git a/app/Presenters/DepreciationReportPresenter.php b/app/Presenters/DepreciationReportPresenter.php new file mode 100644 index 0000000000..d271220b8d --- /dev/null +++ b/app/Presenters/DepreciationReportPresenter.php @@ -0,0 +1,393 @@ + "company", + "searchable" => true, + "sortable" => true, + "switchable" => true, + "title" => trans('general.company'), + "visible" => false, + ], [ + "field" => "category", + "searchable" => true, + "sortable" => true, + "title" => trans('general.category'), + "visible" => true, + ], [ + "field" => "name", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/form.name'), + "visible" => false, + ], [ + "field" => "asset_tag", + "searchable" => true, + "sortable" => true, + "title" => trans('general.asset_tag'), + "visible" => true, + ],[ + "field" => "model", + "searchable" => true, + "sortable" => true, + "title" => trans('general.asset_model'), + "visible" => true, + ], [ + "field" => "model", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/form.model'), + "visible" => true, + ], [ + "field" => "model_number", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/models/table.modelnumber'), + "visible" => false + ], [ + "field" => "serial", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/form.serial'), + "visible" => true, + ], [ + "field" => "depreciation", + "searchable" => true, + "sortable" => true, + "title" => trans('general.depreciation'), + "visible" => true, + ], [ + "field" => "number_of_months", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/depreciations/general.number_of_months'), + "visible" => true, + ], [ + "field" => "status", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/table.status'), + "visible" => true, + ], [ + "field" => "checked_out_to", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/table.checkoutto'), + "visible" => false, + ], [ + "field" => "location", + "searchable" => true, + "sortable" => true, + "title" => trans('admin/hardware/table.location'), + "visible" => true, + ], [ + "field" => "manufacturer", + "searchable" => true, + "sortable" => true, + "title" => trans('general.manufacturer'), + "visible" => false, + ],[ + "field" => "supplier", + "searchable" => true, + "sortable" => true, + "title" => trans('general.supplier'), + "visible" => false, + ], [ + "field" => "purchase_date", + "searchable" => true, + "sortable" => true, + "visible" => true, + "title" => trans('general.purchase_date'), + "formatter" => "dateDisplayFormatter" + ], [ + "field" => "purchase_cost", + "searchable" => true, + "sortable" => true, + "visible" => true, + "title" => trans('general.purchase_cost'), + "footerFormatter" => 'sumFormatter', + ], [ + "field" => "order_number", + "searchable" => true, + "sortable" => true, + "visible" => false, + "title" => trans('general.order_number'), + ], [ + "field" => "eol", + "searchable" => false, + "sortable" => false, + "visible" => false, + "title" => trans('general.eol'), + "formatter" => "dateDisplayFormatter" + ], [ + "field" => "book_value", + "searchable" => true, + "sortable" => true, + "visible" => true, + "title" => trans('admin/hardware/table.book_value') + ], [ + "field" => "monthly_depreciation", + "searchable" => true, + "sortable" => true, + "visible" => true, + "title" => trans('admin/hardware/table.monthly_depreciation') + ],[ + "field" => "diff", + "searchable" => false, + "sortable" => false, + "visible" => true, + "title" => trans('admin/hardware/table.diff') + ],[ + "field" => "warranty_expires", + "searchable" => false, + "sortable" => false, + "visible" => false, + "title" => trans('admin/hardware/form.warranty_expires'), + "formatter" => "dateDisplayFormatter" + ], + ]; + + return json_encode($layout); + } + + + + /** + * Generate html link to this items name. + * @return string + */ + public function nameUrl() + { + return (string) link_to_route('hardware.show', e($this->name), $this->id); + } + + public function modelUrl() + { + if ($this->model->model) { + return $this->model->model->present()->nameUrl(); + } + return ''; + } + + /** + * Generate img tag to this items image. + * @return mixed|string + */ + public function imageUrl() + { + $imagePath = ''; + if ($this->image && !empty($this->image)) { + $imagePath = $this->image; + $imageAlt = $this->name; + } elseif ($this->model && !empty($this->model->image)) { + $imagePath = $this->model->image; + $imageAlt = $this->model->name; + } + $url = config('app.url'); + if (!empty($imagePath)) { + $imagePath = ''.$imageAlt.''; + } + return $imagePath; + } + + /** + * Generate img tag to this items image. + * @return mixed|string + */ + public function imageSrc() + { + $imagePath = ''; + if ($this->image && !empty($this->image)) { + $imagePath = $this->image; + } elseif ($this->model && !empty($this->model->image)) { + $imagePath = $this->model->image; + } + if (!empty($imagePath)) { + return config('app.url').'/uploads/assets/'.$imagePath; + } + return $imagePath; + } + + /** + * Get Displayable Name + * @return string + * + * @todo this should be factored out - it should be subsumed by fullName (below) + * + **/ + public function name() + { + return $this->fullName; + } + + /** + * Helper for notification polymorphism. + * @return mixed + */ + public function fullName() + { + $str = ''; + + // Asset name + if ($this->model->name) { + $str .= $this->model->name; + } + + // Asset tag + if ($this->asset_tag) { + $str .= ' ('.$this->model->asset_tag.')'; + } + + // Asset Model name + if ($this->model->model) { + $str .= ' - '.$this->model->model->name; + } + return $str; + } + /** + * Returns the date this item hits EOL. + * @return false|string + */ + public function eol_date() + { + + if (( $this->purchase_date ) && ( $this->model->model ) && ($this->model->model->eol) ) { + $date = date_create($this->purchase_date); + date_add($date, date_interval_create_from_date_string($this->model->model->eol . ' months')); + return date_format($date, 'Y-m-d'); + } + + } + + /** + * How many months until this asset hits EOL. + * @return null + */ + public function months_until_eol() + { + + $today = date("Y-m-d"); + $d1 = new DateTime($today); + $d2 = new DateTime($this->eol_date()); + + if ($this->eol_date() > $today) { + $interval = $d2->diff($d1); + } else { + $interval = null; + } + + return $interval; + } + + /** + * @return string + * This handles the status label "meta" status of "deployed" if + * it's assigned. Should maybe deprecate. + */ + public function statusMeta() + { + if ($this->model->assigned) { + return 'deployed'; + } + return $this->model->assetstatus->getStatuslabelType(); + } + + /** + * @return string + * This handles the status label "meta" status of "deployed" if + * it's assigned. Should maybe deprecate. + */ + public function statusText() + { + if ($this->model->assigned) { + return trans('general.deployed'); + } + return $this->model->assetstatus->name; + } + + /** + * @return string + * This handles the status label "meta" status of "deployed" if + * it's assigned. Results look like: + * + * (if assigned and the status label is "Ready to Deploy"): + * (Deployed) + * + * (f assigned and status label is not "Ready to Deploy":) + * Deployed (Another Status Label) + * + * (if not deployed:) + * Another Status Label + */ + public function fullStatusText() { + // Make sure the status is valid + if ($this->assetstatus) { + + // If the status is assigned to someone or something... + if ($this->model->assigned) { + + // If it's assigned and not set to the default "ready to deploy" status + if ($this->assetstatus->name != trans('general.ready_to_deploy')) { + return trans('general.deployed'). ' (' . $this->model->assetstatus->name.')'; + } + + // If it's assigned to the default "ready to deploy" status, just + // say it's deployed - otherwise it's confusing to have a status that is + // both "ready to deploy" and deployed at the same time. + return trans('general.deployed'); + } + + // Return just the status name + return $this->model->assetstatus->name; + } + + // This status doesn't seem valid - either data has been manually edited or + // the status label was deleted. + return 'Invalid status'; + } + + /** + * Date the warantee expires. + * @return false|string + */ + public function warrantee_expires() + { + if (($this->purchase_date) && ($this->warranty_months)) { + $date = date_create($this->purchase_date); + date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months')); + return date_format($date, 'Y-m-d'); + } + + return false; + } + + /** + * Url to view this item. + * @return string + */ + public function viewUrl() + { + return route('hardware.show', $this->id); + } + + public function glyph() + { + return ''; + } +}