Skip to content

Commit af88ce5

Browse files
committed
Merge branch 'develop' into feature/improve-checkout-asset-mail-wording
2 parents f202817 + df1c7c4 commit af88ce5

File tree

130 files changed

+4557
-3184
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+4557
-3184
lines changed

.all-contributorsrc

+9
Original file line numberDiff line numberDiff line change
@@ -3307,6 +3307,15 @@
33073307
"contributions": [
33083308
"code"
33093309
]
3310+
},
3311+
{
3312+
"login": "ntaylor-86",
3313+
"name": "Nathan Taylor",
3314+
"avatar_url": "https://avatars.githubusercontent.com/u/28693782?v=4",
3315+
"profile": "https://github.com/ntaylor-86",
3316+
"contributions": [
3317+
"code"
3318+
]
33103319
}
33113320
]
33123321
}

.env.dev.docker

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ DB_USERNAME=snipeit
3535
DB_PASSWORD=changeme1234
3636
DB_PREFIX=null
3737
DB_DUMP_PATH='/usr/bin'
38+
DB_DUMP_SKIP_SSL=true
3839
DB_CHARSET=utf8mb4
3940
DB_COLLATION=utf8mb4_unicode_ci
4041

.env.docker

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ DB_PASSWORD=changeme1234
3535
MYSQL_ROOT_PASSWORD=changeme1234
3636
DB_PREFIX=null
3737
DB_DUMP_PATH='/usr/bin'
38+
DB_DUMP_SKIP_SSL=true
3839
DB_CHARSET=utf8mb4
3940
DB_COLLATION=utf8mb4_unicode_ci
4041

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ DB_USERNAME=null
3030
DB_PASSWORD=null
3131
DB_PREFIX=null
3232
DB_DUMP_PATH='/usr/bin'
33+
DB_DUMP_SKIP_SSL=false
3334
DB_CHARSET=utf8mb4
3435
DB_COLLATION=utf8mb4_unicode_ci
3536
DB_SANITIZE_BY_DEFAULT=false

CONTRIBUTORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
5454
| [<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") |
5555
| [<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") |
5656
| [<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") |
57+
| [<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") |
5758
<!-- ALL-CONTRIBUTORS-LIST:END -->
5859

5960
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace App\Actions\CheckoutRequests;
4+
5+
use App\Models\Actionlog;
6+
use App\Models\Asset;
7+
use App\Models\Company;
8+
use App\Models\Setting;
9+
use App\Models\User;
10+
use App\Notifications\RequestAssetCancelation;
11+
use Illuminate\Auth\Access\AuthorizationException;
12+
13+
class CancelCheckoutRequestAction
14+
{
15+
public static function run(Asset $asset, User $user)
16+
{
17+
if (!Company::isCurrentUserHasAccess($asset)) {
18+
throw new AuthorizationException();
19+
}
20+
21+
$asset->cancelRequest();
22+
23+
$asset->decrement('requests_counter', 1);
24+
25+
$data['item'] = $asset;
26+
$data['target'] = $user;
27+
$data['item_quantity'] = 1;
28+
$settings = Setting::getSettings();
29+
30+
$logaction = new Actionlog();
31+
$logaction->item_id = $data['asset_id'] = $asset->id;
32+
$logaction->item_type = $data['item_type'] = Asset::class;
33+
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
34+
$logaction->target_id = $data['user_id'] = auth()->id();
35+
$logaction->target_type = User::class;
36+
$logaction->location_id = $user->location_id ?? null;
37+
$logaction->logaction('request canceled');
38+
39+
try {
40+
$settings->notify(new RequestAssetCancelation($data));
41+
} catch (\Exception $e) {
42+
\Log::warning($e);
43+
}
44+
45+
return true;
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace App\Actions\CheckoutRequests;
4+
5+
use App\Exceptions\AssetNotRequestable;
6+
use App\Models\Actionlog;
7+
use App\Models\Asset;
8+
use App\Models\Company;
9+
use App\Models\Setting;
10+
use App\Models\User;
11+
use App\Notifications\RequestAssetNotification;
12+
use Illuminate\Auth\Access\AuthorizationException;
13+
use Log;
14+
15+
class CreateCheckoutRequestAction
16+
{
17+
/**
18+
* @throws AssetNotRequestable
19+
* @throws AuthorizationException
20+
*/
21+
public static function run(Asset $asset, User $user): string
22+
{
23+
if (is_null(Asset::RequestableAssets()->find($asset->id))) {
24+
throw new AssetNotRequestable($asset);
25+
}
26+
if (!Company::isCurrentUserHasAccess($asset)) {
27+
throw new AuthorizationException();
28+
}
29+
30+
$data['item'] = $asset;
31+
$data['target'] = $user;
32+
$data['item_quantity'] = 1;
33+
$settings = Setting::getSettings();
34+
35+
$logaction = new Actionlog();
36+
$logaction->item_id = $data['asset_id'] = $asset->id;
37+
$logaction->item_type = $data['item_type'] = Asset::class;
38+
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
39+
$logaction->target_id = $data['user_id'] = auth()->id();
40+
$logaction->target_type = User::class;
41+
$logaction->location_id = $user->location_id ?? null;
42+
$logaction->logaction('requested');
43+
44+
$asset->request();
45+
$asset->increment('requests_counter', 1);
46+
try {
47+
$settings->notify(new RequestAssetNotification($data));
48+
} catch (\Exception $e) {
49+
Log::warning($e);
50+
}
51+
52+
return true;
53+
}
54+
}

app/Console/Commands/SendExpirationAlerts.php

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function handle()
4949
// Send a rollup to the admin, if settings dictate
5050
$recipients = collect(explode(',', $settings->alert_email))
5151
->map(fn($item) => trim($item)) // Trim each email
52+
->filter(fn($item) => !empty($item))
5253
->all();
5354
// Expiring Assets
5455
$assets = Asset::getExpiringWarrantee($alert_interval);

app/Console/Commands/SendUpcomingAuditReport.php

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public function handle()
5555
// Send a rollup to the admin, if settings dictate
5656
$recipients = collect(explode(',', $settings->alert_email))
5757
->map(fn($item) => trim($item))
58+
->filter(fn($item) => !empty($item))
5859
->all();
5960

6061

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace App\Exceptions;
4+
5+
use Exception;
6+
7+
class AssetNotRequestable extends Exception
8+
{
9+
}

app/Exceptions/Handler.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public function render($request, Throwable $e)
141141
$route = 'kits.index';
142142
} elseif ($route == 'assetmaintenances.index') {
143143
$route = 'maintenances.index';
144+
} elseif ($route === 'licenseseats.index') {
145+
$route = 'licenses.index';
144146
}
145147

146148
return redirect()
@@ -203,4 +205,4 @@ public function register()
203205
//
204206
});
205207
}
206-
}
208+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Exceptions;
4+
5+
use Exception;
6+
7+
class UserDoestExistException extends Exception
8+
{
9+
10+
}

app/Http/Controllers/Accessories/AccessoriesController.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@ public function getClone(Accessory $accessory) : View | RedirectResponse
112112
{
113113

114114
$this->authorize('create', Accessory::class);
115-
116-
$accessory = clone $accessory;
117-
$accessory->id = null;
118-
$accessory->location_id = null;
115+
$cloned = clone $accessory;
116+
$cloned->id = null;
117+
$cloned->deleted_at = '';
118+
$cloned->location_id = null;
119119

120120
return view('accessories/edit')
121-
->with('item', $accessory);
121+
->with('item', $cloned);
122122

123123
}
124124

app/Http/Controllers/Account/AcceptanceController.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,12 @@ public function store(Request $request, $id) : RedirectResponse
208208
*/
209209
$branding_settings = SettingsController::getPDFBranding();
210210

211-
if (is_null($branding_settings->logo)){
212-
$path_logo = "";
213-
} else {
211+
$path_logo = "";
212+
213+
// Check for the PDF logo path and use that, otherwise use the regular logo path
214+
if (!is_null($branding_settings->acceptance_pdf_logo)) {
215+
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
216+
} elseif (!is_null($branding_settings->logo)) {
214217
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
215218
}
216219

app/Http/Controllers/Api/AssetsController.php

+20-3
Original file line numberDiff line numberDiff line change
@@ -491,15 +491,32 @@ public function showByTag(Request $request, $tag): JsonResponse | array
491491
public function showBySerial(Request $request, $serial): JsonResponse | array
492492
{
493493
$this->authorize('index', Asset::class);
494-
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
494+
$assets = Asset::where('serial', $serial)->with([
495+
'assetstatus',
496+
'assignedTo',
497+
'company',
498+
'defaultLoc',
499+
'location',
500+
'model.category',
501+
'model.depreciation',
502+
'model.fieldset',
503+
'model.manufacturer',
504+
'supplier',
505+
]);
495506

496507
// Check if they've passed ?deleted=true
497508
if ($request->input('deleted', 'false') == 'true') {
498509
$assets = $assets->withTrashed();
499510
}
500511

501-
if (($assets = $assets->get()) && ($assets->count()) > 0) {
502-
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
512+
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
513+
$limit = app('api_limit_value');
514+
515+
$total = $assets->count();
516+
$assets = $assets->skip($offset)->take($limit)->get();
517+
518+
if (($assets) && ($assets->count()) > 0) {
519+
return (new AssetsTransformer)->transformAssets($assets, $total);
503520
}
504521

505522
// If there are 0 results, return the "no such asset" response
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
6+
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
7+
use App\Exceptions\AssetNotRequestable;
8+
use App\Helpers\Helper;
9+
use App\Http\Controllers\Controller;
10+
use App\Models\Asset;
11+
use Illuminate\Auth\Access\AuthorizationException;
12+
use Illuminate\Http\JsonResponse;
13+
use Exception;
14+
15+
class CheckoutRequest extends Controller
16+
{
17+
public function store(Asset $asset): JsonResponse
18+
{
19+
try {
20+
CreateCheckoutRequestAction::run($asset, auth()->user());
21+
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success')));
22+
} catch (AssetNotRequestable $e) {
23+
return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable'));
24+
} catch (AuthorizationException $e) {
25+
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
26+
} catch (Exception $e) {
27+
report($e);
28+
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
29+
}
30+
}
31+
32+
public function destroy(Asset $asset): JsonResponse
33+
{
34+
try {
35+
CancelCheckoutRequestAction::run($asset, auth()->user());
36+
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled')));
37+
} catch (AuthorizationException $e) {
38+
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
39+
} catch (Exception $e) {
40+
report($e);
41+
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
42+
}
43+
}
44+
}

app/Http/Controllers/AssetMaintenancesController.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public function store(Request $request) : RedirectResponse
117117
) {
118118
$startDate = Carbon::parse($assetMaintenance->start_date);
119119
$completionDate = Carbon::parse($assetMaintenance->completion_date);
120-
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
120+
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
121121
}
122122

123123
// Was the asset maintenance created?
@@ -210,7 +210,7 @@ public function update(Request $request, AssetMaintenance $maintenance) : View |
210210
) {
211211
$startDate = Carbon::parse($maintenance->start_date);
212212
$completionDate = Carbon::parse($maintenance->completion_date);
213-
$maintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
213+
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
214214
}
215215

216216
// Was the asset maintenance created?

app/Http/Controllers/Assets/AssetsController.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public function update(ImageUploadRequest $request, Asset $asset) : RedirectResp
318318
$asset->eol_explicit = false;
319319
} elseif ($request->filled('asset_eol_date')) {
320320
$asset->asset_eol_date = $request->input('asset_eol_date', null);
321-
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
321+
$months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true);
322322
if($asset->model->eol) {
323323
if($months != $asset->model->eol > 0) {
324324
$asset->eol_explicit = true;

app/Http/Controllers/Assets/BulkAssetsController.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,6 @@ public function showCheckout() : View
570570
*/
571571
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
572572
{
573-
574573
$this->authorize('checkout', Asset::class);
575574

576575
try {
@@ -584,6 +583,8 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
584583

585584
$asset_ids = array_filter($request->get('selected_assets'));
586585

586+
$assets = Asset::findOrFail($asset_ids);
587+
587588
if (request('checkout_to_type') == 'asset') {
588589
foreach ($asset_ids as $asset_id) {
589590
if ($target->id == $asset_id) {
@@ -603,9 +604,8 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
603604
}
604605

605606
$errors = [];
606-
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
607-
foreach ($asset_ids as $asset_id) {
608-
$asset = Asset::findOrFail($asset_id);
607+
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
608+
foreach ($assets as $asset) {
609609
$this->authorize('checkout', $asset);
610610

611611
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
@@ -632,7 +632,7 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
632632
// Redirect to the asset management page with error
633633
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
634634
} catch (ModelNotFoundException $e) {
635-
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
635+
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
636636
}
637637

638638
}

0 commit comments

Comments
 (0)