mirror of
https://github.com/snipe/snipe-it.git
synced 2024-11-11 16:14:18 -08:00
Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
467609e561
2
.github/workflows/SA-codeql.yml
vendored
2
.github/workflows/SA-codeql.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
language: [ 'javascript' ]
|
language: [ 'javascript' ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|
2
.github/workflows/codacy-analysis.yml
vendored
2
.github/workflows/codacy-analysis.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# Checkout the repository to the GitHub Actions runner
|
# Checkout the repository to the GitHub Actions runner
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||||
- name: Run Codacy Analysis CLI
|
- name: Run Codacy Analysis CLI
|
||||||
|
|
2
.github/workflows/crowdin-upload.yml
vendored
2
.github/workflows/crowdin-upload.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Crowdin push
|
- name: Crowdin push
|
||||||
uses: crowdin/github-action@v1
|
uses: crowdin/github-action@v1
|
||||||
|
|
2
.github/workflows/docker-alpine.yml
vendored
2
.github/workflows/docker-alpine.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
|
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
|
|
|
@ -180,10 +180,6 @@ class LdapSync extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create user account entries in Snipe-IT */
|
|
||||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
|
|
||||||
$pass = bcrypt($tmp_pass);
|
|
||||||
|
|
||||||
$manager_cache = [];
|
$manager_cache = [];
|
||||||
|
|
||||||
if($ldap_default_group != null) {
|
if($ldap_default_group != null) {
|
||||||
|
@ -229,7 +225,7 @@ class LdapSync extends Command
|
||||||
} else {
|
} else {
|
||||||
// Creating a new user.
|
// Creating a new user.
|
||||||
$user = new User;
|
$user = new User;
|
||||||
$user->password = $pass;
|
$user->password = $user->noPassword();
|
||||||
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
|
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
|
||||||
$item['createorupdate'] = 'created';
|
$item['createorupdate'] = 'created';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\AssetModel;
|
||||||
use App\Models\Component;
|
use App\Models\Component;
|
||||||
use App\Models\Consumable;
|
use App\Models\Consumable;
|
||||||
use App\Models\CustomField;
|
use App\Models\CustomField;
|
||||||
|
@ -643,6 +645,7 @@ class Helper
|
||||||
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
||||||
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
||||||
$components = Component::whereNotNull('min_amt')->get();
|
$components = Component::whereNotNull('min_amt')->get();
|
||||||
|
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
||||||
|
|
||||||
$avail_consumables = 0;
|
$avail_consumables = 0;
|
||||||
$items_array = [];
|
$items_array = [];
|
||||||
|
@ -705,6 +708,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;
|
return $items_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class AssetModelsController extends Controller
|
||||||
'image',
|
'image',
|
||||||
'name',
|
'name',
|
||||||
'model_number',
|
'model_number',
|
||||||
|
'min_amt',
|
||||||
'eol',
|
'eol',
|
||||||
'notes',
|
'notes',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -52,6 +53,7 @@ class AssetModelsController extends Controller
|
||||||
'models.image',
|
'models.image',
|
||||||
'models.name',
|
'models.name',
|
||||||
'model_number',
|
'model_number',
|
||||||
|
'min_amt',
|
||||||
'eol',
|
'eol',
|
||||||
'requestable',
|
'requestable',
|
||||||
'models.notes',
|
'models.notes',
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DepartmentsController extends Controller
|
||||||
$this->authorize('view', Department::class);
|
$this->authorize('view', Department::class);
|
||||||
$allowed_columns = ['id', 'name', 'image', 'users_count'];
|
$allowed_columns = ['id', 'name', 'image', 'users_count'];
|
||||||
|
|
||||||
$departments = Company::scopeCompanyables(Department::select(
|
$departments = Department::select(
|
||||||
'departments.id',
|
'departments.id',
|
||||||
'departments.name',
|
'departments.name',
|
||||||
'departments.phone',
|
'departments.phone',
|
||||||
|
@ -37,8 +37,8 @@ class DepartmentsController extends Controller
|
||||||
'departments.manager_id',
|
'departments.manager_id',
|
||||||
'departments.created_at',
|
'departments.created_at',
|
||||||
'departments.updated_at',
|
'departments.updated_at',
|
||||||
'departments.image'),
|
'departments.image'
|
||||||
"company_id", "departments")->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$departments = $departments->TextSearch($request->input('search'));
|
$departments = $departments->TextSearch($request->input('search'));
|
||||||
|
|
|
@ -363,8 +363,12 @@ class UsersController extends Controller
|
||||||
$user->permissions = $permissions_array;
|
$user->permissions = $permissions_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40);
|
//
|
||||||
$user->password = bcrypt($request->get('password', $tmp_pass));
|
if ($request->filled('password')) {
|
||||||
|
$user->password = bcrypt($request->get('password'));
|
||||||
|
} else {
|
||||||
|
$user->password = $user->noPassword();
|
||||||
|
}
|
||||||
|
|
||||||
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ class AssetModelsController extends Controller
|
||||||
$model->depreciation_id = $request->input('depreciation_id');
|
$model->depreciation_id = $request->input('depreciation_id');
|
||||||
$model->name = $request->input('name');
|
$model->name = $request->input('name');
|
||||||
$model->model_number = $request->input('model_number');
|
$model->model_number = $request->input('model_number');
|
||||||
|
$model->min_amt = $request->input('min_amt');
|
||||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
|
@ -153,6 +154,7 @@ class AssetModelsController extends Controller
|
||||||
$model->eol = $request->input('eol');
|
$model->eol = $request->input('eol');
|
||||||
$model->name = $request->input('name');
|
$model->name = $request->input('name');
|
||||||
$model->model_number = $request->input('model_number');
|
$model->model_number = $request->input('model_number');
|
||||||
|
$model->min_amt = $request->input('min_amt');
|
||||||
$model->manufacturer_id = $request->input('manufacturer_id');
|
$model->manufacturer_id = $request->input('manufacturer_id');
|
||||||
$model->category_id = $request->input('category_id');
|
$model->category_id = $request->input('category_id');
|
||||||
$model->notes = $request->input('notes');
|
$model->notes = $request->input('notes');
|
||||||
|
|
|
@ -191,9 +191,11 @@ class LoginController extends Controller
|
||||||
|
|
||||||
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
|
||||||
|
|
||||||
|
$user->password = $user->noPassword();
|
||||||
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
if (Setting::getSettings()->ldap_pw_sync=='1') {
|
||||||
$user->password = bcrypt($request->input('password'));
|
$user->password = bcrypt($request->input('password'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->email = $ldap_attr['email'];
|
$user->email = $ldap_attr['email'];
|
||||||
$user->first_name = $ldap_attr['firstname'];
|
$user->first_name = $ldap_attr['firstname'];
|
||||||
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
||||||
|
|
|
@ -128,6 +128,13 @@ class LicenseCheckinController extends Controller
|
||||||
$license = License::findOrFail($licenseId);
|
$license = License::findOrFail($licenseId);
|
||||||
$this->authorize('checkin', $license);
|
$this->authorize('checkin', $license);
|
||||||
|
|
||||||
|
if (! $license->reassignable) {
|
||||||
|
// Not allowed to checkin
|
||||||
|
Session::flash('error', 'License not reassignable.');
|
||||||
|
|
||||||
|
return redirect()->back()->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
|
||||||
->whereNotNull('assigned_to')
|
->whereNotNull('assigned_to')
|
||||||
->with('user')
|
->with('user')
|
||||||
|
|
|
@ -1021,7 +1021,12 @@ class ReportsController extends Controller
|
||||||
|
|
||||||
$assetsForReport = $acceptances
|
$assetsForReport = $acceptances
|
||||||
->filter(function ($acceptance) {
|
->filter(function ($acceptance) {
|
||||||
return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance->checkoutable->checkedOutToUser();
|
$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) {
|
->map(function($acceptance) {
|
||||||
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
|
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
|
||||||
|
|
|
@ -68,8 +68,8 @@ class ActionlogsTransformer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$clean_meta = $this->changedInfo($clean_meta);
|
|
||||||
}
|
}
|
||||||
|
$clean_meta= $this->changedInfo($clean_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_url = '';
|
$file_url = '';
|
||||||
|
|
|
@ -47,6 +47,7 @@ class AssetModelsTransformer
|
||||||
] : null,
|
] : null,
|
||||||
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
|
||||||
'model_number' => e($assetmodel->model_number),
|
'model_number' => e($assetmodel->model_number),
|
||||||
|
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
|
||||||
'depreciation' => ($assetmodel->depreciation) ? [
|
'depreciation' => ($assetmodel->depreciation) ? [
|
||||||
'id' => (int) $assetmodel->depreciation->id,
|
'id' => (int) $assetmodel->depreciation->id,
|
||||||
'name'=> e($assetmodel->depreciation->name),
|
'name'=> e($assetmodel->depreciation->name),
|
||||||
|
|
|
@ -79,13 +79,15 @@ class CheckoutableListener
|
||||||
/**
|
/**
|
||||||
* Send the appropriate notification
|
* Send the appropriate notification
|
||||||
*/
|
*/
|
||||||
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
|
if ($event->checkedOutTo && $event->checkoutable){
|
||||||
->where('assigned_to_id', $event->checkedOutTo->id)
|
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
|
||||||
->get();
|
->where('assigned_to_id', $event->checkedOutTo->id)
|
||||||
|
->get();
|
||||||
|
|
||||||
foreach($acceptances as $acceptance){
|
foreach($acceptances as $acceptance){
|
||||||
if($acceptance->isPending()){
|
if($acceptance->isPending()){
|
||||||
$acceptance->delete();
|
$acceptance->delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +144,11 @@ class CheckoutableListener
|
||||||
$notifiables = collect();
|
$notifiables = collect();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify the user who checked out the item
|
* Notify who checked out the item as long as the model can route notifications
|
||||||
*/
|
*/
|
||||||
$notifiables->push($event->checkedOutTo);
|
if (method_exists($event->checkedOutTo, 'routeNotificationFor')) {
|
||||||
|
$notifiables->push($event->checkedOutTo);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify Admin users if the settings is activated
|
* Notify Admin users if the settings is activated
|
||||||
|
|
|
@ -29,6 +29,7 @@ class AssetModel extends SnipeModel
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|min:1|max:255',
|
'name' => 'required|min:1|max:255',
|
||||||
'model_number' => 'max:255|nullable',
|
'model_number' => 'max:255|nullable',
|
||||||
|
'min_amt' => 'integer|min:0|nullable',
|
||||||
'category_id' => 'required|integer|exists:categories,id',
|
'category_id' => 'required|integer|exists:categories,id',
|
||||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||||
'eol' => 'integer:min:0|max:240|nullable',
|
'eol' => 'integer:min:0|max:240|nullable',
|
||||||
|
@ -65,6 +66,7 @@ class AssetModel extends SnipeModel
|
||||||
'fieldset_id',
|
'fieldset_id',
|
||||||
'image',
|
'image',
|
||||||
'manufacturer_id',
|
'manufacturer_id',
|
||||||
|
'min_amt',
|
||||||
'model_number',
|
'model_number',
|
||||||
'name',
|
'name',
|
||||||
'notes',
|
'notes',
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Watson\Validating\ValidatingTrait;
|
||||||
|
|
||||||
class Department extends SnipeModel
|
class Department extends SnipeModel
|
||||||
{
|
{
|
||||||
|
use CompanyableTrait;
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -252,13 +252,10 @@ class Ldap extends Model
|
||||||
$user->last_name = $item['lastname'];
|
$user->last_name = $item['lastname'];
|
||||||
$user->username = $item['username'];
|
$user->username = $item['username'];
|
||||||
$user->email = $item['email'];
|
$user->email = $item['email'];
|
||||||
|
$user->password = $user->noPassword();
|
||||||
|
|
||||||
if (Setting::getSettings()->ldap_pw_sync == '1') {
|
if (Setting::getSettings()->ldap_pw_sync == '1') {
|
||||||
|
|
||||||
$user->password = bcrypt($password);
|
$user->password = bcrypt($password);
|
||||||
} else {
|
|
||||||
$pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 25);
|
|
||||||
$user->password = bcrypt($pass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->activated = 1;
|
$user->activated = 1;
|
||||||
|
@ -268,7 +265,7 @@ class Ldap extends Model
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
return $user;
|
return $user;
|
||||||
} else {
|
} else {
|
||||||
LOG::debug('Could not create user.'.$user->getErrors());
|
\Log::debug('Could not create user.'.$user->getErrors());
|
||||||
throw new Exception('Could not create user: '.$user->getErrors());
|
throw new Exception('Could not create user: '.$user->getErrors());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,7 @@ class SCIMUser extends User
|
||||||
protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false
|
protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false
|
||||||
|
|
||||||
public function __construct(array $attributes = []) {
|
public function __construct(array $attributes = []) {
|
||||||
$attributes['password'] = "*NO PASSWORD*";
|
$attributes['password'] = $this->noPassword();
|
||||||
// $attributes['activated'] = 1;
|
|
||||||
parent::__construct($attributes);
|
parent::__construct($attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -456,6 +456,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||||
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
|
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a common string when the user has been imported/synced from:
|
||||||
|
*
|
||||||
|
* - LDAP without password syncing
|
||||||
|
* - SCIM
|
||||||
|
* - CSV import where no password was provided
|
||||||
|
*
|
||||||
|
* @author A. Gianotto <snipe@snipe.net>
|
||||||
|
* @since [v6.2.0]
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function noPassword()
|
||||||
|
{
|
||||||
|
return "*** NO PASSWORD ***";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query builder scope to return NOT-deleted users
|
* Query builder scope to return NOT-deleted users
|
||||||
|
|
|
@ -65,6 +65,14 @@ class AssetModelPresenter extends Presenter
|
||||||
'title' => trans('admin/models/table.modelnumber'),
|
'title' => trans('admin/models/table.modelnumber'),
|
||||||
'visible' => true,
|
'visible' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'field' => 'min_amt',
|
||||||
|
'searchable' => false,
|
||||||
|
'sortable' => true,
|
||||||
|
'switchable' => true,
|
||||||
|
'title' => trans('mail.min_QTY'),
|
||||||
|
'visible' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'field' => 'assets_count',
|
'field' => 'assets_count',
|
||||||
'searchable' => false,
|
'searchable' => false,
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class FixAssetModelMinQtyNullability extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('models', function (Blueprint $table) {
|
||||||
|
$table->integer('min_amt')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('models', function (Blueprint $table) {
|
||||||
|
$table->integer('min_amt')->nullable(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -454,12 +454,18 @@ $(document).ready(function () {
|
||||||
$('#assigned_location').hide();
|
$('#assigned_location').hide();
|
||||||
$('.notification-callout').fadeOut();
|
$('.notification-callout').fadeOut();
|
||||||
|
|
||||||
|
$('[name="assigned_location"]').val('').trigger('change.select2');
|
||||||
|
$('[name="assigned_user"]').val('').trigger('change.select2');
|
||||||
|
|
||||||
} else if (assignto_type == 'location') {
|
} else if (assignto_type == 'location') {
|
||||||
$('#current_assets_box').fadeOut();
|
$('#current_assets_box').fadeOut();
|
||||||
$('#assigned_asset').hide();
|
$('#assigned_asset').hide();
|
||||||
$('#assigned_user').hide();
|
$('#assigned_user').hide();
|
||||||
$('#assigned_location').show();
|
$('#assigned_location').show();
|
||||||
$('.notification-callout').fadeOut();
|
$('.notification-callout').fadeOut();
|
||||||
|
|
||||||
|
$('[name="assigned_asset"]').val('').trigger('change.select2');
|
||||||
|
$('[name="assigned_user"]').val('').trigger('change.select2');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$('#assigned_asset').hide();
|
$('#assigned_asset').hide();
|
||||||
|
@ -470,6 +476,8 @@ $(document).ready(function () {
|
||||||
}
|
}
|
||||||
$('.notification-callout').fadeIn();
|
$('.notification-callout').fadeIn();
|
||||||
|
|
||||||
|
$('[name="assigned_asset"]').val('').trigger('change.select2');
|
||||||
|
$('[name="assigned_location"]').val('').trigger('change.select2');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,6 +26,7 @@ return array(
|
||||||
'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.',
|
'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.',
|
||||||
'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets',
|
'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets',
|
||||||
'disabled_tooltip' => 'This is disabled because there are no seats currently checked out',
|
'disabled_tooltip' => 'This is disabled because there are no seats currently checked out',
|
||||||
|
'disabled_tooltip_reassignable' => 'This is disabled because the License is not reassignable',
|
||||||
'success' => 'License successfully checked in! | All licenses were successfully checked in!',
|
'success' => 'License successfully checked in! | All licenses were successfully checked in!',
|
||||||
'log_msg' => 'Checked in via bulk license checkout in license GUI',
|
'log_msg' => 'Checked in via bulk license checkout in license GUI',
|
||||||
],
|
],
|
||||||
|
|
|
@ -583,16 +583,22 @@
|
||||||
|
|
||||||
@can('checkin', $license)
|
@can('checkin', $license)
|
||||||
|
|
||||||
@if (($license->seats - $license->availCount()->count()) > 0 )
|
@if (($license->seats - $license->availCount()->count()) <= 0 )
|
||||||
<a href="#" class="btn btn-block bg-purple" style="margin-bottom: 25px;" data-toggle="modal" data-tooltip="true" data-target="#checkinFromAllModal" data-content="{{ trans('general.sure_to_delete') }} data-title="{{ trans('general.delete') }}" onClick="return false;">
|
|
||||||
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
|
|
||||||
</a>
|
|
||||||
@else
|
|
||||||
<span data-tooltip="true" title=" {{ trans('admin/licenses/general.bulk.checkin_all.disabled_tooltip') }}">
|
<span data-tooltip="true" title=" {{ trans('admin/licenses/general.bulk.checkin_all.disabled_tooltip') }}">
|
||||||
<a href="#" class="btn btn-block bg-purple disabled" style="margin-bottom: 25px;">
|
<a href="#" class="btn btn-block bg-purple disabled" style="margin-bottom: 25px;">
|
||||||
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
|
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@elseif (! $license->reassignable)
|
||||||
|
<span data-tooltip="true" title=" {{ trans('admin/licenses/general.bulk.checkin_all.disabled_tooltip_reassignable') }}">
|
||||||
|
<a href="#" class="btn btn-block bg-purple disabled" style="margin-bottom: 25px;">
|
||||||
|
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<a href="#" class="btn btn-block bg-purple" style="margin-bottom: 25px;" data-toggle="modal" data-tooltip="true" data-target="#checkinFromAllModal" data-content="{{ trans('general.sure_to_delete') }} data-title="{{ trans('general.delete') }}" onClick="return false;">
|
||||||
|
{{ trans('admin/licenses/general.bulk.checkin_all.button') }}
|
||||||
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
|
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
|
||||||
@include ('partials.forms.edit.model_number')
|
@include ('partials.forms.edit.model_number')
|
||||||
@include ('partials.forms.edit.depreciation')
|
@include ('partials.forms.edit.depreciation')
|
||||||
|
@include ('partials.forms.edit.minimum_quantity')
|
||||||
|
|
||||||
<!-- EOL -->
|
<!-- EOL -->
|
||||||
|
|
||||||
|
|
94
tests/Feature/Api/Departments/DepartmentIndexTest.php
Normal file
94
tests/Feature/Api/Departments/DepartmentIndexTest.php
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Api\Departments;
|
||||||
|
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Department;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Routing\Route;
|
||||||
|
use Illuminate\Testing\Fluent\AssertableJson;
|
||||||
|
use Tests\Support\InteractsWithSettings;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class DepartmentIndexTest extends TestCase
|
||||||
|
{
|
||||||
|
use InteractsWithSettings;
|
||||||
|
|
||||||
|
public function testViewingDepartmentIndexRequiresAuthentication()
|
||||||
|
{
|
||||||
|
$this->getJson(route('api.departments.index'))->assertRedirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testViewingDepartmentIndexRequiresPermission()
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(User::factory()->create())
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDepartmentIndexReturnsExpectedDepartments()
|
||||||
|
{
|
||||||
|
Department::factory()->count(3)->create();
|
||||||
|
|
||||||
|
$this->actingAsForApi(User::factory()->superuser()->create())
|
||||||
|
->getJson(
|
||||||
|
route('api.departments.index', [
|
||||||
|
'sort' => 'name',
|
||||||
|
'order' => 'asc',
|
||||||
|
'offset' => '0',
|
||||||
|
'limit' => '20',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonStructure([
|
||||||
|
'total',
|
||||||
|
'rows',
|
||||||
|
])
|
||||||
|
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDepartmentIndexAdheresToCompanyScoping()
|
||||||
|
{
|
||||||
|
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||||
|
|
||||||
|
$departmentA = Department::factory()->for($companyA)->create();
|
||||||
|
$departmentB = Department::factory()->for($companyB)->create();
|
||||||
|
|
||||||
|
$superUser = $companyA->users()->save(User::factory()->superuser()->make());
|
||||||
|
$userInCompanyA = $companyA->users()->save(User::factory()->viewDepartments()->make());
|
||||||
|
$userInCompanyB = $companyB->users()->save(User::factory()->viewDepartments()->make());
|
||||||
|
|
||||||
|
$this->settings->disableMultipleFullCompanySupport();
|
||||||
|
|
||||||
|
$this->actingAsForApi($superUser)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseContainsInRows($departmentA)
|
||||||
|
->assertResponseContainsInRows($departmentB);
|
||||||
|
|
||||||
|
$this->actingAsForApi($userInCompanyA)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseContainsInRows($departmentA)
|
||||||
|
->assertResponseContainsInRows($departmentB);
|
||||||
|
|
||||||
|
$this->actingAsForApi($userInCompanyB)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseContainsInRows($departmentA)
|
||||||
|
->assertResponseContainsInRows($departmentB);
|
||||||
|
|
||||||
|
$this->settings->enableMultipleFullCompanySupport();
|
||||||
|
|
||||||
|
$this->actingAsForApi($superUser)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseContainsInRows($departmentA)
|
||||||
|
->assertResponseContainsInRows($departmentB);
|
||||||
|
|
||||||
|
$this->actingAsForApi($userInCompanyA)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseContainsInRows($departmentA)
|
||||||
|
->assertResponseDoesNotContainInRows($departmentB);
|
||||||
|
|
||||||
|
$this->actingAsForApi($userInCompanyB)
|
||||||
|
->getJson(route('api.departments.index'))
|
||||||
|
->assertResponseDoesNotContainInRows($departmentA)
|
||||||
|
->assertResponseContainsInRows($departmentB);
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,32 +56,32 @@ class UsersUpdateTest extends TestCase
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
$user->refresh();
|
$user->refresh();
|
||||||
$this->assertEquals('Mabel', $user->first_name);
|
$this->assertEquals('Mabel', $user->first_name, 'First name was not updated');
|
||||||
$this->assertEquals('Mora', $user->last_name);
|
$this->assertEquals('Mora', $user->last_name, 'Last name was not updated');
|
||||||
$this->assertEquals('mabel', $user->username);
|
$this->assertEquals('mabel', $user->username, 'Username was not updated');
|
||||||
$this->assertTrue(Hash::check('super-secret', $user->password));
|
$this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated');
|
||||||
$this->assertEquals('mabel@onlymurderspod.com', $user->email);
|
$this->assertEquals('mabel@onlymurderspod.com', $user->email, 'Email was not updated');
|
||||||
$this->assertArrayHasKey('a.new.permission', $user->decodePermissions());
|
$this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated');
|
||||||
$this->assertTrue($user->activated);
|
$this->assertTrue((bool)$user->activated, 'User not marked as activated');
|
||||||
$this->assertEquals('619-555-5555', $user->phone);
|
$this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated');
|
||||||
$this->assertEquals('Host', $user->jobtitle);
|
$this->assertEquals('Host', $user->jobtitle, 'Job title was not updated');
|
||||||
$this->assertTrue($user->manager->is($manager));
|
$this->assertTrue($user->manager->is($manager), 'Manager was not updated');
|
||||||
$this->assertEquals('1111', $user->employee_num);
|
$this->assertEquals('1111', $user->employee_num, 'Employee number was not updated');
|
||||||
$this->assertEquals('Pretty good artist', $user->notes);
|
$this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated');
|
||||||
$this->assertTrue($user->company->is($company));
|
$this->assertTrue($user->company->is($company), 'Company was not updated');
|
||||||
$this->assertTrue($user->department->is($department));
|
$this->assertTrue($user->department->is($department), 'Department was not updated');
|
||||||
$this->assertTrue($user->location->is($location));
|
$this->assertTrue($user->location->is($location), 'Location was not updated');
|
||||||
$this->assertEquals(1, $user->remote);
|
$this->assertEquals(1, $user->remote, 'Remote was not updated');
|
||||||
$this->assertTrue($user->groups->contains($groupA));
|
$this->assertTrue($user->groups->contains($groupA), 'Groups were not updated');
|
||||||
$this->assertTrue($user->vip);
|
$this->assertEquals(1, $user->vip, 'VIP was not updated');
|
||||||
$this->assertEquals('2021-08-01', $user->start_date);
|
$this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated');
|
||||||
$this->assertEquals('2025-12-31', $user->end_date);
|
$this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated');
|
||||||
|
|
||||||
// `groups` can be an id or array or ids
|
// `groups` can be an id or array or ids
|
||||||
$this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);
|
$this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);
|
||||||
|
|
||||||
$user->refresh();
|
$user->refresh();
|
||||||
$this->assertTrue($user->groups->contains($groupA));
|
$this->assertTrue($user->groups->contains($groupA), 'Not part of expected group');
|
||||||
$this->assertTrue($user->groups->contains($groupB));
|
$this->assertTrue($user->groups->contains($groupB), 'Not part of expected group');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue