Merge remote-tracking branch 'snipe-it-upstream/develop' into develop

This commit is contained in:
Wes Hulette 2018-09-27 16:39:07 -04:00
commit b64924440f
66 changed files with 1421 additions and 510 deletions

4
.gitignore vendored
View file

@ -52,3 +52,7 @@ tests/_support/_generated/*
*.cache
.vagrant
\.php_cs\.dist
phpmd\.xml

39
.php_cs.dist Normal file
View file

@ -0,0 +1,39 @@
<?php
ini_set('memory_limit', '1024M');
$finder = PhpCsFixer\Finder::create()
->notPath('bootstrap/cache')
->notPath('storage')
->notPath('vendor')
->notPath('node_modules')
->in(__DIR__)
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true)
;
return PhpCsFixer\Config::create()
->setRules(array(
'@Symfony' => true,
'class_definition' => [
'multiLineExtendsEachSingleLine' => true,
],
'ordered_class_elements' => [
'use_trait', 'constant_public', 'constant_protected', 'constant_private',
'property_public', 'property_protected', 'property_private', 'construct',
'destruct', 'magic', 'phpunit', 'method_public', 'method_protected',
'method_private'
],
'function_declaration' => ['closure_function_spacing' => 'none'],
'binary_operator_spaces' => ['default' => 'align_single_space_minimal'],
'array_syntax' => ['syntax' => 'short'],
'concat_space' => ['spacing' => 'one'],
'blank_line_after_namespace' => true,
'linebreak_after_opening_tag' => true,
'not_operator_with_successor_space' => true,
'ordered_imports' => true,
'phpdoc_order' => true,
))
->setFinder($finder);

View file

@ -2,17 +2,15 @@
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Console\Command;
use App\Notifications\ExpectedCheckinNotification;
use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
class SendExpectedCheckinAlerts extends Command
{
/**
* The console command name.
*
@ -29,8 +27,6 @@ class SendExpectedCheckinAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -44,30 +40,25 @@ class SendExpectedCheckinAlerts extends Command
*/
public function handle()
{
$settings = Setting::getSettings();
$settings = Setting::getSettings();
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
$this->info($whenNotify . ' is deadline');
$this->info($assets->count() . ' assets');
foreach ($assets as $asset) {
if ($asset->assigned && $asset->checkedOutToUser()) {
if ($asset->assigned && $asset->checkedOutToUser()) {
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
}
}
// Send a rollup to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AlertRecipient();
if (($assets) && ($assets->count() > 0) && ($settings->alert_email!='')) {
$recipient->notify(new ExpectedCheckinAdminNotification($assets));
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new AlertRecipient($item);
});
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
}
}

View file

@ -4,16 +4,14 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\License;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use DB;
use App\Notifications\ExpiringLicenseNotification;
use App\Notifications\ExpiringAssetsNotification;
use App\Notifications\ExpiringLicenseNotification;
use Illuminate\Console\Command;
class SendExpirationAlerts extends Command
{
/**
* The console command name.
*
@ -30,8 +28,6 @@ class SendExpirationAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -45,46 +41,35 @@ class SendExpirationAlerts extends Command
*/
public function handle()
{
$settings = Setting::getSettings();
$settings = Setting::getSettings();
$threshold = $settings->alert_interval;
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
// Expiring Assets
$assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count'=>$assets->count(), 'threshold' => $threshold]));
// Expiring licenses
$licenses = License::getExpiringLicenses($threshold);
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count'=>$licenses->count(), 'threshold' => $threshold]));
$recipient = new \App\Models\Recipients\AlertRecipient();
if ((Setting::getSettings()->alert_email!='') && ($settings->alerts_enabled==1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new AlertRecipient($item);
});
// Expiring Assets
$assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
if ($assets->count() > 0) {
// Send a rollup to the admin, if settings dictate
$recipient->notify(new ExpiringAssetsNotification($assets, $threshold));
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
}
// Expiring licenses
$licenses = License::getExpiringLicenses($threshold);
if ($licenses->count() > 0) {
$recipient->notify(new ExpiringLicenseNotification($licenses, $threshold));
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
}
} else {
if ($settings->alert_email=='') {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif ($settings->alerts_enabled!=1) {
} elseif (1 != $settings->alerts_enabled) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}
}
}

View file

@ -2,13 +2,12 @@
namespace App\Console\Commands;
use App\Models\Setting;
use DB;
use Mail;
use App\Helpers\Helper;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\InventoryAlert;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class SendInventoryAlerts extends Command
{
@ -28,8 +27,6 @@ class SendInventoryAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@ -45,28 +42,24 @@ class SendInventoryAlerts extends Command
{
$settings = Setting::getSettings();
if (($settings->alert_email!='') && ($settings->alerts_enabled==1)) {
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
$items = Helper::checkLowInventory();
// Send a rollup to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AlertRecipient();
if (($items) && (count($items) > 0)) {
$this->info(trans_choice('mail.low_inventory_alert', count($items)));
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new AlertRecipient($item);
});
if (($items) && (count($items) > 0) && ($settings->alert_email!='')) {
$this->info( trans_choice('mail.low_inventory_alert',count($items)) );
$recipient->notify(new InventoryAlert($items, $settings->alert_threshold));
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
}
} else {
if (Setting::getSettings()->alert_email=='') {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (Setting::getSettings()->alerts_enabled!=1) {
} elseif (1 != $settings->alerts_enabled) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Events;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutAccepted
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(CheckoutAcceptance $acceptance)
{
$this->acceptance = $acceptance;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Events;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutDeclined
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(CheckoutAcceptance $acceptance)
{
$this->acceptance = $acceptance;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutableCheckedIn
{
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedInBy;
public $note;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note)
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutableCheckedOut
{
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedOutBy;
public $note;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedOutBy = $checkedOutBy;
$this->note = $note;
}
}

View file

@ -2,10 +2,12 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
@ -46,7 +48,7 @@ class AccessoryCheckinController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param int $accessoryId
*/
public function store($accessoryUserId = null, $backto = null)
public function store(Request $request, $accessoryUserId = null, $backto = null)
{
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
@ -61,7 +63,8 @@ class AccessoryCheckinController extends Controller
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
$return_to = e($accessory_user->assigned_to);
$accessory->logCheckin(User::find($return_to), e(Input::get('note')));
event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note')));
return redirect()->route("accessories.show", $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\User;
@ -77,10 +78,10 @@ class AccessoryCheckoutController extends Controller
'assigned_to' => $request->get('assigned_to')
]);
$accessory->logCheckout(e(Input::get('note')), $user);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
}

View file

@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers\Account;
use App\Events\CheckoutAccepted;
use App\Events\CheckoutDeclined;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\Contracts\Acceptable;
use App\Models\License;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
class AcceptanceController extends Controller {
/**
* Show a listing of pending checkout acceptances for the current user
*
* @return View
*/
public function index() {
$acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
return view('account/accept.index', compact('acceptances'));
}
/**
* Shows a form to either accept or decline the checkout acceptance
*
* @param int $id
* @return mixed
*/
public function create($id) {
$acceptance = CheckoutAcceptance::find($id);
if (is_null($acceptance)) {
return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
}
return view('account/accept.create', compact('acceptance'));
}
/**
* Stores the accept/decline of the checkout acceptance
*
* @param Request $request
* @param int $id
* @return Redirect
*/
public function store(Request $request, $id) {
$acceptance = CheckoutAcceptance::find($id);
if (is_null($acceptance)) {
return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
}
if (!$request->filled('asset_acceptance')) {
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
}
/**
* Get the signature and save it
*/
if ($request->filled('signature_output')) {
$path = config('app.private_uploads').'/signatures';
$sig_filename = "siglog-" .Str::uuid() . '-'.date('Y-m-d-his').".png";
$data_uri = e($request->input('signature_output'));
$encoded_image = explode(",", $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
file_put_contents($path."/".$sig_filename, $decoded_image);
}
if ($request->input('asset_acceptance') == 'accepted') {
$acceptance->accept($sig_filename);
event(new CheckoutAccepted($acceptance));
$return_msg = trans('admin/users/message.accepted');
} else {
$acceptance->decline($sig_filename);
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
}
return redirect()->to('account/accept')->with('success', $return_msg);
}
}

View file

@ -143,13 +143,16 @@ class AccessoriesController extends Controller
$accessory->lastCheckoutArray = $accessory->lastCheckout->toArray();
$accessory_users = $accessory->users;
if($request->filled('search')){
$accessory_users = $accessory_users->where('first_name', $request->input('search'))->concat($accessory_users->where('last_name', $request->input('search')));
if ($request->filled('search')) {
$accessory_users = $accessory->users()
->where('first_name', 'like', '%'.$request->input('search').'%')
->orWhere('last_name', 'like', '%'.$request->input('search').'%')
->get();
}
$total = $accessory_users->count();
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
}

View file

@ -150,7 +150,7 @@ class LicensesController extends Controller
public function show($id)
{
$this->authorize('view', License::class);
$license = License::findOrFail($id);
$license = License::withCount('freeSeats')->findOrFail($id);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
return (new LicensesTransformer)->transformLicense($license);
}

View file

@ -106,7 +106,26 @@ class LocationsController extends Controller
public function show($id)
{
$this->authorize('view', Location::class);
$location = Location::findOrFail($id);
$location = Location::with('parent', 'manager', 'childLocations')
->select([
'locations.id',
'locations.name',
'locations.address',
'locations.address2',
'locations.city',
'locations.state',
'locations.zip',
'locations.country',
'locations.parent_id',
'locations.manager_id',
'locations.created_at',
'locations.updated_at',
'locations.image',
'locations.currency'
])
->withCount('assignedAssets')
->withCount('assets')
->withCount('users')->findOrFail($id);
return (new LocationsTransformer)->transformLocation($location);
}

View file

@ -83,7 +83,7 @@ class ManufacturersController extends Controller
public function show($id)
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$manufacturer = Manufacturer::withCount('assets as assets_count', 'licenses as licenses_count', 'consumables as consumables_count', 'accessories as accessories_count', 'models as models_count' )->findOrFail($id);
return (new ManufacturersTransformer)->transformManufacturer($manufacturer);
}
@ -120,11 +120,21 @@ class ManufacturersController extends Controller
*/
public function destroy($id)
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$manufacturer = Manufacturer::withCount('assets as assets_count', 'licenses as licenses_count', 'consumables as consumables_count', 'accessories as accessories_count', 'models as models_count' )->findOrFail($id);
$this->authorize('delete', $manufacturer);
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
if (($manufacturer->assets_count == 0) && ($manufacturer->licenses_count==0) && ($manufacturer->consumables_count==0) && ($manufacturer->accessories_count==0) && ($manufacturer->models_count==0) && ($manufacturer->deleted_at=='')) {
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.delete.error')));
}

View file

@ -238,9 +238,6 @@ class StatuslabelsController extends Controller
*/
public function checkIfDeployable($id) {
$statuslabel = Statuslabel::findOrFail($id);
$this->authorize('view', $statuslabel);
if ($statuslabel->getStatuslabelType()=='deployable') {
return '1';
}

View file

@ -203,6 +203,8 @@ class UsersController extends Controller
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
} else {
$user->groups()->sync(array());
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.create')));
}

View file

@ -2,10 +2,12 @@
namespace App\Http\Controllers\Assets;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
@ -82,8 +84,8 @@ class AssetCheckinController extends Controller
// Was the asset updated?
if ($asset->save()) {
$asset->logCheckin($target, e(request('note')));
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note')));
if ($backto=='user') {
return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success'));

View file

@ -2,11 +2,14 @@
namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Http\Controllers\Controller;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Component;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
@ -86,22 +89,16 @@ class ComponentCheckinController extends Controller
DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
$log = new Actionlog();
$log->user_id = auth()->id();
$log->action_type = 'checkin from';
$log->target_type = Asset::class;
$log->target_id = $component_assets->asset_id;
$log->item_id = $component_assets->component_id;
$log->item_type = Component::class;
$log->note = $request->input('note');
$log->save();
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) {
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
$asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note')));
return redirect()->route('components.index')->with('success',
trans('admin/components/message.checkout.success'));
}

View file

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
@ -86,7 +88,8 @@ class ComponentCheckoutController extends Controller
'asset_id' => $asset_id
]);
$component->logCheckout(e(Input::get('note')), $asset);
event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
}
}

View file

@ -2,10 +2,11 @@
namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@ -41,7 +42,7 @@ class ConsumableCheckoutController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store($consumableId)
public function store(Request $request, $consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
@ -67,7 +68,7 @@ class ConsumableCheckoutController extends Controller
'assigned_to' => e(Input::get('assigned_to'))
]);
$consumable->logCheckout(e(Input::get('note')), $user);
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));

View file

@ -2,12 +2,14 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
@ -49,7 +51,7 @@ class LicenseCheckinController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store($seatId = null, $backTo = null)
public function store(Request $request, $seatId = null, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId))) {
@ -88,7 +90,9 @@ class LicenseCheckinController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckin($return_to, e(request('note')));
event(new CheckoutableCheckedIn($license, $return_to, Auth::user(), $request->input('note')));
if ($backTo=='user') {
return redirect()->route("users.show", $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}

View file

@ -2,15 +2,16 @@
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
class LicenseCheckoutController extends Controller
@ -103,7 +104,9 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
$licenseSeat->logCheckout(request('note'), $target);
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
return true;
}
return false;
@ -118,7 +121,9 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
$licenseSeat->logCheckout(request('note'), $target);
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
return true;
}
return false;

View file

@ -7,6 +7,7 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\CustomField;
use App\Models\CheckoutAcceptance;
use App\Models\Depreciation;
use App\Models\License;
use App\Models\Setting;
@ -805,7 +806,20 @@ class ReportsController extends Controller
public function getAssetAcceptanceReport()
{
$this->authorize('reports.view');
$assetsForReport = Asset::notYetAccepted()->with('company')->get();
/**
* Get all assets with pending checkout acceptances
*/
$acceptances = CheckoutAcceptance::pending()->get();
$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return $acceptance->checkoutable;
});
return view('reports/unaccepted_assets', compact('assetsForReport'));
}

View file

@ -199,124 +199,6 @@ class ViewAssetsController extends Controller
// Get the acceptance screen
public function getAcceptAsset($logID = null)
{
$findlog = Actionlog::where('id', $logID)->first();
if (!$findlog) {
return redirect()->to('account/view-assets')->with('error', 'No matching record.');
}
if ($findlog->accepted_id!='') {
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
$user = Auth::user();
// TODO - Fix this for non-assets
if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) {
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
$item = $findlog->item;
// Check if the asset exists
if (is_null($item)) {
// Redirect to the asset management page
return redirect()->to('account')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (!Company::isCurrentUserHasAccess($item)) {
return redirect()->route('requestable-assets')->with('error', trans('general.insufficient_permissions'));
}
return view('account/accept-asset', compact('item'))->with('findlog', $findlog)->with('item', $item);
}
// Save the acceptance
public function postAcceptAsset(Request $request, $logID = null)
{
// Check if the asset exists
if (is_null($findlog = Actionlog::where('id', $logID)->first())) {
// Redirect to the asset management page
return redirect()->to('account/view-assets')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if ($findlog->accepted_id!='') {
// Redirect to the asset management page
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
if (!Input::has('asset_acceptance')) {
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
}
$user = Auth::user();
if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) {
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
if ($request->filled('signature_output')) {
$path = config('app.private_uploads').'/signatures';
$sig_filename = "siglog-".$findlog->id.'-'.date('Y-m-d-his').".png";
$data_uri = e($request->get('signature_output'));
$encoded_image = explode(",", $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
file_put_contents($path."/".$sig_filename, $decoded_image);
}
$logaction = new Actionlog();
if (Input::get('asset_acceptance')=='accepted') {
$logaction_msg = 'accepted';
$accepted="accepted";
$return_msg = trans('admin/users/message.accepted');
} else {
$logaction_msg = 'declined';
$accepted="rejected";
$return_msg = trans('admin/users/message.declined');
}
$logaction->item_id = $findlog->item_id;
$logaction->item_type = $findlog->item_type;
// Asset
if (($findlog->item_id!='') && ($findlog->item_type==Asset::class)) {
if (Input::get('asset_acceptance')!='accepted') {
DB::table('assets')
->where('id', $findlog->item_id)
->update(array('assigned_to' => null));
}
}
$logaction->target_id = $findlog->target_id;
$logaction->target_type = User::class;
$logaction->note = e(Input::get('note'));
$logaction->updated_at = date("Y-m-d H:i:s");
if (isset($sig_filename)) {
$logaction->accept_signature = $sig_filename;
}
$log = $logaction->logaction($logaction_msg);
$update_checkout = DB::table('action_logs')
->where('id', $findlog->id)
->update(array('accepted_id' => $logaction->id));
if (($findlog->item_id!='') && ($findlog->item_type==Asset::class)) {
$affected_asset = $logaction->item;
$affected_asset->accepted = $accepted;
$affected_asset->save();
}
if ($update_checkout) {
return redirect()->to('account/view-assets')->with('success', $return_msg);
}
return redirect()->to('account/view-assets')->with('error', 'Something went wrong ');
return redirect()->route('account.accept');
}
}

View file

@ -0,0 +1,184 @@
<?php
namespace App\Listeners;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Recipients\AdminRecipient;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckinAccessoryNotification;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckinLicenseSeatNotification;
use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
use Illuminate\Support\Facades\Notification;
class CheckoutableListener
{
/**
* Notify the user about the checked out checkoutable
*/
public function onCheckedOut($event) {
/**
* When the item wasn't checked out to a user, we can't send notifications
*/
if(! $event->checkedOutTo instanceof User) {
return;
}
/**
* Make a checkout acceptance and attach it in the notification
*/
$acceptance = $this->getCheckoutAcceptance($event);
Notification::send(
$this->getNotifiables($event),
$this->getCheckoutNotification($event, $acceptance)
);
}
/**
* Notify the user about the checked in checkoutable
*/
public function onCheckedIn($event) {
/**
* When the item wasn't checked out to a user, we can't send notifications
*/
if(!$event->checkedOutTo instanceof User) {
return;
}
/**
* Send the appropriate notification
*/
Notification::send(
$this->getNotifiables($event),
$this->getCheckinNotification($event)
);
}
/**
* Generates a checkout acceptance
* @param Event $event
* @return mixed
*/
private function getCheckoutAcceptance($event) {
if (!$event->checkoutable->requireAcceptance()) {
return null;
}
$acceptance = new CheckoutAcceptance;
$acceptance->checkoutable()->associate($event->checkoutable);
$acceptance->assignedTo()->associate($event->checkedOutTo);
$acceptance->save();
return $acceptance;
}
/**
* Gets the entities to be notified of the passed event
*
* @param Event $event
* @return Collection
*/
private function getNotifiables($event) {
$notifiables = collect();
/**
* Notify the user who checked out the item
*/
$notifiables->push($event->checkedOutTo);
/**
* Notify Admin users if the settings is activated
*/
if (Setting::getSettings()->admin_cc_email != '') {
$notifiables->push(new AdminRecipient());
}
return $notifiables;
}
/**
* Get the appropriate notification for the event
*
* @param CheckoutableCheckedIn $event
* @return Notification
*/
private function getCheckinNotification($event) {
$model = get_class($event->checkoutable);
$notificationClass = null;
switch (get_class($event->checkoutable)) {
case Accessory::class:
$notificationClass = CheckinAccessoryNotification::class;
break;
case Asset::class:
$notificationClass = CheckinAssetNotification::class;
break;
case LicenseSeat::class:
$notificationClass = CheckinLicenseSeatNotification::class;
break;
}
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
}
/**
* Get the appropriate notification for the event
*
* @param CheckoutableCheckedIn $event
* @param CheckoutAcceptance $acceptance
* @return Notification
*/
private function getCheckoutNotification($event, $acceptance) {
$notificationClass = null;
switch (get_class($event->checkoutable)) {
case Accessory::class:
$notificationClass = CheckoutAccessoryNotification::class;
break;
case Asset::class:
$notificationClass = CheckoutAssetNotification::class;
break;
case Consumable::class:
$notificationClass = CheckoutConsumableNotification::class;
break;
case LicenseSeat::class:
$notificationClass = CheckoutLicenseSeatNotification::class;
break;
}
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'App\Events\CheckoutableCheckedIn',
'App\Listeners\CheckoutableListener@onCheckedIn'
);
$events->listen(
'App\Events\CheckoutableCheckedOut',
'App\Listeners\CheckoutableListener@onCheckedOut'
);
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace App\Listeners;
use App\Events\AccessoryCheckedIn;
use App\Events\AccessoryCheckedOut;
use App\Events\AssetCheckedIn;
use App\Events\AssetCheckedOut;
use App\Events\CheckoutAccepted;
use App\Events\CheckoutDeclined;
use App\Events\CheckoutableCheckedIn;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedIn;
use App\Events\ComponentCheckedOut;
use App\Events\ConsumableCheckedOut;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Events\LicenseCheckedIn;
use App\Events\LicenseCheckedOut;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Component;
use App\Models\LicenseSeat;
class LogListener
{
public function onCheckoutableCheckedIn(CheckoutableCheckedIn $event) {
$event->checkoutable->logCheckin($event->checkedOutTo, $event->note);
}
public function onCheckoutableCheckedOut(CheckoutableCheckedOut $event) {
$event->checkoutable->logCheckout($event->note, $event->checkedOutTo);
}
public function onCheckoutAccepted(CheckoutAccepted $event) {
$logaction = new Actionlog();
$logaction->item()->associate($event->acceptance->checkoutable);
$logaction->target()->associate($event->acceptance->assignedTo);
$logaction->accept_signature = $event->acceptance->signature_filename;
$logaction->action_type = 'accepted';
// TODO: log the actual license seat that was checked out
if($event->acceptance->checkoutable instanceof LicenseSeat) {
$logaction->item()->associate($event->acceptance->checkoutable->license);
}
$logaction->save();
}
public function onCheckoutDeclined(CheckoutDeclined $event) {
$logaction = new Actionlog();
$logaction->item()->associate($event->acceptance->checkoutable);
$logaction->target()->associate($event->acceptance->assignedTo);
$logaction->accept_signature = $event->acceptance->signature_filename;
$logaction->action_type = 'declined';
// TODO: log the actual license seat that was checked out
if($event->acceptance->checkoutable instanceof LicenseSeat) {
$logaction->item()->associate($event->acceptance->checkoutable->license);
}
$logaction->save();
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$list = [
'CheckoutableCheckedIn',
'CheckoutableCheckedOut',
'CheckoutAccepted',
'CheckoutDeclined',
];
foreach($list as $event) {
$events->listen(
'App\Events\\' . $event,
'App\Listeners\LogListener@on' . $event
);
}
}
}

View file

@ -1,6 +1,7 @@
<?php
namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -27,6 +28,8 @@ class Accessory extends SnipeModel
];
use Searchable;
use Acceptable;
/**
* The attributes that should be included when searching the model.
@ -47,13 +50,6 @@ class Accessory extends SnipeModel
'supplier' => ['name'],
'location' => ['name']
];
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutAccessoryNotification::class;
public static $checkinClass = CheckinAccessoryNotification::class;
/**
* Accessory validation rules

View file

@ -1,10 +1,14 @@
<?php
namespace App\Models;
use App\Events\AssetCheckedOut;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
use App\Models\User;
use App\Presenters\Presentable;
use AssetPresenter;
use Auth;
@ -17,7 +21,6 @@ use Watson\Validating\ValidatingTrait;
use DB;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\CheckoutAssetNotification;
/**
* Model for Assets.
*
@ -32,12 +35,20 @@ class Asset extends Depreciable
const ASSET = 'asset';
const USER = 'user';
const ACCEPTANCE_PENDING = 'pending';
use Acceptable;
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutAssetNotification::class;
public static $checkinClass = CheckinAssetNotification::class;
* Run after the checkout acceptance was declined by the user
*
* @param User $acceptedBy
* @param string $signature
*/
public function declinedCheckout(User $declinedBy, $signature) {
$this->assigned_to = null;
$this->assigned_type = null;
$this->accepted = null;
$this->save();
}
/**
@ -252,21 +263,11 @@ class Asset extends Depreciable
$this->location_id = $target->id;
}
}
/**
* Does the user have to confirm that they accept the asset?
*
* If so, set the acceptance-status to "pending".
* This value is used in the unaccepted assets reports, for example
*
* @see https://github.com/snipe/snipe-it/issues/5772
*/
if ($this->requireAcceptance() && $target instanceof User) {
$this->accepted = self::ACCEPTANCE_PENDING;
}
if ($this->save()) {
$this->logCheckout($note, $target);
event(new CheckoutableCheckedOut($this, $target, Auth::user(), $note));
$this->increment('checkout_counter', 1);
return true;
}

View file

@ -0,0 +1,114 @@
<?php
namespace App\Models;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CheckoutAcceptance extends Model
{
use SoftDeletes;
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'accepted_at',
'declined_at',
'deleted_at'
];
/**
* The resource that was is out
*
* @return Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function checkoutable() {
return $this->morphTo();
}
/**
* The user that the checkoutable was checked out to
*
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function assignedTo() {
return $this->belongsTo(User::class);
}
/**
* Is this checkout acceptance pending?
*
* @return boolean
*/
public function isPending() {
return $this->accepted_at == null && $this->declined_at == null;
}
/**
* Was the checkoutable checked out to this user?
*
* @param User $user
* @return boolean
*/
public function isCheckedOutTo(User $user) {
return $this->assignedTo->is($user);
}
/**
* Accept the checkout acceptance
*
* @param string $signature_filename
*/
public function accept($signature_filename) {
$this->accepted_at = now();
$this->signature_filename = $signature_filename;
$this->save();
/**
* Update state for the checked out item
*/
$this->checkoutable->acceptedCheckout($this->assignedTo, $signature_filename);
}
/**
* Decline the checkout acceptance
*
* @param string $signature_filename
*/
public function decline($signature_filename) {
$this->declined_at = now();
$this->signature_filename = $signature_filename;
$this->save();
/**
* Update state for the checked out item
*/
$this->checkoutable->declinedCheckout($this->assignedTo, $signature_filename);
}
/**
* Filter checkout acceptences by the user
* @param Illuminate\Database\Eloquent\Builder $query
* @param User $user
* @return Illuminate\Database\Eloquent\Builder
*/
public function scopeForUser(Builder $query, User $user) {
return $query->where('assigned_to_id', $user->id);
}
/**
* Filter to only get pending acceptances
* @param Illuminate\Database\Eloquent\Builder $query
* @return Illuminate\Database\Eloquent\Builder
*/
public function scopePending(Builder $query) {
return $query->whereNull('accepted_at')->whereNull('declined_at');
}
}

View file

@ -20,13 +20,6 @@ class Component extends SnipeModel
protected $dates = ['deleted_at', 'purchase_date'];
protected $table = 'components';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = null;
public static $checkinClass = null;
/**
* Category validation rules

View file

@ -1,6 +1,7 @@
<?php
namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -14,18 +15,14 @@ class Consumable extends SnipeModel
use Loggable, Presentable;
use SoftDeletes;
use Acceptable;
protected $dates = ['deleted_at', 'purchase_date'];
protected $table = 'consumables';
protected $casts = [
'requestable' => 'boolean'
];
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutConsumableNotification::class;
public static $checkinClass = null;
/**
* Category validation rules

View file

@ -12,9 +12,14 @@ use Illuminate\Validation\Rule;
class CustomField extends Model
{
use ValidatingTrait, UniqueUndeletedTrait;
public $guarded=["id"];
public static $PredefinedFormats=[
use ValidatingTrait,
UniqueUndeletedTrait;
public $guarded = [
"id"
];
public static $PredefinedFormats = [
"ANY" => "",
"CUSTOM REGEX" => "",
"ALPHA" => "alpha",
@ -31,6 +36,14 @@ class CustomField extends Model
"BOOLEAN" => "boolean",
];
/**
* Validation rules.
* At least empty array must be provided if using ValidatingTrait.
*
* @var array
*/
protected $rules = [];
/**
* The attributes that are mass assignable.
*
@ -57,7 +70,6 @@ class CustomField extends Model
*/
public static $table_name = "assets";
/**
* Convert the custom field's name property to a db-safe string.
*

View file

@ -7,10 +7,16 @@ use Watson\Validating\ValidatingTrait;
class CustomFieldset extends Model
{
use ValidatingTrait;
protected $guarded=["id"];
public $rules=[
"name" => "required|unique:custom_fieldsets"
/**
* Validation rules
* @var array
*/
protected $rules = [
"name" => "required|unique:custom_fieldsets"
];
/**
@ -21,7 +27,6 @@ class CustomFieldset extends Model
* @var boolean
*/
protected $injectUniqueIdentifier = true;
use ValidatingTrait;
/**

View file

@ -18,12 +18,6 @@ class License extends Depreciable
{
protected $presenter = 'App\Presenters\LicensePresenter';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutLicenseNotification::class;
public static $checkinClass = CheckinLicenseNotification::class;
use SoftDeletes;
use CompanyableTrait;

View file

@ -2,32 +2,37 @@
namespace App\Models;
use App\Models\Loggable;
use App\Models\Traits\Acceptable;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Notifications\CheckoutLicenseNotification;
use App\Notifications\CheckinLicenseNotification;
class LicenseSeat extends Model implements ICompanyableChild
class LicenseSeat extends SnipeModel implements ICompanyableChild
{
use CompanyableChildTrait;
use SoftDeletes;
use Loggable;
protected $presenter = 'App\Presenters\LicenseSeatPresenter';
use Presentable;
protected $dates = ['deleted_at'];
protected $guarded = 'id';
protected $table = 'license_seats';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutLicenseNotification::class;
public static $checkinClass = CheckinLicenseNotification::class;
use Acceptable;
public function getCompanyableParents()
{
return ['asset', 'license'];
}
public function getEula() {
return $this->license->getEula();
}
/**
* Establishes the seat -> license relationship
*

View file

@ -20,13 +20,13 @@ class Location extends SnipeModel
protected $dates = ['deleted_at'];
protected $table = 'locations';
protected $rules = array(
'name' => 'required|min:2|max:255|unique_undeleted',
'city' => 'min:2|max:255|nullable',
'country' => 'min:2|max:2|nullable',
'name' => 'required|min:2|max:255|unique_undeleted',
'city' => 'min:2|max:255|nullable',
'country' => 'min:2|max:2|nullable',
'address' => 'max:80|nullable',
'address2' => 'max:80|nullable',
'zip' => 'min:3|max:10|nullable',
// 'manager_id' => 'exists:users'
'zip' => 'min:3|max:10|nullable',
'manager_id' => 'exists:users,id|nullable'
);
/**
@ -56,6 +56,7 @@ class Location extends SnipeModel
'country',
'zip',
'ldap_ou',
'manager_id',
'currency',
'image',
];

View file

@ -6,14 +6,7 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CheckoutRequest;
use App\Models\User;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\AuditNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckinAccessoryNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Notifications\CheckinLicenseNotification;
use Illuminate\Support\Facades\Auth;
@ -38,7 +31,6 @@ trait Loggable
*/
public function logCheckout($note, $target /* What are we checking out to? */)
{
$settings = Setting::getSettings();
$log = new Actionlog;
$log = $this->determineLogItemType($log);
$log->user_id = Auth::user()->id;
@ -63,29 +55,6 @@ trait Loggable
$log->note = $note;
$log->logaction('checkout');
$params = [
'item' => $log->item,
'target_type' => $log->target_type,
'target' => $target,
'admin' => $log->user,
'note' => $note,
'log_id' => $log->id,
'settings' => $settings,
];
$checkoutClass = null;
if (method_exists($target, 'notify')) {
$target->notify(new static::$checkoutClass($params));
}
// Send to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AdminRecipient();
if (($settings->admin_cc_email!='') && (static::$checkoutClass!='')) {
$recipient->notify(new static::$checkoutClass($params));
}
return $log;
}
@ -112,7 +81,6 @@ trait Loggable
*/
public function logCheckin($target, $note)
{
$settings = Setting::getSettings();
$log = new Actionlog;
$log->target_type = get_class($target);
$log->target_id = $target->id;
@ -140,29 +108,6 @@ trait Loggable
$log->user_id = Auth::user()->id;
$log->logaction('checkin from');
$params = [
'target' => $target,
'item' => $log->item,
'admin' => $log->user,
'note' => $note,
'target_type' => $log->target_type,
'settings' => $settings,
];
$checkinClass = null;
if (method_exists($target, 'notify')) {
$target->notify(new static::$checkinClass($params));
}
// Send to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AdminRecipient();
if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) {
$recipient->notify(new static::$checkinClass($params));
}
return $log;
}

View file

@ -1,14 +1,11 @@
<?php
namespace App\Models\Recipients;
use App\Models\Setting;
class AlertRecipient extends Recipient{
public function __construct()
class AlertRecipient extends Recipient
{
public function __construct(string $email)
{
$settings = Setting::getSettings();
$this->email = $settings->alert_email;
$this->email = trim($email);
}
}

View file

@ -1,12 +1,12 @@
<?php
namespace App\Models\Recipients;
use Illuminate\Notifications\Notifiable;
abstract class Recipient {
abstract class Recipient
{
use Notifiable;
protected $email;
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Models\Traits;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
/**
* This trait allows models to have a callback after their checkout gets accepted or declined.
*
* @author Till Deeke <kontakt@tilldeeke.de>
*/
trait Acceptable {
/**
* Run after the checkout acceptance was accepted by the user
*
* @param User $acceptedBy
* @param string $signature
*/
public function acceptedCheckout(User $acceptedBy, $signature) {}
/**
* Run after the checkout acceptance was declined by the user
*
* @param User $acceptedBy
* @param string $signature
*/
public function declinedCheckout(User $declinedBy, $signature) {}
}

View file

@ -126,12 +126,13 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
$user_permissions = json_decode($this->permissions, true);
$is_user_section_permissions_set = ($user_permissions != '') && array_key_exists($section, $user_permissions);
//If the user is explicitly granted, return true
if (($user_permissions!='') && ((array_key_exists($section, $user_permissions)) && ($user_permissions[$section]=='1'))) {
if ($is_user_section_permissions_set && ($user_permissions[$section]=='1')) {
return true;
}
// If the user is explicitly denied, return false
if (($user_permissions=='') || array_key_exists($section, $user_permissions) && ($user_permissions[$section]=='-1')) {
if ($is_user_section_permissions_set && ($user_permissions[$section]=='-1')) {
return false;
}

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Models\Accessory;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\User;
@ -15,31 +16,19 @@ use Illuminate\Support\Facades\Mail;
class CheckinAccessoryNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
public function __construct(Accessory $accessory, $checkedOutTo, User $checkedInby, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->target_type = $params['target'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->item = $accessory;
$this->target = $checkedOutTo;
$this->admin = $checkedInby;
$this->note = $note;
$this->settings = Setting::getSettings();
}
/**

View file

@ -2,13 +2,14 @@
namespace App\Notifications;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use App\Models\User;
use Illuminate\Notifications\Notification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class CheckinAssetNotification extends Notification
{
@ -20,19 +21,15 @@ class CheckinAssetNotification extends Notification
*
* @param $params
*/
public function __construct($params)
public function __construct(Asset $asset, $checkedOutTo, User $checkedInBy, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
$this->target = $checkedOutTo;
$this->item = $asset;
$this->admin = $checkedInBy;
$this->note = $note;
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->settings = Setting::getSettings();
$this->expected_checkin = '';
if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date',

View file

@ -2,6 +2,8 @@
namespace App\Notifications;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\User;
@ -12,7 +14,7 @@ use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckinLicenseNotification extends Notification
class CheckinLicenseSeatNotification extends Notification
{
use Queueable;
/**
@ -25,19 +27,13 @@ class CheckinLicenseNotification extends Notification
*
* @param $params
*/
public function __construct($params)
public function __construct(LicenseSeat $licenseSeat, $checkedOutTo, User $checkedInBy, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->settings = $params['settings'];
$this->target_type = $params['target_type'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->target = $checkedOutTo;
$this->item = $licenseSeat->license;
$this->admin = $checkedInBy;
$this->note = $note;
$this->settings = Setting::getSettings();
}
/**

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Models\Accessory;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\User;
@ -15,33 +16,20 @@ use Illuminate\Support\Facades\Mail;
class CheckoutAccessoryNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
public function __construct(Accessory $accessory, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->item = $accessory;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->settings = Setting::getSettings();
}
@ -132,6 +120,8 @@ class CheckoutAccessoryNotification extends Notification
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
return (new MailMessage)->markdown('notifications.markdown.checkout-accessory',
[
'item' => $this->item,
@ -140,7 +130,7 @@ class CheckoutAccessoryNotification extends Notification
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
'accept_url' => $accept_url,
])
->subject(trans('mail.Confirm_accessory_delivery'));

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
@ -13,31 +14,25 @@ use Illuminate\Contracts\Queue\ShouldQueue;
class CheckoutAssetNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->item = $asset;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->settings = Setting::getSettings();
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
if ($this->item->last_checkout) {
$this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date',
@ -146,17 +141,18 @@ class CheckoutAssetNotification extends Notification
$fields = $this->item->model->fieldset->fields;
}
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
$message = (new MailMessage)->markdown('notifications.markdown.checkout-asset',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'log_id' => $this->note,
'target' => $this->target,
'fields' => $fields,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
'accept_url' => $accept_url,
'last_checkout' => $this->last_checkout,
'expected_checkin' => $this->expected_checkin,
])

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Models\Consumable;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\User;
@ -25,21 +26,16 @@ class CheckoutConsumableNotification extends Notification
*
* @param $params
*/
public function __construct($params)
public function __construct(Consumable $consumable, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->item = $consumable;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->settings = Setting::getSettings();
}
@ -126,16 +122,17 @@ class CheckoutConsumableNotification extends Notification
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
return (new MailMessage)->markdown('notifications.markdown.checkout-consumable',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'log_id' => $this->note,
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
'accept_url' => $accept_url,
])
->subject(trans('mail.Confirm_consumable_delivery'));

View file

@ -2,6 +2,8 @@
namespace App\Notifications;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\User;
@ -12,7 +14,7 @@ use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutLicenseNotification extends Notification
class CheckoutLicenseSeatNotification extends Notification
{
use Queueable;
/**
@ -25,23 +27,15 @@ class CheckoutLicenseNotification extends Notification
*
* @param $params
*/
public function __construct($params)
public function __construct(LicenseSeat $licenseSeat, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
$this->target_type = $params['target_type'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
$this->item = $licenseSeat->license;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->settings = Setting::getSettings();
}
/**
@ -125,6 +119,8 @@ class CheckoutLicenseNotification extends Notification
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
return (new MailMessage)->markdown('notifications.markdown.checkout-license',
[
'item' => $this->item,
@ -133,7 +129,7 @@ class CheckoutLicenseNotification extends Notification
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
'accept_url' => $accept_url,
])
->subject(trans('mail.Confirm_license_delivery'));

View file

@ -3,8 +3,8 @@
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class InventoryAlert extends Notification
{
@ -33,14 +33,13 @@ class InventoryAlert extends Notification
*/
public function via($notifiable)
{
$notifyBy = [];
$notifyBy[]='mail';
$notifyBy[] = 'mail';
return $notifyBy;
}
public function toSlack($notifiable)
{
}
/**
@ -51,17 +50,16 @@ class InventoryAlert extends Notification
*/
public function toMail($params)
{
$message = (new MailMessage)->markdown('notifications.markdown.report-low-inventory',
$message = (new MailMessage)->markdown(
'notifications.markdown.report-low-inventory',
[
'items' => $this->items,
'threshold' => $this->threshold,
])
]
)
->subject(trans('mail.Low_Inventory_Report'));
return $message;
}
/**

View file

@ -0,0 +1,18 @@
<?php
namespace App\Presenters;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Gate;
/**
* Class LicensePresenter
* @package App\Presenters
*/
class LicenseSeatPresenter extends Presenter
{
public function name()
{
return $this->model->license->name;
}
}

View file

@ -3,6 +3,8 @@
namespace App\Providers;
use Illuminate\Support\Facades\Event;
use App\Listeners\CheckoutableListener;
use App\Listeners\LogListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
@ -23,6 +25,15 @@ class EventServiceProvider extends ServiceProvider
],
];
/**
* The subscriber classes to register.
*
* @var array
*/
protected $subscribe = [
LogListener::class,
CheckoutableListener::class
];
/**
* Register any events for your application.

View file

@ -0,0 +1,41 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCheckoutAcceptancesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('checkout_acceptances', function (Blueprint $table) {
$table->increments('id');
$table->morphs('checkoutable');
$table->integer('assigned_to_id')->unsigned();
$table->string('signature_filename')->nullable();
$table->timestamp('accepted_at')->nullable();
$table->timestamp('declined_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('checkout_acceptances');
}
}

View file

@ -0,0 +1,43 @@
<?php
use App\Models\Actionlog;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreateCheckoutAcceptancesForUnacceptedAssets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Get all assets not accepted
$assets = DB::table('assets')->where('assigned_type', 'App\Models\User')->where('accepted', 'pending')->get();
$acceptances = [];
foreach($assets as $asset) {
$acceptances[] = [
'checkoutable_type' => 'App\Models\Asset',
'checkoutable_id' => $asset->id,
'assigned_to_id' => $asset->assigned_to,
];
}
DB::table('checkout_acceptances')->insert($acceptances);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View file

@ -17,8 +17,24 @@ else
fi
# create data directories
for dir in 'data/private_uploads' 'data/uploads' 'data/uploads/avatars' 'data/uploads/barcodes' 'data/uploads/categories' 'data/uploads/companies' 'data/uploads/departments' 'data/uploads/locations' 'data/uploads/manufacturers' 'data/uploads/models' 'data/uploads/suppliers' 'dumps' 'keys'; do
mkdir -p "/var/lib/snipeit/$dir"
for dir in \
'data/private_uploads' \
'data/uploads/accessories' \
'data/uploads/avatars' \
'data/uploads/barcodes' \
'data/uploads/categories' \
'data/uploads/companies' \
'data/uploads/components' \
'data/uploads/consumables' \
'data/uploads/departments' \
'data/uploads/locations' \
'data/uploads/manufacturers' \
'data/uploads/models' \
'data/uploads/suppliers' \
'dumps' \
'keys'
do
[ ! -d "/var/lib/snipeit/$dir" ] && mkdir -p "/var/lib/snipeit/$dir"
done
chown -R docker:root /var/lib/snipeit/data/*
@ -26,9 +42,10 @@ chown -R docker:root /var/lib/snipeit/dumps
chown -R docker:root /var/lib/snipeit/keys
# If the Oauth DB files are not present copy the vendor files over to the db migrations
if [ ! -f "/var/www/html/database/migrations/*create_oauth*" ]; then
if [ ! -f "/var/www/html/database/migrations/*create_oauth*" ]
then
cp -ax /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/
fi
. /etc/apache2/envvars
. /etc/apache2/envvars
exec apache2 -DNO_DETACH < /dev/null

54
phpmd.xml Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Laravel and similar phpmd ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
Inspired by https://github.com/phpmd/phpmd/issues/137
using http://phpmd.org/documentation/creating-a-ruleset.html
</description>
<!-- se importan los rulesets, en este caso todos. -->
<rule ref="rulesets/cleancode.xml">
<exclude name="StaticAccess"/>
</rule>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
<rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
<rule ref="rulesets/codesize.xml/TooManyFields"/>
<rule ref="rulesets/codesize.xml/TooManyMethods">
<properties>
<property name="maxmethods" value="30"/>
</properties>
</rule>
<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity"/>
<rule ref="rulesets/controversial.xml"/>
<rule ref="rulesets/design.xml">
<exclude name="CouplingBetweenObjects"/>
</rule>
<!-- beware the façades yo. -->
<rule ref="rulesets/design.xml/CouplingBetweenObjects">
<properties>
<property name="minimum" value="20"/>
</properties>
</rule>
<!-- se importa naming y se excluye ShortVariable para ser ajustada despues. -->
<rule ref="rulesets/naming.xml">
<exclude name="ShortVariable"/>
</rule>
<rule ref="rulesets/naming.xml/ShortVariable"
since="0.2"
message="Avoid variables with short names like {0}. Configured minimum length is {1}."
class="PHPMD\Rule\Naming\ShortVariable"
externalInfoUrl="http://phpmd.org/rules/naming.html#shortvariable">
<priority>3</priority>
<properties>
<property name="minimum" description="Minimum length for a variable, property or parameter name" value="3"/>
<property name="exceptions" value="id,q,w,i,j,v,e,f,fp" />
</properties>
</rule>
<rule ref="rulesets/unusedcode.xml"/>
</ruleset>

View file

@ -0,0 +1,135 @@
@extends('layouts/default')
{{-- Page title --}}
@section('title')
Accept {{ $acceptance->checkoutable->present()->name() }}
@parent
@stop
{{-- Page content --}}
@section('content')
<link rel="stylesheet" href="{{ asset('css/signature-pad.css') }}">
<style>
.form-horizontal .control-label, .form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline {
padding-top: 17px;
padding-right: 10px;
}
#eula_div {
width: 100%;
height: auto;
overflow: scroll;
}
</style>
<form class="form-horizontal" method="post" action="" autocomplete="off">
<!-- CSRF Token -->
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
<div class="row">
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2">
<div class="panel box box-default">
<div class="box-body">
<div class="col-md-12">
<div class="radio">
<label>
<input type="radio" name="asset_acceptance" id="accepted" value="accepted">
I accept
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="asset_acceptance" id="declined" value="declined">
I decline
</label>
</div>
@if ($acceptance->checkoutable->getEula())
<div class="col-md-12" style="padding-top: 20px">
<div id="eula_div">
{!! $acceptance->checkoutable->getEula() !!}
</div>
</div>
@endif
@if ($snipeSettings->require_accept_signature=='1')
<div class="col-md-12 col-sm-12 text-center" style="padding-top: 20px">
<h3>Sign below to indicate that you agree to the terms of service:</h3>
<div id="signature-pad" class="m-signature-pad">
<div class="m-signature-pad--body col-md-12 col-sm-12 col-lg-12 col-xs-12">
<canvas></canvas>
<input type="hidden" name="signature_output" id="signature_output">
</div>
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-center">
<button type="button" class="btn btn-sm btn-default clear" data-action="clear" id="clear_button">Clear Signature</button>
</div>
</div>
</div> <!-- .col-md-12.text-center-->
@endif
</div><!-- / col-md-12 -->
</div> <!-- / box-body -->
<div class="box-footer text-right">
<button type="submit" class="btn btn-success" id="submit-button"><i class="fa fa-check icon-white"></i> {{ trans('general.submit') }}</button>
</div><!-- /.box-footer -->
</div> <!-- / box-default -->
</div> <!-- / col -->
</div> <!-- / row -->
</form>
@stop
@section('moar_scripts')
<script src="{{ asset('js/signature_pad.min.js') }}"></script>
<script nonce="{{ csrf_token() }}">
var wrapper = document.getElementById("signature-pad"),
clearButton = wrapper.querySelector("[data-action=clear]"),
saveButton = wrapper.querySelector("[data-action=save]"),
canvas = wrapper.querySelector("canvas"),
signaturePad;
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
}
window.onresize = resizeCanvas;
resizeCanvas();
signaturePad = new SignaturePad(canvas);
$('#clear_button').on("click", function (event) {
signaturePad.clear();
});
$('#submit-button').on("click", function (event) {
if (signaturePad.isEmpty()) {
alert("Please provide signature first.");
return false;
} else {
$('#signature_output').val(signaturePad.toDataURL());
}
});
</script>
@stop

View file

@ -0,0 +1,61 @@
@extends('layouts/default')
{{-- Page title --}}
@section('title')
Accept assets {{ $user->present()->fullName() }}
@parent
@stop
{{-- Account page content --}}
@section('content')
<div class="row">
<div class="col-md-12">
<div class="box box-default">
<div class="box-body">
<!-- checked out Accessories table -->
<div class="table-responsive">
<table
data-cookie-id-table="pendingAcceptances"
data-pagination="true"
data-id-table="pendingAcceptances"
data-search="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-refresh="true"
data-sort-order="asc"
id="pendingAcceptances"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "my-pending-acceptances-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($acceptances as $acceptance)
<tr>
<td>{{ $acceptance->checkoutable->present()->name }}</td>
<td><a href="{{ route('account.accept.item', $acceptance) }}" class="btn btn-default btn-sm">Accept/Decline</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div> <!-- .box-body-->
</div><!--.box.box-default-->
</div> <!-- .col-md-12-->
</div> <!-- .row-->
@stop
@section('moar_scripts')
@include ('partials.bootstrap-table')
@stop

View file

@ -59,8 +59,10 @@
<label>
<input type="checkbox" value="1" name="update_location" class="minimal" {{ Input::old('update_location') == '1' ? ' checked="checked"' : '' }}> Update asset location
</label>
sdd
<p class="help-block">Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log.</p>
<a href="#" class="text-dark-gray" tabindex="0" role="button" data-toggle="popover" data-trigger="focus" title="<i class='fa fa-life-ring'></i> More Info" data-html="true" data-content="Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log.<br><br>Note that is this asset is checked out, it will not change the location of the person, asset or location it is checked out to."><i class="fa fa-life-ring"></i></a>
</div>
</div>

View file

@ -51,7 +51,7 @@
<div class="col-sm-offset-3 col-md-9">
<label>
<input type="checkbox" value="1" name="update_location" class="minimal" {{ Input::old('update_location') == '1' ? ' checked="checked"' : '' }}> Update asset location
</label> <a href="#" class="text-dark-gray" tabindex="0" role="button" data-toggle="popover" data-trigger="focus" title="More Info" data-html="true" data-content="Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log.<br><br>Note that is this asset is checked out, it will not change the location of the person, asset or location it is checked out to."><i class="fa fa-life-ring"></i></a>
</label> <a href="#" class="text-dark-gray" tabindex="0" role="button" data-toggle="popover" data-trigger="focus" title="<i class='fa fa-life-ring'></i> More Info" data-html="true" data-content="Checking this box will edit the asset record to reflect this new location. Leaving it unchecked will simply note the location in the audit log.<br><br>Note that is this asset is checked out, it will not change the location of the person, asset or location it is checked out to."><i class="fa fa-life-ring"></i></a>
</div>
</div>

View file

@ -309,7 +309,11 @@
<i class="fa fa-check fa-disk fa-fw"></i>
Requested Assets
</a></li>
<li {!! (Request::is('account/accept') ? ' class="active"' : '') !!}>
<a href="{{ route('account.accept') }}">
<i class="fa fa-check fa-disk fa-fw"></i>
Accept Assets
</a></li>

View file

@ -112,7 +112,7 @@
{{ $model->manufacturer->name }}
@endcan
</li>
@endif
@if ($model->manufacturer->url)
<li>
<i class="fa fa-globe"></i> <a href="{{ $model->manufacturer->url }}">{{ $model->manufacturer->url }}</a>
@ -138,7 +138,7 @@
<i class="fa fa-envelope"></i> <a href="mailto:{{ $model->manufacturer->support_email }}">{{ $model->manufacturer->support_email }}</a>
</li>
@endif
@endif
@if ($model->model_number)
<li>
{{ trans('general.model_no') }}:

View file

@ -31,7 +31,7 @@
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0">
{{ $header or '' }}
{{ $header ?? '' }}
<!-- Email Body -->
<tr>
@ -42,14 +42,14 @@
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy or '' }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer or '' }}
{{ $footer ?? '' }}
</table>
</td>
@ -57,3 +57,4 @@
</table>
</body>
</html>

View file

@ -251,10 +251,6 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () {
'accept-asset/{logID}',
[ 'as' => 'account/accept-assets', 'uses' => 'ViewAssetsController@getAcceptAsset' ]
);
Route::post(
'accept-asset/{logID}',
[ 'as' => 'account/asset-accepted', 'uses' => 'ViewAssetsController@postAcceptAsset' ]
);
# Profile
Route::get(
@ -274,6 +270,15 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () {
# Account Dashboard
Route::get('/', [ 'as' => 'account', 'uses' => 'ViewAssetsController@getIndex' ]);
Route::get('accept', 'Account\AcceptanceController@index')
->name('account.accept');
Route::get('accept/{id}', 'Account\AcceptanceController@create')
->name('account.accept.item');
Route::post('accept/{id}', 'Account\AcceptanceController@store');
});

View file

@ -39,11 +39,11 @@ class DepreciationTest extends BaseTest
{
$category = $this->createValidCategory('license-graphics-category');
$depreciation = $this->createValidDepreciation('computer', ['name' => 'New Depreciation']);
$licenses = factory(App\Models\License::class, 5)->states('photoshop')->create([
$licenses = factory(App\Models\LicenseModel::class, 5)->states('photoshop')->create([
'depreciation_id'=>$depreciation->id,
'category_id' => $category->id
]);
$this->assertEquals(5,$depreciation->has_licenses());
$this->assertEquals(5,$depreciation->licenses()->count());
}
}