Merge remote-tracking branch 'origin/develop'

Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/all.css
#	public/css/dist/bootstrap-table.css
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
This commit is contained in:
snipe 2025-04-01 11:25:33 +01:00
commit 5eb9f353b5
27 changed files with 372 additions and 79 deletions

View file

@ -3307,6 +3307,15 @@
"contributions": [
"code"
]
},
{
"login": "ntaylor-86",
"name": "Nathan Taylor",
"avatar_url": "https://avatars.githubusercontent.com/u/28693782?v=4",
"profile": "https://github.com/ntaylor-86",
"contributions": [
"code"
]
}
]
}

View file

@ -35,6 +35,7 @@ DB_USERNAME=snipeit
DB_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci

View file

@ -35,6 +35,7 @@ DB_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci

View file

@ -30,6 +30,7 @@ DB_USERNAME=null
DB_PASSWORD=null
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=false
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false

View file

@ -54,6 +54,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "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

@ -570,7 +570,6 @@ class BulkAssetsController extends Controller
*/
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
$this->authorize('checkout', Asset::class);
try {
@ -584,6 +583,8 @@ class BulkAssetsController extends Controller
$asset_ids = array_filter($request->get('selected_assets'));
$assets = Asset::findOrFail($asset_ids);
if (request('checkout_to_type') == 'asset') {
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
@ -603,9 +604,8 @@ class BulkAssetsController extends Controller
}
$errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
foreach ($assets as $asset) {
$this->authorize('checkout', $asset);
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
@ -632,7 +632,7 @@ class BulkAssetsController extends Controller
// Redirect to the asset management page with error
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
} catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
}
}

View file

@ -493,6 +493,17 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.eol');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('order')) {
$header[] = trans('admin/hardware/form.order');
}
@ -579,17 +590,6 @@ class ReportsController extends Controller
$header[] = trans('general.status');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('checkout_date')) {
$header[] = trans('admin/hardware/table.checkout_date');
}
@ -805,6 +805,19 @@ class ReportsController extends Controller
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('order')) {
$row[] = ($asset->order_number) ? $asset->order_number : '';
}
@ -938,19 +951,6 @@ class ReportsController extends Controller
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('checkout_date')) {
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}

View file

@ -395,13 +395,22 @@ class UsersController extends Controller
// Make sure the user can view users at all
$this->authorize('view', User::class);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
$user = User::with([
'consumables',
'accessories',
'licenses',
'userloc',
])
->withTrashed()
->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user);
$userlog = $user->userlog->load('item');
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
return view('users/view', [
'user' => $user,
'settings' => Setting::getSettings(),
]);
}

View file

@ -29,6 +29,12 @@ class StoreLabelSettings extends FormRequest
return $label->getName();
})->values()->toArray();
if (empty($this->input('label2_template'))) {
$this->merge([
'label2_template' => 'DefaultLabel',
]);
}
return [
'labels_per_page' => 'numeric',
'labels_width' => 'numeric',

View file

@ -101,11 +101,8 @@ class LocationsTransformer
$array = [
'id' => $accessory_checkout->id,
'assigned_to' => $accessory_checkout->assigned_to,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,
],
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory_checkout->accessory->image)) : null,
'accessory' => $this->transformAccessory($accessory_checkout->accessory),
'image' => ($accessory_checkout?->accessory?->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [
'id' => (int) $accessory_checkout->adminuser->id,
@ -153,4 +150,16 @@ class LocationsTransformer
return $array;
}
}
private function transformAccessory(?Accessory $accessory): ?array
{
if ($accessory) {
return [
'id' => $accessory->id,
'name' => $accessory->name,
];
}
return null;
}
}

View file

@ -67,7 +67,8 @@ final class Company extends SnipeModel
'phone',
'fax',
'email',
'created_by'
'created_by',
'notes',
];
private static function isFullMultipleCompanySupportEnabled()

View file

@ -81,6 +81,7 @@ return [
'unix_socket' => env('DB_SOCKET', ''),
'dump' => [
'dump_binary_path' => env('DB_DUMP_PATH', '/usr/local/bin'), // only the path, so without 'mysqldump'
'skip_ssl' => env('DB_DUMP_SKIP_SSL', false), // turn off SSL if not available
'use_single_transaction' => false,
'timeout' => 60 * 5, // 5 minute timeout
//'exclude_tables' => ['table1', 'table2'],

View file

@ -295,6 +295,11 @@ class UserFactory extends Factory
return $this->appendPermission(['companies.delete' => '1']);
}
public function editCompanies()
{
return $this->appendPermission(['companies.edit' => '1']);
}
public function viewUsers()
{
return $this->appendPermission(['users.view' => '1']);

16
package-lock.json generated
View file

@ -6,7 +6,7 @@
"": {
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.2",
"acorn": "^8.12.0",
"acorn": "^8.14.1",
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
"ajv": "^6.12.6",
@ -15,7 +15,7 @@
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-datepicker": "^1.10.0",
"bootstrap-less": "^3.3.8",
"bootstrap-table": "1.24.0",
"bootstrap-table": "1.24.1",
"canvas-confetti": "^1.9.3",
"chart.js": "^2.9.4",
"clipboard": "^2.0.11",
@ -2502,9 +2502,9 @@
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"bin": {
"acorn": "bin/acorn"
},
@ -3702,9 +3702,9 @@
"license": "MIT"
},
"node_modules/bootstrap-table": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.24.0.tgz",
"integrity": "sha512-dyRf5PQwTgFHj9yjuPXa+GIf4JpuQhsgD1CJrOqhw40qI2gTb3mJfRdoBc7iF2bqzOl+k0RnbAlhSPbGe4VS+w==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.24.1.tgz",
"integrity": "sha512-que7o2Z6R0I3cfwfm6qg4XnoUK9A/8162HUErFYg3fGtzjk5OjLJx6Ji9p3oKz+IAIVvBCr9sqMqujEG47k3zA==",
"peerDependencies": {
"jquery": "3"
}

View file

@ -26,7 +26,7 @@
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.2",
"acorn": "^8.12.0",
"acorn": "^8.14.1",
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
"ajv": "^6.12.6",
@ -35,7 +35,7 @@
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-datepicker": "^1.10.0",
"bootstrap-less": "^3.3.8",
"bootstrap-table": "1.24.0",
"bootstrap-table": "1.24.1",
"canvas-confetti": "^1.9.3",
"chart.js": "^2.9.4",
"clipboard": "^2.0.11",

View file

@ -1,7 +1,7 @@
/**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
*
* @version v1.24.0
* @version v1.24.1
* @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=091d9625203be910caca3e229afe438f",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=18787b3f00a3be7be38ee4e26cbd2a07",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/all.css": "/css/dist/all.css?id=8c095763cd1a12d882e050d89d314ef9",
"/css/dist/all.css": "/css/dist/all.css?id=e06ce6503c567fecd77ad3fe64250fe2",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
@ -90,8 +90,8 @@
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=109ad919b74a62a8a223361da1651bbc",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=53c2e50ef821f7b8dd514611d5e0772c",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=331c85bd61ffa93af09273d1bc2add5a",
"/js/dist/bootstrap-table-locale-all.min.js": "/js/dist/bootstrap-table-locale-all.min.js?id=02dfc50d5b951dc6d260bd508968d319",
"/js/dist/bootstrap-table-en-US.min.js": "/js/dist/bootstrap-table-en-US.min.js?id=1c350eabf064c309f67d6779e5cc4afa",
"/js/dist/bootstrap-table-locale-all.min.js": "/js/dist/bootstrap-table-locale-all.min.js?id=5e93ef0a1889bed3f92a705dc1e92c9b",
"/js/dist/bootstrap-table-en-US.min.js": "/js/dist/bootstrap-table-en-US.min.js?id=c0f21fb7e62d6f0a0153f1cdbf26782a",
"/css/dist/skins/_all-skins.min.css": "/css/dist/skins/_all-skins.min.css?id=6bf62cdec2477f3176df196fd0c99662",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=d34ae2483cbe2c77478c45f4006eba55",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
@ -108,8 +108,8 @@
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=53edc92eb2d272744bc7404ec259930e",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=ceded08e0cc745a83c13647035b03406",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=54d676a6ea8677dd48f6c4b3041292cf",
"/js/build/vendor.js": "/js/build/vendor.js?id=89dffa552c6e3abe3a2aac6c9c7b466b",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=61285c8ac5ea7b46002ea8c451c94e60",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=757648759dcd365f5708e95b985971ff",
"/js/dist/all.js": "/js/dist/all.js?id=1a7bd87b50e85aa4f2c4fd6f5ed7722c"
}

View file

@ -292,8 +292,10 @@
</div>
<div class="col-md-9">
<span class="js-copy">{{ $consumable->item_no }}</span>
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
<span class="js-copy-item_no">{{ $consumable->item_no }}</span>
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy-item_no"
aria-hidden="true" data-tooltip="true" data-placement="top"
title="{{ trans('general.copy_to_clipboard') }}">
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
</i>
@ -308,8 +310,10 @@
</div>
<div class="col-md-9">
<span class="js-copy">{{ $consumable->model_number }}</span>
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
<span class="js-copy-model_no">{{ $consumable->model_number }}</span>
<i class="fa-regular fa-clipboard js-copy-link" data-clipboard-target=".js-copy-model_no"
aria-hidden="true" data-tooltip="true" data-placement="top"
title="{{ trans('general.copy_to_clipboard') }}">
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
</i>

View file

@ -726,15 +726,21 @@
@endphp
@if ($fieldSize>0)
<span id="text-{{ $field->id }}-to-hide">{{ str_repeat('*', $fieldSize) }}</span>
<span class="js-copy-{{ $field->id }} hidden-print" id="text-{{ $field->id }}-to-show" style="font-size: 0px;">
@if (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
<a href="{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}" target="_new">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</a>
@if (($field->format=='URL') && ($asset->{$field->db_column_name()}!=''))
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;"><a
href="{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}"
target="_new">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</a></span>
@elseif (($field->format=='DATE') && ($asset->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false)) }}
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;">{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($asset->{$field->db_column_name()}, 'date', false)) }}</span>
@else
{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;">{{ Helper::gracefulDecrypt($field, $asset->{$field->db_column_name()}) }}</span>
@endif
</span>
<i class="fa-regular fa-clipboard js-copy-link hidden-print" data-clipboard-target=".js-copy-{{ $field->id }}" aria-hidden="true" data-tooltip="true" data-placement="top" title="{{ trans('general.copy_to_clipboard') }}">
<span class="sr-only">{{ trans('general.copy_to_clipboard') }}</span>
</i>

View file

@ -56,7 +56,7 @@
{{ trans('general.date') }}
</th>
<th class="col-sm-2" data-searchable="true" data-sortable="true" data-field="created_by" data-formatter="usersLinkObjFormatter">
{{ trans('general.admin') }}
{{ trans('general.created_by') }}
</th>
<th class="col-sm-2" data-field="action_type">
{{ trans('general.action') }}

View file

@ -147,6 +147,16 @@
{{ trans('admin/hardware/form.eol_date') }}
</label>
<label class="form-control">
<input type="checkbox" name="warranty" value="1" @checked($template->checkmarkValue('warranty')) />
{{ trans('admin/hardware/form.warranty') }}
</label>
<label class="form-control">
<input type="checkbox" name="depreciation" value="1" @checked($template->checkmarkValue('depreciation')) />
{{ trans('general.depreciation') }}
</label>
<label class="form-control">
<input type="checkbox" name="order" value="1" @checked($template->checkmarkValue('order')) />
{{ trans('admin/hardware/form.order') }}
@ -182,16 +192,6 @@
{{ trans('general.status') }}
</label>
<label class="form-control">
<input type="checkbox" name="warranty" value="1" @checked($template->checkmarkValue('warranty')) />
{{ trans('admin/hardware/form.warranty') }}
</label>
<label class="form-control">
<input type="checkbox" name="depreciation" value="1" @checked($template->checkmarkValue('depreciation')) />
{{ trans('general.depreciation') }}
</label>
<label class="form-control">
<input type="checkbox" name="checkout_date" value="1" @checked($template->checkmarkValue('checkout_date')) />
{{ trans('admin/hardware/table.checkout_date') }}

View file

@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Authentication;
use App\Models\User;
use Tests\TestCase;
class LoginTest extends TestCase
{
public function testLogsFailedLoginAttempt()
{
User::factory()->create(['username' => 'username_here']);
$this->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
->post('/login', [
'username' => 'username_here',
'password' => 'not a real password',
], [
'User-Agent' => 'Some Custom User Agent',
]);
$this->assertDatabaseHas('login_attempts', [
'username' => 'username_here',
'remote_ip' => '127.0.0.100',
'user_agent' => 'Some Custom User Agent',
'successful' => 0,
]);
}
public function testLogsSuccessfulLogin()
{
User::factory()->create(['username' => 'username_here']);
$this->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
->post('/login', [
'username' => 'username_here',
'password' => 'password',
], [
'User-Agent' => 'Some Custom User Agent',
]);
$this->assertDatabaseHas('login_attempts', [
'username' => 'username_here',
'remote_ip' => '127.0.0.100',
'user_agent' => 'Some Custom User Agent',
'successful' => 1,
]);
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Tests\Feature\Checkouts\Ui;
use App\Mail\CheckoutAssetMail;
use App\Models\Asset;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\ExpectationFailedException;
use Tests\TestCase;
class BulkAssetCheckoutTest extends TestCase
{
public function testRequiresPermission()
{
$this->actingAs(User::factory()->create())
->post(route('hardware.bulkcheckout.store'), [
'selected_assets' => [1],
'checkout_to_type' => 'user',
'assigned_user' => 1,
'assigned_asset' => null,
'checkout_at' => null,
'expected_checkin' => null,
'note' => null,
])
->assertForbidden();
}
public function testCanBulkCheckoutAssets()
{
Mail::fake();
$assets = Asset::factory()->requiresAcceptance()->count(2)->create();
$user = User::factory()->create(['email' => 'someone@example.com']);
$checkoutAt = now()->subWeek()->format('Y-m-d');
$expectedCheckin = now()->addWeek()->format('Y-m-d');
$this->actingAs(User::factory()->checkoutAssets()->viewAssets()->create())
->followingRedirects()
->post(route('hardware.bulkcheckout.store'), [
'selected_assets' => $assets->pluck('id')->toArray(),
'checkout_to_type' => 'user',
'assigned_user' => $user->id,
'assigned_asset' => null,
'checkout_at' => $checkoutAt,
'expected_checkin' => $expectedCheckin,
'note' => null,
])
->assertOk();
$assets = $assets->fresh();
$assets->each(function ($asset) use ($expectedCheckin, $checkoutAt, $user) {
$asset->assignedTo()->is($user);
$asset->last_checkout = $checkoutAt;
$asset->expected_checkin = $expectedCheckin;
});
Mail::assertSent(CheckoutAssetMail::class, 2);
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasTo('someone@example.com');
});
}
public function testHandleMissingModelBeingIncluded()
{
Mail::fake();
$this->actingAs(User::factory()->checkoutAssets()->create())
->post(route('hardware.bulkcheckout.store'), [
'selected_assets' => [
Asset::factory()->requiresAcceptance()->create()->id,
9999999,
],
'checkout_to_type' => 'user',
'assigned_user' => User::factory()->create(['email' => 'someone@example.com'])->id,
'assigned_asset' => null,
'checkout_at' => null,
'expected_checkin' => null,
'note' => null,
])
->assertSessionHas('error', trans_choice('admin/hardware/message.multi-checkout.error', 2));
try {
Mail::assertNotSent(CheckoutAssetMail::class);
} catch (ExpectationFailedException $e) {
$this->fail('Asset checkout email was sent when the entire checkout failed.');
}
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Tests\Feature\Companies\Api;
use App\Models\User;
use Tests\Concerns\TestsPermissionsRequirement;
use Tests\TestCase;
class CreateCompaniesTest extends TestCase implements TestsPermissionsRequirement
{
public function testRequiresPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.companies.store'))
->assertForbidden();
}
public function testValidationForCreatingCompany()
{
$this->actingAsForApi(User::factory()->createCompanies()->create())
->postJson(route('api.companies.store'))
->assertStatus(200)
->assertStatusMessageIs('error')
->assertJsonStructure([
'messages' => [
'name',
],
]);
}
public function testCanCreateCompany()
{
$this->actingAsForApi(User::factory()->createCompanies()->create())
->postJson(route('api.companies.store'), [
'name' => 'My Cool Company',
'notes' => 'A Cool Note',
])
->assertStatus(200)
->assertStatusMessageIs('success');
$this->assertDatabaseHas('companies', [
'name' => 'My Cool Company',
'notes' => 'A Cool Note',
]);
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Tests\Feature\Companies\Api;
use App\Models\Company;
use App\Models\User;
use Tests\TestCase;
class UpdateCompaniesTest extends TestCase
{
public function testRequiresPermissionToPatchCompany()
{
$company = Company::factory()->create();
$this->actingAsForApi(User::factory()->create())
->patchJson(route('api.companies.update', $company))
->assertForbidden();
}
public function testValidationForPatchingCompany()
{
$company = Company::factory()->create();
$this->actingAsForApi(User::factory()->editCompanies()->create())
->patchJson(route('api.companies.update', ['company' => $company->id]), [
'name' => '',
])
->assertStatus(200)
->assertStatusMessageIs('error')
->assertJsonStructure([
'messages' => [
'name',
],
]);
}
public function testCanPatchCompany()
{
$company = Company::factory()->create();
$this->actingAsForApi(User::factory()->editCompanies()->create())
->patchJson(route('api.companies.update', ['company' => $company->id]), [
'name' => 'A Changed Name',
'notes' => 'A Changed Note',
])
->assertStatus(200)
->assertStatusMessageIs('success');
$company->refresh();
$this->assertEquals('A Changed Name', $company->name);
$this->assertEquals('A Changed Note', $company->notes);
}
}