Skip to content

Commit

Permalink
Merge pull request #15714 from akemidx/saving_custom_report_template
Browse files Browse the repository at this point in the history
Saved Custom Report Templates
  • Loading branch information
snipe authored Dec 19, 2024
2 parents 1434522 + 92f33c0 commit 15296d2
Show file tree
Hide file tree
Showing 28 changed files with 1,593 additions and 212 deletions.
80 changes: 80 additions & 0 deletions app/Http/Controllers/ReportTemplatesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace App\Http\Controllers;

use App\Models\CustomField;
use App\Models\ReportTemplate;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;

class ReportTemplatesController extends Controller
{
public function store(Request $request): RedirectResponse
{
$this->authorize('reports.view');

// Ignore "options" rules since data does not come in under that key...
$validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));

$report = $request->user()->reportTemplates()->create([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
]);

session()->flash('success', trans('admin/reports/message.create.success'));

return redirect()->route('report-templates.show', $report->id);
}

public function show(ReportTemplate $reportTemplate)
{
$this->authorize('reports.view');

$customfields = CustomField::get();
$report_templates = ReportTemplate::orderBy('name')->get();

return view('reports/custom', [
'customfields' => $customfields,
'report_templates' => $report_templates,
'template' => $reportTemplate,
]);
}

public function edit(ReportTemplate $reportTemplate)
{
$this->authorize('reports.view');

return view('reports/custom', [
'customfields' => CustomField::get(),
'template' => $reportTemplate,
]);
}

public function update(Request $request, ReportTemplate $reportTemplate): RedirectResponse
{
$this->authorize('reports.view');

// Ignore "options" rules since data does not come in under that key...
$validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));

$reportTemplate->update([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
]);

session()->flash('success', trans('admin/reports/message.update.success'));

return redirect()->route('report-templates.show', $reportTemplate->id);
}

public function destroy(ReportTemplate $reportTemplate): RedirectResponse
{
$this->authorize('reports.view');

$reportTemplate->delete();

return redirect()->route('reports/custom')
->with('success', trans('admin/reports/message.delete.success'));
}
}
20 changes: 18 additions & 2 deletions app/Http/Controllers/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use App\Models\CustomField;
use App\Models\Depreciation;
use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
Expand Down Expand Up @@ -394,12 +395,27 @@ public function exportLicenseReport() : Response
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
*/
public function getCustomReport() : View
public function getCustomReport(Request $request) : View
{
$this->authorize('reports.view');
$customfields = CustomField::get();
$report_templates = ReportTemplate::orderBy('name')->get();

return view('reports/custom')->with('customfields', $customfields);
// The view needs a template to render correctly, even if it is empty...
$template = new ReportTemplate;

// Set the report's input values in the cases we were redirected back
// with validation errors so the report is populated as expected.
if ($request->old()) {
$template->name = $request->old('name');
$template->options = $request->old();
}

return view('reports/custom', [
'customfields' => $customfields,
'report_templates' => $report_templates,
'template' => $template,
]);
}

/**
Expand Down
241 changes: 241 additions & 0 deletions app/Models/ReportTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;

class ReportTemplate extends Model
{
use HasFactory;
use SoftDeletes;
use ValidatingTrait;

protected $casts = [
'options' => 'array',
];

protected $fillable = [
'created_by',
'name',
'options',
];

protected $rules = [
'name' => [
'required',
'string',
],
'options' => [
'required',
'array',
],
];

protected static function booted()
{
// Scope to current user
static::addGlobalScope('current_user', function (Builder $builder) {
if (auth()->check()) {
$builder->where('created_by', auth()->id());
}
});

static::created(function (ReportTemplate $reportTemplate) {
$logAction = new Actionlog([
'item_type' => ReportTemplate::class,
'item_id' => $reportTemplate->id,
'created_by' => auth()->id(),
]);

$logAction->logaction('create');
});

static::updated(function (ReportTemplate $reportTemplate) {
$changed = [];

foreach ($reportTemplate->getDirty() as $key => $value) {
$changed[$key] = [
'old' => $reportTemplate->getOriginal($key),
'new' => $reportTemplate->getAttribute($key),
];
}

$logAction = new Actionlog();
$logAction->item_type = ReportTemplate::class;
$logAction->item_id = $reportTemplate->id;
$logAction->created_by = auth()->id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
});

static::deleted(function (ReportTemplate $reportTemplate) {
$logAction = new Actionlog([
'item_type' => ReportTemplate::class,
'item_id' => $reportTemplate->id,
'created_by' => auth()->id(),
]);

$logAction->logaction('delete');
});
}

/**
* Establishes the report template -> creator relationship.
*
*/
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}

/**
* Get the value of a checkbox field for the given field name.
*
* @param string $fieldName
* @param string $fallbackValue The value to return if the report template is not saved yet.
*
*/
public function checkmarkValue(string $fieldName, string $fallbackValue = '1'): string
{
// Assuming we're using the null object pattern, and an empty model
// was passed to the view when showing the default report page,
// return the fallback value so that checkboxes are checked by default.
if (is_null($this->id)) {
return $fallbackValue;
}

// If the model does exist then return the value of the field
// or return 0 so the checkbox is unchecked.
// Falling back to 0 here is because checkboxes are not sent
// in the request when unchecked so they are not
// actually saved in the model's options.
return $this->options[$fieldName] ?? '0';
}

/**
* Get the value of a radio field for the given field name.
*
* @param string $fieldName
* @param string $value The value to check against.
* @param bool $isDefault Whether the radio input being checked is the default.
*
*/
public function radioValue(string $fieldName, string $value, bool $isDefault = false): bool
{
$fieldExists = array_has($this->options, $fieldName);

// If the field doesn't exist but the radio input
// being checked is the default then return true.
if (!$fieldExists && $isDefault) {
return true;
}

// If the field exists and matches what we're checking then return true.
if ($fieldExists && $this->options[$fieldName] === $value) {
return true;
}

// Otherwise return false.
return false;
}

/**
* Get the value of a select field for the given field name.
*
* @param string $fieldName
* @param string|null $model The Eloquent model to check against.
*
* @return mixed|null
*
*/
public function selectValue(string $fieldName, string $model = null)
{
// If the field does not exist then return null.
if (!isset($this->options[$fieldName])) {
return null;
}

$value = $this->options[$fieldName];

// If the value was stored as an array, most likely
// due to a previously being a multi-select,
// then return the first value.
if (is_array($value)) {
$value = $value[0];
}

// If a model is provided then we should ensure we only return
// the value if the model still exists.
// Note: It is possible $value is an id that no longer exists and this will return null.
if ($model) {
$foundModel = $model::find($value);

return $foundModel ? $foundModel->id : null;
}

return $value;
}

/**
* Get the values of a multi-select field for the given field name.
*
* @param string $fieldName
* @param string|null $model The Eloquent model to check against.
*
* @return iterable
*
*/
public function selectValues(string $fieldName, string $model = null): iterable
{
// If the field does not exist then return an empty array.
if (!isset($this->options[$fieldName])) {
return [];
}

// If a model is provided then we should ensure we only return
// the ids of models that exist and are not deleted.
if ($model) {
return $model::findMany($this->options[$fieldName])->pluck('id');
}

// Wrap the value in an array if needed. This is to ensure
// values previously stored as a single value,
// most likely from a single select, are returned as an array.
if (!is_array($this->options[$fieldName])) {
return [$this->options[$fieldName]];
}

return $this->options[$fieldName];
}

/**
* Get the value of a text field for the given field name.
*
* @param string $fieldName
* @param string|null $fallbackValue
*
* @return string
*/
public function textValue(string $fieldName, string|null $fallbackValue = ''): string
{
// Assuming we're using the null object pattern,
// return the default value if the object is not saved yet.
if (is_null($this->id)) {
return (string) $fallbackValue;
}

// Return the field's value if it exists
// and return the default value if not.
return $this->options[$fieldName] ?? '';
}

public function getDisplayNameAttribute()
{
return $this->name;
}
}
10 changes: 10 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Notifications\Notifiable;
Expand Down Expand Up @@ -361,6 +362,15 @@ public function licenses()
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at');
}

/**
* Establishes the user -> reportTemplates relationship
*
*/
public function reportTemplates(): HasMany
{
return $this->hasMany(ReportTemplate::class, 'created_by');
}

/**
* Establishes a count of all items assigned
*
Expand Down
Loading

0 comments on commit 15296d2

Please sign in to comment.