diff --git a/app/Events/CheckoutAccepted.php b/app/Events/CheckoutAccepted.php new file mode 100644 index 0000000000..0b26e69475 --- /dev/null +++ b/app/Events/CheckoutAccepted.php @@ -0,0 +1,27 @@ +acceptance = $acceptance; + } +} diff --git a/app/Events/CheckoutDeclined.php b/app/Events/CheckoutDeclined.php new file mode 100644 index 0000000000..2aae6dd914 --- /dev/null +++ b/app/Events/CheckoutDeclined.php @@ -0,0 +1,27 @@ +acceptance = $acceptance; + } +} diff --git a/app/Events/CheckoutableCheckedIn.php b/app/Events/CheckoutableCheckedIn.php new file mode 100644 index 0000000000..0f6a96491a --- /dev/null +++ b/app/Events/CheckoutableCheckedIn.php @@ -0,0 +1,30 @@ +checkoutable = $checkoutable; + $this->checkedOutTo = $checkedOutTo; + $this->checkedInBy = $checkedInBy; + $this->note = $note; + } +} diff --git a/app/Events/CheckoutableCheckedOut.php b/app/Events/CheckoutableCheckedOut.php new file mode 100644 index 0000000000..5e6ffd243a --- /dev/null +++ b/app/Events/CheckoutableCheckedOut.php @@ -0,0 +1,30 @@ +checkoutable = $checkoutable; + $this->checkedOutTo = $checkedOutTo; + $this->checkedOutBy = $checkedOutBy; + $this->note = $note; + } +} diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php index 83d59ab774..930e9b6619 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php @@ -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')); } diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 50c174f0c1..5ec8a8704f 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -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')); } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php new file mode 100644 index 0000000000..198e11e998 --- /dev/null +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -0,0 +1,125 @@ +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); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php index 7eed360a6a..e87a1df23c 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -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')); diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php index 89eab1f5fa..dfba1dcf84 100644 --- a/app/Http/Controllers/Components/ComponentCheckinController.php +++ b/app/Http/Controllers/Components/ComponentCheckinController.php @@ -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')); } diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php index 8922317faf..bfa9e8967b 100644 --- a/app/Http/Controllers/Components/ComponentCheckoutController.php +++ b/app/Http/Controllers/Components/ComponentCheckoutController.php @@ -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')); } } diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index 6de35ff9e1..e3e3e11b03 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -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')); diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index f942aa1e7e..4bddbd5753 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -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')); } diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index 570cd6b410..5d878a1506 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -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; diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 05e0a0f13c..79d41f7b9c 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -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')); } diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index 2308b804a9..8022815bad 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -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'); } } diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php new file mode 100644 index 0000000000..e06c6e1e40 --- /dev/null +++ b/app/Listeners/CheckoutableListener.php @@ -0,0 +1,184 @@ +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' + ); + } + +} \ No newline at end of file diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php new file mode 100644 index 0000000000..cdb4fda1fc --- /dev/null +++ b/app/Listeners/LogListener.php @@ -0,0 +1,90 @@ +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 + ); + } + } + +} \ No newline at end of file diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index 8fa27515e9..76b50704c6 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -1,6 +1,7 @@ ['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 diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 4d8ca1c89c..c2d48cee2b 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -1,10 +1,14 @@ 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; } diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php new file mode 100644 index 0000000000..6ff850f41f --- /dev/null +++ b/app/Models/CheckoutAcceptance.php @@ -0,0 +1,114 @@ +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'); + } +} diff --git a/app/Models/Component.php b/app/Models/Component.php index 8ccc3c3070..e880234ba7 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -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 diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index b1d3fe222c..596106c262 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -1,6 +1,7 @@ '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 diff --git a/app/Models/License.php b/app/Models/License.php index a3ac4c5985..c953bf9dd5 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -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; diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php index 500391041d..ab514c0a58 100755 --- a/app/Models/LicenseSeat.php +++ b/app/Models/LicenseSeat.php @@ -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 * diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php index 61b1d6deb7..f3914d9db5 100644 --- a/app/Models/Loggable.php +++ b/app/Models/Loggable.php @@ -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; } diff --git a/app/Models/Traits/Acceptable.php b/app/Models/Traits/Acceptable.php new file mode 100644 index 0000000000..a0c93a861b --- /dev/null +++ b/app/Models/Traits/Acceptable.php @@ -0,0 +1,31 @@ + + */ +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) {} +} diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 052279892e..ee3bcac6ab 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -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(); } /** diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php index 78f3f00664..4053f22ffc 100644 --- a/app/Notifications/CheckinAssetNotification.php +++ b/app/Notifications/CheckinAssetNotification.php @@ -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', diff --git a/app/Notifications/CheckinLicenseNotification.php b/app/Notifications/CheckinLicenseSeatNotification.php similarity index 86% rename from app/Notifications/CheckinLicenseNotification.php rename to app/Notifications/CheckinLicenseSeatNotification.php index f8a52ccf3c..69554d493b 100644 --- a/app/Notifications/CheckinLicenseNotification.php +++ b/app/Notifications/CheckinLicenseSeatNotification.php @@ -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(); } /** diff --git a/app/Notifications/CheckoutAccessoryNotification.php b/app/Notifications/CheckoutAccessoryNotification.php index 4e8950619d..11368bf06c 100644 --- a/app/Notifications/CheckoutAccessoryNotification.php +++ b/app/Notifications/CheckoutAccessoryNotification.php @@ -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')); diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index 8451d6dcd9..db4ba294f5 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -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, ]) diff --git a/app/Notifications/CheckoutConsumableNotification.php b/app/Notifications/CheckoutConsumableNotification.php index d9a80bfc3c..40780c7abc 100644 --- a/app/Notifications/CheckoutConsumableNotification.php +++ b/app/Notifications/CheckoutConsumableNotification.php @@ -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')); diff --git a/app/Notifications/CheckoutLicenseNotification.php b/app/Notifications/CheckoutLicenseSeatNotification.php similarity index 85% rename from app/Notifications/CheckoutLicenseNotification.php rename to app/Notifications/CheckoutLicenseSeatNotification.php index 93530e2cde..74df6e2f46 100644 --- a/app/Notifications/CheckoutLicenseNotification.php +++ b/app/Notifications/CheckoutLicenseSeatNotification.php @@ -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')); diff --git a/app/Presenters/LicenseSeatPresenter.php b/app/Presenters/LicenseSeatPresenter.php new file mode 100644 index 0000000000..5267aa7290 --- /dev/null +++ b/app/Presenters/LicenseSeatPresenter.php @@ -0,0 +1,18 @@ +model->license->name; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 9d3e0f8a98..28c3aecf06 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -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. diff --git a/database/migrations/2018_07_28_023826_create_checkout_acceptances_table.php b/database/migrations/2018_07_28_023826_create_checkout_acceptances_table.php new file mode 100644 index 0000000000..67bc3b800a --- /dev/null +++ b/database/migrations/2018_07_28_023826_create_checkout_acceptances_table.php @@ -0,0 +1,41 @@ +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'); + } +} diff --git a/database/migrations/2018_09_10_082212_create_checkout_acceptances_for_unaccepted_assets.php b/database/migrations/2018_09_10_082212_create_checkout_acceptances_for_unaccepted_assets.php new file mode 100644 index 0000000000..ddf6949b69 --- /dev/null +++ b/database/migrations/2018_09_10_082212_create_checkout_acceptances_for_unaccepted_assets.php @@ -0,0 +1,43 @@ +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() + { + // + } +} diff --git a/resources/views/account/accept/create.blade.php b/resources/views/account/accept/create.blade.php new file mode 100644 index 0000000000..67ca326e16 --- /dev/null +++ b/resources/views/account/accept/create.blade.php @@ -0,0 +1,135 @@ +@extends('layouts/default') + +{{-- Page title --}} +@section('title') + Accept {{ $acceptance->checkoutable->present()->name() }} + @parent +@stop + + +{{-- Page content --}} +@section('content') + + + + + + + +
+ +@stop + +@section('moar_scripts') + + + +@stop diff --git a/resources/views/account/accept/index.blade.php b/resources/views/account/accept/index.blade.php new file mode 100755 index 0000000000..4a699ea4f9 --- /dev/null +++ b/resources/views/account/accept/index.blade.php @@ -0,0 +1,61 @@ +@extends('layouts/default') + +{{-- Page title --}} +@section('title') +Accept assets {{ $user->present()->fullName() }} +@parent +@stop + +{{-- Account page content --}} +@section('content') +| Name | +Actions | +
|---|---|
| {{ $acceptance->checkoutable->present()->name }} | +Accept/Decline | +