Merge branch 'snipeit_v7_laravel10' into chore/sc-16907

This commit is contained in:
Spencer Long 2023-11-15 20:37:15 -06:00 committed by GitHub
commit 19850e8c5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
101 changed files with 1525 additions and 463 deletions

View file

@ -2970,6 +2970,15 @@
"contributions": [
"code"
]
},
{
"login": "mmanjos",
"name": "mmanjos",
"avatar_url": "https://avatars.githubusercontent.com/u/3483684?v=4",
"profile": "https://github.com/mmanjos",
"contributions": [
"code"
]
}
]
}

View file

@ -159,6 +159,7 @@ LOG_CHANNEL=stderr
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC
APP_FORCE_TLS=false
GOOGLE_MAPS_API=
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600

View file

@ -25,9 +25,8 @@ jobs:
fail-fast: false
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1.1"
- "8.1"
- "8.2"
name: PHP ${{ matrix.php-version }}
@ -58,11 +57,17 @@ jobs:
- 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: Setup Laravel
env:
DB_CONNECTION: mysql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:

View file

@ -1,5 +1,5 @@
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-327-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
[![All Contributors](https://img.shields.io/badge/all_contributors-328-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
## Snipe-IT - Open Source Asset Management System
@ -145,7 +145,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -18,7 +18,7 @@ class LdapSync extends Command
*
* @var string
*/
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}';
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=*} {--base_dn=} {--filter=} {--summary} {--json_summary}';
/**
* The console command description.
@ -83,7 +83,15 @@ class LdapSync extends Command
$summary = [];
try {
if ($this->option('base_dn') != '') {
if ( $this->option('location_id') != '') {
foreach($this->option('location_id') as $location_id){
$location_ou= Location::where('id', '=', $location_id)->value('ldap_ou');
$search_base = $location_ou;
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
}
}
else if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
@ -106,7 +114,6 @@ class LdapSync extends Command
/* Determine which location to assign users to by default. */
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
if ($this->option('location') != '') {
if ($location = Location::where('name', '=', $this->option('location'))->first()) {
Log::debug('Location name ' . $this->option('location') . ' passed');
@ -114,13 +121,14 @@ class LdapSync extends Command
}
} elseif ($this->option('location_id') != '') {
if ($location = Location::where('id', '=', $this->option('location_id'))->first()) {
Log::debug('Location ID '.$this->option('location_id').' passed');
foreach($this->option('location_id') as $location_id) {
if ($location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
}
}
}
if (! isset($location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}

View file

@ -150,6 +150,11 @@ class Handler extends ExceptionHandler
return redirect()->guest('login');
}
protected function invalidJson($request, ValidationException $exception)
{
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200);
}
/**
* A list of the inputs that are never flashed for validation exceptions.

View file

@ -73,10 +73,14 @@ class Helper
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.3]
* @return array
* @return string
*/
public static function defaultChartColors($index = 0)
public static function defaultChartColors(int $index = 0)
{
if ($index < 0) {
$index = 0;
}
$colors = [
'#008941',
'#FF4A46',
@ -349,7 +353,19 @@ class Helper
$total_colors = count($colors);
if ($index >= $total_colors) {
$index = $index - $total_colors;
\Log::error('Status label count is '.$index.' and exceeds the allowed count of 266.');
//patch fix for array key overflow (color count starts at 1, array starts at 0)
$index = $index - $total_colors - 1;
//constraints to keep result in 0-265 range. This should never be needed, but if something happens
//to create this many status labels and it DOES happen, this will keep it from failing at least.
if($index < 0) {
$index = 0;
}
elseif($index >($total_colors - 1)) {
$index = $total_colors - 1;
}
}
return $colors[$index];

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
@ -33,6 +34,7 @@ use TCPDF;
use Validator;
use Route;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@ -48,7 +50,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $audit = null)
{
@ -295,7 +297,7 @@ class AssetsController extends Controller
}
if ($request->filled('order_number')) {
$assets->where('assets.order_number', '=', $request->get('order_number'));
$assets->where('assets.order_number', '=', strval($request->get('order_number')));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
@ -443,7 +445,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
@ -474,7 +476,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
* @return \Illuminate\Http\JsonResponse
*/
public function selectlist(Request $request)
{
@ -530,12 +532,10 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function store(ImageUploadRequest $request)
public function store(StoreAssetRequest $request)
{
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
@ -639,7 +639,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@ -667,9 +667,10 @@ class AssetsController extends Controller
}
$asset = $request->handleImages($asset);
$model = AssetModel::find($asset->model_id);
// Update custom fields
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
@ -720,7 +721,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
@ -749,7 +750,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v5.1.18]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function restore(Request $request, $assetId = null)
{
@ -789,7 +790,7 @@ class AssetsController extends Controller
* @author [N. Butler]
* @param string $tag
* @since [v6.0.5]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
{
@ -805,7 +806,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
@ -889,7 +890,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkin(Request $request, $asset_id)
{
@ -945,7 +946,7 @@ class AssetsController extends Controller
*
* @author [A. Janes] [<ajanes@adagiohealth.org>]
* @since [v6.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkinByTag(Request $request, $tag = null)
{
@ -971,7 +972,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function audit(Request $request)
@ -1032,24 +1033,54 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
$allowed_columns = [
'name',
'asset_tag',
'serial',
'model_number',
'image',
'purchase_cost',
'expected_checkin',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
}
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
->requestableAssets();
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name())) {
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
}
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = str_replace('custom_fields.', '', $request->input('sort'));
// This handles all the pivot sorting (versus the assets.* fields
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
switch ($request->input('sort')) {
case 'model':
$assets->OrderModels($order);
@ -1057,17 +1088,19 @@ class AssetsController extends Controller
case 'model_number':
$assets->OrderModelNumber($order);
break;
case 'category':
$assets->OrderCategory($order);
break;
case 'manufacturer':
$assets->OrderManufacturer($order);
case 'location':
$assets->OrderLocation($order);
break;
default:
$assets->orderBy('assets.created_at', $order);
$assets->orderBy($column_sort, $order);
break;
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();

View file

@ -11,6 +11,7 @@ use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use DB;
class ProfileController extends Controller
@ -48,14 +49,23 @@ class ProfileController extends Controller
{
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
$results = [];
$results = array();
$show_field = array();
$showable_fields = array();
$results['total'] = $checkoutRequests->count();
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
$showable_fields[] = $field->db_column_name();
}
}
foreach ($checkoutRequests as $checkoutRequest) {
// Make sure the asset and request still exist
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
$results['rows'][] = [
$assets = [
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
'name' => e($checkoutRequest->itemRequested()->present()->name()),
'type' => e($checkoutRequest->itemType()),
@ -64,7 +74,16 @@ class ProfileController extends Controller
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
foreach ($showable_fields as $showable_field_name) {
$show_field['custom_fields.'.$showable_field_name] = $checkoutRequest->itemRequested()->{$showable_field_name};
}
// Merge the plain asset data and the custom fields data
$results['rows'][] = array_merge($assets, $show_field);
}
}
return $results;

View file

@ -179,9 +179,14 @@ class AssetModelsController extends Controller
if ($model->save()) {
if ($model->wasChanged('eol')) {
if ($model->eol > 0) {
$newEol = $model->eol;
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
} elseif ($model->eol == 0) {
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('null')]);
}
}
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
}

View file

@ -137,7 +137,7 @@ class AssetsController extends Controller
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date());
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
@ -204,12 +204,8 @@ class AssetsController extends Controller
}
if ($success) {
// Redirect to the asset listing page
$minutes = 518400;
// dd( $_POST['options']);
// Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes));
return redirect()->route('hardware.index')
->with('success', trans('admin/hardware/message.create.success'));
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => $asset->asset_tag]));
}
@ -309,14 +305,15 @@ class AssetsController extends Controller
$asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = $request->input('purchase_cost', null);
$asset->purchase_date = $request->input('purchase_date', null);
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && $asset->model->eol) {
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
$asset->purchase_date = $request->input('purchase_date', null);
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
$asset->eol_explicit = false;
} elseif ($request->filled('asset_eol_date')) {
$asset->asset_eol_date = $request->input('asset_eol_date', null);
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($asset->model->eol) {
if($months != $asset->model->eol) {
if($months != $asset->model->eol > 0) {
$asset->eol_explicit = true;
} else {
$asset->eol_explicit = false;
@ -324,6 +321,9 @@ class AssetsController extends Controller
} else {
$asset->eol_explicit = true;
}
} elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
$asset->asset_eol_date = null;
$asset->eol_explicit = false;
}
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);

View file

@ -112,7 +112,8 @@ class BulkAssetsController extends Controller
public function update(Request $request)
{
$this->authorize('update', Asset::class);
$error_bag = [];
$has_errors = 0;
$error_array = array();
// Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index');
@ -120,7 +121,6 @@ class BulkAssetsController extends Controller
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if (Session::exists('ids')) {
@ -160,7 +160,6 @@ class BulkAssetsController extends Controller
$this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('model_id')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id')
@ -187,6 +186,7 @@ class BulkAssetsController extends Controller
$this->update_array['purchase_cost'] = $request->input('purchase_cost');
}
if ($request->filled('company_id')) {
$this->update_array['company_id'] = $request->input('company_id');
if ($request->input('company_id') == 'clear') {
@ -195,68 +195,108 @@ class BulkAssetsController extends Controller
}
if ($request->filled('rtd_location_id')) {
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) {
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
}
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
$this->update_array['location_id'] = $request->input('rtd_location_id');
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
}
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) {
$this->update_array['location_id'] = $request->input('rtd_location_id');
}
}
$changed = [];
$assetCollection = Asset::where('id' ,$assetId)->get();
$asset = Asset::find($assetId);
foreach ($this->update_array as $key => $value) {
if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) {
$changed[$key]['old'] = $assetCollection->toArray()[0][$key];
if ($this->update_array[$key] != $asset->{$key}) {
$changed[$key]['old'] = $asset->{$key};
$changed[$key]['new'] = $this->update_array[$key];
}
}
$logAction = new Actionlog();
$logAction->item_type = Asset::class;
$logAction->item_id = $assetId;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
if ($custom_fields_present) {
$asset = Asset::find($assetId);
$assetCustomFields = $asset->model()->first()->fieldset;
$model = $asset->model()->first();
// Use the rules of the new model fieldsets if the model changed
if ($request->filled('model_id')) {
$this->update_array['model_id'] = $request->input('model_id');
$model = \App\Models\AssetModel::find($request->input('model_id'));
}
// Make sure this model is valid
$assetCustomFields = ($model) ? $model->fieldset : null;
if ($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if (array_key_exists($field->db_column, $this->update_array)) {
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted=='1')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
/*
* Check if the decrypted existing value is different from one we just submitted
* and if not, pull it out of the object since it shouldn't really be updating at all.
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
* different times will have different values, so it will *look* like it was updated
* but it wasn't.
*/
if ($decrypted_old != $this->update_array[$field->db_column]) {
$asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
} else {
/*
* Remove the encrypted custom field from the update_array, since nothing changed
*/
unset($this->update_array[$field->db_column]);
unset($asset->{$field->db_column});
}
/*
* These custom fields aren't encrypted, just carry on as usual
*/
} else {
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
// Check if this is an array, and if so, flatten it
if (is_array($this->update_array[$field->db_column])) {
$asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]);
} else {
$asset->{$field->db_column} = $this->update_array[$field->db_column];
$saved = $asset->save();
if(!$saved) {
$error_bag[] = $asset->getErrors();
}
continue;
} else {
$array = $this->update_array;
array_except($array, $field->db_column);
$asset->save($array);
}
if (!$asset->save()) {
$error_bag[] = $asset->getErrors();
}
}
}
} else {
Asset::find($assetId)->update($this->update_array);
} // endforeach
} // end custom field check
} // end custom fields handler
// Check if it passes validation, and then try to save
if (!$asset->update($this->update_array)) {
// Build the error array
foreach ($asset->getErrors()->toArray() as $key => $message) {
for ($x = 0; $x < count($message); $x++) {
$error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x];
$has_errors++;
}
}
if(!empty($error_bag)) {
$errors = [];
//find the customfield name from the name of the messagebag items
foreach ($error_bag as $key => $bag) {
foreach($bag->keys() as $key => $value) {
CustomField::where('db_column', $value)->get()->map(function($item) use (&$errors) {
$errors[] = $item->name;
});
}
}
return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors));
} // end if saved
} // end asset foreach
if ($has_errors > 0) {
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
}
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
}
// no values given, nothing to update

View file

@ -97,7 +97,7 @@ class ComponentCheckinController extends Controller
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
if ($backto == 'asset'){
return redirect()->route('hardware.view', $asset->id)->with('success',
return redirect()->route('hardware.show', $asset->id)->with('success',
trans('admin/components/message.checkin.success'));
}

View file

@ -110,6 +110,7 @@ class CustomFieldsController extends Controller
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"user_id" => Auth::id()
]);
@ -267,6 +268,7 @@ class CustomFieldsController extends Controller
$field->display_in_user_view = $display_in_user_view;
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));

View file

@ -7,8 +7,10 @@ use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Labels\Label;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Setting;
use App\Models\Supplier;
use App\Models\User;
use App\View\Label as LabelView;
use Illuminate\Support\Facades\Storage;
@ -33,18 +35,20 @@ class LabelsController extends Controller
$exampleAsset->name = 'JEN-867-5309';
$exampleAsset->asset_tag = '100001';
$exampleAsset->serial = 'SN9876543210';
$exampleAsset->asset_eol_date = '2025-01-01';
$exampleAsset->order_number = '12345';
$exampleAsset->purchase_date = '2023-01-01';
$exampleAsset->status_id = 1;
$exampleAsset->company = new Company();
$exampleAsset->company->id = 999999;
$exampleAsset->company->name = 'Test Company Limited';
$exampleAsset->company->image = 'company-image-test.png';
$exampleAsset->company = new Company([
'name' => 'Test Company Limited',
'phone' => '1-555-555-5555',
'email' => 'company@example.com',
]);
$exampleAsset->assignedto = new User();
$exampleAsset->assignedto->id = 999999;
$exampleAsset->assignedto->first_name = 'Test';
$exampleAsset->assignedto->last_name = 'Person';
$exampleAsset->assignedto->username = 'Test.Person';
$exampleAsset->assignedto->employee_num = '0123456789';
$exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker']));
$exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']);
$exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']);
$exampleAsset->model = new AssetModel();
$exampleAsset->model->id = 999999;
@ -53,6 +57,10 @@ class LabelsController extends Controller
$exampleAsset->model->manufacturer = new Manufacturer();
$exampleAsset->model->manufacturer->id = 999999;
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
$exampleAsset->model->manufacturer->support_email = 'support@test.com';
$exampleAsset->model->manufacturer->support_phone = '1-555-555-5555';
$exampleAsset->model->manufacturer->support_url = 'https://example.com';
$exampleAsset->supplier = new Supplier(['name' => 'Test Company Limited']);
$exampleAsset->model->category = new Category();
$exampleAsset->model->category->id = 999999;
$exampleAsset->model->category->name = 'Test Category';

View file

@ -101,7 +101,7 @@ class LicenseCheckinController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note')));
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
if ($backTo == 'user') {
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));

View file

@ -105,7 +105,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}
@ -122,7 +122,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}

View file

@ -23,6 +23,7 @@ use Input;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
/**
@ -246,6 +247,9 @@ class ReportsController extends Controller
trans('general.action'),
trans('general.type'),
trans('general.item'),
trans('general.license_serial'),
trans('general.model_name'),
trans('general.model_no'),
'To',
trans('general.notes'),
'Changed',
@ -288,6 +292,9 @@ class ReportsController extends Controller
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
($actionlog->item->serial) ? $actionlog->item->serial : null,
($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
($actionlog->item->model) ? $actionlog->item->model->model_number : null,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
@ -403,11 +410,12 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function postCustom(Request $request)
public function postCustom(CustomAssetReportRequest $request)
{
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
@ -526,6 +534,30 @@ class ReportsController extends Controller
$header[] = trans('admin/users/table.title');
}
if ($request->filled('phone')) {
$header[] = trans('admin/users/table.phone');
}
if ($request->filled('user_address')) {
$header[] = trans('admin/reports/general.custom_export.user_address');
}
if ($request->filled('user_city')) {
$header[] = trans('admin/reports/general.custom_export.user_city');
}
if ($request->filled('user_state')) {
$header[] = trans('admin/reports/general.custom_export.user_state');
}
if ($request->filled('user_country')) {
$header[] = trans('admin/reports/general.custom_export.user_country');
}
if ($request->filled('user_zip')) {
$header[] = trans('admin/reports/general.custom_export.user_zip');
}
if ($request->filled('status')) {
$header[] = trans('general.status');
}
@ -742,7 +774,7 @@ class ReportsController extends Controller
}
if ($request->filled('eol')) {
$row[] = ($asset->purchase_date != '') ? $asset->present()->eol_date() : '';
$row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
}
if ($request->filled('order')) {
@ -826,6 +858,54 @@ class ReportsController extends Controller
}
}
if ($request->filled('phone')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->phone : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_address')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->address : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_city')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->city : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_state')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->state : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_country')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->country : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_zip')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->zip : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}

View file

@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest;
use App\Models\CustomField;
use App\Models\Group;
use App\Models\Setting;
use App\Models\Asset;
@ -26,7 +27,7 @@ use Response;
use App\Http\Requests\SlackSettingsRequest;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Validator;
use Illuminate\Support\Facades\Validator;
/**
* This controller handles all actions related to Settings for
@ -809,9 +810,10 @@ class SettingsController extends Controller
*/
public function getLabels()
{
$setting = Setting::getSettings();
return view('settings.labels', compact('setting'));
return view('settings.labels', [
'setting' => Setting::getSettings(),
'customFields' => CustomField::all(),
]);
}
/**
@ -1248,13 +1250,11 @@ class SettingsController extends Controller
if (!$request->hasFile('file')) {
return redirect()->route('settings.backups.index')->with('error', 'No file uploaded');
} else {
$max_file_size = Helper::file_upload_max_size();
$rules = [
$validator = Validator::make($request->all(), [
'file' => 'required|mimes:zip|max:'.$max_file_size,
];
$validator = \Validator::make($request->all(), $rules);
]);
if ($validator->passes()) {
@ -1265,7 +1265,7 @@ class SettingsController extends Controller
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
}
return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
return redirect()->route('settings.backups.index')->withErrors($validator);
}

View file

@ -125,10 +125,26 @@ class BulkUsersController extends Controller
];
}
/**
* Check to see if the user wants to actually blank out the values vs skip them
*/
if ($request->input('null_location_id')=='1') {
$this->update_array['location_id'] = null;
}
if ($request->input('null_department_id')=='1') {
$this->update_array['department_id'] = null;
}
if ($request->input('null_manager_id')=='1') {
$this->update_array['manager_id'] = null;
}
if ($request->input('null_company_id')=='1') {
$this->update_array['company_id'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}

View file

@ -49,15 +49,19 @@ class LDAPImportController extends Controller
{
$this->authorize('update', User::class);
// Call Artisan LDAP import command.
$location_id = $request->input('location_id');
Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]);
Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]);
// Collect and parse JSON summary.
$ldap_results_json = Artisan::output();
$ldap_results = json_decode($ldap_results_json, true);
if (!$ldap_results) {
return redirect()->back()->withInput()->with('error', trans('general.no_results'));
}
// Direct user to appropriate status page.
if ($ldap_results['error']) {
return redirect()->back()->withInput()->with('error', $ldap_results['error_message']);
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
class CustomAssetReportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'purchase_start' => 'date|date_format:Y-m-d|nullable',
'purchase_end' => 'date|date_format:Y-m-d|nullable',
'created_start' => 'date|date_format:Y-m-d|nullable',
'created_end' => 'date|date_format:Y-m-d|nullable',
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
'checkout_date_end' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_start' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_end' => 'date|date_format:Y-m-d|nullable',
'checkin_date_start' => 'date|date_format:Y-m-d|nullable',
'checkin_date_end' => 'date|date_format:Y-m-d|nullable',
'last_audit_start' => 'date|date_format:Y-m-d|nullable',
'last_audit_end' => 'date|date_format:Y-m-d|nullable',
'next_audit_start' => 'date|date_format:Y-m-d|nullable',
'next_audit_end' => 'date|date_format:Y-m-d|nullable',
];
}
public function response(array $errors)
{
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreAssetRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', new Asset);
}
public function prepareForValidation(): void
{
//
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = array_merge(
(new Asset)->getRules(),
parent::rules(),
);
return $rules;
}
}

View file

@ -3,6 +3,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Setting;
use App\Models\Company;
@ -12,6 +13,7 @@ use App\Models\AssetModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
class ActionlogsTransformer
{
@ -98,6 +100,13 @@ class ActionlogsTransformer
\Log::debug('custom fields do not match');
$clean_meta[$fieldname]['old'] = "************";
$clean_meta[$fieldname]['new'] = "************";
// Display the changes if the user is an admin or superadmin
if (Gate::allows('admin')) {
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
}
}

View file

@ -7,7 +7,8 @@ use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Carbon\Carbon;
use Auth;
class AssetsTransformer
{
@ -38,7 +39,7 @@ class AssetsTransformer
'byod' => ($asset->byod ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => (($asset->model) && ($asset->model->eol != '')) ? $asset->model->eol : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id,
@ -231,6 +232,29 @@ class AssetsTransformer
'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id),
];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
// Only display this if it's allowed via the custom field setting
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
$value = $asset->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) {
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->db_column] = e($value);
}
$array['custom_fields'] = $fields_array;
}
} else {
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
}
$permissions_array['available_actions'] = [
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,

View file

@ -34,7 +34,7 @@ class AccessoryImporter extends ItemImporter
}
$this->log('Updating Accessory');
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$this->item['model_number'] = trim($this->findCsvMatch($row, "model_number"));
$accessory->update($this->sanitizeItemForUpdating($accessory));
$accessory->save();

View file

@ -5,6 +5,9 @@ namespace App\Importer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\User;
use App\Events\CheckoutableCheckedIn;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class AssetImporter extends ItemImporter
@ -80,13 +83,13 @@ class AssetImporter extends ItemImporter
$this->log('No Matching Asset, Creating a new one');
$asset = new Asset;
}
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
$this->item['image'] = $this->findCsvMatch($row, 'image');
$this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
$this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes'));
$this->item['image'] = trim($this->findCsvMatch($row, 'image'));
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0;
$asset->requestable = $this->item['requestable'];
$this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
$this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months')));
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
$this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
$this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0;
// If no status ID is found
@ -141,7 +144,13 @@ class AssetImporter extends ItemImporter
// If we have a target to checkout to, lets do so.
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
//-- the class that needs to use it (command importer or GUI importer inside the project).
if (isset($target)) {
if (isset($target) && ($target !== false)) {
if (!is_null($asset->assigned_to)){
if ($asset->assigned_to != $target->id){
event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s')));
}
}
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
}

View file

@ -28,8 +28,8 @@ class ComponentImporter extends ItemImporter
{
$component = null;
$this->log('Creating Component');
$component = Component::where('name', $this->item['name'])
->where('serial', $this->item['serial'])
$component = Component::where('name', trim($this->item['name']))
->where('serial', trim($this->item['serial']))
->first();
if ($component) {

View file

@ -26,7 +26,7 @@ class ConsumableImporter extends ItemImporter
*/
public function createConsumableIfNotExists($row)
{
$consumable = Consumable::where('name', $this->item['name'])->first();
$consumable = Consumable::where('name', trim($this->item['name']))->first();
if ($consumable) {
if (! $this->updating) {
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
@ -41,9 +41,9 @@ class ConsumableImporter extends ItemImporter
}
$this->log('No matching consumable, creating one');
$consumable = new Consumable();
$this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
$this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
$consumable->fill($this->sanitizeItemForStoring($consumable));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$consumable->unsetEventDispatcher();

View file

@ -19,22 +19,76 @@ abstract class Importer
* Id of User performing import
* @var
*/
protected $user_id;
/**
* Are we updating items in the import
* @var bool
*/
protected $updating;
/**
* Default Map of item fields->csv names
*
* This has been moved into app/Http/Livewire/Importer.php to be more granular.
* @todo - remove references to this property since we don't use it anymore.
* This private variable is ONLY used for the cli-importer.
*
* @todo - find a way to make this less duplicative
* @var array
*/
private $defaultFieldMap = [
'asset_tag' => 'asset tag',
'activated' => 'activated',
'category' => 'category',
'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already.
'checkout_location' => 'checkout location',
'company' => 'company',
'item_name' => 'item name',
'item_number' => 'item number',
'image' => 'image',
'expiration_date' => 'expiration date',
'location' => 'location',
'notes' => 'notes',
'license_email' => 'licensed to email',
'license_name' => 'licensed to name',
'maintained' => 'maintained',
'manufacturer' => 'manufacturer',
'asset_model' => 'model name',
'model_number' => 'model number',
'order_number' => 'order number',
'purchase_cost' => 'purchase cost',
'purchase_date' => 'purchase date',
'purchase_order' => 'purchase order',
'qty' => 'quantity',
'reassignable' => 'reassignable',
'requestable' => 'requestable',
'seats' => 'seats',
'serial' => 'serial number',
'status' => 'status',
'supplier' => 'supplier',
'termination_date' => 'termination date',
'warranty_months' => 'warranty',
'full_name' => 'full name',
'email' => 'email',
'username' => 'username',
'address' => 'address',
'address2' => 'address2',
'city' => 'city',
'state' => 'state',
'country' => 'country',
'zip' => 'zip',
'jobtitle' => 'job title',
'employee_num' => 'employee number',
'phone_number' => 'phone number',
'first_name' => 'first name',
'last_name' => 'last name',
'department' => 'department',
'manager_name' => 'manager full name',
'manager_username' => 'manager username',
'min_amt' => 'minimum quantity',
'remote' => 'remote',
'vip' => 'vip',
];
/**
* Map of item fields->csv names
@ -281,9 +335,11 @@ abstract class Importer
$user_array['email'] = User::generateEmailFromFullName($user_array['full_name']);
}
// Get some variables for $user_formatted_array in case we need them later
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
if (empty($user_array['first_name'])) {
// Get some fields for first name and last name based off of full name
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
$user_array['first_name'] = $user_formatted_array['first_name'];
$user_array['last_name'] = $user_formatted_array['last_name'];
}

View file

@ -372,7 +372,7 @@ class ItemImporter extends Importer
if (empty($asset_statuslabel_name)) {
return null;
}
$status = Statuslabel::where(['name' => $asset_statuslabel_name])->first();
$status = Statuslabel::where(['name' => trim($asset_statuslabel_name)])->first();
if ($status) {
$this->log('A matching Status '.$asset_statuslabel_name.' already exists');
@ -381,7 +381,7 @@ class ItemImporter extends Importer
}
$this->log('Creating a new status');
$status = new Statuslabel();
$status->name = $asset_statuslabel_name;
$status->name = trim($asset_statuslabel_name);
$status->deployable = 1;
$status->pending = 0;
@ -420,7 +420,7 @@ class ItemImporter extends Importer
//Otherwise create a manufacturer.
$manufacturer = new Manufacturer();
$manufacturer->name = $item_manufacturer;
$manufacturer->name = trim($item_manufacturer);
$manufacturer->user_id = $this->user_id;
if ($manufacturer->save()) {

View file

@ -55,19 +55,19 @@ class LicenseImporter extends ItemImporter
$this->log('No Matching License, Creating a new one');
$license = new License;
}
$asset_tag = $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); // used for checkout out to an asset.
$asset_tag = $this->item['asset_tag'] = trim($this->findCsvMatch($row, 'asset_tag')); // used for checkout out to an asset.
$this->item["expiration_date"] = null;
if ($this->findCsvMatch($row, "expiration_date")!='') {
$this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime($this->findCsvMatch($row, "expiration_date")));
$this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime(trim($this->findCsvMatch($row, "expiration_date"))));
}
$this->item['license_email'] = $this->findCsvMatch($row, 'license_email');
$this->item['license_name'] = $this->findCsvMatch($row, 'license_name');
$this->item['maintained'] = $this->findCsvMatch($row, 'maintained');
$this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order');
$this->item['order_number'] = $this->findCsvMatch($row, 'order_number');
$this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable');
$this->item['manufacturer'] = $this->createOrFetchManufacturer($this->findCsvMatch($row, 'manufacturer'));
$this->item['license_email'] = trim($this->findCsvMatch($row, 'license_email'));
$this->item['license_name'] = trim($this->findCsvMatch($row, 'license_name'));
$this->item['maintained'] = trim($this->findCsvMatch($row, 'maintained'));
$this->item['purchase_order'] = trim($this->findCsvMatch($row, 'purchase_order'));
$this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number'));
$this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable'));
$this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer')));
if($this->item['reassignable'] == "")
{

View file

@ -53,21 +53,21 @@ class LocationImporter extends ItemImporter
}
// Pull the records from the CSV to determine their values
$this->item['name'] = $this->findCsvMatch($row, 'name');
$this->item['address'] = $this->findCsvMatch($row, 'address');
$this->item['address2'] = $this->findCsvMatch($row, 'address2');
$this->item['city'] = $this->findCsvMatch($row, 'city');
$this->item['state'] = $this->findCsvMatch($row, 'state');
$this->item['country'] = $this->findCsvMatch($row, 'country');
$this->item['zip'] = $this->findCsvMatch($row, 'zip');
$this->item['currency'] = $this->findCsvMatch($row, 'currency');
$this->item['ldap_ou'] = $this->findCsvMatch($row, 'ldap_ou');
$this->item['manager'] = $this->findCsvMatch($row, 'manager');
$this->item['manager_username'] = $this->findCsvMatch($row, 'manager_username');
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
$this->item['address2'] = trim($this->findCsvMatch($row, 'address2'));
$this->item['city'] = trim($this->findCsvMatch($row, 'city'));
$this->item['state'] = trim($this->findCsvMatch($row, 'state'));
$this->item['country'] = trim($this->findCsvMatch($row, 'country'));
$this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
$this->item['currency'] = trim($this->findCsvMatch($row, 'currency'));
$this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou'));
$this->item['manager'] = trim($this->findCsvMatch($row, 'manager'));
$this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username'));
$this->item['user_id'] = \Auth::user()->id;
if ($this->findCsvMatch($row, 'parent_location')) {
$this->item['parent_id'] = $this->createOrFetchLocation($this->findCsvMatch($row, 'parent_location'));
$this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location')));
}
if (!empty($this->item['manager'])) {

View file

@ -42,32 +42,32 @@ class UserImporter extends ItemImporter
public function createUserIfNotExists(array $row)
{
// Pull the records from the CSV to determine their values
$this->item['id'] = $this->findCsvMatch($row, 'id');
$this->item['username'] = $this->findCsvMatch($row, 'username');
$this->item['first_name'] = $this->findCsvMatch($row, 'first_name');
$this->item['last_name'] = $this->findCsvMatch($row, 'last_name');
$this->item['email'] = $this->findCsvMatch($row, 'email');
$this->item['gravatar'] = $this->findCsvMatch($row, 'gravatar');
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
$this->item['website'] = $this->findCsvMatch($row, 'website');
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
$this->item['address'] = $this->findCsvMatch($row, 'address');
$this->item['city'] = $this->findCsvMatch($row, 'city');
$this->item['state'] = $this->findCsvMatch($row, 'state');
$this->item['country'] = $this->findCsvMatch($row, 'country');
$this->item['start_date'] = $this->findCsvMatch($row, 'start_date');
$this->item['end_date'] = $this->findCsvMatch($row, 'end_date');
$this->item['zip'] = $this->findCsvMatch($row, 'zip');
$this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0;
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
$this->item['id'] = trim($this->findCsvMatch($row, 'id'));
$this->item['username'] = trim($this->findCsvMatch($row, 'username'));
$this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
$this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
$this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar'));
$this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number'));
$this->item['website'] = trim($this->findCsvMatch($row, 'website'));
$this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
$this->item['city'] = trim($this->findCsvMatch($row, 'city'));
$this->item['state'] = trim($this->findCsvMatch($row, 'state'));
$this->item['country'] = trim($this->findCsvMatch($row, 'country'));
$this->item['start_date'] = trim($this->findCsvMatch($row, 'start_date'));
$this->item['end_date'] = trim($this->findCsvMatch($row, 'end_date'));
$this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
$this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0;
$this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num'));
$this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department'))));
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
$this->item['vip'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'autoassign_licenses')) ==1 ) ? '1' : 0;
$this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0;
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
$user_department = $this->findCsvMatch($row, 'department');
$user_department = trim($this->findCsvMatch($row, 'department'));
if ($this->shouldUpdateField($user_department)) {
$this->item['department_id'] = $this->createOrFetchDepartment($user_department);
}

View file

@ -91,7 +91,7 @@ class Asset extends Depreciable
protected $rules = [
'name' => 'max:255|nullable',
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL',
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array',
'status_id' => 'required|integer|exists:status_labels,id',
'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable|digits_between:0,240',
@ -100,7 +100,7 @@ class Asset extends Depreciable
'expected_checkin' => 'date|nullable',
'location_id' => 'exists:locations,id|nullable',
'rtd_location_id' => 'exists:locations,id|nullable',
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag',
'purchase_date' => 'date|date_format:Y-m-d|nullable',
'serial' => 'unique_serial|nullable',
'purchase_cost' => 'numeric|nullable|gte:0',
@ -212,6 +212,7 @@ class Asset extends Depreciable
$this->rules += $model->fieldset->validation_rules();
if ($this->model->fieldset){
foreach ($this->model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
@ -219,8 +220,7 @@ class Asset extends Depreciable
}
}
}
}
return parent::save($params);
}
@ -789,7 +789,6 @@ class Asset extends Depreciable
}
/**
* Get the next autoincremented asset tag
*
@ -952,6 +951,7 @@ class Asset extends Depreciable
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%')
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',

View file

@ -53,6 +53,12 @@ class CustomField extends Model
'field_encrypted' => 'nullable|boolean',
'auto_add_to_fieldsets' => 'boolean',
'show_in_listview' => 'boolean',
'show_in_requestable_list' => 'boolean',
'show_in_email' => 'boolean',
];
protected $casts = [
'show_in_requestable_list' => 'boolean',
];
/**
@ -72,7 +78,8 @@ class CustomField extends Model
'display_in_user_view',
'auto_add_to_fieldsets',
'show_in_listview',
'show_in_email',
'show_in_requestable_list',
];
/**
@ -243,8 +250,6 @@ class CustomField extends Model
/**
* Gets the DB column name.
*
* @todo figure out if this is still needed? I don't know WTF it's for.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return string

View file

@ -92,6 +92,8 @@ class CustomFieldset extends Model
array_push($rule, $field->attributes['format']);
$rules[$field->db_column_name()] = $rule;
//add not_array to rules for all fields
$rules[$field->db_column_name()][] = 'not_array';
}
return $rules;

View file

@ -14,6 +14,14 @@ class FieldOption {
public function getValue(Asset $asset) {
$dataPath = collect(explode('.', $this->dataSource));
// assignedTo directly on the asset is a special case where
// we want to avoid returning the property directly
// and instead return the entity's presented name.
if ($dataPath[0] === 'assignedTo'){
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
}
return $dataPath->reduce(function ($myValue, $path) {
try { return $myValue ? $myValue->{$path} : ${$myValue}; }
catch (\Exception $e) { return $myValue; }

View file

@ -32,6 +32,7 @@ class License extends Depreciable
protected $guarded = 'id';
protected $table = 'licenses';
protected $casts = [
'purchase_date' => 'date',
'expiration_date' => 'date',
@ -668,7 +669,7 @@ class License extends Depreciable
return self::whereNotNull('expiration_date')
->whereNull('deleted_at')
->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) '))
->whereRaw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) ')
->where('expiration_date', '>', date('Y-m-d'))
->orderBy('expiration_date', 'ASC')
->get();

View file

@ -92,6 +92,10 @@ class Setting extends Model
'google_client_secret',
];
protected $casts = [
'label2_asset_logo' => 'boolean',
];
/**
* Get the app settings.
* Cache is expired on Setting model saved in EventServiceProvider.

View file

@ -26,7 +26,6 @@ class CheckinAccessoryNotification extends Notification
$this->admin = $checkedInby;
$this->note = $note;
$this->settings = Setting::getSettings();
\Log::debug('Constructor for notification fired');
}
/**

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;

View file

@ -11,7 +11,7 @@ use Carbon\Carbon;
class AssetObserver
{
/**
* Listen to the User created event.
* Listen to the Asset updating event. This fires automatically every time an existing asset is saved.
*
* @param Asset $asset
* @return void
@ -137,14 +137,14 @@ class AssetObserver
public function saving(Asset $asset)
{
// determine if calculated eol and then calculate it - this should only happen on a new asset
if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && !is_null($asset->model->eol)){
if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && ($asset->model->eol > 0)){
$asset->asset_eol_date = $asset->purchase_date->addMonths($asset->model->eol)->format('Y-m-d');
$asset->eol_explicit = false;
}
// determine if explicit and set eol_explicit to true
if (!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) {
if($asset->model->eol) {
if($asset->model->eol > 0) {
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($months != $asset->model->eol) {
$asset->eol_explicit = true;
@ -153,7 +153,7 @@ class AssetObserver
} elseif (!is_null($asset->asset_eol_date) && is_null($asset->purchase_date)) {
$asset->eol_explicit = true;
}
if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol))) {
if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol) || ($asset->model->eol == 0))) {
$asset->eol_explicit = true;
}

View file

@ -104,7 +104,7 @@ class AssetModelPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.eol'),
'title' => trans('admin/hardware/form.eol_rate'),
'visible' => true,
],
[

View file

@ -173,7 +173,7 @@ class AssetPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('general.eol'),
'title' => trans('admin/hardware/form.eol_rate'),
],
[
'field' => 'asset_eol_date',

View file

@ -106,7 +106,7 @@ class LocationPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/locations/table.address'),
'title' => trans('admin/locations/table.address2'),
'visible' => false,
],
[

View file

@ -48,6 +48,8 @@ class ValidationServiceProvider extends ServiceProvider
// Unique only if undeleted
// This works around the use case where multiple deleted items have the same unique attribute.
// (I think this is a bug in Laravel's validator?)
// $parameters is the rule parameters, like `unique_undeleted:users,id` - $parameters[0] is users, $parameters[1] is id
// the UniqueUndeletedTrait prefills these so you can just use `unique_undeleted` in your rules (but this would only work directly in the model)
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) {
$count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
@ -232,6 +234,10 @@ class ValidationServiceProvider extends ServiceProvider
return true;
}
});
Validator::extend('not_array', function ($attribute, $value, $parameters, $validator) {
return !is_array($value);
});
}
/**

View file

@ -103,19 +103,12 @@ class Label implements View
$logo = null;
// Should we be trying to use a logo at all?
if ($settings->label2_asset_logo='1') {
// If we don't have a company image, fall back to the general site label image
if (!empty($settings->label_logo)) {
$logo = Storage::disk('public')->path('/'.e($settings->label_logo));
}
// If we have a company logo, use that first
if (($asset->company) && ($asset->company->image!='')) {
// Should we use the assets assigned company logo? (A.K.A. "Is `Labels > Use Asset Logo` enabled?"), and do we have a company logo?
if ($settings->label2_asset_logo && $asset->company && $asset->company->image!='') {
$logo = Storage::disk('public')->path('companies/'.e($asset->company->image));
}
} elseif (!empty($settings->label_logo)) {
// Use the general site label logo, if available
$logo = Storage::disk('public')->path('/'.e($settings->label_logo));
}
if (!empty($logo)) {

View file

@ -17,7 +17,7 @@
}
],
"require": {
"php": "8.1 - 8.2",
"php": "^8.1",
"ext-curl": "*",
"ext-fileinfo": "*",
"ext-json": "*",

6
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6a7666957fcf514f54340bf80ad18726",
"content-hash": "6f3c589a1c2a3a3dc24535abba26e1a6",
"packages": [
{
"name": "alek13/slack",
@ -15699,7 +15699,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "8.0 - 8.2",
"php": "^8.1",
"ext-curl": "*",
"ext-fileinfo": "*",
"ext-json": "*",
@ -15707,5 +15707,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View file

@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v6.2.3',
'full_app_version' => 'v6.2.3 - build 11759-g8c4bf74f9',
'build_version' => '11759',
'full_app_version' => 'v6.2.3 - build 11936-gb47e734b3',
'build_version' => '11936',
'prerelease_version' => '',
'hash_version' => 'g8c4bf74f9',
'full_hash' => 'v6.2.3-42-g8c4bf74f9',
'hash_version' => 'gb47e734b3',
'full_hash' => 'v6.2.3-175-gb47e734b3',
'branch' => 'develop',
);

View file

@ -38,7 +38,7 @@ class ActionlogFactory extends Factory
{
return $this->state(function () {
$target = User::inRandomOrder()->first();
$asset = Asset::RTD()->inRandomOrder()->first();
$asset = Asset::inRandomOrder()->RTD()->first();
$asset->update(
[

View file

@ -26,6 +26,7 @@ class CustomFieldFactory extends Factory
'format' => '',
'element' => 'text',
'auto_add_to_fieldsets' => '0',
'show_in_requestable_list' => '0',
];
}
@ -66,6 +67,7 @@ class CustomFieldFactory extends Factory
return [
'name' => 'CPU',
'help_text' => 'The speed of the processor on this device.',
'show_in_requestable_list' => '1',
];
});
}
@ -79,4 +81,28 @@ class CustomFieldFactory extends Factory
];
});
}
public function testEncrypted()
{
return $this->state(function () {
return [
'name' => 'Test Encrypted',
'field_encrypted' => '1',
'help_text' => 'This is a sample encrypted field.',
];
});
}
public function testCheckbox()
{
return $this->state(function () {
return [
'name' => 'Test Checkbox',
'help_text' => 'This is a sample checkbox.',
'field_values' => "One\nTwo\nThree",
'element' => 'checkbox',
];
});
}
}

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddShowInRequestableToCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fields', function (Blueprint $table) {
$table->boolean('show_in_requestable_list')->after('show_in_email')->nullable()->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('custom_fields', function (Blueprint $table) {
if (Schema::hasColumn('custom_fields', 'show_in_requestable_list')) {
$table->dropColumn('show_in_requestable_list');
}
});
}
}

View file

@ -33,6 +33,9 @@ class CustomFieldSeeder extends Seeder
CustomField::factory()->count(1)->ram()->create();
CustomField::factory()->count(1)->cpu()->create();
CustomField::factory()->count(1)->macAddress()->create();
CustomField::factory()->count(1)->testEncrypted()->create();
CustomField::factory()->count(1)->testCheckbox()->create();
DB::table('custom_field_custom_fieldset')->insert([
[
@ -66,6 +69,33 @@ class CustomFieldSeeder extends Seeder
'required' => 0,
],
[
'custom_field_id' => '6',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '6',
'custom_fieldset_id' => '1',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '7',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '7',
'custom_fieldset_id' => '1',
'order' => 0,
'required' => 0,
],
]);
}
}

View file

@ -38,12 +38,13 @@ class DatabaseSeeder extends Seeder
$this->call(DepreciationSeeder::class);
$this->call(StatuslabelSeeder::class);
$this->call(AccessorySeeder::class);
$this->call(CustomFieldSeeder::class);
$this->call(AssetSeeder::class);
$this->call(LicenseSeeder::class);
$this->call(ComponentSeeder::class);
$this->call(ConsumableSeeder::class);
$this->call(ActionlogSeeder::class);
$this->call(CustomFieldSeeder::class);
Artisan::call('snipeit:sync-asset-locations', ['--output' => 'all']);
$output = Artisan::output();

View file

@ -9,7 +9,7 @@ services:
ports:
- "8000:80"
volumes:
- ./logs:/var/www/html/storage/logs
- ./storage/logs:/var/www/html/storage/logs
depends_on:
- mariadb
- redis

View file

@ -3,6 +3,7 @@
return array(
'does_not_exist' => 'The accessory [:id] does not exist.',
'not_found' => 'That accessory was not found.',
'assoc_users' => 'This accessory currently has :count items checked out to users. Please check in the accessories and and try again. ',
'create' => array(

View file

@ -34,7 +34,8 @@ return [
'create_field' => 'New Custom Field',
'create_field_title' => 'Create a new custom field',
'value_encrypted' => 'The value of this field is encrypted in the database. Only admin users will be able to view the decrypted value',
'show_in_email' => 'Include the value of this field in checkout emails sent to the user? Encrypted fields cannot be included in emails.',
'show_in_email' => 'Include the value of this field in checkout emails sent to the user? Encrypted fields cannot be included in emails',
'show_in_email_short' => 'Include in emails.',
'help_text' => 'Help Text',
'help_text_description' => 'This is optional text that will appear below the form elements while editing an asset to provide context on the field.',
'about_custom_fields_title' => 'About Custom Fields',
@ -51,7 +52,10 @@ return [
'display_in_user_view_table' => 'Visible to User',
'auto_add_to_fieldsets' => 'Automatically add this to every new fieldset',
'add_to_preexisting_fieldsets' => 'Add to any existing fieldsets',
'show_in_listview' => 'Show in list views by default. Authorized users will still be able to show/hide via the column selector.',
'show_in_listview' => 'Show in list views by default. Authorized users will still be able to show/hide via the column selector',
'show_in_listview_short' => 'Show in lists',
'show_in_requestable_list_short' => 'Show in requestable assets list',
'show_in_requestable_list' => 'Show value in requestable assets list. Encrypted fields will not be shown',
'encrypted_options' => 'This field is encrypted, so some display options will not be available.',
];

View file

@ -49,6 +49,7 @@ return [
'asset_location' => 'Update Asset Location',
'asset_location_update_default_current' => 'Update default location AND actual location',
'asset_location_update_default' => 'Update only default location',
'asset_location_update_actual' => 'Update only actual location',
'asset_not_deployable' => 'That asset status is not deployable. This asset cannot be checked out.',
'asset_deployable' => 'That status is deployable. This asset can be checked out.',
'processing_spinner' => 'Processing... (This might take a bit of time on large files)',

View file

@ -11,6 +11,7 @@ return [
'create' => [
'error' => 'Asset was not created, please try again. :(',
'success' => 'Asset created successfully. :)',
'success_linked' => 'Asset with tag :tag was created successfully. <strong><a href=":link" style="color: white;">Click here to view</a></strong>.',
],
'update' => [

View file

@ -15,6 +15,7 @@ return [
'print_all_assigned' => 'Print All Assigned',
'name' => 'Location Name',
'address' => 'Address',
'address2' => 'Address Line 2',
'zip' => 'Postal Code',
'locations' => 'Locations',
'parent' => 'Parent',

View file

@ -6,5 +6,12 @@ return [
'send_reminder' => 'Send reminder',
'reminder_sent' => 'Reminder sent',
'acceptance_deleted' => 'Acceptance request deleted',
'acceptance_request' => 'Acceptance request'
'acceptance_request' => 'Acceptance request',
'custom_export' => [
'user_address' => 'User Address',
'user_city' => 'User City',
'user_state' => 'User State',
'user_country' => 'User Country',
'user_zip' => 'User Zip'
]
];

View file

@ -8,6 +8,7 @@ return array(
'user_exists' => 'User already exists!',
'user_not_found' => 'User does not exist.',
'user_login_required' => 'The login field is required',
'user_has_no_assets_assigned' => 'No assets currently assigned to user.',
'user_password_required' => 'The password is required.',
'insufficient_permissions' => 'Insufficient Permissions.',
'user_deleted_warning' => 'This user has been deleted. You will have to restore this user to edit them or assign them new assets.',

View file

@ -368,7 +368,7 @@ return [
'consumables_count' => 'Consumables Count',
'components_count' => 'Components Count',
'licenses_count' => 'Licenses Count',
'notification_error' => 'Error:',
'notification_error' => 'Error',
'notification_error_hint' => 'Please check the form below for errors',
'notification_bulk_error_hint' => 'The following fields had validation errors and were not edited:',
'notification_success' => 'Success',

View file

@ -31,4 +31,5 @@ return [
'depreciations' => 'You can set up asset depreciations to depreciate assets based on straight-line depreciation.',
'empty_file' => 'The importer detects that this file is empty.'
];

View file

@ -95,6 +95,7 @@ return [
'url' => 'The :attribute format is invalid.',
'unique_undeleted' => 'The :attribute must be unique.',
'non_circular' => 'The :attribute must not create a circular reference.',
'not_array' => 'The :attribute field cannot be an array.',
'disallow_same_pwd_as_user_fields' => 'Password cannot be the same as the username.',
'letters' => 'Password must contain at least one letter.',
'numbers' => 'Password must contain at least one number.',

View file

@ -24,7 +24,7 @@
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-refresh="true"
data-show-refresh="false"
data-sort-order="asc"
id="pendingAcceptances"
class="table table-striped snipe-table"

View file

@ -65,6 +65,12 @@
<th class="col-md-2" data-field="location" data-sortable="true">{{ trans('admin/hardware/table.location') }}</th>
<th class="col-md-2" data-field="status" data-sortable="true">{{ trans('admin/hardware/table.status') }}</th>
<th class="col-md-2" data-field="expected_checkin" data-formatter="dateDisplayFormatter" data-sortable="true">{{ trans('admin/hardware/form.expected_checkin') }}</th>
@foreach(\App\Models\CustomField::get() as $field)
@if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1'))
<th class="col-md-2" data-field="custom_fields.{{ $field->db_column }}" data-sortable="true">{{ $field->name }}</th>
@endif
@endforeach
<th class="col-md-1" data-formatter="assetRequestActionsFormatter" data-field="actions" data-sortable="false">{{ trans('table.actions') }}</th>
</tr>
</thead>

View file

@ -32,13 +32,19 @@
}'>
<thead>
<tr>
<th class="col-md-1" data-field="image" data-formatter="imageFormatter">{{ trans('general.image') }}</th>
<th class="col-md-2" data-field="name">{{ trans('general.item_name') }}</th>
<th class="col-md-2" data-field="type">{{ trans('general.type') }}</th>
<th class="col-md-2" data-field="qty">{{ trans('general.qty') }}</th>
<th class="col-md-2" data-field="location">{{ trans('admin/hardware/table.location') }}</th>
<th class="col-md-2" data-field="expected_checkin" data-formatter="dateDisplayFormatter"> {{ trans('admin/hardware/form.expected_checkin') }}</th>
<th class="col-md-2" data-field="request_date" data-formatter="dateDisplayFormatter"> {{ trans('general.requested_date') }}</th>
<th data-field="image" data-formatter="imageFormatter">{{ trans('general.image') }}</th>
<th data-field="name">{{ trans('general.item_name') }}</th>
<th data-field="type">{{ trans('general.type') }}</th>
<th data-field="qty">{{ trans('general.qty') }}</th>
<th data-field="location">{{ trans('admin/hardware/table.location') }}</th>
<th data-field="expected_checkin" data-formatter="dateDisplayFormatter"> {{ trans('admin/hardware/form.expected_checkin') }}</th>
<th data-field="request_date" data-formatter="dateDisplayFormatter"> {{ trans('general.requested_date') }}</th>
@foreach(\App\Models\CustomField::get() as $field)
@if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1'))
<th data-field="custom_fields.{{ $field->db_column }}">{{ $field->name }}</th>
@endif
@endforeach
</tr>
</thead>
</table>

View file

@ -388,7 +388,7 @@
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-show-refresh="false"
data-sort-order="asc"
id="userAssets"
class="table table-striped snipe-table"
@ -478,7 +478,7 @@
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-refresh="true"
data-show-refresh="false"
data-sort-order="asc"
id="userLicenses"
class="table table-striped snipe-table"
@ -525,7 +525,7 @@
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-show-refresh="false"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table table-hover"
@ -576,7 +576,7 @@
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-show-refresh="false"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table table-hover"

View file

@ -88,7 +88,7 @@
}
@endphp
<div class="col-md-8 required">
{{ Form::select("format",Helper::predefined_formats(), ($field_format == '') ? $field->format : $field_format, array('class'=>'format select2 form-control', 'aria-label'=>'format')) }}
{{ Form::select("format",Helper::predefined_formats(), ($field_format == '') ? $field->format : $field_format, array('class'=>'format select2 form-control', 'aria-label'=>'format', 'style' => 'width:100%;')) }}
{!! $errors->first('format', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
</div>
@ -118,23 +118,20 @@
</div>
</div>
<!-- Set up checkbox form group -->
<div class="form-group">
<!-- Auto-Add to Future Fieldsets -->
<div class="form-group {{ $errors->has('auto_add_to_fieldsets') ? ' has-error' : '' }}" id="auto_add_to_fieldsets">
<!-- Encrypted warning callout box -->
@if (($field->id) && ($field->field_encrypted=='1'))
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="auto_add_to_fieldsets" aria-label="auto_add_to_fieldsets" value="1"{{ (old('auto_add_to_fieldsets') || $field->auto_add_to_fieldsets) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.auto_add_to_fieldsets') }}
</label>
<div class="alert alert-warning fade in">
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
<strong>{{ trans('general.notification_warning') }}:</strong>
{{ trans('admin/custom_fields/general.encrypted_options') }}
</div>
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="show_in_listview" aria-label="show_in_listview" value="1"{{ (old('show_in_listview') || $field->show_in_listview) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_listview') }}
</label>
</div>
@endif
@if (!$field->id)
<!-- Encrypted -->
@ -144,37 +141,69 @@
{{ trans('admin/custom_fields/general.encrypt_field') }}
</label>
</div>
<div class="col-md-9 col-md-offset-3" id="encrypt_warning" style="display:none;">
<div class="callout callout-danger">
<p><i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ trans('admin/custom_fields/general.encrypt_field_help') }}</p>
</div>
</div>
@endif
<!-- Show in Email -->
<!-- Auto-Add to Future Fieldsets -->
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="auto_add_to_fieldsets" aria-label="auto_add_to_fieldsets" value="1"{{ (old('auto_add_to_fieldsets') || $field->auto_add_to_fieldsets) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.auto_add_to_fieldsets') }}
</label>
</div>
<!-- Show in list view -->
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="show_in_listview" aria-label="show_in_listview" value="1"{{ (old('show_in_listview') || $field->show_in_listview) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_listview') }}
</label>
</div>
@if ((!$field->id) || ($field->field_encrypted=='0'))
<!-- Show in requestable list view -->
<div class="col-md-9 col-md-offset-3" id="show_in_requestable_list">
<label class="form-control">
<input type="checkbox" name="show_in_requestable_list" aria-label="show_in_requestable_list" value="1"{{ (old('show_in_requestable_list') || $field->show_in_requestable_list) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_requestable_list') }}
</label>
</div>
<!-- Show in Email -->
<div class="col-md-9 col-md-offset-3" id="show_in_email">
<label class="form-control">
<input type="checkbox" name="show_in_email" aria-label="show_in_email" value="1"{{ (old('show_in_email') || $field->show_in_email) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.show_in_email') }}
</label>
</div>
<!-- Value Must be Unique -->
<div class="col-md-9 col-md-offset-3" id="is_unique">
<label class="form-control">
<input type="checkbox" name="is_unique" aria-label="is_unique" value="1"{{ (old('is_unique') || $field->is_unique) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.is_unique') }}
</label>
</div>
@endif
<!-- Show in View All Assets profile view -->
<div class="col-md-9 col-md-offset-3">
<div class="col-md-9 col-md-offset-3" id="display_in_user_view">
<label class="form-control">
<input type="checkbox" name="display_in_user_view" aria-label="display_in_user_view" value="1" {{ (old('display_in_user_view') || $field->display_in_user_view) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.display_in_user_view') }}
</label>
</div>
<!-- Value Must be Unique -->
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="is_unique" aria-label="is_unique" value="1"{{ (old('is_unique') || $field->is_unique) ? ' checked="checked"' : '' }}>
{{ trans('admin/custom_fields/general.is_unique') }}
</label>
</div>
</div>
@ -287,11 +316,13 @@
$("#show_in_email").hide();
$("#display_in_user_view").hide();
$("#is_unique").hide();
$("#show_in_requestable_list").hide();
} else {
$("#encrypt_warning").hide();
$("#show_in_email").show();
$("#display_in_user_view").show();
$("#is_unique").show();
$("#show_in_requestable_list").show();
}
});

View file

@ -145,12 +145,14 @@
<th data-sortable="true"><i class="fa fa-lock" aria-hidden="true"></i>
<span class="hidden-xs hidden-sm hidden-md hidden-lg">{{ trans('admin/custom_fields/general.encrypted') }}</span>
</th>
<th data-sortable="true"><i class="fa fa-list" aria-hidden="true"></i>
<th data-sortable="true" class="text-center"><i class="fa fa-list" aria-hidden="true"></i>
<span class="hidden-xs hidden-sm hidden-md hidden-lg">{{ trans('admin/custom_fields/general.show_in_listview_short') }}</span>
</th>
<th data-visible="false" data-sortable="true"><i class="fa fa-eye" aria-hidden="true"><span class="sr-only">Visible to User</span></i></th>
<th data-sortable="true" data-searchable="true"><i class="fa fa-envelope" aria-hidden="true"><span class="sr-only">Show in Email</span></i></th>
<th data-sortable="true" data-searchable="true">{{ trans('admin/custom_fields/general.field_element_short') }}</th>
<th data-visible="false" data-sortable="true" class="text-center"><i class="fa fa-eye" aria-hidden="true"><span class="sr-only">Visible to User</span></i></th>
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa fa-envelope" aria-hidden="true"><span class="sr-only">{{ trans('admin/custom_fields/general.show_in_email_short') }}</span></i></th>
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa fa-laptop fa-fw" aria-hidden="true"><span class="sr-only">{{ trans('admin/custom_fields/general.show_in_requestable_list_short') }}</span></i></th>
<th data-sortable="true" data-searchable="true" class="text-center"><i class="fa-solid fa-fingerprint"><span class="sr-only">{{ trans('admin/custom_fields/general.unique') }}</span></i></th>
<th data-sortable="true" data-searchable="true" class="text-center">{{ trans('admin/custom_fields/general.field_element_short') }}</th>
<th data-searchable="true">{{ trans('admin/custom_fields/general.fieldsets') }}</th>
<th>{{ trans('button.actions') }}</th>
</tr>
@ -161,7 +163,7 @@
<td>{{ $field->name }}</td>
<td>{{ $field->help_text }}</td>
<td class='text-center'>{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td class="text-center">{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td>
<code>{{ $field->convertUnicodeDbSlug() }}</code>
@if ($field->convertUnicodeDbSlug()!=$field->db_column)
@ -170,10 +172,12 @@
@endif
</td>
<td>{{ $field->format }}</td>
<td>{!! ($field->field_encrypted=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td>{!! ($field->show_in_listview=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td>{!! ($field->display_in_user_view=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td class='text-center'>{!! ($field->show_in_email=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td class="text-center">{!! ($field->field_encrypted=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td class="text-center">{!! ($field->show_in_listview=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td class="text-center">{!! ($field->display_in_user_view=='1' ? '<i class="fa fa-check text-success"></i>' : '<i class="fa fa-times text-danger"></i>') !!}</td>
<td class="text-center">{!! ($field->show_in_email=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td class="text-center">{!! ($field->show_in_requestable_list=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td class="text-center">{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
<td>{{ $field->element }}</td>
<td>
@foreach($field->fieldset as $fieldset)

View file

@ -274,10 +274,80 @@
</div> <!--/row-->
<div class="row">
<div class="col-md-6">
@if ($snipeSettings->full_multiple_companies_support=='1')
<!-- Companies -->
<div class="box box-default">
<div class="box-header with-border">
<h2 class="box-title">{{ trans('general.companies') }}</h2>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse">
<i class="fas fa-minus" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.collapse') }}</span>
</button>
</div>
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table
data-cookie-id-table="dashCompanySummary"
data-height="400"
data-pagination="true"
data-side-pagination="server"
data-sort-order="desc"
data-sort-field="assets_count"
id="dashCompanySummary"
class="table table-striped snipe-table"
data-url="{{ route('api.companies.index', ['sort' => 'assets_count', 'order' => 'asc']) }}">
<thead>
<tr>
<th class="col-sm-3" data-visible="true" data-field="name" data-formatter="companiesLinkFormatter" data-sortable="true">{{ trans('general.name') }}</th>
<th class="col-sm-1" data-visible="true" data-field="users_count" data-sortable="true">
<i class="fas fa-users" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.people') }}</span>
</th>
<th class="col-sm-1" data-visible="true" data-field="assets_count" data-sortable="true">
<i class="fas fa-barcode" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.asset_count') }}</span>
</th>
<th class="col-sm-1" data-visible="true" data-field="accessories_count" data-sortable="true">
<i class="far fa-keyboard" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.accessories_count') }}</span>
</th>
<th class="col-sm-1" data-visible="true" data-field="consumables_count" data-sortable="true">
<i class="fas fa-tint" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.consumables_count') }}</span>
</th>
<th class="col-sm-1" data-visible="true" data-field="components_count" data-sortable="true">
<i class="far fa-hdd" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.components_count') }}</span>
</th>
<th class="col-sm-1" data-visible="true" data-field="licenses_count" data-sortable="true">
<i class="far fa-save" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.licenses_count') }}</span>
</th>
</tr>
</thead>
</table>
</div>
</div> <!-- /.col -->
<div class="text-center col-md-12" style="padding-top: 10px;">
<a href="{{ route('companies.index') }}" class="btn btn-primary btn-sm" style="width: 100%">{{ trans('general.viewall') }}</a>
</div>
</div> <!-- /.row -->
</div><!-- /.box-body -->
</div> <!-- /.box -->
@else
<!-- Locations -->
<div class="box box-default">
<div class="box-header with-border">
<h2 class="box-title">{{ trans('general.asset') }} {{ trans('general.locations') }}</h2>
<h2 class="box-title">{{ trans('general.locations') }}</h2>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse">
<i class="fas fa-minus" aria-hidden="true"></i>
@ -331,6 +401,9 @@
</div><!-- /.box-body -->
</div> <!-- /.box -->
@endif
</div>
<div class="col-md-6">

View file

@ -62,7 +62,7 @@
</div>
<div class="col-md-5">
<label class="form-control">
{{ Form::checkbox('null_expected_checkin_date', '1', false, ['checked' => 'false']) }}
{{ Form::checkbox('null_expected_checkin_date', '1', false) }}
{{ trans_choice('general.set_to_null', count($assets), ['asset_count' => count($assets)]) }}
</label>
</div>
@ -96,6 +96,10 @@
{{ Form::radio('update_real_loc', '0', old('update_real_loc'), ['aria-label'=>'update_default_loc']) }}
{{ trans('admin/hardware/form.asset_location_update_default') }}
</label>
<label class="form-control">
{{ Form::radio('update_real_loc', '2', old('update_real_loc'), ['aria-label'=>'update_default_loc']) }}
{{ trans('admin/hardware/form.asset_location_update_actual') }}
</label>
</div>
</div> <!--/form-group-->

View file

@ -34,7 +34,7 @@
{{ trans('general.assets') }}
@if (Request::has('order_number'))
: Order #{{ Request::get('order_number') }}
: Order #{{ strval(Request::get('order_number')) }}
@endif
@stop
@ -88,7 +88,7 @@
class="table table-striped snipe-table"
data-url="{{ route('api.assets.index',
array('status' => e(Request::get('status')),
'order_number'=>e(Request::get('order_number')),
'order_number'=>e(strval(Request::get('order_number'))),
'company_id'=>e(Request::get('company_id')),
'status_id'=>e(Request::get('status_id')))) }}"
data-export-options='{

View file

@ -631,7 +631,7 @@
</div>
@endif
@if (($asset->model) && ($asset->model->eol))
@if (($asset->asset_eol_date) && ($asset->purchase_date))
<div class="row">
<div class="col-md-2">
<strong>
@ -639,7 +639,7 @@
</strong>
</div>
<div class="col-md-6">
{{ $asset->model->eol }}
{{ Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date) }}
{{ trans('admin/hardware/form.months') }}
</div>
@ -650,6 +650,9 @@
<div class="col-md-2">
<strong>
{{ trans('admin/hardware/form.eol_date') }}
@if ($asset->purchase_date)
{!! $asset->asset_eol_date < date("Y-m-d") ? '<i class="fas fa-exclamation-triangle text-orange" aria-hidden="true"></i>' : '' !!}
@endif
</strong>
</div>
<div class="col-md-6">

View file

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ ($snipeSettings) && ($snipeSettings->site_name) ? $snipeSettings->site_name : 'Snipe-IT' }}</title>
<link rel="shortcut icon" type="image/ico" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url('').e($snipeSettings->favicon) : 'favicon.ico' }} ">
<link rel="shortcut icon" type="image/ico" href="{{ ($snipeSettings) && ($snipeSettings->favicon!='') ? Storage::disk('public')->url(e($snipeSettings->favicon)) : config('app.url').'/favicon.ico' }}">
{{-- stylesheets --}}
<link rel="stylesheet" href="{{ url(mix('css/dist/all.css')) }}">

View file

@ -79,7 +79,7 @@
<div class="input-group col-md-4">
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="expiration_date" id="expiration_date" value="{{ old('expiration_date', $item->expiration_date) }}" maxlength="10">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="expiration_date" id="expiration_date" value="{{ old('expiration_date', ($item->expiration_date) ? $item->expiration_date->format('Y-m-d') : '') }}" maxlength="10">
<span class="input-group-addon"><i class="fas fa-calendar" aria-hidden="true"></i></span>
</div>
{!! $errors->first('expiration_date', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
@ -93,7 +93,7 @@
<div class="input-group col-md-4">
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="termination_date" id="termination_date" value="{{ old('termination_date', $item->termination_date) }}" maxlength="10">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="termination_date" id="termination_date" value="{{ old('termination_date', ($item->termination_date) ? $item->termination_date->format('Y-m-d') : '') }}" maxlength="10">
<span class="input-group-addon"><i class="fas fa-calendar" aria-hidden="true"></i></span>
</div>
{!! $errors->first('termination_date', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}

View file

@ -235,9 +235,16 @@
])
}}
</div>
@if ($activeFile->first_row)
<div class="col-md-5">
<p class="form-control-static">{{ str_limit($activeFile->first_row[$index], 50, '...') }}</p>
</div>
@else
@php
$statusText = trans('help.empty_file');
$statusType = 'info';
@endphp
@endif
</div><!-- /div row -->
@endforeach
@else

View file

@ -104,6 +104,17 @@
</span>
</a>
</li>
<li>
<a href="#history" data-toggle="tab">
<span class="hidden-lg hidden-md">
<i class="fas fa-hdd fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.history') }}
</span>
</a>
</li>
</ul>
@ -319,6 +330,51 @@
</div><!-- /.table-responsive -->
</div><!-- /.tab-pane -->
<div class="tab-pane" id="history">
<h2 class="box-title">{{ trans('general.history') }}</h2>
<!-- checked out assets table -->
<div class="row">
<div class="col-md-12">
<table
class="table table-striped snipe-table"
id="assetHistory"
data-pagination="true"
data-id-table="assetHistory"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-location-asset-{{ $location->id }}-history",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-url="{{ route('api.activity.index', ['target_id' => $location->id, 'target_type' => 'location']) }}"
data-cookie-id-table="assetHistory"
data-cookie="true">
<thead>
<tr>
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
<th class="col-sm-2" data-visible="true" data-field="action_date" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
<th class="col-sm-2" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
<th class="col-sm-2" data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
<th class="col-sm-2" data-field="note">{{ trans('general.notes') }}</th>
<th class="col-md-3" data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
<th class="col-md-3" data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
<th class="col-sm-2" data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">{{ trans('admin/hardware/table.changed')}}</th>
</tr>
</thead>
</table>
</div>
</div> <!-- /.row -->
</div> <!-- /.tab-pane history -->
</div><!--/.col-md-9-->
</div><!--/.col-md-9-->
</div><!--/.col-md-9-->

View file

@ -46,7 +46,7 @@
data-sort-order="asc"
id="manufacturersTable"
class="table table-striped snipe-table"
data-url="{{route('api.manufacturers.index', ['deleted' => e(Request::get('deleted')) ]) }}"
data-url="{{route('api.manufacturers.index', ['deleted' => (request('deleted')=='true') ? 'true' : 'false' ]) }}"
data-export-options='{
"fileName": "export-manufacturers-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]

View file

@ -37,21 +37,20 @@
@elseif ($field->element=='checkbox')
<!-- Checkboxes -->
@foreach ($field->formatFieldValuesAsArray() as $key => $value)
<div>
<label>
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" class="minimal" {{ isset($item) ? (in_array($value, array_map('trim', explode(',', $item->{$field->db_column_name()}))) ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($key, array_map('trim', explode(',', $field->defaultValue($model->id)))) ? ' checked="checked"' : '')) }}>
<label class="form-control">
<input type="checkbox" value="{{ $value }}" name="{{ $field->db_column_name() }}[]" {{ isset($item) ? (in_array($value, array_map('trim', explode(',', $item->{$field->db_column_name()}))) ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($key, array_map('trim', explode(',', $field->defaultValue($model->id)))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@endforeach
@elseif ($field->element=='radio')
@foreach ($field->formatFieldValuesAsArray() as $value)
<div>
<label>
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" class="minimal" {{ isset($item) ? ($item->{$field->db_column_name()} == $value ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($value, explode(', ', $field->defaultValue($model->id))) ? ' checked="checked"' : '')) }}>
<label class="form-control">
<input type="radio" value="{{ $value }}" name="{{ $field->db_column_name() }}" {{ isset($item) ? ($item->{$field->db_column_name()} == $value ? ' checked="checked"' : '') : (Request::old($field->db_column_name()) != '' ? ' checked="checked"' : (in_array($value, explode(', ', $field->defaultValue($model->id))) ? ' checked="checked"' : '')) }}>
{{ $value }}
</label>
</div>
@endforeach
@endif

View file

@ -236,6 +236,12 @@
</li>
@endif
@if ($model->min_amt)
<li>{{ trans('general.min_amt') }}:
{{$model->min_amt }}
</li>
@endif
@if ($model->manufacturer)
<li>
{{ trans('general.manufacturer') }}:

View file

@ -35,13 +35,25 @@
@endif
@if ($message = Session::get('success-unescaped'))
<div class="col-md-12">
<div class="alert alert-success fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-check faa-pulse animated"></i>
<strong>{{ trans('general.notification_success') }}: </strong>
{!! $message !!}
</div>
</div>
@endif
@if ($assets = Session::get('assets'))
@foreach ($assets as $asset)
<div class="col-md-12">
<div class="alert alert-info fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-info-circle faa-pulse animated"></i>
<strong>{{ trans('general.asset_information') }} </strong>
<strong>{{ trans('general.asset_information') }}:</strong>
<ul>
@isset ($asset->model->name)
<li><b>{{ trans('general.model_name') }} </b> {{ $asset->model->name }}</li>
@ -67,7 +79,7 @@
<div class="alert alert-info fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-info-circle faa-pulse animated"></i>
<strong>{{ trans('general.consumable_information') }}</strong>
<strong>{{ trans('general.consumable_information') }}: </strong>
<ul><li><b>{{ trans('general.consumable_name') }}</b> {{ $consumable->name }}</li></ul>
</div>
</div>
@ -81,7 +93,7 @@
<div class="alert alert-info fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-info-circle faa-pulse animated"></i>
<strong>{{ trans('general.accessory_information') }} </strong>
<strong>{{ trans('general.accessory_information') }}:</strong>
<ul><li><b>{{ trans('general.accessory_name') }}</b> {{ $accessory->name }}</li></ul>
</div>
</div>
@ -94,7 +106,7 @@
<div class="alert alert alert-danger fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
<strong>{{ trans('general.error') }} </strong>
<strong>{{ trans('general.error') }}: </strong>
{{ $message }}
</div>
</div>
@ -107,7 +119,7 @@
<div class="alert alert alert-danger fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
<strong>{{ trans('general.notification_error') }} </strong>
<strong>{{ trans('general.notification_error') }}: </strong>
{{ $message }}
</div>
</div>
@ -115,17 +127,19 @@
@endif
@if ($messages = Session::get('bulk_errors'))
@if ($messages = Session::get('bulk_asset_errors'))
<div class="col-md-12">
<div class="alert alert alert-danger fade in">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
<strong>{{ trans('general.notification_error') }}: </strong>
{{ trans('general.notification_bulk_error_hint') }}
@foreach($messages as $message)
@foreach($messages as $key => $message)
@for ($x = 0; $x < count($message); $x++)
<ul>
<li>{{ $message }}</li>
<li>{{ $message[$x] }}</li>
</ul>
@endfor
@endforeach
</div>
</div>

View file

@ -530,15 +530,37 @@
function changeLogFormatter(value) {
var result = '';
var pretty_index = '';
for (var index in value) {
result += index + ': <del>' + value[index].old + '</del> <i class="fas fa-long-arrow-alt-right" aria-hidden="true"></i> ' + value[index].new + '<br>'
// Check if it's a custom field
if (index.startsWith('_snipeit_')) {
pretty_index = index.replace("_snipeit_", "Custom:_");
} else {
pretty_index = index;
}
extra_pretty_index = prettyLog(pretty_index);
result += extra_pretty_index + ': <del>' + value[index].old + '</del> <i class="fas fa-long-arrow-alt-right" aria-hidden="true"></i> ' + value[index].new + '<br>'
}
return result;
}
function prettyLog(str) {
let frags = str.split('_');
for (let i = 0; i < frags.length; i++) {
frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1);
}
return frags.join(' ');
}
// Create a linked phone number in the table list
function phoneFormatter(value) {

View file

@ -1,9 +1,9 @@
<!-- Purchase Date -->
<!-- EOL Date -->
<div class="form-group {{ $errors->has('asset_eol_date') ? ' has-error' : '' }}">
<label for="asset_eol_date" class="col-md-3 control-label">{{ trans('admin/hardware/form.eol_date') }}</label>
<div class="input-group col-md-4">
<div class="input-group date" data-provide="datepicker" data-date-clear-btn="true" data-date-format="yyyy-mm-dd" data-autoclose="true">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="asset_eol_date" id="asset_eol_date" readonly value="{{ old('asset_eol_date', optional($item->asset_eol_date)->format('Y-m-d') ?? $item->present()->eol_date() ?? '') }}" style="background-color:inherit">
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="asset_eol_date" id="asset_eol_date" readonly value="{{ old('asset_eol_date', optional($item->asset_eol_date)->format('Y-m-d') ?? $item->asset_eol_date ?? '') }}" style="background-color:inherit" />
<span class="input-group-addon"><i class="fas fa-calendar" aria-hidden="true"></i></span>
</div>
{!! $errors->first('asset_eol_date', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}

View file

@ -304,7 +304,54 @@
<label style="grid-area: label-title">Label</label>
<input style="grid-area: label-field" x-model="option.label" />
<label style="grid-area: source-title">DataSource</label>
<input style="grid-area: source-field" x-model="option.datasource" />
<select style="grid-area: source-field" x-model="option.datasource">
<optgroup label="Asset">
<option value="asset_tag">Asset Tag</option>
<option value="name">Asset Name</option>
<option value="serial">Asset Serial</option>
<option value="asset_eol_date">Asset EOL Date</option>
<option value="order_number">Asset Order Number</option>
<option value="purchase_date">Asset Purchase Date</option>
<option value="assignedTo">Assigned To</option>
</optgroup>
<optgroup label="Asset Model">
<option value="model.name">Asset Model Name</option>
<option value="model.model_number">Asset Model Number</option>
</optgroup>
<optgroup label="Manufacturer">
<option value="model.manufacturer.name">Manufacturer Name</option>
<option value="model.manufacturer.support_email">Manufacturer Support Email</option>
<option value="model.manufacturer.support_phone">Manufacturer Support Phone</option>
<option value="model.manufacturer.support_url">Manufacturer Support URL</option>
</optgroup>
<optgroup label="Category">
<option value="model.category.name">Category Name</option>
</optgroup>
<optgroup label="Status">
<option value="assetstatus.name">Status</option>
</optgroup>
<optgroup label="Supplier">
<option value="supplier.name">Supplier Name</option>
</optgroup>
<optgroup label="Default Location">
<option value="defaultLoc.name">Default Location Name</option>
<option value="defaultLoc.phone">Default Location Phone</option>
</optgroup>
<optgroup label="Location">
<option value="location.name">Location Name</option>
<option value="location.phone">Location Phone</option>
</optgroup>
<optgroup label="Company">
<option value="company.email">Company Email</option>
<option value="company.name">Company Name</option>
<option value="company.phone">Company Phone</option>
</optgroup>
<optgroup label="Custom Fields">
@foreach($customFields as $customField)
<option value="{{ $customField->db_column }}">{{ $customField->name }}</option>
@endforeach
</optgroup>
</select>
</div>
</template>
</template>

View file

@ -221,6 +221,38 @@
{{ trans('admin/users/table.title') }}
</label>
<!-- new -->
<label class="form-control">
{{ Form::checkbox('phone', '1', '1') }}
{{ trans('admin/users/table.phone') }}
</label>
<label class="form-control">
{{ Form::checkbox('user_address', '1', '1') }}
{{ trans('general.address') }}
</label>
<label class="form-control">
{{Form::checkbox('user_city', '1', '1')}}
{{ trans('general.city') }}
</label>
<label class="form-control">
{{Form::checkbox('user_state', '1', '1')}}
{{ trans('general.state') }}
</label>
<label class="form-control">
{{Form::checkbox('user_country', '1', '1')}}
{{ trans('general.country') }}
</label>
<label class="form-control">
{{Form::checkbox('user_zip', '1', '1')}}
{{ trans('general.zip') }}
</label>
@if ($customfields->count() > 0)
@ -259,79 +291,131 @@
<!-- Order Number -->
<div class="form-group">
<label for="by_order_number" class="col-md-3 control-label">{{ trans('general.order_number') }}</label>
<div class="col-md-5 col-sm-8">
<div class="col-md-7">
<input class="form-control" type="text" name="by_order_number" value="" aria-label="by_order_number">
</div>
</div>
<!-- Purchase Date -->
<div class="form-group purchase-range">
<div class="form-group purchase-range{{ ($errors->has('purchase_start') || $errors->has('purchase_end')) ? ' has-error' : '' }}">
<label for="purchase_start" class="col-md-3 control-label">{{ trans('general.purchase_date') }} {{ trans('general.range') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="purchase_start" aria-label="purchase_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="purchase_start" aria-label="purchase_start" value="{{ old('purchase_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="purchase_end" aria-label="purchase_end">
<input type="text" class="form-control" name="purchase_end" aria-label="purchase_end" value="{{ old('purchase_end') }}">
</div>
@if ($errors->has('purchase_start') || $errors->has('purchase_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('purchase_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('purchase_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Created Date -->
<div class="form-group purchase-range">
<div class="form-group purchase-range{{ ($errors->has('created_start') || $errors->has('created_end')) ? ' has-error' : '' }}">
<label for="created_start" class="col-md-3 control-label">{{ trans('general.created_at') }} {{ trans('general.range') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="created_start" aria-label="created_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="created_start" aria-label="created_start" value="{{ old('created_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="created_end" aria-label="created_end">
<input type="text" class="form-control" name="created_end" aria-label="created_end" value="{{ old('created_end') }}">
</div>
@if ($errors->has('created_start') || $errors->has('created_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('created_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('created_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Checkout Date -->
<div class="form-group checkout-range">
<div class="form-group checkout-range{{ ($errors->has('checkout_date_start') || $errors->has('checkout_date_end')) ? ' has-error' : '' }}">
<label for="checkout_date" class="col-md-3 control-label">{{ trans('general.checkout') }} {{ trans('general.range') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="checkout_date_start" aria-label="checkout_date_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="checkout_date_start" aria-label="checkout_date_start" value="{{ old('checkout_date_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="checkout_date_end" aria-label="checkout_date_end">
<input type="text" class="form-control" name="checkout_date_end" aria-label="checkout_date_end" value="{{ old('checkout_date_end') }}">
</div>
@if ($errors->has('checkout_date_start') || $errors->has('checkout_date_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('checkout_date_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('checkout_date_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Last Checkin Date -->
<div class="form-group checkin-range">
<div class="form-group checkin-range{{ ($errors->has('checkin_date_start') || $errors->has('checkin_date_end')) ? ' has-error' : '' }}">
<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">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="checkin_date_start" aria-label="checkin_date_start" value="{{ old('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">
<input type="text" class="form-control" name="checkin_date_end" aria-label="checkin_date_end" value="{{ old('checkin_date_end') }}">
</div>
@if ($errors->has('checkin_date_start') || $errors->has('checkin_date_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('checkin_date_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('checkin_date_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Expected Checkin Date -->
<div class="form-group expected_checkin-range">
<div class="form-group expected_checkin-range{{ ($errors->has('expected_checkin_start') || $errors->has('expected_checkin_end')) ? ' has-error' : '' }}">
<label for="expected_checkin_start" class="col-md-3 control-label">{{ trans('admin/hardware/form.expected_checkin') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="expected_checkin_start" aria-label="expected_checkin_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="expected_checkin_start" aria-label="expected_checkin_start" value="{{ old('expected_checkin_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="expected_checkin_end" aria-label="expected_checkin_end">
<input type="text" class="form-control" name="expected_checkin_end" aria-label="expected_checkin_end" value="{{ old('expected_checkin_end') }}">
</div>
@if ($errors->has('expected_checkin_start') || $errors->has('expected_checkin_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('expected_checkin_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('expected_checkin_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Last Audit Date -->
<div class="form-group last_audit-range">
<div class="form-group last_audit-range{{ ($errors->has('last_audit_start') || $errors->has('last_audit_end')) ? ' has-error' : '' }}">
<label for="last_audit_start" class="col-md-3 control-label">{{ trans('general.last_audit') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="last_audit_start" aria-label="last_audit_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="last_audit_start" aria-label="last_audit_start" value="{{ old('last_audit_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="last_audit_end" aria-label="last_audit_end">
<input type="text" class="form-control" name="last_audit_end" aria-label="last_audit_end" value="{{ old('last_audit_end') }}">
</div>
@if ($errors->has('last_audit_start') || $errors->has('last_audit_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('last_audit_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('last_audit_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<!-- Next Audit Date -->
<div class="form-group next_audit-range">
<div class="form-group next_audit-range{{ ($errors->has('next_audit_start') || $errors->has('next_audit_end')) ? ' has-error' : '' }}">
<label for="next_audit_start" class="col-md-3 control-label">{{ trans('general.next_audit_date') }}</label>
<div class="input-daterange input-group col-md-6" id="datepicker">
<input type="text" class="form-control" name="next_audit_start" aria-label="nex_audit_start">
<div class="input-daterange input-group col-md-7" id="datepicker">
<input type="text" class="form-control" name="next_audit_start" aria-label="next_audit_start" value="{{ old('next_audit_start') }}">
<span class="input-group-addon">to</span>
<input type="text" class="form-control" name="next_audit_end" aria-label="next_audit_end">
<input type="text" class="form-control" name="next_audit_end" aria-label="next_audit_end" value="{{ old('next_audit_end') }}">
</div>
@if ($errors->has('next_audit_start') || $errors->has('next_audit_end'))
<div class="col-md-9 col-lg-offset-3">
{!! $errors->first('next_audit_start', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{!! $errors->first('next_audit_end', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
@endif
</div>
<div class="col-md-9 col-md-offset-3">

View file

@ -137,7 +137,7 @@
@csrf
<div class="form-group {{ $errors->has((isset($fieldname) ? $fieldname : 'image')) ? 'has-error' : '' }}" style="margin-bottom: 0px;">
<div class="form-group {{ $errors->has((isset($fieldname) ? $fieldname : 'file')) ? 'has-error' : '' }}" style="margin-bottom: 0px;">
<div class="col-md-8 col-xs-8">
@ -145,25 +145,17 @@
<label class="btn btn-default col-md-12 col-xs-12" aria-hidden="true">
<i class="fas fa-paperclip" aria-hidden="true"></i>
{{ trans('button.select_file') }}
<input type="file" name="file" class="js-uploadFile" id="uploadFile" data-maxsize="{{ Helper::file_upload_max_size() }}" accept="application/zip" style="display:none;" aria-label="file" aria-hidden="true">
</label>
</div>
<div class="col-md-4 col-xs-4">
<button class="btn btn-primary col-md-12 col-xs-12" id="uploadButton" disabled>{{ trans('button.upload') }} <span id="uploadIcon"></span></button>
</div>
<div class="col-md-12">
<p class="label label-default col-md-12" style="font-size: 120%!important; margin-top: 10px; margin-bottom: 10px;" id="uploadFile-info"></p>
<p class="help-block" style="margin-top: 10px;" id="uploadFile-status">{{ trans_choice('general.filetypes_accepted_help', 1, ['size' => Helper::file_upload_max_size_readable(), 'types' => '.zip']) }}</p>
{!! $errors->first('image', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
{!! $errors->first('file', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>

View file

@ -216,7 +216,7 @@
{{ Form::label('label2_fields', trans('admin/settings/general.label2_fields')) }}
</div>
<div class="col-md-9">
@include('partials.label2-field-definitions', [ 'name' => 'label2_fields', 'value' => old('label2_fields', $setting->label2_fields) ])
@include('partials.label2-field-definitions', [ 'name' => 'label2_fields', 'value' => old('label2_fields', $setting->label2_fields), 'customFields' => $customFields ])
{!! $errors->first('label2_fields', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<p class="help-block">{{ trans('admin/settings/general.label2_fields_help') }}</p>
</div>

View file

@ -39,6 +39,16 @@
@include ('partials.forms.edit.department-select', ['translated_name' => trans('general.department'), 'fieldname' => 'department_id'])
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('null_department_id', '1', false) }}
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.department'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Location -->
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@ -55,11 +65,31 @@
<!-- Company -->
@if (\App\Models\Company::canManageUsersCompanies())
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.select_company'), 'fieldname' => 'company_id'])
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('null_company_id', '1', false) }}
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.company'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
@endif
<!-- Manager -->
@include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id'])
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('null_manager_id', '1', false) }}
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('admin/users/table.manager'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- language -->
<div class="form-group {{ $errors->has('locale') ? 'has-error' : '' }}">
<label class="col-md-3 control-label" for="locale">{{ trans('general.language') }}</label>

View file

@ -48,6 +48,7 @@
<th class="col-md-3">{{ trans('general.name') }}</th>
<th class="col-md-3">{{ trans('general.email') }}</th>
<th class="col-md-3">{{ trans('general.username') }}</th>
<th class="col-md-3">{{ trans('general.employee_number') }}</th>
<th class="col-md-3">{{ trans('general.groups') }}</th>
<th class="col-md-1 text-right">
<i class="fas fa-barcode fa-fw" aria-hidden="true" style="font-size: 17px;"></i>
@ -80,6 +81,9 @@
<td>
{{ $user->username }}
</td>
<td>
{{ $user->employee_num }}
</td>
<td>
@foreach ($user->groups as $group)

View file

@ -29,7 +29,7 @@
<div class="col-md-12">
<!-- Location -->
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id[]', 'multiple' => true])
</div>
</div>

View file

@ -80,8 +80,8 @@
<td>{{ $counter }}</td>
<td>{{ $asset->asset_tag }}</td>
<td>{{ $asset->name }}</td>
<td>{{ $asset->model->category->name }}</td>
<td>{{ $asset->model->name }}</td>
<td>{{ (($asset->model) && ($asset->model->category)) ? $asset->model->category->name : trans('general.invalid_category') }}</td>
<td>{{ ($asset->model) ? $asset->model->name : trans('general.invalid_model') }}</td>
<td>{{ $asset->serial }}</td>
<td>
{{ $asset->last_checkout }}</td>

View file

@ -224,6 +224,11 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser
[SettingsController::class, 'postUploadBackup']
)->name('settings.backups.upload');
// Handle redirect from after POST request from backup restore
Route::get('/restore/{filename?}', function () {
return redirect(route('settings.backups.index'));
});
Route::get('/', [SettingsController::class, 'getBackups'])->name('settings.backups.index');
});

View file

@ -122,9 +122,10 @@ Route::group(
[AssetCheckinController::class, 'store']
)->name('hardware.checkin.store');
Route::get('{assetId}/view',
[AssetsController::class, 'show']
)->name('hardware.view');
// Redirect old legacy /asset_id/view urls to the resource route version
Route::get('{assetId}/view', function ($assetId) {
return redirect()->route('hardware.show', ['hardware' => $assetId]);
});
Route::get('{assetId}/qr_code',
[AssetsController::class, 'getQrCode']
@ -178,13 +179,17 @@ Route::group(
Route::post('bulkcheckout',
[BulkAssetsController::class, 'storeCheckout']
)->name('hardware.bulkcheckout.store');
});
Route::resource('hardware',
AssetsController::class,
[
'middleware' => ['auth'],
'parameters' => ['asset' => 'asset_id'
'parameters' => ['asset' => 'asset_id',
'names' => [
'show' => 'view',
],
],
]);

View file

@ -0,0 +1,19 @@
<?php
namespace Tests\Feature\Api\Assets;
use App\Models\User;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class AssetStoreTest extends TestCase
{
use InteractsWithSettings;
public function testRequiresPermissionToCreateAsset()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.assets.store'))
->assertForbidden();
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class LicenseCheckoutTest extends TestCase
{
use InteractsWithSettings;
public function testNotesAreStoredInActionLogOnCheckoutToAsset()
{
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create();
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
->post("/licenses/{$licenseSeat->license->id}/checkout", [
'checkout_to_type' => 'asset',
'assigned_to' => null,
'asset_id' => $asset->id,
'notes' => 'oh hi there',
]);
$this->assertDatabaseHas('action_logs', [
'action_type' => 'checkout',
'target_id' => $asset->id,
'target_type' => Asset::class,
'item_id' => $licenseSeat->license->id,
'item_type' => License::class,
'note' => 'oh hi there',
]);
}
public function testNotesAreStoredInActionLogOnCheckoutToUser()
{
$admin = User::factory()->superuser()->create();
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
->post("/licenses/{$licenseSeat->license->id}/checkout", [
'checkout_to_type' => 'user',
'assigned_to' => $admin->id,
'asset_id' => null,
'notes' => 'oh hi there',
]);
$this->assertDatabaseHas('action_logs', [
'action_type' => 'checkout',
'target_id' => $admin->id,
'target_type' => User::class,
'item_id' => $licenseSeat->license->id,
'item_type' => License::class,
'note' => 'oh hi there',
]);
}
}

View file

@ -11,6 +11,7 @@ use PHPUnit\Framework\Assert;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class CustomReportTest extends TestCase
{
use InteractsWithSettings;

Some files were not shown because too many files have changed in this diff Show more