From 82743abadcd5327394707a3993a297cdd582296e Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 14:15:18 +0100 Subject: [PATCH 01/23] Cleans up the consumables index controller Signed-off-by: snipe --- .../Controllers/Api/ConsumablesController.php | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 7c65e49bc..3054d9c03 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -27,27 +27,8 @@ class ConsumablesController extends Controller { $this->authorize('index', Consumable::class); - // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations - // Relations will be handled in query scopes a little further down. - $allowed_columns = - [ - 'id', - 'name', - 'order_number', - 'min_amt', - 'purchase_date', - 'purchase_cost', - 'company', - 'category', - 'model_number', - 'item_no', - 'qty', - 'image', - 'notes', - ]; - - $consumables = Consumable::select('consumables.*') - ->with('company', 'location', 'category', 'users', 'manufacturer'); + $consumables = Consumable::with('company', 'location', 'category', 'manufacturer') + ->withCount('users as consumables_users_count'); if ($request->filled('search')) { $consumables = $consumables->TextSearch(e($request->input('search'))); @@ -89,15 +70,9 @@ class ConsumablesController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value'); $limit = app('api_limit_value'); - - $allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image']; $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort_override = $request->input('sort'); - $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; - - - switch ($sort_override) { + switch ($request->input('sort')) { case 'category': $consumables = $consumables->OrderCategory($order); break; @@ -111,10 +86,30 @@ class ConsumablesController extends Controller $consumables = $consumables->OrderCompany($order); break; case 'supplier': - $components = $consumables->OrderSupplier($order); + $consumables = $consumables->OrderSupplier($order); break; default: - $consumables = $consumables->orderBy($column_sort, $order); + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'order_number', + 'min_amt', + 'purchase_date', + 'purchase_cost', + 'company', + 'category', + 'model_number', + 'item_no', + 'manufacturer', + 'location', + 'qty', + 'image' + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $consumables = $consumables->orderBy($sort, $order); break; } From 9e9b07f10f1c37ae5070add08d7f3163905f3e29 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:23:59 +0100 Subject: [PATCH 02/23] Removed duplicate status check Signed-off-by: snipe --- tests/Feature/Categories/Api/IndexCategoriesTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Feature/Categories/Api/IndexCategoriesTest.php b/tests/Feature/Categories/Api/IndexCategoriesTest.php index fc48fdc54..d27bfbb06 100644 --- a/tests/Feature/Categories/Api/IndexCategoriesTest.php +++ b/tests/Feature/Categories/Api/IndexCategoriesTest.php @@ -32,7 +32,6 @@ class IndexCategoriesTest extends TestCase 'limit' => '20', ])) ->assertOk() - ->assertOk() ->assertJsonStructure([ 'total', 'rows', From da2b64c96d9551d220eddf37c10b452d87d155eb Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:24:08 +0100 Subject: [PATCH 03/23] Added string Signed-off-by: snipe --- resources/lang/en-US/admin/consumables/message.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en-US/admin/consumables/message.php b/resources/lang/en-US/admin/consumables/message.php index c0d0aa7f6..e2591503b 100644 --- a/resources/lang/en-US/admin/consumables/message.php +++ b/resources/lang/en-US/admin/consumables/message.php @@ -2,6 +2,7 @@ return array( + 'invalid_category_type' => 'The category must be a consumable category.', 'does_not_exist' => 'Consumable does not exist.', 'create' => array( From a6b2f5df1d01a28d1de77c5f0e5c473c6be64549 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:24:56 +0100 Subject: [PATCH 04/23] Added tests Signed-off-by: snipe --- .../Consumables/Api/ConsumableUpdateTest.php | 52 +++++++++++++++++++ .../Consumables/Api/ConsumableViewTest.php | 51 ++++++++++++++++++ .../Consumables/Ui/ConsumableViewTest.php | 26 ++++++++++ 3 files changed, 129 insertions(+) create mode 100644 tests/Feature/Consumables/Api/ConsumableUpdateTest.php create mode 100644 tests/Feature/Consumables/Api/ConsumableViewTest.php create mode 100644 tests/Feature/Consumables/Ui/ConsumableViewTest.php diff --git a/tests/Feature/Consumables/Api/ConsumableUpdateTest.php b/tests/Feature/Consumables/Api/ConsumableUpdateTest.php new file mode 100644 index 000000000..1c1e05d4d --- /dev/null +++ b/tests/Feature/Consumables/Api/ConsumableUpdateTest.php @@ -0,0 +1,52 @@ +create(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->patchJson(route('api.consumables.update', $consumable), [ + 'name' => 'Test Consumable', + ]) + ->assertOk() + ->assertStatusMessageIs('success') + ->assertStatus(200) + ->json(); + + $consumable->refresh(); + $this->assertEquals('Test Consumable', $consumable->name, 'Name was not updated'); + + } + + public function testCannotUpdateConsumableViaPatchWithInvalidCategoryType() + { + $category = Category::factory()->create(['category_type' => 'asset']); + $consumable = Consumable::factory()->create(); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->patchJson(route('api.consumables.update', $consumable), [ + 'name' => 'Test Consumable', + 'category_id' => $category->id, + ]) + ->assertOk() + ->assertStatusMessageIs('error') + ->assertStatus(200) + ->json(); + + $category->refresh(); + $this->assertNotEquals('Test Consumable', $consumable->name, 'Name was not updated'); + $this->assertNotEquals('consumable', $consumable->category_id, 'Category was not updated'); + + } + +} diff --git a/tests/Feature/Consumables/Api/ConsumableViewTest.php b/tests/Feature/Consumables/Api/ConsumableViewTest.php new file mode 100644 index 000000000..c6410216e --- /dev/null +++ b/tests/Feature/Consumables/Api/ConsumableViewTest.php @@ -0,0 +1,51 @@ +count(2)->create(); + + $consumableA = Consumable::factory()->for($companyA)->create(); + $consumableB = Consumable::factory()->for($companyB)->create(); + + $superUser = $companyA->users()->save(User::factory()->superuser()->make()); + $userInCompanyA = $companyA->users()->save(User::factory()->viewConsumables()->make()); + $userInCompanyB = $companyB->users()->save(User::factory()->viewConsumables()->make()); + + $this->settings->disableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.consumables.show', $consumableA)) + ->assertOk(); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.consumables.show', $consumableA)) + ->assertOk(); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.consumables.show', $consumableB)) + ->assertOk(); + + $this->settings->enableMultipleFullCompanySupport(); + + $this->actingAsForApi($superUser) + ->getJson(route('api.consumables.show', $consumableA)) + ->assertOk(); + + $this->actingAsForApi($userInCompanyA) + ->getJson(route('api.consumables.index')) + ->assertOk(); + + $this->actingAsForApi($userInCompanyB) + ->getJson(route('api.consumables.index')) + ->assertOk(); + } +} diff --git a/tests/Feature/Consumables/Ui/ConsumableViewTest.php b/tests/Feature/Consumables/Ui/ConsumableViewTest.php new file mode 100644 index 000000000..9633896c2 --- /dev/null +++ b/tests/Feature/Consumables/Ui/ConsumableViewTest.php @@ -0,0 +1,26 @@ +create(); + $this->actingAs(User::factory()->create()) + ->get(route('consumables.show', $consumable)) + ->assertForbidden(); + } + + public function testUserCanListConsumables() + { + $consumable = Consumable::factory()->create(); + $this->actingAs(User::factory()->superuser()->create()) + ->get(route('consumables.show', $consumable)) + ->assertOk(); + } +} From a7a1a377dae867c80a7c252eb0a30b957c83411a Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:25:07 +0100 Subject: [PATCH 05/23] Added form request for consumables Signed-off-by: snipe --- app/Http/Requests/StoreConsumableRequest.php | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/Http/Requests/StoreConsumableRequest.php diff --git a/app/Http/Requests/StoreConsumableRequest.php b/app/Http/Requests/StoreConsumableRequest.php new file mode 100644 index 000000000..9062b07cc --- /dev/null +++ b/app/Http/Requests/StoreConsumableRequest.php @@ -0,0 +1,56 @@ +category_id) { + if ($category = Category::find($this->category_id)) { + $this->merge([ + 'category_type' => $category->category_type ?? null, + ]); + } + } + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return array_merge( + ['category_type' => 'in:consumable'], + parent::rules(), + ); + } + + public function messages(): array + { + $messages = ['category_type.in' => trans('admin/consumables/message.invalid_category_type')]; + return $messages; + } + + public function response(array $errors) + { + return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag); + } +} From 2b5463475c955359d12b0037ce4f6e206388008a Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:25:26 +0100 Subject: [PATCH 06/23] Added test Signed-off-by: snipe --- .../Consumables/Api/ConsumableIndexTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Feature/Consumables/Api/ConsumableIndexTest.php b/tests/Feature/Consumables/Api/ConsumableIndexTest.php index b16dfdfa5..f1d3ad7f0 100644 --- a/tests/Feature/Consumables/Api/ConsumableIndexTest.php +++ b/tests/Feature/Consumables/Api/ConsumableIndexTest.php @@ -54,4 +54,29 @@ class ConsumableIndexTest extends TestCase ->assertResponseDoesNotContainInRows($consumableA) ->assertResponseContainsInRows($consumableB); } + + public function testConsumableIndexReturnsExpectedSearchResults() + { + Consumable::factory()->count(10)->create(); + Consumable::factory()->count(1)->create(['name' => 'My Test Consumable']); + + $this->actingAsForApi(User::factory()->superuser()->create()) + ->getJson( + route('api.consumables.index', [ + 'search' => 'My Test Consumable', + 'sort' => 'name', + 'order' => 'asc', + 'offset' => '0', + 'limit' => '20', + ])) + ->assertOk() + ->assertJsonStructure([ + 'total', + 'rows', + ]) + ->assertJson([ + 'total' => 1, + ]); + + } } From abd79219ddc92d983fc4833beb987941865d1a12 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:26:24 +0100 Subject: [PATCH 07/23] Use form request to check for valid category Signed-off-by: snipe --- app/Http/Controllers/Api/ConsumablesController.php | 5 +++-- .../Consumables/ConsumablesController.php | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 3054d9c03..ccb0593d7 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedOut; use App\Helpers\Helper; use App\Http\Controllers\Controller; +use App\Http\Requests\StoreConsumableRequest; use App\Http\Transformers\ConsumablesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Company; @@ -126,7 +127,7 @@ class ConsumablesController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request */ - public function store(ImageUploadRequest $request) : JsonResponse + public function store(StoreConsumableRequest $request) : JsonResponse { $this->authorize('create', Consumable::class); $consumable = new Consumable; @@ -162,7 +163,7 @@ class ConsumablesController extends Controller * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id */ - public function update(ImageUploadRequest $request, $id) : JsonResponse + public function update(StoreConsumableRequest $request, $id) : JsonResponse { $this->authorize('update', Consumable::class); $consumable = Consumable::findOrFail($id); diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index b33e6e07a..e438e33bc 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -8,8 +8,10 @@ use App\Http\Requests\ImageUploadRequest; use App\Models\Company; use App\Models\Consumable; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Validator; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; +use App\Http\Requests\StoreConsumableRequest; /** * This controller handles all actions related to Consumables for @@ -62,7 +64,7 @@ class ConsumablesController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(StoreConsumableRequest $request) { $this->authorize('create', Consumable::class); $consumable = new Consumable(); @@ -99,10 +101,8 @@ class ConsumablesController extends Controller * @param int $consumableId * @see ConsumablesController::postEdit() method that stores the form data. * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($consumableId = null) + public function edit($consumableId = null) : View | RedirectResponse { if ($item = Consumable::find($consumableId)) { $this->authorize($item); @@ -124,7 +124,7 @@ class ConsumablesController extends Controller * @see ConsumablesController::getEdit() method that stores the form data. * @since [v1.0] */ - public function update(ImageUploadRequest $request, $consumableId = null) + public function update(StoreConsumableRequest $request, $consumableId = null) { if (is_null($consumable = Consumable::find($consumableId))) { return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist')); From 759bb822ff9316040975be61f39d9c9f8f50ab69 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 15:59:18 +0100 Subject: [PATCH 08/23] Make sure the user exists Signed-off-by: snipe --- app/Models/ConsumableAssignment.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Models/ConsumableAssignment.php b/app/Models/ConsumableAssignment.php index 1e21a7f7d..ae84cd19d 100644 --- a/app/Models/ConsumableAssignment.php +++ b/app/Models/ConsumableAssignment.php @@ -3,13 +3,19 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Watson\Validating\ValidatingTrait; class ConsumableAssignment extends Model { use CompanyableTrait; + use ValidatingTrait; protected $table = 'consumables_users'; + public $rules = [ + 'assigned_to' => 'exists:users,id', + ]; + public function consumable() { return $this->belongsTo(\App\Models\Consumable::class); From 9676d934d29693830c2073c92696e2dd0f80e504 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:00:08 +0100 Subject: [PATCH 09/23] Drop the with users on the create checkout screen Signed-off-by: snipe --- .../Consumables/ConsumableCheckoutController.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index 919cd1698..fd690fede 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -4,12 +4,11 @@ namespace App\Http\Controllers\Consumables; use App\Events\CheckoutableCheckedOut; use App\Http\Controllers\Controller; -use App\Models\Accessory; use App\Models\Consumable; use App\Models\User; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; class ConsumableCheckoutController extends Controller { @@ -20,13 +19,11 @@ class ConsumableCheckoutController extends Controller * @see ConsumableCheckoutController::store() method that stores the data. * @since [v1.0] * @param int $id - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($id) + public function create($id) : View | RedirectResponse { - if ($consumable = Consumable::with('users')->find($id)) { + if ($consumable = Consumable::find($id)) { $this->authorize('checkout', $consumable); From e123eeea9e893583f106ccd90174b05171853c2d Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:00:20 +0100 Subject: [PATCH 10/23] Added error message if category type is invalid Signed-off-by: snipe --- resources/views/partials/forms/edit/category-select.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/views/partials/forms/edit/category-select.blade.php b/resources/views/partials/forms/edit/category-select.blade.php index f555c6240..66800deed 100644 --- a/resources/views/partials/forms/edit/category-select.blade.php +++ b/resources/views/partials/forms/edit/category-select.blade.php @@ -25,4 +25,6 @@ {!! $errors->first($fieldname, '
') !!} + + {!! $errors->first('category_type', '
') !!} From b10441c92867d58d3b80cf97d07549614942a21e Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:01:18 +0100 Subject: [PATCH 11/23] Detach associated pivot records Signed-off-by: snipe --- app/Http/Controllers/Consumables/ConsumablesController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index e438e33bc..f6b4f0139 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -182,6 +182,7 @@ class ConsumablesController extends Controller return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found')); } $this->authorize($consumable); + $consumable->users()->detach(); $consumable->delete(); // Redirect to the locations management page return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success')); From d9a114090bd8b05e43d4c706cc15a6528e5fef7f Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:18:33 +0100 Subject: [PATCH 12/23] Added buttons Signed-off-by: snipe --- resources/views/consumables/view.blade.php | 53 ++++++++++++++++------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/resources/views/consumables/view.blade.php b/resources/views/consumables/view.blade.php index 80987335f..b56015524 100644 --- a/resources/views/consumables/view.blade.php +++ b/resources/views/consumables/view.blade.php @@ -176,6 +176,7 @@ {{ trans('general.delete') }} + @endforeach @@ -254,6 +255,19 @@ @endif + @if ($consumable->notes) + +
+ + {{ trans('general.notes') }}: + +
+
+ {!! nl2br(Helper::parseEscapedMarkedownInline($consumable->notes)) !!} +
+ + @endif + @can('checkout', \App\Models\Consumable::class)
@@ -268,22 +282,24 @@ @endif
+ @can('update', \App\Models\Consumable::class) + + @endcan + + @can('delete', $consumable) +
+ @if ($consumable->deleted_at=='') + + {{ trans('general.delete') }} + @endif +
+ @endcan @endcan - @if ($consumable->notes) - -
- - {{ trans('general.notes') }}: - -
-
- {!! nl2br(Helper::parseEscapedMarkedownInline($consumable->notes)) !!} -
- - @endif - @@ -297,5 +313,16 @@ @stop @section('moar_scripts') + + @include ('partials.bootstrap-table', ['exportFile' => 'consumable' . $consumable->name . '-export', 'search' => false]) @stop From 94bfe7d9c8b75191c61a98815bd38736193f9ac3 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:18:42 +0100 Subject: [PATCH 13/23] Require the user exists Signed-off-by: snipe --- app/Models/ConsumableAssignment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/ConsumableAssignment.php b/app/Models/ConsumableAssignment.php index ae84cd19d..db0cfa4bd 100644 --- a/app/Models/ConsumableAssignment.php +++ b/app/Models/ConsumableAssignment.php @@ -13,7 +13,7 @@ class ConsumableAssignment extends Model protected $table = 'consumables_users'; public $rules = [ - 'assigned_to' => 'exists:users,id', + 'assigned_to' => 'required|exists:users,id', ]; public function consumable() From 42dbc894e0a601911b1ae9058a7c382e886d3da0 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:21:45 +0100 Subject: [PATCH 14/23] Better handle numRemaining for consumables Signed-off-by: snipe --- app/Models/Consumable.php | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 008dffa4c..944ac5bfd 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -10,12 +10,21 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; +use Illuminate\Database\Eloquent\Relations\Relation; +use App\Presenters\ConsumablePresenter; +use App\Models\Actionlog; +use App\Models\ConsumableAssignment; +use App\Models\User; +use App\Models\Location; +use App\Models\Manufacturer; +use App\Models\Supplier; +use App\Models\Category; class Consumable extends SnipeModel { use HasFactory; - protected $presenter = \App\Presenters\ConsumablePresenter::class; + protected $presenter = ConsumablePresenter::class; use CompanyableTrait; use Loggable, Presentable; use SoftDeletes; @@ -37,10 +46,10 @@ class Consumable extends SnipeModel */ public $rules = [ 'name' => 'required|min:3|max:255', - 'qty' => 'required|integer|min:0', + 'qty' => 'required|integer|min:0|max:99999', 'category_id' => 'required|integer', 'company_id' => 'integer|nullable', - 'min_amt' => 'integer|min:0|nullable', + 'min_amt' => 'integer|min:0|max:99999|nullable', 'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -109,7 +118,7 @@ class Consumable extends SnipeModel */ public function uploads() { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') + return $this->hasMany(Actionlog::class, 'item_id') ->where('item_type', '=', self::class) ->where('action_type', '=', 'uploaded') ->whereNotNull('filename') @@ -147,7 +156,7 @@ class Consumable extends SnipeModel */ public function admin() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(User::class, 'user_id'); } /** @@ -159,7 +168,7 @@ class Consumable extends SnipeModel */ public function consumableAssignments() { - return $this->hasMany(\App\Models\ConsumableAssignment::class); + return $this->hasMany(ConsumableAssignment::class); } /** @@ -183,7 +192,7 @@ class Consumable extends SnipeModel */ public function manufacturer() { - return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); + return $this->belongsTo(Manufacturer::class, 'manufacturer_id'); } /** @@ -195,7 +204,7 @@ class Consumable extends SnipeModel */ public function location() { - return $this->belongsTo(\App\Models\Location::class, 'location_id'); + return $this->belongsTo(Location::class, 'location_id'); } /** @@ -207,7 +216,7 @@ class Consumable extends SnipeModel */ public function category() { - return $this->belongsTo(\App\Models\Category::class, 'category_id'); + return $this->belongsTo(Category::class, 'category_id'); } @@ -220,7 +229,7 @@ class Consumable extends SnipeModel */ public function assetlog() { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed(); + return $this->hasMany(Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed(); } /** @@ -244,11 +253,10 @@ class Consumable extends SnipeModel * * @author [A. Gianotto] [] * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function users() + public function users() : Relation { - return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); + return $this->belongsToMany(User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); } /** @@ -260,7 +268,7 @@ class Consumable extends SnipeModel */ public function supplier() { - return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id'); + return $this->belongsTo(Supplier::class, 'supplier_id'); } @@ -317,10 +325,7 @@ class Consumable extends SnipeModel */ public function numCheckedOut() { - $checkedout = 0; - $checkedout = $this->users->count(); - - return $checkedout; + return $this->consumables_users_count ?? $this->users()->count(); } /** @@ -332,7 +337,7 @@ class Consumable extends SnipeModel */ public function numRemaining() { - $checkedout = $this->users->count(); + $checkedout = $this->numCheckedOut(); $total = $this->qty; $remaining = $total - $checkedout; From 67d6f7119d5270af49553a1ff0fc2f217e95b2a0 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:22:04 +0100 Subject: [PATCH 15/23] Prevent a ludicrous amount of consumables Signed-off-by: snipe --- resources/views/partials/forms/edit/quantity.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/partials/forms/edit/quantity.blade.php b/resources/views/partials/forms/edit/quantity.blade.php index f8a421adc..b41d13f65 100644 --- a/resources/views/partials/forms/edit/quantity.blade.php +++ b/resources/views/partials/forms/edit/quantity.blade.php @@ -4,8 +4,10 @@
- +
+
{!! $errors->first('qty', '') !!} +
From 00d7b36c3653dc7ceed101c1c6976584879ca88f Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:22:22 +0100 Subject: [PATCH 16/23] Eager load suppliers Signed-off-by: snipe --- app/Http/Controllers/Api/ConsumablesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index ccb0593d7..1665b7f4f 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -28,7 +28,7 @@ class ConsumablesController extends Controller { $this->authorize('index', Consumable::class); - $consumables = Consumable::with('company', 'location', 'category', 'manufacturer') + $consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer') ->withCount('users as consumables_users_count'); if ($request->filled('search')) { From 2415408a5005404dd142d8f29870ea67a713160d Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:22:41 +0100 Subject: [PATCH 17/23] Prevent ludicrious amount of min_qty Signed-off-by: snipe --- .../views/partials/forms/edit/minimum_quantity.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/partials/forms/edit/minimum_quantity.blade.php b/resources/views/partials/forms/edit/minimum_quantity.blade.php index 1566ab2e9..798c6fe72 100644 --- a/resources/views/partials/forms/edit/minimum_quantity.blade.php +++ b/resources/views/partials/forms/edit/minimum_quantity.blade.php @@ -3,13 +3,13 @@
- +
{!! $errors->first('min_amt', '') !!} From 58bc84436c6892b05523b855140745ef0baa32b6 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:23:05 +0100 Subject: [PATCH 18/23] Fixed incorrect jquery validation class Signed-off-by: snipe --- resources/views/layouts/default.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 74e9d5be6..63c5bca44 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -976,7 +976,7 @@ dir="{{ in_array(app()->getLocale(),['ar-SA','fa-IR', 'he-IL']) ? 'rtl' : 'ltr' // Reference: https://jqueryvalidation.org/validate/ $('#create-form').validate({ ignore: 'input[type=hidden]', - errorClass: 'help-block form-error', + errorClass: 'alert-msg', errorElement: 'span', errorPlacement: function(error, element) { $(element).hasClass('select2') || $(element).hasClass('js-data-ajax') From 5108b1f3eda32f9e61d80d8ffd87f914a1bfa97c Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:35:16 +0100 Subject: [PATCH 19/23] Capture the log meta for what was changed Signed-off-by: snipe --- app/Observers/ConsumableObserver.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index 08c56d2ee..27b3c7f97 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -16,12 +16,26 @@ class ConsumableObserver */ public function updated(Consumable $consumable) { - $logAction = new Actionlog(); - $logAction->item_type = Consumable::class; - $logAction->item_id = $consumable->id; - $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); - $logAction->logaction('update'); + + $changed = []; + + foreach ($consumable->getRawOriginal() as $key => $value) { + // Check and see if the value changed + if ($consumable->getRawOriginal()[$key] != $consumable->getAttributes()[$key]) { + $changed[$key]['old'] = $consumable->getRawOriginal()[$key]; + $changed[$key]['new'] = $consumable->getAttributes()[$key]; + } + } + + if (count($changed) > 0) { + $logAction = new Actionlog(); + $logAction->item_type = Consumable::class; + $logAction->item_id = $consumable->id; + $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->user_id = Auth::id(); + $logAction->log_meta = json_encode($changed); + $logAction->logaction('update'); + } } /** From 23f1489b2b9075bd6078748702f7b35767d9d23a Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 16:46:49 +0100 Subject: [PATCH 20/23] Added history tab Signed-off-by: snipe --- resources/views/consumables/view.blade.php | 62 +++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/resources/views/consumables/view.blade.php b/resources/views/consumables/view.blade.php index b56015524..e6b813723 100644 --- a/resources/views/consumables/view.blade.php +++ b/resources/views/consumables/view.blade.php @@ -45,6 +45,17 @@ @endcan +
  • + + + + +
  • + @can('update', $consumable)
  • @@ -95,7 +106,56 @@
  • - @can('consumables.files', $consumable) +
    + +
    +
    + + + + + + + + + + + + + + + + + + + +
    {{ trans('general.date') }}{{ trans('general.admin') }}{{ trans('general.action') }}{{ trans('general.file_name') }}{{ trans('general.item') }}{{ trans('general.target') }}{{ trans('general.notes') }}{{ trans('general.signature') }}{{ trans('general.download') }}{{ trans('admin/hardware/table.changed')}}{{ trans('admin/settings/general.login_ip') }}{{ trans('admin/settings/general.login_user_agent') }}{{ trans('general.action_source') }}
    +
    +
    +
    + + + @can('consumables.files', $consumable)
    From d4a66f9b6c5a349dbfed1da84c76d3776534ed93 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 17:00:12 +0100 Subject: [PATCH 21/23] Use better icon for deleting Signed-off-by: snipe --- app/Presenters/ActionlogPresenter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 2794b6c5f..ebbe3d782 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -75,7 +75,7 @@ class ActionlogPresenter extends Presenter } if ($this->actionType()=='delete') { - return 'fa-solid fa-user-xmark'; + return 'fa-solid fa-trash'; } if ($this->actionType()=='update') { From c2fe3b54590932346cb358d5c677c02b9822ccff Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 17:00:38 +0100 Subject: [PATCH 22/23] Use the observer to delete associated files and detach users Signed-off-by: snipe --- .../Consumables/ConsumablesController.php | 2 +- app/Observers/ConsumableObserver.php | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index f6b4f0139..5685944a5 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -182,7 +182,7 @@ class ConsumablesController extends Controller return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found')); } $this->authorize($consumable); - $consumable->users()->detach(); + $consumable->delete(); // Redirect to the locations management page return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success')); diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index 27b3c7f97..1bd6e7cde 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -5,6 +5,8 @@ namespace App\Observers; use App\Models\Actionlog; use App\Models\Consumable; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class ConsumableObserver { @@ -66,6 +68,28 @@ class ConsumableObserver */ public function deleting(Consumable $consumable) { + + $consumable->users()->detach(); + + foreach ($consumable->uploads() as $file) { + try { + Storage::disk('public')->delete('consumables/'.$file); + } catch (\Exception $e) { + Log::info($e); + } + } + + try { + Storage::disk('public')->delete('consumables/'.$consumable->image); + } catch (\Exception $e) { + Log::info($e); + } + + $consumable->image = null; + $consumable->save(); + + + $logAction = new Actionlog(); $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; From 1527679f4b9eb596ad4034ee0971be2314f603c9 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 11 Jul 2024 17:26:43 +0100 Subject: [PATCH 23/23] Handle deletions on the observer Signed-off-by: snipe --- app/Observers/ConsumableObserver.php | 8 ++++++-- .../views/consumables/checkout.blade.php | 19 +++++++++++++++++++ resources/views/consumables/view.blade.php | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index 1bd6e7cde..377995ebb 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -70,15 +70,19 @@ class ConsumableObserver { $consumable->users()->detach(); + $uploads = $consumable->uploads; - foreach ($consumable->uploads() as $file) { + foreach ($uploads as $file) { try { - Storage::disk('public')->delete('consumables/'.$file); + Storage::delete('private_uploads/consumables/'.$file->filename); + $file->delete(); } catch (\Exception $e) { Log::info($e); } } + + try { Storage::disk('public')->delete('consumables/'.$consumable->image); } catch (\Exception $e) { diff --git a/resources/views/consumables/checkout.blade.php b/resources/views/consumables/checkout.blade.php index bde94bc6f..29b68b6ce 100644 --- a/resources/views/consumables/checkout.blade.php +++ b/resources/views/consumables/checkout.blade.php @@ -37,6 +37,25 @@
    @endif + +
    + +
    +

    {{ $consumable->qty }}

    +
    +
    + + +
    + +
    +

    {{ $consumable->numRemaining() }}

    +
    +
    + + + + @include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_to', 'required'=> 'true']) diff --git a/resources/views/consumables/view.blade.php b/resources/views/consumables/view.blade.php index e6b813723..5a25659fb 100644 --- a/resources/views/consumables/view.blade.php +++ b/resources/views/consumables/view.blade.php @@ -351,7 +351,7 @@ @can('delete', $consumable)
    @if ($consumable->deleted_at=='') - {{ trans('general.delete') }} @endif