diff --git a/app/Events/ItemDeclined.php b/app/Events/CheckoutAccepted.php similarity index 57% rename from app/Events/ItemDeclined.php rename to app/Events/CheckoutAccepted.php index a735c193ef..0b26e69475 100644 --- a/app/Events/ItemDeclined.php +++ b/app/Events/CheckoutAccepted.php @@ -4,29 +4,24 @@ 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 ItemDeclined +class CheckoutAccepted { use Dispatchable, SerializesModels; - public $item; - public $declinedBy; - public $signature; - /** * Create a new event instance. * * @return void */ - public function __construct(Acceptable $item, User $declinedBy, string $signature) + public function __construct(CheckoutAcceptance $acceptance) { - $this->item = $item; - $this->declinedBy = $declinedBy; - $this->signature = $signature; + $this->acceptance = $acceptance; } } diff --git a/app/Events/ItemAccepted.php b/app/Events/CheckoutDeclined.php similarity index 58% rename from app/Events/ItemAccepted.php rename to app/Events/CheckoutDeclined.php index 3c854d9ea5..2aae6dd914 100644 --- a/app/Events/ItemAccepted.php +++ b/app/Events/CheckoutDeclined.php @@ -4,29 +4,24 @@ 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 ItemAccepted +class CheckoutDeclined { use Dispatchable, SerializesModels; - - public $item; - public $acceptedBy; - public $signature; /** * Create a new event instance. * * @return void */ - public function __construct(Acceptable $item, User $acceptedBy, string $signature) + public function __construct(CheckoutAcceptance $acceptance) { - $this->item = $item; - $this->acceptedBy = $acceptedBy; - $this->signature = $signature; + $this->acceptance = $acceptance; } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index ecf9924277..5c7235a2ae 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -1,10 +1,13 @@ pending()->get(); + + return view('account/accept.index', compact('acceptances')); } - public function edit(Request $request, $type, $id) { + public function create(Request $request, $id) { - $item = $this->getItemById($type, $id); + $acceptance = CheckoutAcceptance::find($id); - if (is_null($item)) { + if (is_null($acceptance)) { return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); } - if ($item->isAccepted()) { + if (! $acceptance->isPending()) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); } - if (! $item->isCheckedOutTo(Auth::user())) { + if (! $acceptance->isCheckedOutTo(Auth::user())) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); } - if (!Company::isCurrentUserHasAccess($item)) { + if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) { return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions')); } - return view('account/accept', compact('item')); + return view('account/accept.create', compact('acceptance')); } - public function update(Request $request, $type, $id) { - $item = $this->getItemById($type, $id); + public function store(Request $request, $id) { + + $acceptance = CheckoutAcceptance::find($id); - if (is_null($item)) { + if (is_null($acceptance)) { return redirect()->reoute('account.accept')->with('error', trans('admin/hardware/message.does_not_exist')); } - if ($item->isAccepted()) { + if (! $acceptance->isPending()) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); - } + } - if (! $item->isCheckedOutTo(Auth::user())) { + if (! $acceptance->isCheckedOutTo(Auth::user())) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); } - if (!Company::isCurrentUserHasAccess($item)) { + 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')); @@ -79,41 +85,30 @@ class AcceptanceController extends Controller { if ($request->input('asset_acceptance') == 'accepted') { - $item->accept(Auth::user(), $sig_filename); + $acceptance->accepted_at = now(); + $acceptance->signature_filename = $sig_filename; + $acceptance->save(); - event(new ItemAccepted($item, Auth::user(), $sig_filename)); + // TODO: Update state for the checkoutable + + event(new CheckoutAccepted($acceptance)); $return_msg = trans('admin/users/message.accepted'); } else { - $item->decline(Auth::user(), $sig_filename); + $acceptance->declined_at = now(); + $acceptance->signature_filename = $sig_filename; + $acceptance->save(); - event(new ItemDeclined($item, Auth::user(), $sig_filename)); + // TODO: Update state for the checkoutable + + event(new CheckoutDeclined($acceptance)); $return_msg = trans('admin/users/message.declined'); } return redirect()->to('account/accept')->with('success', $return_msg); - } - - private function getItemById($type, $id) : ? Acceptable { - switch ($type) { - case 'asset': - $item = Asset::findOrFail($id); - break; - case 'consumable': - $item = Consumable::findOrFail($id); - break; - case 'license': - $item = License::findOrFail($id); - break; - default: - $item = null; - break; - } - - return $item; } } \ No newline at end of file diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php index 858c9ec291..9cd8ec2717 100644 --- a/app/Listeners/LogListener.php +++ b/app/Listeners/LogListener.php @@ -6,6 +6,8 @@ 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\ComponentCheckedIn; use App\Events\ComponentCheckedOut; use App\Events\ConsumableCheckedOut; @@ -16,6 +18,7 @@ use App\Events\LicenseCheckedOut; use App\Models\Actionlog; use App\Models\Asset; use App\Models\Component; +use App\Models\LicenseSeat; class LogListener @@ -65,23 +68,34 @@ class LogListener $event->license->logCheckout($event->note, $event->checkedOutTo); } - public function onItemAccepted(ItemAccepted $event) { + public function onCheckoutAccepted(CheckoutAccepted $event) { $logaction = new Actionlog(); - $logaction->item()->associate($event->item); - $logaction->target()->associate($event->acceptedBy); - $logaction->accept_signature = $event->signature; + + $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 onItemDeclined(ItemDeclined $event) { + public function onCheckoutDeclined(CheckoutDeclined $event) { $logaction = new Actionlog(); - $logaction->item()->associate($event->item); - $logaction->target()->associate($event->declinedBy); - $logaction->accept_signature = $event->signature; + $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(); } @@ -102,8 +116,8 @@ class LogListener 'ConsumableCheckedOut', 'LicenseCheckedIn', 'LicenseCheckedOut', - 'ItemAccepted', - 'ItemDeclined', + 'CheckoutAccepted', + 'CheckoutDeclined', ]; foreach($list as $event) { diff --git a/app/Listeners/SendingCheckOutNotificationsListener.php b/app/Listeners/SendingCheckOutNotificationsListener.php index 1968c13d1a..ed459c5a59 100644 --- a/app/Listeners/SendingCheckOutNotificationsListener.php +++ b/app/Listeners/SendingCheckOutNotificationsListener.php @@ -2,6 +2,7 @@ namespace App\Listeners; +use App\Models\CheckoutAcceptance; use App\Models\Recipients\AdminRecipient; use App\Models\Setting; use App\Models\User; @@ -9,6 +10,7 @@ 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 SendingCheckOutNotificationsListener @@ -24,9 +26,20 @@ class SendingCheckOutNotificationsListener return; } + /** + * Make a checkout acceptance and attach it in the notification + */ + $acceptance = null; + if ($event->consumable->requireAcceptance()) { + $acceptance = new CheckoutAcceptance; + $acceptance->checkoutable()->associate($event->consumable); + $acceptance->assignedTo()->associate($event->checkedOutTo); + $acceptance->save(); + } + Notification::send( $this->getNotifiables($event), - new CheckoutConsumableNotification($event->consumable, $event->checkedOutTo, $event->checkedOutBy, $event->note) + new CheckoutConsumableNotification($event->consumable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note) ); } @@ -41,16 +54,27 @@ class SendingCheckOutNotificationsListener return; } + /** + * Make a checkout acceptance and attach it in the notification + */ + $acceptance = null; + if ($event->accessory->requireAcceptance()) { + $acceptance = new CheckoutAcceptance; + $acceptance->checkoutable()->associate($event->accessory); + $acceptance->assignedTo()->associate($event->checkedOutTo); + $acceptance->save(); + } + Notification::send( $this->getNotifiables($event), - new CheckoutAccessoryNotification($event->accessory, $event->checkedOutTo, $event->checkedOutBy, $event->note) + new CheckoutAccessoryNotification($event->accessory, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note) ); } /** * Notify the user about the checked out license */ - public function onLicenseCheckedOut($event) { + public function onLicenseSeatCheckedOut($event) { /** * When the item wasn't checked out to a user, we can't send notifications */ @@ -58,9 +82,20 @@ class SendingCheckOutNotificationsListener return; } + /** + * Make a checkout acceptance and attach it in the notification + */ + $acceptance = null; + if ($event->licenseSeat->license->requireAcceptance()) { + $acceptance = new CheckoutAcceptance; + $acceptance->checkoutable()->associate($event->licenseSeat); + $acceptance->assignedTo()->associate($event->checkedOutTo); + $acceptance->save(); + } + Notification::send( $this->getNotifiables($event), - new CheckoutLicenseNotification($event->license, $event->checkedOutTo, $event->checkedOutBy, $event->note) + new CheckoutLicenseSeatNotification($event->licenseSeat, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note) ); } @@ -75,9 +110,20 @@ class SendingCheckOutNotificationsListener return; } + /** + * Make a checkout acceptance and attach it in the notification + */ + $acceptance = null; + if ($event->asset->requireAcceptance()) { + $acceptance = new CheckoutAcceptance; + $acceptance->checkoutable()->associate($event->asset); + $acceptance->assignedTo()->associate($event->checkedOutTo); + $acceptance->save(); + } + Notification::send( $this->getNotifiables($event), - new CheckoutAssetNotification($event->asset, $event->checkedOutTo, $event->checkedOutBy, $event->note) + new CheckoutAssetNotification($event->asset, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note) ); } @@ -123,8 +169,8 @@ class SendingCheckOutNotificationsListener ); $events->listen( - 'App\Events\LicenseCheckedOut', - 'App\Listeners\SendingCheckOutNotificationsListener@onLicenseCheckedOut' + 'App\Events\LicenseSeatCheckedOut', + 'App\Listeners\SendingCheckOutNotificationsListener@onLicenseSeatCheckedOut' ); $events->listen( diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php new file mode 100644 index 0000000000..f3d2355e53 --- /dev/null +++ b/app/Models/CheckoutAcceptance.php @@ -0,0 +1,50 @@ +morphTo(); + } + + public function assignedTo() { + return $this->belongsTo(User::class); + } + + public function isPending() { + return $this->accepted_at == null && $this->declined_at == null; + } + + public function isCheckedOutTo(User $user) { + return $this->assignedTo->is($user); + } + + public function scopeForUser(Builder $query, User $user) { + return $query->where('assigned_to_id', $user->id); + } + + public function scopePending(Builder $query) { + return $query->whereNull('accepted_at')->whereNull('declined_at'); + } +} diff --git a/app/Notifications/CheckoutAccessoryNotification.php b/app/Notifications/CheckoutAccessoryNotification.php index c00da8d633..11368bf06c 100644 --- a/app/Notifications/CheckoutAccessoryNotification.php +++ b/app/Notifications/CheckoutAccessoryNotification.php @@ -20,13 +20,14 @@ class CheckoutAccessoryNotification extends Notification /** * Create a new notification instance. */ - public function __construct(Accessory $accessory, $checkedOutTo, User $checkedOutBy, $note) + public function __construct(Accessory $accessory, $checkedOutTo, User $checkedOutBy, $acceptance, $note) { $this->item = $accessory; $this->admin = $checkedOutBy; $this->note = $note; $this->target = $checkedOutTo; + $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); @@ -119,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, @@ -127,7 +130,7 @@ class CheckoutAccessoryNotification extends Notification 'target' => $this->target, 'eula' => $eula, 'req_accept' => $req_accept, - 'accept_url' => route('account.accept.item', ['accessory', $this->item->id]), + 'accept_url' => $accept_url, ]) ->subject(trans('mail.Confirm_accessory_delivery')); diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index f0323342f0..db4ba294f5 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -20,13 +20,14 @@ class CheckoutAssetNotification extends Notification * * @param $params */ - public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $note) + public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note) { $this->item = $asset; $this->admin = $checkedOutBy; $this->note = $note; $this->target = $checkedOutTo; + $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); @@ -140,6 +141,8 @@ 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, @@ -149,7 +152,7 @@ class CheckoutAssetNotification extends Notification 'fields' => $fields, 'eula' => $eula, 'req_accept' => $req_accept, - 'accept_url' => route('account.accept.item', ['asset', $this->item->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 7c6dfb7b3f..40780c7abc 100644 --- a/app/Notifications/CheckoutConsumableNotification.php +++ b/app/Notifications/CheckoutConsumableNotification.php @@ -26,13 +26,14 @@ class CheckoutConsumableNotification extends Notification * * @param $params */ - public function __construct(Consumable $consumable, $checkedOutTo, User $checkedOutBy, $note) + public function __construct(Consumable $consumable, $checkedOutTo, User $checkedOutBy, $acceptance, $note) { $this->item = $consumable; $this->admin = $checkedOutBy; $this->note = $note; $this->target = $checkedOutTo; + $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); @@ -121,6 +122,8 @@ 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, @@ -129,7 +132,7 @@ class CheckoutConsumableNotification extends Notification 'target' => $this->target, 'eula' => $eula, 'req_accept' => $req_accept, - 'accept_url' => route('account.accept.item', ['consumable', $this->item->id]), + 'accept_url' => $accept_url, ]) ->subject(trans('mail.Confirm_consumable_delivery')); 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..6d1f1f2b3a --- /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'); + + $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/resources/views/account/accept.blade.php b/resources/views/account/accept/create.blade.php similarity index 96% rename from resources/views/account/accept.blade.php rename to resources/views/account/accept/create.blade.php index 9073f8516a..67ca326e16 100644 --- a/resources/views/account/accept.blade.php +++ b/resources/views/account/accept/create.blade.php @@ -2,7 +2,7 @@ {{-- Page title --}} @section('title') - Accept {{ $item->present()->name() }} + Accept {{ $acceptance->checkoutable->present()->name() }} @parent @stop @@ -52,10 +52,10 @@ - @if ($item->getEula()) + @if ($acceptance->checkoutable->getEula())
- {!! $item->getEula() !!} + {!! $acceptance->checkoutable->getEula() !!}
@endif 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') +
+
+
+ +
+ + +
+ + + + + + + + + @foreach ($acceptances as $acceptance) + + + + + @endforeach + +
NameActions
{{ $acceptance->checkoutable->present()->name }}Accept/Decline
+
+ +
+
+
+
+ +@stop + +@section('moar_scripts') + @include ('partials.bootstrap-table') +@stop diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 196b085bbc..874a1eef01 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -317,7 +317,11 @@ Requested Assets - +
  • + + + Accept Assets +
  • diff --git a/routes/web.php b/routes/web.php index 4b6d13a7cd..eae9209345 100644 --- a/routes/web.php +++ b/routes/web.php @@ -278,10 +278,10 @@ Route::group([ 'prefix' => 'account', 'middleware' => ['auth']], function () { Route::get('accept', 'Account\AcceptanceController@index') ->name('account.accept'); - Route::get('accept/{type}/{id}', 'Account\AcceptanceController@edit') + Route::get('accept/{id}', 'Account\AcceptanceController@create') ->name('account.accept.item'); - Route::post('accept/{type}/{id}', 'Account\AcceptanceController@update'); + Route::post('accept/{id}', 'Account\AcceptanceController@store'); });