mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-09 23:24:06 -08:00
Merge branch 'develop' into has_custom_fields_trait_rebase
This commit is contained in:
commit
a3a786f2af
|
@ -6,7 +6,6 @@ environment:
|
|||
|
||||
services:
|
||||
- mysql: 5.7
|
||||
- dusk:
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -43,19 +42,3 @@ pipeline:
|
|||
- name: PHPUnit Feature Tests
|
||||
cmd: |
|
||||
php artisan test --testsuite Feature
|
||||
|
||||
# - name: Browser Tests
|
||||
# cmd: |
|
||||
# cp -v .env.dusk.example .env.dusk.ci
|
||||
# sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci
|
||||
# sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
|
||||
# #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci
|
||||
# sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci
|
||||
# sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci
|
||||
# sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci
|
||||
# sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci
|
||||
#
|
||||
# php -S [::0]:8000 -t public 2>server.log &
|
||||
# sleep 2
|
||||
# php artisan dusk:chrome-driver $CHROME_DRIVER
|
||||
# php artisan dusk --env=ci
|
||||
|
|
73
.github/workflows/tests.yml
vendored
Normal file
73
.github/workflows/tests.yml
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: snipeit
|
||||
ports:
|
||||
- 33306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1.1"
|
||||
|
||||
name: PHP ${{ matrix.php-version }}
|
||||
|
||||
steps:
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: none
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Copy .env
|
||||
run: |
|
||||
cp -v .env.testing.example .env
|
||||
cp -v .env.testing.example .env.testing
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
||||
|
||||
- name: Generate key
|
||||
run: php artisan key:generate
|
||||
|
||||
- name: Directory Permissions
|
||||
run: chmod -R 777 storage bootstrap/cache
|
||||
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
env:
|
||||
DB_CONNECTION: mysql
|
||||
DB_DATABASE: snipeit
|
||||
DB_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
run: php artisan test --parallel
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,8 +1,6 @@
|
|||
.couscous
|
||||
.DS_Store
|
||||
.env
|
||||
.env.dusk.*
|
||||
!.env.dusk.example
|
||||
.env.testing
|
||||
phpstan.neon
|
||||
.idea
|
||||
|
|
65
TESTING.md
65
TESTING.md
|
@ -9,7 +9,39 @@ Before starting, follow the [instructions](README.md#installation) for installin
|
|||
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
|
||||
|
||||
`cp .env.testing.example .env.testing`
|
||||
> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
|
||||
|
||||
The following should work for running tests in memory with sqlite:
|
||||
```
|
||||
# --------------------------------------------
|
||||
# REQUIRED: BASIC APP SETTINGS
|
||||
# --------------------------------------------
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_TIMEZONE='UTC'
|
||||
APP_LOCALE=en
|
||||
|
||||
# --------------------------------------------
|
||||
# REQUIRED: DATABASE SETTINGS
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=sqlite_testing
|
||||
#DB_HOST=127.0.0.1
|
||||
#DB_PORT=3306
|
||||
#DB_DATABASE=null
|
||||
#DB_USERNAME=null
|
||||
#DB_PASSWORD=null
|
||||
```
|
||||
|
||||
To use MySQL you should update the `DB_` variables to match your local test database:
|
||||
```
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE={}
|
||||
DB_USERNAME={}
|
||||
DB_PASSWORD={}
|
||||
```
|
||||
|
||||
Now you are ready to run the entire test suite from your terminal:
|
||||
|
||||
|
@ -18,34 +50,3 @@ Now you are ready to run the entire test suite from your terminal:
|
|||
To run individual test files, you can pass the path to the test that you want to run:
|
||||
|
||||
`php artisan test tests/Unit/AccessoryTest.php`
|
||||
|
||||
## Browser Tests
|
||||
|
||||
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
|
||||
|
||||
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
|
||||
|
||||
`cp .env.dusk.example .env.dusk.local`
|
||||
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
|
||||
|
||||
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
|
||||
|
||||
### Running Browser Tests
|
||||
|
||||
Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
|
||||
|
||||
`php artisan serve`
|
||||
|
||||
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
|
||||
|
||||
`php artisan dusk`
|
||||
|
||||
To run individual test files, you can pass the path to the test that you want to run:
|
||||
|
||||
`php artisan dusk tests/Browser/LoginTest.php`
|
||||
|
||||
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
|
||||
|
||||
`php artisan dusk:chrome-driver --detect`
|
||||
|
||||
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.
|
||||
|
|
|
@ -15,18 +15,20 @@ class CheckoutableCheckedIn
|
|||
public $checkedInBy;
|
||||
public $note;
|
||||
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
|
||||
public $originalValues;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null)
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null, $originalValues = [])
|
||||
{
|
||||
$this->checkoutable = $checkoutable;
|
||||
$this->checkedOutTo = $checkedOutTo;
|
||||
$this->checkedInBy = $checkedInBy;
|
||||
$this->note = $note;
|
||||
$this->action_date = $action_date ?? date('Y-m-d');
|
||||
$this->originalValues = $originalValues;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,17 +14,19 @@ class CheckoutableCheckedOut
|
|||
public $checkedOutTo;
|
||||
public $checkedOutBy;
|
||||
public $note;
|
||||
public $originalValues;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
|
||||
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [])
|
||||
{
|
||||
$this->checkoutable = $checkoutable;
|
||||
$this->checkedOutTo = $checkedOutTo;
|
||||
$this->checkedOutBy = $checkedOutBy;
|
||||
$this->note = $note;
|
||||
$this->originalValues = $originalValues;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Helpers;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\CustomField;
|
||||
|
@ -654,6 +656,7 @@ class Helper
|
|||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||
$components = Component::whereNotNull('min_amt')->get();
|
||||
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
||||
|
||||
$avail_consumables = 0;
|
||||
$items_array = [];
|
||||
|
@ -716,6 +719,28 @@ class Helper
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($asset_models as $asset_model){
|
||||
|
||||
$asset = new Asset();
|
||||
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
|
||||
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
|
||||
|
||||
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
|
||||
if ($avail > 0) {
|
||||
$percent = number_format((($avail / $total_owned) * 100), 0);
|
||||
} else {
|
||||
$percent = 100;
|
||||
}
|
||||
$items_array[$all_count]['id'] = $asset_model->id;
|
||||
$items_array[$all_count]['name'] = $asset_model->name;
|
||||
$items_array[$all_count]['type'] = 'models';
|
||||
$items_array[$all_count]['percent'] = $percent;
|
||||
$items_array[$all_count]['remaining'] = $avail;
|
||||
$items_array[$all_count]['min_amt'] = $asset_model->min_amt;
|
||||
$all_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $items_array;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,22 +161,19 @@ class AccessoriesFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// Display the file inline
|
||||
if (request('inline') == 'true') {
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ class AssetModelsController extends Controller
|
|||
'image',
|
||||
'name',
|
||||
'model_number',
|
||||
'min_amt',
|
||||
'eol',
|
||||
'notes',
|
||||
'created_at',
|
||||
|
@ -52,6 +53,7 @@ class AssetModelsController extends Controller
|
|||
'models.image',
|
||||
'models.name',
|
||||
'model_number',
|
||||
'min_amt',
|
||||
'eol',
|
||||
'requestable',
|
||||
'models.notes',
|
||||
|
|
|
@ -857,6 +857,7 @@ class AssetsController extends Controller
|
|||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->accepted = null;
|
||||
|
@ -876,10 +877,14 @@ class AssetsController extends Controller
|
|||
}
|
||||
|
||||
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||
$originalValues['action_date'] = $checkin_at;
|
||||
}
|
||||
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
|
||||
}
|
||||
|
|
|
@ -75,7 +75,6 @@ class UsersController extends Controller
|
|||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
|
||||
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
|
||||
if ($request->filled('activated')) {
|
||||
|
@ -271,6 +270,8 @@ class UsersController extends Controller
|
|||
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
|
||||
$users = $users->withTrashed();
|
||||
}
|
||||
|
||||
$users = Company::scopeCompanyables($users);
|
||||
|
||||
$total = $users->count();
|
||||
$users = $users->skip($offset)->take($limit)->get();
|
||||
|
|
|
@ -77,6 +77,7 @@ class AssetModelsController extends Controller
|
|||
$model->depreciation_id = $request->input('depreciation_id');
|
||||
$model->name = $request->input('name');
|
||||
$model->model_number = $request->input('model_number');
|
||||
$model->min_amt = $request->input('min_amt');
|
||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||
$model->category_id = $request->input('category_id');
|
||||
$model->notes = $request->input('notes');
|
||||
|
@ -154,6 +155,7 @@ class AssetModelsController extends Controller
|
|||
$model->eol = $request->input('eol');
|
||||
$model->name = $request->input('name');
|
||||
$model->model_number = $request->input('model_number');
|
||||
$model->min_amt = $request->input('min_amt');
|
||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||
$model->category_id = $request->input('category_id');
|
||||
$model->notes = $request->input('notes');
|
||||
|
@ -287,6 +289,7 @@ class AssetModelsController extends Controller
|
|||
return view('models/edit')
|
||||
->with('depreciation_list', Helper::depreciationList())
|
||||
->with('item', $model)
|
||||
->with('model_id', $model_to_clone->id)
|
||||
->with('clone_model', $model_to_clone);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ class AssetModelsFilesController extends Controller
|
|||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($modelId = null, $fileId = null, $download = true)
|
||||
public function show($modelId = null, $fileId = null)
|
||||
{
|
||||
$model = AssetModel::find($modelId);
|
||||
// the asset is valid
|
||||
|
@ -99,12 +99,13 @@ class AssetModelsFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
if ($download != 'true') {
|
||||
if ($contents = file_get_contents(Storage::url($file))) {
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
|
|
@ -68,6 +68,7 @@ class AssetCheckinController extends Controller
|
|||
|
||||
$asset->expected_checkin = null;
|
||||
$asset->last_checkout = null;
|
||||
$asset->last_checkin = now();
|
||||
$asset->assigned_to = null;
|
||||
$asset->assignedTo()->disassociate($asset);
|
||||
$asset->assigned_type = null;
|
||||
|
@ -108,8 +109,11 @@ class AssetCheckinController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
$checkin_at = date('Y-m-d H:i:s');
|
||||
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
|
||||
$originalValues['action_date'] = $checkin_at;
|
||||
$checkin_at = $request->get('checkin_at');
|
||||
}
|
||||
|
||||
|
@ -132,7 +136,7 @@ class AssetCheckinController extends Controller
|
|||
|
||||
// Was the asset updated?
|
||||
if ($asset->save()) {
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
|
||||
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
|
||||
|
||||
if ((isset($user)) && ($backto == 'user')) {
|
||||
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
|
||||
|
|
|
@ -79,7 +79,7 @@ class AssetFilesController extends Controller
|
|||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($assetId = null, $fileId = null, $download = true)
|
||||
public function show($assetId = null, $fileId = null)
|
||||
{
|
||||
$asset = Asset::find($assetId);
|
||||
// the asset is valid
|
||||
|
@ -103,12 +103,13 @@ class AssetFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
if ($download != 'true') {
|
||||
if ($contents = file_get_contents(Storage::url($file))) {
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
|
|
@ -58,8 +58,14 @@ class BulkAssetsController extends Controller
|
|||
switch ($request->input('bulk_actions')) {
|
||||
case 'labels':
|
||||
$this->authorize('view', Asset::class);
|
||||
$assets_found = Asset::find($asset_ids);
|
||||
|
||||
if ($assets_found->isEmpty()){
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
return (new Label)
|
||||
->with('assets', Asset::find($asset_ids))
|
||||
->with('assets', $assets_found)
|
||||
->with('settings', Setting::getSettings())
|
||||
->with('bulkedit', true)
|
||||
->with('count', 0);
|
||||
|
|
|
@ -132,7 +132,7 @@ class ComponentsFilesController extends Controller
|
|||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($componentId = null, $fileId = null, $download = true)
|
||||
public function show($componentId = null, $fileId = null)
|
||||
{
|
||||
\Log::debug('Private filesystem is: '.config('filesystems.default'));
|
||||
$component = Component::find($componentId);
|
||||
|
@ -157,21 +157,17 @@ class ComponentsFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// Display the file inline
|
||||
if (request('inline') == 'true') {
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class ConsumablesFilesController extends Controller
|
|||
* @return \Symfony\Consumable\HttpFoundation\Response
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function show($consumableId = null, $fileId = null, $download = true)
|
||||
public function show($consumableId = null, $fileId = null)
|
||||
{
|
||||
$consumable = Consumable::find($consumableId);
|
||||
|
||||
|
@ -155,22 +155,19 @@ class ConsumablesFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
// Display the file inline
|
||||
if (request('inline') == 'true') {
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,21 +152,19 @@ class LicenseFilesController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
} else {
|
||||
|
||||
if (request('inline') == 'true') {
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
|
||||
return Storage::download($file, $log->filename, $headers);
|
||||
}
|
||||
|
||||
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
|
||||
// won't work, as they're not accessible via the web
|
||||
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
|
||||
return StorageHelper::downloader($file);
|
||||
} else {
|
||||
if ($download != 'true') {
|
||||
\Log::debug('display the file');
|
||||
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
|
||||
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
|
||||
}
|
||||
|
||||
return JsonResponse::create(['error' => 'Failed validation: '], 500);
|
||||
}
|
||||
|
||||
return StorageHelper::downloader($file);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -545,6 +545,10 @@ class ReportsController extends Controller
|
|||
$header[] = trans('admin/hardware/table.checkout_date');
|
||||
}
|
||||
|
||||
if ($request->filled('checkin_date')) {
|
||||
$header[] = trans('admin/hardware/table.last_checkin_date');
|
||||
}
|
||||
|
||||
if ($request->filled('expected_checkin')) {
|
||||
$header[] = trans('admin/hardware/form.expected_checkin');
|
||||
}
|
||||
|
@ -651,6 +655,14 @@ class ReportsController extends Controller
|
|||
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
|
||||
}
|
||||
|
||||
if (($request->filled('checkin_date_start'))) {
|
||||
$assets->whereBetween('last_checkin', [
|
||||
Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
|
||||
// use today's date is `checkin_date_end` is not provided
|
||||
Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
|
||||
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
|
||||
}
|
||||
|
@ -835,6 +847,12 @@ class ReportsController extends Controller
|
|||
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
|
||||
}
|
||||
|
||||
if ($request->filled('checkin_date')) {
|
||||
$row[] = ($asset->last_checkin)
|
||||
? Carbon::parse($asset->last_checkin)->format('Y-m-d')
|
||||
: '';
|
||||
}
|
||||
|
||||
if ($request->filled('expected_checkin')) {
|
||||
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
|
||||
}
|
||||
|
@ -1003,7 +1021,12 @@ class ReportsController extends Controller
|
|||
|
||||
$assetsForReport = $acceptances
|
||||
->filter(function ($acceptance) {
|
||||
return $acceptance->checkoutable_type == 'App\Models\Asset';
|
||||
$acceptance_checkoutable_flag = false;
|
||||
if ($acceptance->checkoutable){
|
||||
$acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser();
|
||||
}
|
||||
|
||||
return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag;
|
||||
})
|
||||
->map(function($acceptance) {
|
||||
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Users;
|
||||
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssetFileRequest;
|
||||
use App\Models\Actionlog;
|
||||
|
@ -139,18 +140,25 @@ class UserFilesController extends Controller
|
|||
|
||||
// the license is valid
|
||||
if (isset($user->id)) {
|
||||
|
||||
$this->authorize('view', $user);
|
||||
|
||||
$log = Actionlog::find($fileId);
|
||||
$file = $log->get_src('users');
|
||||
|
||||
return Response::download($file); //FIXME this doesn't use the new StorageHelper yet, but it's complicated...
|
||||
// Display the file inline
|
||||
if (request('inline') == 'true') {
|
||||
$headers = [
|
||||
'Content-Disposition' => 'inline',
|
||||
];
|
||||
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
|
||||
}
|
||||
|
||||
return Storage::download('private_uploads/users/'.$log->filename);
|
||||
|
||||
}
|
||||
// Prepare the error message
|
||||
$error = trans('admin/users/message.user_not_found', ['id' => $userId]);
|
||||
|
||||
// Redirect to the licence management page
|
||||
return redirect()->route('users.index')->with('error', $error);
|
||||
// Redirect to the user management page if the user doesn't exist
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class SaveUserRequest extends FormRequest
|
|||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'manager_id' => 'nullable|exists:users,id',
|
||||
];
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace App\Http\Transformers;
|
|||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Company;
|
||||
use App\Models\Supplier;
|
||||
|
@ -42,6 +43,7 @@ class ActionlogsTransformer
|
|||
public function transformActionlog (Actionlog $actionlog, $settings = null)
|
||||
{
|
||||
$icon = $actionlog->present()->icon();
|
||||
$custom_field = CustomField::all();
|
||||
if ($actionlog->filename!='') {
|
||||
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
|
||||
}
|
||||
|
@ -54,11 +56,19 @@ class ActionlogsTransformer
|
|||
|
||||
if ($meta_array) {
|
||||
foreach ($meta_array as $fieldname => $fieldata) {
|
||||
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
|
||||
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
|
||||
if( str_starts_with($fieldname, '_snipeit_')){
|
||||
if( $custom_field->where('db_column', '=', $fieldname)->where('field_encrypted', true)){
|
||||
$clean_meta[$fieldname]['old'] = "encrypted";
|
||||
$clean_meta[$fieldname]['new'] = "encrypted";
|
||||
}
|
||||
}
|
||||
else {
|
||||
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
|
||||
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$clean_meta= $this->changedInfo($clean_meta);
|
||||
}
|
||||
|
||||
|
@ -123,6 +133,9 @@ class ActionlogsTransformer
|
|||
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
|
||||
];
|
||||
|
||||
// \Log::info("Clean Meta is: ".print_r($clean_meta,true));
|
||||
//dd($array);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
@ -143,39 +156,70 @@ class ActionlogsTransformer
|
|||
* @param array $clean_meta
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function changedInfo(array $clean_meta)
|
||||
{
|
||||
{ $location = Location::withTrashed()->get();
|
||||
$supplier = Supplier::withTrashed()->get();
|
||||
$model = AssetModel::withTrashed()->get();
|
||||
$company = Company::get();
|
||||
|
||||
|
||||
if(array_key_exists('rtd_location_id',$clean_meta)) {
|
||||
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". Location::find($clean_meta['rtd_location_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". Location::find($clean_meta['rtd_location_id']['new'])->name : trans('general.unassigned');
|
||||
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $location->find($clean_meta['rtd_location_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $location->find($clean_meta['rtd_location_id']['new'])->name : trans('general.unassigned');
|
||||
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
|
||||
unset($clean_meta['rtd_location_id']);
|
||||
}
|
||||
if(array_key_exists('location_id', $clean_meta)) {
|
||||
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ".Location::find($clean_meta['location_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ".Location::find($clean_meta['location_id']['new'])->name : trans('general.unassigned');
|
||||
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ".$location->find($clean_meta['location_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ".$location->find($clean_meta['location_id']['new'])->name : trans('general.unassigned');
|
||||
$clean_meta['Current Location'] = $clean_meta['location_id'];
|
||||
unset($clean_meta['location_id']);
|
||||
}
|
||||
if(array_key_exists('model_id', $clean_meta)) {
|
||||
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".AssetModel::withTrashed()->find($clean_meta['model_id']['old'])->name;
|
||||
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".AssetModel::withTrashed()->find($clean_meta['model_id']['new'])->name; /* model is required at asset creation */
|
||||
|
||||
$oldModel = $model->find($clean_meta['model_id']['old']);
|
||||
$oldModelName = $oldModel->name ?? trans('admin/models/message.deleted');
|
||||
|
||||
$newModel = $model->find($clean_meta['model_id']['new']);
|
||||
$newModelName = $newModel->name ?? trans('admin/models/message.deleted');
|
||||
|
||||
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
|
||||
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
|
||||
|
||||
$clean_meta['Model'] = $clean_meta['model_id'];
|
||||
unset($clean_meta['model_id']);
|
||||
}
|
||||
if(array_key_exists('company_id', $clean_meta)) {
|
||||
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."]".Company::find($clean_meta['company_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ".Company::find($clean_meta['company_id']['new'])->name : trans('general.unassigned');
|
||||
|
||||
$oldCompany = $company->find($clean_meta['company_id']['old']);
|
||||
$oldCompanyName = $oldCompany->name ?? trans('admin/companies/message.deleted');
|
||||
|
||||
$newCompany = $company->find($clean_meta['company_id']['new']);
|
||||
$newCompanyName = $newCompany->name ?? trans('admin/companies/message.deleted');
|
||||
|
||||
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
|
||||
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
|
||||
$clean_meta['Company'] = $clean_meta['company_id'];
|
||||
unset($clean_meta['company_id']);
|
||||
}
|
||||
if(array_key_exists('supplier_id', $clean_meta)) {
|
||||
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ".Supplier::find($clean_meta['supplier_id']['old'])->name : trans('general.unassigned');
|
||||
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ".Supplier::find($clean_meta['supplier_id']['new'])->name : trans('general.unassigned');
|
||||
|
||||
$oldSupplier = $supplier->find($clean_meta['supplier_id']['old']);
|
||||
$oldSupplierName = $oldSupplier->name ?? trans('admin/suppliers/message.deleted');
|
||||
|
||||
$newSupplier = $supplier->find($clean_meta['supplier_id']['new']);
|
||||
$newSupplierName = $newSupplier->name ?? trans('admin/suppliers/message.deleted');
|
||||
|
||||
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
|
||||
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
|
||||
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
|
||||
unset($clean_meta['supplier_id']);
|
||||
}
|
||||
if(array_key_exists('asset_eol_date', $clean_meta)) {
|
||||
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
|
||||
unset($clean_meta['asset_eol_date']);
|
||||
}
|
||||
|
||||
return $clean_meta;
|
||||
|
||||
|
@ -183,4 +227,4 @@ class ActionlogsTransformer
|
|||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ class AssetModelsTransformer
|
|||
] : null,
|
||||
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
||||
'model_number' => e($assetmodel->model_number),
|
||||
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
|
||||
'depreciation' => ($assetmodel->depreciation) ? [
|
||||
'id' => (int) $assetmodel->depreciation->id,
|
||||
'name'=> e($assetmodel->depreciation->name),
|
||||
|
|
|
@ -33,7 +33,7 @@ class LogListener
|
|||
*/
|
||||
public function onCheckoutableCheckedIn(CheckoutableCheckedIn $event)
|
||||
{
|
||||
$event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date);
|
||||
$event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date, $event->originalValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,7 @@ class LogListener
|
|||
*/
|
||||
public function onCheckoutableCheckedOut(CheckoutableCheckedOut $event)
|
||||
{
|
||||
$event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout);
|
||||
$event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout, $event->originalValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,6 +87,7 @@ class Asset extends Depreciable
|
|||
protected $casts = [
|
||||
'purchase_date' => 'date',
|
||||
'last_checkout' => 'datetime',
|
||||
'last_checkin' => 'datetime',
|
||||
'expected_checkin' => 'date',
|
||||
'last_audit_date' => 'datetime',
|
||||
'next_audit_date' => 'date',
|
||||
|
@ -96,7 +97,6 @@ class Asset extends Depreciable
|
|||
'location_id' => 'integer',
|
||||
'rtd_company_id' => 'integer',
|
||||
'supplier_id' => 'integer',
|
||||
'byod' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
|
@ -119,6 +119,7 @@ class Asset extends Depreciable
|
|||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'supplier_id' => 'exists:suppliers,id|nullable',
|
||||
'asset_eol_date' => 'date|max:10|min:10|nullable',
|
||||
'byod' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -312,6 +313,13 @@ class Asset extends Depreciable
|
|||
}
|
||||
}
|
||||
|
||||
$originalValues = $this->getRawOriginal();
|
||||
|
||||
// attempt to detect change in value if different from today's date
|
||||
if ($checkout_at && strpos($checkout_at, date('Y-m-d')) === false) {
|
||||
$originalValues['action_date'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if ($this->save()) {
|
||||
if (is_int($admin)) {
|
||||
$checkedOutBy = User::findOrFail($admin);
|
||||
|
@ -320,7 +328,7 @@ class Asset extends Depreciable
|
|||
} else {
|
||||
$checkedOutBy = Auth::user();
|
||||
}
|
||||
event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note));
|
||||
event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note, $originalValues));
|
||||
|
||||
$this->increment('checkout_counter', 1);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ class AssetModel extends SnipeModel
|
|||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255',
|
||||
'model_number' => 'max:255|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||
'eol' => 'integer:min:0|max:240|nullable',
|
||||
|
@ -65,6 +66,7 @@ class AssetModel extends SnipeModel
|
|||
'fieldset_id',
|
||||
'image',
|
||||
'manufacturer_id',
|
||||
'min_amt',
|
||||
'model_number',
|
||||
'name',
|
||||
'notes',
|
||||
|
|
|
@ -323,7 +323,10 @@ class License extends Depreciable
|
|||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
return $this->category->checkin_email;
|
||||
if ($this->category) {
|
||||
return $this->category->checkin_email;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -335,7 +338,11 @@ class License extends Depreciable
|
|||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->category->require_acceptance;
|
||||
if ($this->category) {
|
||||
return $this->category->require_acceptance;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -348,14 +355,16 @@ class License extends Depreciable
|
|||
*/
|
||||
public function getEula()
|
||||
{
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ($this->category->use_default_eula == '1') {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return false;
|
||||
if ($this->category){
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ($this->category->use_default_eula == '1') {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,10 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
|
|||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->license->category->require_acceptance;
|
||||
if ($this->license && $this->license->category) {
|
||||
return $this->license->category->require_acceptance;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEula()
|
||||
|
|
|
@ -23,7 +23,7 @@ trait Loggable
|
|||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logCheckout($note, $target, $action_date = null)
|
||||
public function logCheckout($note, $target, $action_date = null, $originalValues = [])
|
||||
{
|
||||
$log = new Actionlog;
|
||||
$log = $this->determineLogItemType($log);
|
||||
|
@ -62,6 +62,23 @@ trait Loggable
|
|||
$log->action_date = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
|
||||
|
||||
foreach ($originalValues as $key => $value) {
|
||||
if ($key == 'action_date' && $value != $action_date) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
|
||||
} elseif ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
$log->logaction('checkout');
|
||||
|
||||
return $log;
|
||||
|
@ -89,7 +106,7 @@ trait Loggable
|
|||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logCheckin($target, $note, $action_date = null)
|
||||
public function logCheckin($target, $note, $action_date = null, $originalValues = [])
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$log = new Actionlog;
|
||||
|
@ -114,13 +131,9 @@ trait Loggable
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
$log->location_id = null;
|
||||
$log->note = $note;
|
||||
$log->action_date = $action_date;
|
||||
if (! $log->action_date) {
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if (! $log->action_date) {
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
|
@ -130,6 +143,23 @@ trait Loggable
|
|||
$log->user_id = Auth::user()->id;
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
|
||||
|
||||
foreach ($originalValues as $key => $value) {
|
||||
if ($key == 'action_date' && $value != $action_date) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
|
||||
} elseif ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
$log->logaction('checkin from');
|
||||
|
||||
// $params = [
|
||||
|
|
|
@ -69,15 +69,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
|||
];
|
||||
|
||||
protected $casts = [
|
||||
'activated' => 'boolean',
|
||||
'manager_id' => 'integer',
|
||||
'location_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
'vip' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'autoassign_licenses' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -103,6 +100,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
|||
'state' => 'min:2|max:191|nullable',
|
||||
'country' => 'min:2|max:191|nullable',
|
||||
'zip' => 'max:10|nullable',
|
||||
'vip' => 'boolean',
|
||||
'remote' => 'boolean',
|
||||
'activated' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -750,4 +750,26 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
|||
{
|
||||
return $this->locale;
|
||||
}
|
||||
public function getUserTotalCost(){
|
||||
$asset_cost= 0;
|
||||
$license_cost= 0;
|
||||
$accessory_cost= 0;
|
||||
foreach ($this->assets as $asset){
|
||||
$asset_cost += $asset->purchase_cost;
|
||||
$this->asset_cost = $asset_cost;
|
||||
}
|
||||
foreach ($this->licenses as $license){
|
||||
$license_cost += $license->purchase_cost;
|
||||
$this->license_cost = $license_cost;
|
||||
}
|
||||
foreach ($this->accessories as $accessory){
|
||||
$accessory_cost += $accessory->purchase_cost;
|
||||
$this->accessory_cost = $accessory_cost;
|
||||
}
|
||||
|
||||
$this->total_user_cost = ($asset_cost + $accessory_cost + $license_cost);
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,14 @@ class AssetModelPresenter extends Presenter
|
|||
'title' => trans('admin/models/table.modelnumber'),
|
||||
'visible' => true,
|
||||
],
|
||||
[
|
||||
'field' => 'min_amt',
|
||||
'searchable' => false,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('mail.min_QTY'),
|
||||
'visible' => true,
|
||||
],
|
||||
[
|
||||
'field' => 'assets_count',
|
||||
'searchable' => false,
|
||||
|
|
|
@ -556,8 +556,10 @@ class AssetPresenter extends Presenter
|
|||
public function dynamicWarrantyUrl()
|
||||
{
|
||||
$warranty_lookup_url = $this->model->model->manufacturer->warranty_lookup_url;
|
||||
$url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale,$warranty_lookup_url));
|
||||
$url = (str_replace('{SERIAL}',$this->model->serial,$url));
|
||||
$url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale, $warranty_lookup_url));
|
||||
$url = (str_replace('{SERIAL}', urlencode($this->model->serial), $url));
|
||||
$url = (str_replace('{MODEL_NAME}', urlencode($this->model->model->name), $url));
|
||||
$url = (str_replace('{MODEL_NUMBER}', urlencode($this->model->model->model_number), $url));
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
|
|
@ -75,12 +75,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
// Only load rollbar if there is a rollbar key and the app is in production
|
||||
if (($this->app->environment('production')) && (config('logging.channels.rollbar.access_token'))) {
|
||||
$this->app->register(\Rollbar\Laravel\RollbarServiceProvider::class);
|
||||
}
|
||||
|
||||
// Only load dusk's service provider if the app is in local or develop mode
|
||||
if ($this->app->environment(['local', 'develop'])) {
|
||||
$this->app->register(\Laravel\Dusk\DuskServiceProvider::class);
|
||||
}
|
||||
}
|
||||
|
||||
$this->app->singleton('ArieTimmerman\Laravel\SCIMServer\SCIMConfig', SnipeSCIMConfig::class); // this overrides the default SCIM configuration with our own
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
"require-dev": {
|
||||
"brianium/paratest": "^v6.4.4",
|
||||
"fakerphp/faker": "^1.16",
|
||||
"laravel/dusk": "^v7.9.0",
|
||||
"mockery/mockery": "^1.4",
|
||||
"nunomaduro/larastan": "^2.0",
|
||||
"nunomaduro/phpinsights": "^2.7",
|
||||
|
@ -94,7 +93,6 @@
|
|||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/DuskTestCase.php",
|
||||
"tests/TestCase.php"
|
||||
],
|
||||
"psr-4": {
|
||||
|
|
505
composer.lock
generated
505
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -69,7 +69,7 @@ return [
|
|||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'port' => env('DB_PORT', 3306),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
return array (
|
||||
'app_version' => 'v6.1.2',
|
||||
'full_app_version' => 'v6.1.2 - build 10938-g32747cafd',
|
||||
'build_version' => '10938',
|
||||
'app_version' => 'v6.2.0-pre',
|
||||
'full_app_version' => 'v6.2.0-pre - build 11391-g319cb2305',
|
||||
'build_version' => '11391',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'g32747cafd',
|
||||
'full_hash' => 'v6.1.2-89-g32747cafd',
|
||||
'hash_version' => 'g319cb2305',
|
||||
'full_hash' => 'v6.2.0-pre-451-g319cb2305',
|
||||
'branch' => 'develop',
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddLastCheckinToAssets extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('assets', function (Blueprint $table) {
|
||||
$table->dateTime('last_checkin')->after('last_checkout')->nullable();
|
||||
});
|
||||
|
||||
DB::statement(
|
||||
"UPDATE " . DB::getTablePrefix() . "assets SET last_checkin=(SELECT MAX(" . DB::getTablePrefix() . "action_logs.action_date) FROM " . DB::getTablePrefix() . "action_logs WHERE item_type='App\\\Models\\\Asset' AND " . DB::getTablePrefix() . "action_logs.item_id=" . DB::getTablePrefix() . "assets.id AND " . DB::getTablePrefix() . "action_logs.action_type='checkin from')"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('assets', function (Blueprint $table) {
|
||||
$table->dropColumn('last_checkin');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddMinAmtToModelsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('models', function (Blueprint $table) {
|
||||
$table->integer('min_amt')->after('model_number')->default(null);;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('models', function (Blueprint $table) {
|
||||
$table->dropColumn('min_amt');
|
||||
});
|
||||
}
|
||||
}
|
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -5,7 +5,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"acorn": "^8.9.0",
|
||||
"acorn-import-assertions": "^1.9.0",
|
||||
"admin-lte": "^2.4.18",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"jquery-ui-bundle": "^1.12.1",
|
||||
"jquery.iframe-transport": "^1.0.0",
|
||||
"jspdf-autotable": "^3.5.30",
|
||||
"less": "^4.1.2",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^6.0",
|
||||
"list.js": "^1.5.0",
|
||||
"morris.js": "github:morrisjs/morris.js",
|
||||
|
@ -1810,9 +1810,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "6.4.0",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
|
||||
"integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -2620,9 +2621,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.12.3",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.3.tgz",
|
||||
"integrity": "sha512-fLz2dfYQ3xCk7Ip8LiIpV2W+9brUyex2TAE7Z0BCvZdUDklJE+n+a8gCgLWzfZ0GzZNZu7HUP8Z0z6Xbm6fsSA==",
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.0.tgz",
|
||||
"integrity": "sha512-7FYR1Yz3evIjlJD1mZ3SYWSw+jlOmQGeQ1QiSufSQ6J84XMQFkzxm6OobiZ928SfqhGdoIp2SsABNsS4rXMMJw==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
|
@ -7457,8 +7458,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/less": {
|
||||
"version": "4.1.3",
|
||||
"license": "Apache-2.0",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
|
||||
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
||||
"dependencies": {
|
||||
"copy-anything": "^2.0.1",
|
||||
"parse-node-version": "^1.0.1",
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"vue-template-compiler": "2.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"acorn": "^8.9.0",
|
||||
"acorn-import-assertions": "^1.9.0",
|
||||
"admin-lte": "^2.4.18",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"jquery-ui-bundle": "^1.12.1",
|
||||
"jquery.iframe-transport": "^1.0.0",
|
||||
"jspdf-autotable": "^3.5.30",
|
||||
"less": "^4.1.2",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^6.0",
|
||||
"list.js": "^1.5.0",
|
||||
"morris.js": "github:morrisjs/morris.js",
|
||||
|
|
BIN
public/css/dist/all.css
vendored
BIN
public/css/dist/all.css
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/dist/all-defer.js
vendored
BIN
public/js/dist/all-defer.js
vendored
Binary file not shown.
|
@ -18,22 +18,22 @@
|
|||
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
|
||||
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
|
||||
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=c5f8982eb14ba2c7e0c0fec60519afc6",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=829410b57e28448fef5c57beda0861e3",
|
||||
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=e2e2b1797606a266ed55549f5bb5a179",
|
||||
"/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=fe912d1c4a7e0e1db87a64eb7e54c945",
|
||||
"/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=31a6b5ecfc8d018d0e3a294f0c80e9e9",
|
||||
"/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=bf8eabe300a00a3adb0293596987abc4",
|
||||
"/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=cd687455c6d6c058e2e9f84f409e2965",
|
||||
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=eea38615e7b5dbbaf88c263f2230cc32",
|
||||
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=6ebbf5afc34f54463abc2b81ca637364",
|
||||
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=67b8a78b7e80e805cfa4ee0421895ba4",
|
||||
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=a656b2d865fe379d8851757e8e4001ef",
|
||||
"/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=a6761c03f9c021aeb9754283755b660e",
|
||||
"/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=75a44676b684c784508452a53867e8da",
|
||||
"/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=c612044da7f7b81ab8f2cadf94964208",
|
||||
"/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=3867ccc7ab681d3c27d1e97a792b46d3",
|
||||
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=7f63d634454e771396bce3e09dfcdbc5",
|
||||
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=70ad875b2378eb850254f01dec991ade",
|
||||
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=d36941873b661076f146b0221f13497d",
|
||||
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=281bcfe26549412d128f695234961081",
|
||||
"/js/build/vendor.js": "/js/build/vendor.js?id=8ac1d250496313e93744790e5138305d",
|
||||
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=df78f0c4cc93c29c02a41144590f6350",
|
||||
"/js/dist/all.js": "/js/dist/all.js?id=b487452bd6bef7fa6201586415d383f2",
|
||||
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7829a391ab2f89926465398bebb7df8d",
|
||||
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=07e52318da2cdf3171c4d88113f25fb6",
|
||||
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=44f9320d0739f419c9246f7f39395b02",
|
||||
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
return [
|
||||
'does_not_exist' => 'Company does not exist.',
|
||||
'deleted' => 'Deleted company',
|
||||
'assoc_users' => 'This company is currently associated with at least one model and cannot be deleted. Please update your models to no longer reference this company and try again. ',
|
||||
'create' => [
|
||||
'error' => 'Company was not created, please try again.',
|
||||
|
|
|
@ -14,6 +14,7 @@ return [
|
|||
'dl_csv' => 'Download CSV',
|
||||
'eol' => 'EOL',
|
||||
'id' => 'ID',
|
||||
'last_checkin_date' => 'Last Checkin Date',
|
||||
'location' => 'Location',
|
||||
'purchase_cost' => 'Cost',
|
||||
'purchase_date' => 'Purchased',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
return array(
|
||||
|
||||
'support_url_help' => 'Use <code>{LOCALE}</code> and <code>{SERIAL}</code> in your URL as variables to have those values auto-populate when viewing assets.',
|
||||
'support_url_help' => 'Variables <code>{LOCALE}</code>, <code>{SERIAL}</code>, <code>{MODEL_NUMBER}</code>, and <code>{MODEL_NAME}</code> may be used in your URL to have those values auto-populate when viewing assets - for example https://support.apple.com/{LOCALE}/{SERIAL}.',
|
||||
'does_not_exist' => 'Manufacturer does not exist.',
|
||||
'assoc_users' => 'This manufacturer is currently associated with at least one model and cannot be deleted. Please update your models to no longer reference this manufacturer and try again. ',
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
return array(
|
||||
|
||||
'deleted' => 'Deleted asset model',
|
||||
'does_not_exist' => 'Model does not exist.',
|
||||
'no_association' => 'WARNING! The asset model for this item is invalid or missing!',
|
||||
'no_association_fix' => 'This will break things in weird and horrible ways. Edit this asset now to assign it a model.',
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
return array(
|
||||
|
||||
'deleted' => 'Deleted supplier',
|
||||
'does_not_exist' => 'Supplier does not exist.',
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ return array(
|
|||
'show_deleted' => 'Show Deleted Users',
|
||||
'title' => 'Title',
|
||||
'to_restore_them' => 'to restore them.',
|
||||
'total_assets_cost' => "Total Assets Cost",
|
||||
'updateuser' => 'Update User',
|
||||
'username' => 'Username',
|
||||
'user_deleted_text' => 'This user has been marked as deleted.',
|
||||
|
|
|
@ -210,10 +210,15 @@
|
|||
</td>
|
||||
<td>
|
||||
@if ($file->filename)
|
||||
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id, 'download' => 'true']) }}" class="btn btn-default">
|
||||
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.download') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $file->created_at }}</td>
|
||||
|
|
|
@ -203,10 +203,15 @@
|
|||
</td>
|
||||
<td>
|
||||
@if ($file->filename)
|
||||
<a href="{{ route('show.componentfile', [$component->id, $file->id, 'download' => 'true']) }}" class="btn btn-default">
|
||||
<a href="{{ route('show.componentfile', [$component->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.download') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show.componentfile', [$component->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $file->created_at }}</td>
|
||||
|
|
|
@ -160,10 +160,14 @@
|
|||
</td>
|
||||
<td>
|
||||
@if ($file->filename)
|
||||
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id, 'download' => 'true']) }}" class="btn btn-default">
|
||||
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.download') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $file->created_at }}</td>
|
||||
|
|
|
@ -1266,9 +1266,13 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/assets/'.$file->filename)))
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id]) }}" class="btn btn-default">
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'download'=>'true']) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
|
@ -1363,9 +1367,14 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-default">
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -474,10 +474,15 @@
|
|||
</td>
|
||||
<td>
|
||||
@if ($file->filename)
|
||||
<a href="{{ route('show.licensefile', [$license->id, $file->id, 'download' => 'true']) }}" class="btn btn-default">
|
||||
<a href="{{ route('show.licensefile', [$license->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.download') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show.licensefile', [$license->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $file->created_at }}</td>
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
<div class="modal-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-12">
|
||||
|
||||
<label class="btn btn-default">
|
||||
{{ trans('button.select_file') }}
|
||||
<label class="btn btn-default btn-block">
|
||||
{{ trans('button.select_files') }}
|
||||
<input type="file" name="file[]" multiple="true" class="js-uploadFile" id="uploadFile" data-maxsize="{{ Helper::file_upload_max_size() }}" accept="image/*,.csv,.zip,.rar,.doc,.docx,.xls,.xlsx,.xml,.lic,.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,text/plain,.pdf,application/rtf,application/json" style="display:none" required>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-12">
|
||||
<span id="uploadFile-info"></span>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
|
||||
@include ('partials.forms.edit.model_number')
|
||||
@include ('partials.forms.edit.depreciation')
|
||||
@include ('partials.forms.edit.minimum_quantity')
|
||||
|
||||
<!-- EOL -->
|
||||
|
||||
|
@ -34,7 +35,8 @@
|
|||
</div>
|
||||
|
||||
<!-- Custom Fieldset -->
|
||||
@livewire('custom-field-set-default-values-for-model',["model_id" => $item->id])
|
||||
<!-- If $item->id is null we are cloning the model and we need the $model_id variable -->
|
||||
@livewire('custom-field-set-default-values-for-model',["model_id" => $item->id ?? $model_id ?? null ])
|
||||
|
||||
@include ('partials.forms.edit.notes')
|
||||
@include ('partials.forms.edit.requestable', ['requestable_text' => trans('admin/models/general.requestable')])
|
||||
|
|
|
@ -168,9 +168,14 @@
|
|||
</td>
|
||||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
|
||||
<a href="{{ route('show/modelfile', [$model->id, $file->id]) }}" class="btn btn-default">
|
||||
<a href="{{ route('show/modelfile', [$model->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/modelfile', [$model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -141,6 +141,11 @@
|
|||
{{ trans('admin/hardware/table.checkout_date') }}
|
||||
</label>
|
||||
|
||||
<label class="form-control">
|
||||
{{ Form::checkbox('checkin_date', '1', '1') }}
|
||||
{{ trans('admin/hardware/table.last_checkin_date') }}
|
||||
</label>
|
||||
|
||||
<label class="form-control">
|
||||
{{ Form::checkbox('expected_checkin', '1', '1') }}
|
||||
{{ trans('admin/hardware/form.expected_checkin') }}
|
||||
|
@ -289,6 +294,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Last Checkin Date -->
|
||||
<div class="form-group checkin-range">
|
||||
<label for="checkin_date" class="col-md-3 control-label">{{ trans('admin/hardware/table.last_checkin_date') }}</label>
|
||||
<div class="input-daterange input-group col-md-6" id="datepicker">
|
||||
<input type="text" class="form-control" name="checkin_date_start" aria-label="checkin_date_start">
|
||||
<span class="input-group-addon">{{ strtolower(trans('general.to')) }}</span>
|
||||
<input type="text" class="form-control" name="checkin_date_end" aria-label="checkin_date_end">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expected Checkin Date -->
|
||||
<div class="form-group expected_checkin-range">
|
||||
<label for="expected_checkin_start" class="col-md-3 control-label">{{ trans('admin/hardware/form.expected_checkin') }}</label>
|
||||
|
@ -381,6 +396,13 @@
|
|||
format: 'yyyy-mm-dd'
|
||||
});
|
||||
|
||||
$('.checkin-range .input-daterange').datepicker({
|
||||
clearBtn: true,
|
||||
todayHighlight: true,
|
||||
endDate: '0d',
|
||||
format: 'yyyy-mm-dd'
|
||||
});
|
||||
|
||||
$('.expected_checkin-range .input-daterange').datepicker({
|
||||
clearBtn: true,
|
||||
todayHighlight: true,
|
||||
|
|
|
@ -18,6 +18,15 @@
|
|||
.checkbox label {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
Don't make the password field *look* readonly - this is for usability, so admins don't think they can't edit this field.
|
||||
*/
|
||||
.form-control[readonly] {
|
||||
background-color: white;
|
||||
color: #555555;
|
||||
cursor:text;
|
||||
}
|
||||
</style>
|
||||
|
||||
@if ((!function_exists('ldap_connect')) || (!function_exists('ldap_set_option')) || (!function_exists('ldap_bind')))
|
||||
|
@ -34,10 +43,12 @@
|
|||
@endif
|
||||
|
||||
|
||||
{{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'false', 'class' => 'form-horizontal', 'role' => 'form']) }}
|
||||
{{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form']) }}
|
||||
<!-- CSRF Token -->
|
||||
{{csrf_field()}}
|
||||
|
||||
<input type="hidden" name="username" value="{{ Request::old('username', $user->username) }}">
|
||||
|
||||
<!-- this is a hack to prevent Chrome from trying to autocomplete fields -->
|
||||
<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="display:none;" />
|
||||
<input type="password" name="password_fake" id="password_fake" value="" style="display:none;" />
|
||||
|
@ -54,7 +65,6 @@
|
|||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
|
||||
<div class="col-md-11 col-md-offset-1">
|
||||
|
||||
<!-- Enable LDAP -->
|
||||
|
@ -230,7 +240,7 @@
|
|||
{{ Form::label('ldap_uname', trans('admin/settings/general.ldap_uname')) }}
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{{ Form::text('ldap_uname', Request::old('ldap_uname', $setting->ldap_uname), ['class' => 'form-control','placeholder' => trans('general.example') .'binduser@example.com', $setting->demoMode]) }}
|
||||
{{ Form::text('ldap_uname', Request::old('ldap_uname', $setting->ldap_uname), ['class' => 'form-control','autocomplete' => 'off', 'placeholder' => trans('general.example') .'binduser@example.com', $setting->demoMode]) }}
|
||||
{!! $errors->first('ldap_uname', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
|
||||
@if (config('app.lock_passwords')===true)
|
||||
<p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p>
|
||||
|
@ -244,7 +254,7 @@
|
|||
{{ Form::label('ldap_pword', trans('admin/settings/general.ldap_pword')) }}
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{{ Form::password('ldap_pword', ['class' => 'form-control','placeholder' => trans('general.example') .' binduserpassword', $setting->demoMode]) }}
|
||||
{{ Form::password('ldap_pword', ['class' => 'form-control', 'autocomplete' => 'off', 'onfocus' => "this.removeAttribute('readonly');", $setting->demoMode, ' readonly']) }}
|
||||
{!! $errors->first('ldap_pword', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
|
||||
@if (config('app.lock_passwords')===true)
|
||||
<p class="text-warning"><i class="fas fa-lock" aria-hidden="true"></i> {{ trans('general.feature_disabled') }}</p>
|
||||
|
@ -538,7 +548,7 @@
|
|||
<input type="text" name="ldaptest_user" id="ldaptest_user" class="form-control" placeholder="LDAP username">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="password" name="ldaptest_password" id="ldaptest_password" class="form-control" placeholder="LDAP password">
|
||||
<input type="password" name="ldaptest_password" id="ldaptest_password" class="form-control" placeholder="LDAP password" autocomplete="off" readonly onfocus="this.removeAttribute('readonly');">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a class="btn btn-default btn-sm" id="ldaptestlogin" style="margin-right: 10px;">{{ trans('admin/settings/general.ldap_test') }}</a>
|
||||
|
|
|
@ -638,6 +638,27 @@
|
|||
|
||||
</div>
|
||||
@endif
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-3">
|
||||
{{ trans('admin/users/table.total_assets_cost') }}
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
{{Helper::formatCurrencyOutput($user->getUserTotalCost()->total_user_cost)}}
|
||||
<a id="optional_info" class="text-primary">
|
||||
<i class="fa fa-caret-right fa-2x" id="optional_info_icon"></i>
|
||||
<strong>{{ trans('admin/hardware/form.optional_infos') }}</strong>
|
||||
</a>
|
||||
</div>
|
||||
<div id="optional_details" class="col-md-12" style="display:none">
|
||||
<div class="col-md-3" style="border-top:none;"></div>
|
||||
<div class="col-md-9" style="border-top:none;">
|
||||
{{trans('general.assets').': '. Helper::formatCurrencyOutput($user->getUserTotalCost()->asset_cost)}}<br>
|
||||
{{trans('general.licenses').': '. Helper::formatCurrencyOutput($user->getUserTotalCost()->license_cost)}}<br>
|
||||
{{trans('general.accessories').': '.Helper::formatCurrencyOutput($user->getUserTotalCost()->accessory_cost)}}<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!--/end striped container-->
|
||||
</div> <!-- end col-md-9 -->
|
||||
|
@ -892,7 +913,7 @@
|
|||
<td>
|
||||
@if (($file->filename) && (Storage::exists('private_uploads/users/'.$file->filename)))
|
||||
@if (Helper::checkUploadIsImage($file->get_src('users')))
|
||||
<a href="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', ['userId' => $user->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
|
||||
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="img-thumbnail" style="max-width: 50px;"></a>
|
||||
@else
|
||||
{{ trans('general.preview_not_available') }}
|
||||
@endif
|
||||
|
@ -916,10 +937,14 @@
|
|||
<td>
|
||||
@if ($file->filename)
|
||||
@if (Storage::exists('private_uploads/users/'.$file->filename))
|
||||
<a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-default">
|
||||
<a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-sm btn-default">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ trans('general.download') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||
</a>
|
||||
@endif
|
||||
@endif
|
||||
</td>
|
||||
|
@ -1109,6 +1134,12 @@ $(function () {
|
|||
|
||||
}
|
||||
});
|
||||
$("#optional_info").on("click",function(){
|
||||
$('#optional_details').fadeToggle(100);
|
||||
$('#optional_info_icon').toggleClass('fa-caret-right fa-caret-down');
|
||||
var optional_info_open = $('#optional_info_icon').hasClass('fa-caret-down');
|
||||
document.cookie = "optional_info_open="+optional_info_open+'; path=/';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class LoginTest extends DuskTestCase
|
||||
{
|
||||
/**
|
||||
* Test login
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testLoginPageLoadsAndUserCanLogin()
|
||||
{
|
||||
// Create a new user
|
||||
$user = User::factory()->make();
|
||||
|
||||
// We override the existing password to use a hash of one we know
|
||||
$user->password = '$2y$10$8o5W8fgAKJbN3Kz4taepeeRVgKsG8pkZ1L4eJfdEKrn2mgI/JgCJy';
|
||||
|
||||
// We want a user that is a superuser
|
||||
$user->permissions = '{"superuser": 1}';
|
||||
|
||||
$user->save();
|
||||
|
||||
Setting::factory()->create();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visitRoute('login')
|
||||
->assertSee(trans('auth/general.login_prompt'));
|
||||
});
|
||||
|
||||
$this->browse(function ($browser) use ($user) {
|
||||
$browser->visitRoute('login')
|
||||
->type('username', $user->username)
|
||||
->type('password', 'password')
|
||||
->press(trans('auth/general.login'))
|
||||
->assertPathIs('/');
|
||||
$browser->screenshot('dashboard');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class HomePage extends Page
|
||||
{
|
||||
/**
|
||||
* Get the URL for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
return '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the browser is on the page.
|
||||
*
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
public function assert(Browser $browser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element shortcuts for the page.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function elements()
|
||||
{
|
||||
return [
|
||||
'@element' => '#selector',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Page as BasePage;
|
||||
|
||||
abstract class Page extends BasePage
|
||||
{
|
||||
/**
|
||||
* Get the global element shortcuts for the site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function siteElements()
|
||||
{
|
||||
return [
|
||||
'@element' => '#selector',
|
||||
];
|
||||
}
|
||||
}
|
2
tests/Browser/console/.gitignore
vendored
2
tests/Browser/console/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
2
tests/Browser/screenshots/.gitignore
vendored
2
tests/Browser/screenshots/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
2
tests/Browser/source/.gitignore
vendored
2
tests/Browser/source/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\TestCase as BaseTestCase;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class DuskTestCase extends BaseTestCase
|
||||
{
|
||||
use CreatesApplication;
|
||||
use DatabaseMigrations;
|
||||
|
||||
/**
|
||||
* Prepare for Dusk test execution.
|
||||
*
|
||||
* @beforeClass
|
||||
* @return void
|
||||
*/
|
||||
public static function prepare()
|
||||
{
|
||||
if (!file_exists(realpath(__DIR__ . '/../') . '/.env.dusk.local')) {
|
||||
throw new RuntimeException(
|
||||
'.env.dusk.local file does not exist. Aborting to avoid wiping your local database'
|
||||
);
|
||||
}
|
||||
|
||||
if (! static::runningInSail()) {
|
||||
static::startChromeDriver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the RemoteWebDriver instance.
|
||||
*
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
|
||||
*/
|
||||
protected function driver()
|
||||
{
|
||||
$options = (new ChromeOptions)->addArguments(collect([
|
||||
'--window-size=1920,1080',
|
||||
])->unless($this->hasHeadlessDisabled(), function ($items) {
|
||||
return $items->merge([
|
||||
'--disable-gpu',
|
||||
'--headless',
|
||||
]);
|
||||
})->all());
|
||||
|
||||
return RemoteWebDriver::create(
|
||||
$_ENV['DUSK_DRIVER_URL'] ?? 'http://127.0.0.1:9515',
|
||||
DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY, $options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Dusk command has disabled headless mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasHeadlessDisabled()
|
||||
{
|
||||
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
|
||||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
|
||||
}
|
||||
}
|
30
tests/Feature/Api/Assets/AssetCheckinTest.php
Normal file
30
tests/Feature/Api/Assets/AssetCheckinTest.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Assets;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Tests\Support\InteractsWithSettings;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssetCheckinTest extends TestCase
|
||||
{
|
||||
use InteractsWithSettings;
|
||||
|
||||
public function testLastCheckInFieldIsSetOnCheckin()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$asset = Asset::factory()->create(['last_checkin' => null]);
|
||||
|
||||
$asset->checkOut(User::factory()->create(), $admin, now());
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->postJson(route('api.asset.checkin', $asset))
|
||||
->assertOk();
|
||||
|
||||
$this->assertNotNull(
|
||||
$asset->fresh()->last_checkin,
|
||||
'last_checkin field should be set on checkin'
|
||||
);
|
||||
}
|
||||
}
|
68
tests/Feature/Api/Users/UpdateUserApiTest.php
Normal file
68
tests/Feature/Api/Users/UpdateUserApiTest.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Users;
|
||||
|
||||
use App\Models\User;
|
||||
use Tests\Support\InteractsWithSettings;
|
||||
use Tests\TestCase;
|
||||
use Tests\Support\InteractsWithAuthentication;
|
||||
|
||||
|
||||
class UpdateUserApiTest extends TestCase
|
||||
{
|
||||
use InteractsWithSettings;
|
||||
use InteractsWithAuthentication;
|
||||
|
||||
public function testApiUsersCanBeActivatedWithNumber()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => 0]);
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->patch(route('api.users.update', $user), [
|
||||
'activated' => 1,
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testApiUsersCanBeActivatedWithBooleanTrue()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => false]);
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->patch(route('api.users.update', $user), [
|
||||
'activated' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testApiUsersCanBeDeactivatedWithNumber()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => true]);
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->patch(route('api.users.update', $user), [
|
||||
'activated' => 0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testApiUsersCanBeDeactivatedWithBooleanFalse()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => true]);
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->patch(route('api.users.update', $user), [
|
||||
'activated' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\Feature\Api\Users;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Laravel\Passport\Passport;
|
||||
use Tests\Support\InteractsWithSettings;
|
||||
|
@ -83,4 +84,67 @@ class UsersSearchTest extends TestCase
|
|||
'Expected deleted user does not appear in results'
|
||||
);
|
||||
}
|
||||
|
||||
public function testUsersScopedToCompanyWhenMultipleFullCompanySupportEnabled()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
$companyA = Company::factory()
|
||||
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
|
||||
->create();
|
||||
|
||||
Company::factory()
|
||||
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
|
||||
->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
|
||||
->getJson(route('api.users.index'))
|
||||
->assertOk();
|
||||
|
||||
$results = collect($response->json('rows'));
|
||||
|
||||
$this->assertTrue(
|
||||
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
|
||||
'User index does not contain expected user'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
|
||||
'User index contains unexpected user from another company'
|
||||
);
|
||||
}
|
||||
|
||||
public function testUsersScopedToCompanyDuringSearchWhenMultipleFullCompanySupportEnabled()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
$companyA = Company::factory()
|
||||
->has(User::factory(['first_name' => 'Company A', 'last_name' => 'User']))
|
||||
->create();
|
||||
|
||||
Company::factory()
|
||||
->has(User::factory(['first_name' => 'Company B', 'last_name' => 'User']))
|
||||
->create();
|
||||
|
||||
$response = $this->actingAsForApi(User::factory()->for($companyA)->viewUsers()->create())
|
||||
->getJson(route('api.users.index', [
|
||||
'deleted' => 'false',
|
||||
'company_id' => null,
|
||||
'search' => 'user',
|
||||
'order' => 'asc',
|
||||
'offset' => '0',
|
||||
'limit' => '20',
|
||||
]))
|
||||
->assertOk();
|
||||
|
||||
$results = collect($response->json('rows'));
|
||||
|
||||
$this->assertTrue(
|
||||
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company A')),
|
||||
'User index does not contain expected user'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$results->pluck('name')->contains(fn($text) => str_contains($text, 'Company B')),
|
||||
'User index contains unexpected user from another company'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
87
tests/Feature/Api/Users/UsersUpdateTest.php
Normal file
87
tests/Feature/Api/Users/UsersUpdateTest.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Users;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Department;
|
||||
use App\Models\Group;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Tests\Support\InteractsWithSettings;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UsersUpdateTest extends TestCase
|
||||
{
|
||||
use InteractsWithSettings;
|
||||
|
||||
public function testCanUpdateUserViaPatch()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$manager = User::factory()->create();
|
||||
$company = Company::factory()->create();
|
||||
$department = Department::factory()->create();
|
||||
$location = Location::factory()->create();
|
||||
[$groupA, $groupB] = Group::factory()->count(2)->create();
|
||||
|
||||
$user = User::factory()->create([
|
||||
'activated' => false,
|
||||
'remote' => false,
|
||||
'vip' => false,
|
||||
]);
|
||||
|
||||
$this->actingAsForApi($admin)
|
||||
->patchJson(route('api.users.update', $user), [
|
||||
'first_name' => 'Mabel',
|
||||
'last_name' => 'Mora',
|
||||
'username' => 'mabel',
|
||||
'password' => 'super-secret',
|
||||
'email' => 'mabel@onlymurderspod.com',
|
||||
'permissions' => '{"a.new.permission":"1"}',
|
||||
'activated' => true,
|
||||
'phone' => '619-555-5555',
|
||||
'jobtitle' => 'Host',
|
||||
'manager_id' => $manager->id,
|
||||
'employee_num' => '1111',
|
||||
'notes' => 'Pretty good artist',
|
||||
'company_id' => $company->id,
|
||||
'department_id' => $department->id,
|
||||
'location_id' => $location->id,
|
||||
'remote' => true,
|
||||
'groups' => $groupA->id,
|
||||
'vip' => true,
|
||||
'start_date' => '2021-08-01',
|
||||
'end_date' => '2025-12-31',
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$user->refresh();
|
||||
$this->assertEquals('Mabel', $user->first_name, 'First name was not updated');
|
||||
$this->assertEquals('Mora', $user->last_name, 'Last name was not updated');
|
||||
$this->assertEquals('mabel', $user->username, 'Username was not updated');
|
||||
$this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated');
|
||||
$this->assertEquals('mabel@onlymurderspod.com', $user->email, 'Email was not updated');
|
||||
$this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated');
|
||||
$this->assertTrue((bool)$user->activated, 'User not marked as activated');
|
||||
$this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated');
|
||||
$this->assertEquals('Host', $user->jobtitle, 'Job title was not updated');
|
||||
$this->assertTrue($user->manager->is($manager), 'Manager was not updated');
|
||||
$this->assertEquals('1111', $user->employee_num, 'Employee number was not updated');
|
||||
$this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated');
|
||||
$this->assertTrue($user->company->is($company), 'Company was not updated');
|
||||
$this->assertTrue($user->department->is($department), 'Department was not updated');
|
||||
$this->assertTrue($user->location->is($location), 'Location was not updated');
|
||||
$this->assertEquals(1, $user->remote, 'Remote was not updated');
|
||||
$this->assertTrue($user->groups->contains($groupA), 'Groups were not updated');
|
||||
$this->assertEquals(1, $user->vip, 'VIP was not updated');
|
||||
$this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated');
|
||||
$this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated');
|
||||
|
||||
// `groups` can be an id or array or ids
|
||||
$this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);
|
||||
|
||||
$user->refresh();
|
||||
$this->assertTrue($user->groups->contains($groupA), 'Not part of expected group');
|
||||
$this->assertTrue($user->groups->contains($groupB), 'Not part of expected group');
|
||||
}
|
||||
}
|
32
tests/Feature/Assets/AssetCheckinTest.php
Normal file
32
tests/Feature/Assets/AssetCheckinTest.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Tests\Support\InteractsWithSettings;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssetCheckinTest extends TestCase
|
||||
{
|
||||
use InteractsWithSettings;
|
||||
|
||||
public function testLastCheckInFieldIsSetOnCheckin()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$asset = Asset::factory()->create(['last_checkin' => null]);
|
||||
|
||||
$asset->checkOut(User::factory()->create(), $admin, now());
|
||||
|
||||
$this->actingAs($admin)
|
||||
->post(route('hardware.checkin.store', [
|
||||
'assetId' => $asset->id,
|
||||
]))
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertNotNull(
|
||||
$asset->fresh()->last_checkin,
|
||||
'last_checkin field should be set on checkin'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -107,4 +107,29 @@ class CustomReportTest extends TestCase
|
|||
->assertDontSeeTextInStreamedResponse('Asset A')
|
||||
->assertSeeTextInStreamedResponse('Asset B');
|
||||
}
|
||||
|
||||
public function testCanLimitAssetsByLastCheckIn()
|
||||
{
|
||||
Asset::factory()->create(['name' => 'Asset A', 'last_checkin' => '2023-08-01']);
|
||||
Asset::factory()->create(['name' => 'Asset B', 'last_checkin' => '2023-08-02']);
|
||||
Asset::factory()->create(['name' => 'Asset C', 'last_checkin' => '2023-08-03']);
|
||||
Asset::factory()->create(['name' => 'Asset D', 'last_checkin' => '2023-08-04']);
|
||||
Asset::factory()->create(['name' => 'Asset E', 'last_checkin' => '2023-08-05']);
|
||||
|
||||
$this->actingAs(User::factory()->canViewReports()->create())
|
||||
->post('reports/custom', [
|
||||
'asset_name' => '1',
|
||||
'asset_tag' => '1',
|
||||
'serial' => '1',
|
||||
'checkin_date' => '1',
|
||||
'checkin_date_start' => '2023-08-02',
|
||||
'checkin_date_end' => '2023-08-04',
|
||||
])->assertOk()
|
||||
->assertHeader('content-type', 'text/csv; charset=UTF-8')
|
||||
->assertDontSeeTextInStreamedResponse('Asset A')
|
||||
->assertSeeTextInStreamedResponse('Asset B')
|
||||
->assertSeeTextInStreamedResponse('Asset C')
|
||||
->assertSeeTextInStreamedResponse('Asset D')
|
||||
->assertDontSeeTextInStreamedResponse('Asset E');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ class UpdateUserTest extends TestCase
|
|||
{
|
||||
use InteractsWithSettings;
|
||||
|
||||
public function testUsersCanBeActivated()
|
||||
public function testUsersCanBeActivatedWithNumber()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => false]);
|
||||
$user = User::factory()->create(['activated' => 0]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('users.update', $user), [
|
||||
|
@ -22,10 +22,25 @@ class UpdateUserTest extends TestCase
|
|||
'activated' => 1,
|
||||
]);
|
||||
|
||||
$this->assertTrue($user->refresh()->activated);
|
||||
$this->assertEquals(1, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testUsersCanBeDeactivated()
|
||||
public function testUsersCanBeActivatedWithBooleanTrue()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => false]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('users.update', $user), [
|
||||
'first_name' => $user->first_name,
|
||||
'username' => $user->username,
|
||||
'activated' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testUsersCanBeDeactivatedWithNumber()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => true]);
|
||||
|
@ -34,12 +49,25 @@ class UpdateUserTest extends TestCase
|
|||
->put(route('users.update', $user), [
|
||||
'first_name' => $user->first_name,
|
||||
'username' => $user->username,
|
||||
// checkboxes that are not checked are
|
||||
// not included in the request payload
|
||||
// 'activated' => 0,
|
||||
'activated' => 0,
|
||||
]);
|
||||
|
||||
$this->assertFalse($user->refresh()->activated);
|
||||
$this->assertEquals(0, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testUsersCanBeDeactivatedWithBooleanFalse()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$user = User::factory()->create(['activated' => true]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('users.update', $user), [
|
||||
'first_name' => $user->first_name,
|
||||
'username' => $user->username,
|
||||
'activated' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $user->refresh()->activated);
|
||||
}
|
||||
|
||||
public function testUsersUpdatingThemselvesDoNotDeactivateTheirAccount()
|
||||
|
@ -50,12 +78,8 @@ class UpdateUserTest extends TestCase
|
|||
->put(route('users.update', $admin), [
|
||||
'first_name' => $admin->first_name,
|
||||
'username' => $admin->username,
|
||||
// checkboxes that are disabled are not
|
||||
// included in the request payload
|
||||
// even if they are checked
|
||||
// 'activated' => 0,
|
||||
]);
|
||||
|
||||
$this->assertTrue($admin->refresh()->activated);
|
||||
$this->assertEquals(1, $admin->refresh()->activated);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue