Merge pull request #5913 from tilldeeke/refactore-checkout-checkin-notification-sending

Refactor: Decouple checkin/checkout notifications from logging
This commit is contained in:
Brady Wetherington 2018-09-21 18:18:27 -07:00 committed by GitHub
commit c97db3259f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1154 additions and 374 deletions

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; namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
@ -46,7 +48,7 @@ class AccessoryCheckinController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param int $accessoryId * @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 // Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
@ -61,7 +63,8 @@ class AccessoryCheckinController extends Controller
// Was the accessory updated? // Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
$return_to = e($accessory_user->assigned_to); $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')); 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; namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\User; use App\Models\User;
@ -77,10 +78,10 @@ class AccessoryCheckoutController extends Controller
'assigned_to' => $request->get('assigned_to') '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(); 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 // Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success')); 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

@ -2,10 +2,12 @@
namespace App\Http\Controllers\Assets; namespace App\Http\Controllers\Assets;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest; use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
@ -82,8 +84,8 @@ class AssetCheckinController extends Controller
// Was the asset updated? // Was the asset updated?
if ($asset->save()) { if ($asset->save()) {
$asset->logCheckin($target, e(request('note')));
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note')));
if ($backto=='user') { if ($backto=='user') {
return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success')); 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; namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Component; use App\Models\Component;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
@ -86,22 +89,16 @@ class ComponentCheckinController extends Controller
DB::table('components_assets')->where('id', DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); $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, // If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record // we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) { if ($qty_remaining_in_checkout == 0) {
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete(); 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', return redirect()->route('components.index')->with('success',
trans('admin/components/message.checkout.success')); trans('admin/components/message.checkout.success'));
} }

View file

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Components; namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Component; use App\Models\Component;
@ -86,7 +88,8 @@ class ComponentCheckoutController extends Controller
'asset_id' => $asset_id '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')); return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
} }
} }

View file

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

View file

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

View file

@ -7,6 +7,7 @@ use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetMaintenance; use App\Models\AssetMaintenance;
use App\Models\CustomField; use App\Models\CustomField;
use App\Models\CheckoutAcceptance;
use App\Models\Depreciation; use App\Models\Depreciation;
use App\Models\License; use App\Models\License;
use App\Models\Setting; use App\Models\Setting;
@ -805,7 +806,20 @@ class ReportsController extends Controller
public function getAssetAcceptanceReport() public function getAssetAcceptanceReport()
{ {
$this->authorize('reports.view'); $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')); return view('reports/unaccepted_assets', compact('assetsForReport'));
} }

View file

@ -199,124 +199,6 @@ class ViewAssetsController extends Controller
// Get the acceptance screen // Get the acceptance screen
public function getAcceptAsset($logID = null) public function getAcceptAsset($logID = null)
{ {
return redirect()->route('account.accept');
$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 ');
} }
} }

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 <?php
namespace App\Models; namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable; use App\Models\Traits\Searchable;
use App\Presenters\Presentable; use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -27,6 +28,8 @@ class Accessory extends SnipeModel
]; ];
use Searchable; use Searchable;
use Acceptable;
/** /**
* The attributes that should be included when searching the model. * The attributes that should be included when searching the model.
@ -47,13 +50,6 @@ class Accessory extends SnipeModel
'supplier' => ['name'], 'supplier' => ['name'],
'location' => ['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 * Accessory validation rules

View file

@ -1,10 +1,14 @@
<?php <?php
namespace App\Models; namespace App\Models;
use App\Events\AssetCheckedOut;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed; use App\Exceptions\CheckoutNotAllowed;
use App\Http\Traits\UniqueSerialTrait; use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait; use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable; use App\Models\Traits\Searchable;
use App\Models\User;
use App\Presenters\Presentable; use App\Presenters\Presentable;
use AssetPresenter; use AssetPresenter;
use Auth; use Auth;
@ -17,7 +21,6 @@ use Watson\Validating\ValidatingTrait;
use DB; use DB;
use App\Notifications\CheckinAssetNotification; use App\Notifications\CheckinAssetNotification;
use App\Notifications\CheckoutAssetNotification; use App\Notifications\CheckoutAssetNotification;
/** /**
* Model for Assets. * Model for Assets.
* *
@ -32,12 +35,20 @@ class Asset extends Depreciable
const ASSET = 'asset'; const ASSET = 'asset';
const USER = 'user'; const USER = 'user';
const ACCEPTANCE_PENDING = 'pending'; use Acceptable;
/** /**
* Set static properties to determine which checkout/checkin handlers we should use * Run after the checkout acceptance was declined by the user
*/ *
public static $checkoutClass = CheckoutAssetNotification::class; * @param User $acceptedBy
public static $checkinClass = CheckinAssetNotification::class; * @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; $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()) { if ($this->save()) {
$this->logCheckout($note, $target);
event(new CheckoutableCheckedOut($this, $target, Auth::user(), $note));
$this->increment('checkout_counter', 1); $this->increment('checkout_counter', 1);
return true; 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 $dates = ['deleted_at', 'purchase_date'];
protected $table = 'components'; 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 * Category validation rules

View file

@ -1,6 +1,7 @@
<?php <?php
namespace App\Models; namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable; use App\Models\Traits\Searchable;
use App\Presenters\Presentable; use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -14,18 +15,14 @@ class Consumable extends SnipeModel
use Loggable, Presentable; use Loggable, Presentable;
use SoftDeletes; use SoftDeletes;
use Acceptable;
protected $dates = ['deleted_at', 'purchase_date']; protected $dates = ['deleted_at', 'purchase_date'];
protected $table = 'consumables'; protected $table = 'consumables';
protected $casts = [ protected $casts = [
'requestable' => 'boolean' '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 * Category validation rules

View file

@ -18,12 +18,6 @@ class License extends Depreciable
{ {
protected $presenter = 'App\Presenters\LicensePresenter'; 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 SoftDeletes;
use CompanyableTrait; use CompanyableTrait;

View file

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

View file

@ -6,14 +6,7 @@ use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\CheckoutRequest; use App\Models\CheckoutRequest;
use App\Models\User; use App\Models\User;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\AuditNotification; 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; use Illuminate\Support\Facades\Auth;
@ -38,7 +31,6 @@ trait Loggable
*/ */
public function logCheckout($note, $target /* What are we checking out to? */) public function logCheckout($note, $target /* What are we checking out to? */)
{ {
$settings = Setting::getSettings();
$log = new Actionlog; $log = new Actionlog;
$log = $this->determineLogItemType($log); $log = $this->determineLogItemType($log);
$log->user_id = Auth::user()->id; $log->user_id = Auth::user()->id;
@ -63,29 +55,6 @@ trait Loggable
$log->note = $note; $log->note = $note;
$log->logaction('checkout'); $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; return $log;
} }
@ -112,7 +81,6 @@ trait Loggable
*/ */
public function logCheckin($target, $note) public function logCheckin($target, $note)
{ {
$settings = Setting::getSettings();
$log = new Actionlog; $log = new Actionlog;
$log->target_type = get_class($target); $log->target_type = get_class($target);
$log->target_id = $target->id; $log->target_id = $target->id;
@ -140,29 +108,6 @@ trait Loggable
$log->user_id = Auth::user()->id; $log->user_id = Auth::user()->id;
$log->logaction('checkin from'); $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; return $log;
} }

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

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
namespace App\Notifications; namespace App\Notifications;
use App\Models\Accessory;
use App\Models\Setting; use App\Models\Setting;
use App\Models\SnipeModel; use App\Models\SnipeModel;
use App\Models\User; use App\Models\User;
@ -15,33 +16,20 @@ use Illuminate\Support\Facades\Mail;
class CheckoutAccessoryNotification extends Notification class CheckoutAccessoryNotification extends Notification
{ {
use Queueable; use Queueable;
/**
* @var
*/
private $params;
/** /**
* Create a new notification instance. * 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(); $eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance(); $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', return (new MailMessage)->markdown('notifications.markdown.checkout-accessory',
[ [
'item' => $this->item, 'item' => $this->item,
@ -140,7 +130,7 @@ class CheckoutAccessoryNotification extends Notification
'target' => $this->target, 'target' => $this->target,
'eula' => $eula, 'eula' => $eula,
'req_accept' => $req_accept, 'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id, 'accept_url' => $accept_url,
]) ])
->subject(trans('mail.Confirm_accessory_delivery')); ->subject(trans('mail.Confirm_accessory_delivery'));

View file

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

View file

@ -2,6 +2,7 @@
namespace App\Notifications; namespace App\Notifications;
use App\Models\Consumable;
use App\Models\Setting; use App\Models\Setting;
use App\Models\SnipeModel; use App\Models\SnipeModel;
use App\Models\User; use App\Models\User;
@ -25,21 +26,16 @@ class CheckoutConsumableNotification extends Notification
* *
* @param $params * @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->item = $consumable;
$this->note = $params['note']; $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(); $eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance(); $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', return (new MailMessage)->markdown('notifications.markdown.checkout-consumable',
[ [
'item' => $this->item, 'item' => $this->item,
'admin' => $this->admin, 'admin' => $this->admin,
'note' => $this->note, 'note' => $this->note,
'log_id' => $this->note,
'target' => $this->target, 'target' => $this->target,
'eula' => $eula, 'eula' => $eula,
'req_accept' => $req_accept, 'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id, 'accept_url' => $accept_url,
]) ])
->subject(trans('mail.Confirm_consumable_delivery')); ->subject(trans('mail.Confirm_consumable_delivery'));

View file

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

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; namespace App\Providers;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use App\Listeners\CheckoutableListener;
use App\Listeners\LogListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends 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. * 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

@ -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

@ -309,7 +309,11 @@
<i class="fa fa-check fa-disk fa-fw"></i> <i class="fa fa-check fa-disk fa-fw"></i>
Requested Assets Requested Assets
</a></li> </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

@ -251,10 +251,6 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () {
'accept-asset/{logID}', 'accept-asset/{logID}',
[ 'as' => 'account/accept-assets', 'uses' => 'ViewAssetsController@getAcceptAsset' ] [ 'as' => 'account/accept-assets', 'uses' => 'ViewAssetsController@getAcceptAsset' ]
); );
Route::post(
'accept-asset/{logID}',
[ 'as' => 'account/asset-accepted', 'uses' => 'ViewAssetsController@postAcceptAsset' ]
);
# Profile # Profile
Route::get( Route::get(
@ -274,6 +270,15 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () {
# Account Dashboard # Account Dashboard
Route::get('/', [ 'as' => 'account', 'uses' => 'ViewAssetsController@getIndex' ]); 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');
}); });