mirror of
https://github.com/grokability/snipe-it.git
synced 2026-03-12 17:52:00 +08:00
Merge pull request #18095 from marcusmoore/5947-bulk-checkout-rollup
#5947 - roll up bulk asset checkout email
This commit is contained in:
24
app/Events/CheckoutablesCheckedOutInBulk.php
Normal file
24
app/Events/CheckoutablesCheckedOutInBulk.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CheckoutablesCheckedOutInBulk
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Collection $assets,
|
||||
public Model $target,
|
||||
public User $admin,
|
||||
public string $checkout_at,
|
||||
public string $expected_checkin,
|
||||
public string $note,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Assets;
|
||||
|
||||
use App\Events\CheckoutablesCheckedOutInBulk;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\CheckInOutRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -12,6 +13,7 @@ use App\Models\Setting;
|
||||
use App\View\Label;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
@@ -644,6 +646,8 @@ class BulkAssetsController extends Controller
|
||||
*/
|
||||
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
|
||||
{
|
||||
Context::add('action', 'bulk_asset_checkout');
|
||||
|
||||
$this->authorize('checkout', Asset::class);
|
||||
|
||||
try {
|
||||
@@ -730,6 +734,15 @@ class BulkAssetsController extends Controller
|
||||
});
|
||||
|
||||
if (! $errors) {
|
||||
CheckoutablesCheckedOutInBulk::dispatch(
|
||||
$assets,
|
||||
$target,
|
||||
$admin,
|
||||
$checkout_at,
|
||||
$expected_checkin,
|
||||
e($request->get('note')),
|
||||
);
|
||||
|
||||
// Redirect to the new asset page
|
||||
return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ use App\Notifications\CheckoutConsumableNotification;
|
||||
use App\Notifications\CheckoutLicenseSeatNotification;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
@@ -441,12 +442,17 @@ class CheckoutableListener
|
||||
private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool
|
||||
{
|
||||
/**
|
||||
* Send an email if any of the following conditions are met:
|
||||
* Send an email if we didn't get here from a bulk checkout
|
||||
* and any of the following conditions are met:
|
||||
* 1. The asset requires acceptance
|
||||
* 2. The item has a EULA
|
||||
* 3. The item should send an email at check-in/check-out
|
||||
*/
|
||||
|
||||
if (Context::get('action') === 'bulk_asset_checkout') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($checkoutable->requireAcceptance()) {
|
||||
return true;
|
||||
}
|
||||
@@ -464,6 +470,10 @@ class CheckoutableListener
|
||||
|
||||
private function shouldSendEmailToAlertAddress($acceptance = null): bool
|
||||
{
|
||||
if (Context::get('action') === 'bulk_asset_checkout') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
if (!$setting) {
|
||||
|
||||
154
app/Listeners/CheckoutablesCheckedOutInBulkListener.php
Normal file
154
app/Listeners/CheckoutablesCheckedOutInBulkListener.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\CheckoutablesCheckedOutInBulk;
|
||||
use App\Mail\BulkAssetCheckoutMail;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class CheckoutablesCheckedOutInBulkListener
|
||||
{
|
||||
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen(
|
||||
CheckoutablesCheckedOutInBulk::class,
|
||||
CheckoutablesCheckedOutInBulkListener::class
|
||||
);
|
||||
}
|
||||
|
||||
public function handle(CheckoutablesCheckedOutInBulk $event): void
|
||||
{
|
||||
$notifiableUser = $this->getNotifiableUser($event);
|
||||
|
||||
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($notifiableUser, $event->assets);
|
||||
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($event->assets);
|
||||
|
||||
if ($shouldSendEmailToUser && $notifiableUser) {
|
||||
try {
|
||||
Mail::to($notifiableUser)->send(new BulkAssetCheckoutMail(
|
||||
$event->assets,
|
||||
$event->target,
|
||||
$event->admin,
|
||||
$event->checkout_at,
|
||||
$event->expected_checkin,
|
||||
$event->note,
|
||||
));
|
||||
|
||||
Log::info('BulkAssetCheckoutMail sent to checkout target');
|
||||
} catch (Exception $e) {
|
||||
Log::debug("Exception caught during BulkAssetCheckoutMail to target: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldSendEmailToAlertAddress && Setting::getSettings()->admin_cc_email) {
|
||||
try {
|
||||
Mail::to(Setting::getSettings()->admin_cc_email)->send(new BulkAssetCheckoutMail(
|
||||
$event->assets,
|
||||
$event->target,
|
||||
$event->admin,
|
||||
$event->checkout_at,
|
||||
$event->expected_checkin,
|
||||
$event->note,
|
||||
));
|
||||
|
||||
Log::info('BulkAssetCheckoutMail sent to admin_cc_email');
|
||||
} catch (Exception $e) {
|
||||
Log::debug("Exception caught during BulkAssetCheckoutMail to admin_cc_email: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function shouldSendCheckoutEmailToUser(?User $user, Collection $assets): bool
|
||||
{
|
||||
if (!$user?->email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasAssetWithEula($assets)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->hasAssetWithCategorySettingToSendEmail($assets)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->hasAssetThatRequiresAcceptance($assets);
|
||||
}
|
||||
|
||||
private function shouldSendEmailToAlertAddress(Collection $assets): bool
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
if (!$setting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($setting->admin_cc_always) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->hasAssetThatRequiresAcceptance($assets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $setting->admin_cc_email;
|
||||
}
|
||||
|
||||
private function hasAssetWithEula(Collection $assets): bool
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->getEula()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasAssetWithCategorySettingToSendEmail(Collection $assets): bool
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->checkin_email()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasAssetThatRequiresAcceptance(Collection $assets): bool
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
if ($asset->requireAcceptance()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getNotifiableUser(CheckoutablesCheckedOutInBulk $event): ?Model
|
||||
{
|
||||
$target = $event->target;
|
||||
|
||||
if ($target instanceof Asset) {
|
||||
$target->load('assignedTo');
|
||||
return $target->assignedto;
|
||||
}
|
||||
|
||||
if ($target instanceof Location) {
|
||||
return $target->manager;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
165
app/Mail/BulkAssetCheckoutMail.php
Normal file
165
app/Mail/BulkAssetCheckoutMail.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BulkAssetCheckoutMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public bool $requires_acceptance;
|
||||
|
||||
public Collection $assetsByCategory;
|
||||
|
||||
public function __construct(
|
||||
public Collection $assets,
|
||||
public Model $target,
|
||||
public User $admin,
|
||||
public string $checkout_at,
|
||||
public string $expected_checkin,
|
||||
public string $note,
|
||||
) {
|
||||
$this->requires_acceptance = $this->requiresAcceptance();
|
||||
|
||||
$this->loadCustomFieldsOnAssets();
|
||||
$this->loadEulasOnAssets();
|
||||
|
||||
$this->assetsByCategory = $this->groupAssetsByCategory();
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: $this->getSubject(),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.markdown.bulk-asset-checkout-mail',
|
||||
with: [
|
||||
'introduction' => $this->getIntroduction(),
|
||||
'requires_acceptance' => $this->requires_acceptance,
|
||||
'requires_acceptance_info' => $this->getRequiresAcceptanceInfo(),
|
||||
'requires_acceptance_prompt' => $this->getRequiresAcceptancePrompt(),
|
||||
'singular_eula' => $this->getSingularEula(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getSubject(): string
|
||||
{
|
||||
if ($this->assets->count() > 1) {
|
||||
return ucfirst(trans('general.assets_checked_out_count'));
|
||||
}
|
||||
|
||||
return trans('mail.Asset_Checkout_Notification', ['tag' => $this->assets->first()->asset_tag]);
|
||||
}
|
||||
|
||||
private function loadCustomFieldsOnAssets(): void
|
||||
{
|
||||
$this->assets = $this->assets->map(function (Asset $asset) {
|
||||
$fields = $asset->model?->fieldset?->fields->filter(function (CustomField $field) {
|
||||
return $field->show_in_email && !$field->field_encrypted;
|
||||
});
|
||||
|
||||
$asset->setRelation('fields', $fields);
|
||||
|
||||
return $asset;
|
||||
});
|
||||
}
|
||||
|
||||
private function loadEulasOnAssets(): void
|
||||
{
|
||||
$this->assets = $this->assets->map(function (Asset $asset) {
|
||||
$asset->eula = $asset->getEula();
|
||||
|
||||
return $asset;
|
||||
});
|
||||
}
|
||||
|
||||
private function groupAssetsByCategory(): Collection
|
||||
{
|
||||
return $this->assets->groupBy(fn($asset) => $asset->model->category->id);
|
||||
}
|
||||
|
||||
private function getIntroduction(): string
|
||||
{
|
||||
if ($this->target instanceof Location) {
|
||||
return trans_choice('mail.new_item_checked_location', $this->assets->count(), ['location' => $this->target->name]);
|
||||
}
|
||||
|
||||
return trans_choice('mail.new_item_checked', $this->assets->count());
|
||||
}
|
||||
|
||||
private function getRequiresAcceptanceInfo(): ?string
|
||||
{
|
||||
if (!$this->requires_acceptance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trans_choice('mail.items_checked_out_require_acceptance', $this->assets->count());
|
||||
}
|
||||
|
||||
private function getRequiresAcceptancePrompt(): ?string
|
||||
{
|
||||
if (!$this->requires_acceptance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$acceptanceUrl = $this->assets->count() === 1
|
||||
? route('account.accept.item', $this->assets->first())
|
||||
: route('account.accept');
|
||||
|
||||
return
|
||||
sprintf(
|
||||
'**[✔ %s](%s)**',
|
||||
trans_choice('mail.click_here_to_review_terms_and_accept_item', $this->assets->count()),
|
||||
$acceptanceUrl,
|
||||
);
|
||||
}
|
||||
|
||||
private function getSingularEula()
|
||||
{
|
||||
// get unique categories from all assets
|
||||
$categories = $this->assets->pluck('model.category.id')->unique();
|
||||
|
||||
// if assets do not have the same category then return early...
|
||||
if ($categories->count() > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if assets do have the same category then return the shared EULA
|
||||
if ($categories->count() === 1) {
|
||||
return $this->assets->first()->getEula();
|
||||
}
|
||||
}
|
||||
|
||||
private function requiresAcceptance(): bool
|
||||
{
|
||||
foreach ($this->assets as $asset) {
|
||||
if ($asset->requireAcceptance()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Attachment;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutAssetMail extends BaseMailable
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Listeners\CheckoutableListener;
|
||||
use App\Listeners\CheckoutablesCheckedOutInBulkListener;
|
||||
use App\Listeners\LogListener;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
@@ -31,5 +32,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
protected $subscribe = [
|
||||
LogListener::class,
|
||||
CheckoutableListener::class,
|
||||
CheckoutablesCheckedOutInBulkListener::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -215,4 +215,34 @@ class CategoryFactory extends Factory
|
||||
'require_acceptance' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendsCheckinEmail()
|
||||
{
|
||||
return $this->state([
|
||||
'checkin_email' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function doesNotSendCheckinEmail()
|
||||
{
|
||||
return $this->state([
|
||||
'checkin_email' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasLocalEula()
|
||||
{
|
||||
return $this->state([
|
||||
'use_default_eula' => false,
|
||||
'eula_text' => 'Some EULA text here',
|
||||
]);
|
||||
}
|
||||
|
||||
public function withNoLocalOrGlobalEula()
|
||||
{
|
||||
return $this->state([
|
||||
'use_default_eula' => false,
|
||||
'eula_text' => '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,12 +84,14 @@ return [
|
||||
'new_item_checked' => 'A new item has been checked out under your name, details are below.|:count new items have been checked out under your name, details are below.',
|
||||
'new_item_checked_with_acceptance' => 'A new item has been checked out under your name that requires acceptance, details are below.|:count new items have been checked out under your name that requires acceptance, details are below.',
|
||||
'new_item_checked_location' => 'A new item has been checked out to :location, details are below.|:count new items have been checked out to :location, details are below.',
|
||||
'items_checked_out_require_acceptance' => 'The checked out item requires acceptance.|One or more items require acceptance.',
|
||||
'recent_item_checked' => 'An item was recently checked out under your name that requires acceptance, details are below.',
|
||||
'notes' => 'Notes',
|
||||
'password' => 'Password',
|
||||
'password_reset' => 'Password Reset',
|
||||
'read_the_terms' => 'Please read the terms of use below.',
|
||||
'read_the_terms_and_click' => 'Please read the terms of use below, and click on the link at the bottom to confirm that you read and agree to the terms of use, and have received the asset.',
|
||||
'click_here_to_review_terms_and_accept_item' => 'Click here to review the terms of use and accept the item|Click here to review the terms of use and accept the items',
|
||||
'requested' => 'Requested',
|
||||
'reset_link' => 'Your Password Reset Link',
|
||||
'reset_password' => 'Click here to reset your password:',
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<x-mail::message>
|
||||
|
||||
<style>
|
||||
th, td {
|
||||
vertical-align: top;
|
||||
}
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{ $introduction }}
|
||||
|
||||
@if ($requires_acceptance)
|
||||
{{ $requires_acceptance_info }}
|
||||
|
||||
{{ $requires_acceptance_prompt }}
|
||||
<hr>
|
||||
@endif
|
||||
|
||||
@if ((isset($expected_checkin)) && ($expected_checkin!=''))
|
||||
**{{ trans('mail.expecting_checkin_date') }}**: {{ Helper::getFormattedDateObject($expected_checkin, 'date', false) }}
|
||||
@endif
|
||||
|
||||
@if ($note)
|
||||
**{{ trans('mail.additional_notes') }}**: {{ $note }}
|
||||
@endif
|
||||
|
||||
@foreach($assetsByCategory as $group)
|
||||
<x-mail::panel>
|
||||
|
||||
**{{ $group->first()->model->category->name }}**
|
||||
|
||||
<x-mail::table>
|
||||
| | |
|
||||
| ------------- | ------------- |
|
||||
@foreach($group as $asset)
|
||||
| **{{ trans('general.asset_tag') }}** | <a href="{{ route('hardware.show', $asset->id) }}">{{ $asset->display_name }}</a><br><small>{{trans('mail.serial').': '.$asset->serial}}</small> |
|
||||
@if (isset($asset->manufacturer))
|
||||
| **{{ trans('general.manufacturer') }}** | {{ $asset->manufacturer->name }} |
|
||||
@endif
|
||||
@if (isset($asset->model))
|
||||
| **{{ trans('general.asset_model') }}** | {{ $asset->model->name }} |
|
||||
@endif
|
||||
@if ((isset($asset->model?->model_number)))
|
||||
| **{{ trans('general.model_no') }}** | {{ $asset->model->model_number }} |
|
||||
@endif
|
||||
@if (isset($asset->assetstatus))
|
||||
| **{{ trans('general.status') }}** | {{ $asset->assetstatus->name }} |
|
||||
@endif
|
||||
@if($asset->fields)
|
||||
@foreach($asset->fields as $field)
|
||||
@if ($asset->{ $field->db_column_name() } != '')
|
||||
| **{{ $field->name }}** | {{ $asset->{ $field->db_column_name() } }} |
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@if(!$loop->last)
|
||||
| <hr> | <hr> |
|
||||
@endif
|
||||
@endforeach
|
||||
</x-mail::table>
|
||||
|
||||
@if (!$singular_eula && $group->first()->eula)
|
||||
<hr>
|
||||
{{ $group->first()->eula }}
|
||||
@endif
|
||||
|
||||
</x-mail::panel>
|
||||
@endforeach
|
||||
|
||||
@if ($singular_eula)
|
||||
<x-mail::panel>
|
||||
{{ $singular_eula }}
|
||||
</x-mail::panel>
|
||||
@endif
|
||||
|
||||
@if ($requires_acceptance)
|
||||
{{ $requires_acceptance_prompt }}
|
||||
@endif
|
||||
|
||||
**{{ trans('general.administrator') }}**: {{ $admin->display_name }}
|
||||
|
||||
{{ trans('mail.best_regards') }}<br>
|
||||
|
||||
{{ $snipeSettings->site_name }}
|
||||
</x-mail::message>
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Feature\Checkouts\Ui;
|
||||
|
||||
use App\Mail\BulkAssetCheckoutMail;
|
||||
use App\Mail\CheckoutAssetMail;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
@@ -59,10 +60,16 @@ class BulkAssetCheckoutTest extends TestCase
|
||||
$asset->last_checkout = $checkoutAt;
|
||||
$asset->expected_checkin = $expectedCheckin;
|
||||
$this->assertHasTheseActionLogs($asset, ['create', 'checkout']); //Note: '$this' gets auto-bound in closures, so this does work.
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => Asset::class,
|
||||
'checkoutable_id' => $asset->id,
|
||||
'assigned_to_id' => $user->id,
|
||||
'qty' => 1,
|
||||
]);
|
||||
});
|
||||
|
||||
Mail::assertSent(CheckoutAssetMail::class, 2);
|
||||
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
|
||||
Mail::assertNotSent(CheckoutAssetMail::class);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo('someone@example.com');
|
||||
});
|
||||
}
|
||||
|
||||
252
tests/Feature/Notifications/Email/BulkCheckoutEmailTest.php
Normal file
252
tests/Feature/Notifications/Email/BulkCheckoutEmailTest.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications\Email;
|
||||
|
||||
use App\Mail\BulkAssetCheckoutMail;
|
||||
use App\Mail\CheckoutAssetMail;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Category;
|
||||
use App\Models\Location;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[Group('notifications')]
|
||||
class BulkCheckoutEmailTest extends TestCase
|
||||
{
|
||||
private Collection $assets;
|
||||
private Model $assignee;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$this->settings->disableAdminCC();
|
||||
$this->settings->disableAdminCCAlways();
|
||||
|
||||
$this->assets = Asset::factory()->requiresAcceptance()->count(2)->create();
|
||||
$this->assignee = User::factory()->create();
|
||||
}
|
||||
|
||||
public function test_sent_to_user()
|
||||
{
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 1);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo($this->assignee->email);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sent_to_location_manager()
|
||||
{
|
||||
$manager = User::factory()->create();
|
||||
|
||||
$this->assignee = Location::factory()->for($manager, 'manager')->create();
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 1);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) use ($manager) {
|
||||
return $mail->hasTo($manager->email);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sent_to_user_asset_is_checked_out_to()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assignee = Asset::factory()->assignedToUser($user)->create();
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 1);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) use ($user) {
|
||||
return $mail->hasTo($user->email);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_not_sent_to_user_when_user_does_not_have_email_address()
|
||||
{
|
||||
$this->assignee = User::factory()->create(['email' => null]);
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
Mail::assertNotSent(BulkAssetCheckoutMail::class);
|
||||
}
|
||||
|
||||
public function test_not_sent_to_user_if_assets_do_not_require_acceptance()
|
||||
{
|
||||
$this->assets = Asset::factory()->doesNotRequireAcceptance()->count(2)->create();
|
||||
|
||||
$category = Category::factory()
|
||||
->doesNotRequireAcceptance()
|
||||
->doesNotSendCheckinEmail()
|
||||
->withNoLocalOrGlobalEula()
|
||||
->create();
|
||||
|
||||
$this->assets->each(fn($asset) => $asset->model->category()->associate($category)->save());
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
Mail::assertNotSent(BulkAssetCheckoutMail::class);
|
||||
}
|
||||
|
||||
public function test_sent_when_assets_do_not_require_acceptance_but_have_a_eula()
|
||||
{
|
||||
$this->assets = Asset::factory()->count(2)->create();
|
||||
|
||||
$category = Category::factory()
|
||||
->doesNotRequireAcceptance()
|
||||
->doesNotSendCheckinEmail()
|
||||
->hasLocalEula()
|
||||
->create();
|
||||
|
||||
$this->assets->each(fn($asset) => $asset->model->category()->associate($category)->save());
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 1);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo($this->assignee->email);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sent_when_assets_do_not_require_acceptance_or_have_a_eula_but_category_is_set_to_send_email()
|
||||
{
|
||||
$this->assets = Asset::factory()->count(2)->create();
|
||||
|
||||
$category = Category::factory()
|
||||
->doesNotRequireAcceptance()
|
||||
->withNoLocalOrGlobalEula()
|
||||
->sendsCheckinEmail()
|
||||
->create();
|
||||
|
||||
$this->assets->each(fn($asset) => $asset->model->category()->associate($category)->save());
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 1);
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo($this->assignee->email);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sent_to_cc_address_when_assets_require_acceptance()
|
||||
{
|
||||
$this->assets = Asset::factory()->requiresAcceptance()->count(2)->create();
|
||||
|
||||
$this->settings->enableAdminCC('cc@example.com')->disableAdminCCAlways();
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, 2);
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo($this->assignee->email);
|
||||
});
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo('cc@example.com');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sent_to_cc_address_when_assets_do_not_require_acceptance_or_have_eula_but_admin_cc_always_enabled()
|
||||
{
|
||||
$this->settings->enableAdminCC('cc@example.com')->enableAdminCCAlways();
|
||||
|
||||
$this->assets = Asset::factory()->doesNotRequireAcceptance()->count(2)->create();
|
||||
|
||||
$category = Category::factory()
|
||||
->doesNotRequireAcceptance()
|
||||
->doesNotSendCheckinEmail()
|
||||
->withNoLocalOrGlobalEula()
|
||||
->create();
|
||||
|
||||
$this->assets->each(fn($asset) => $asset->model->category()->associate($category)->save());
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
|
||||
Mail::assertSent(BulkAssetCheckoutMail::class, function (BulkAssetCheckoutMail $mail) {
|
||||
return $mail->hasTo('cc@example.com');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_not_sent_to_cc_address_if_assets_do_not_require_acceptance()
|
||||
{
|
||||
$this->settings->enableAdminCC('cc@example.com')->disableAdminCCAlways();
|
||||
|
||||
$this->assets = Asset::factory()->doesNotRequireAcceptance()->count(2)->create();
|
||||
|
||||
$category = Category::factory()
|
||||
->doesNotRequireAcceptance()
|
||||
->doesNotSendCheckinEmail()
|
||||
->withNoLocalOrGlobalEula()
|
||||
->create();
|
||||
|
||||
$this->assets->each(fn($asset) => $asset->model->category()->associate($category)->save());
|
||||
|
||||
$this->sendRequest();
|
||||
|
||||
$this->assertSingularCheckoutEmailNotSent();
|
||||
Mail::assertNotSent(BulkAssetCheckoutMail::class);
|
||||
}
|
||||
|
||||
private function sendRequest()
|
||||
{
|
||||
$assigned = match (get_class($this->assignee)) {
|
||||
User::class => [
|
||||
'checkout_to_type' => 'user',
|
||||
'assigned_user' => $this->assignee->id,
|
||||
],
|
||||
Location::class => [
|
||||
'checkout_to_type' => 'location',
|
||||
'assigned_location' => $this->assignee->id,
|
||||
],
|
||||
Asset::class => [
|
||||
'checkout_to_type' => 'asset',
|
||||
'assigned_asset' => $this->assignee->id,
|
||||
],
|
||||
// we shouldn't get here...
|
||||
default => [],
|
||||
};
|
||||
|
||||
$this->actingAs(User::factory()->checkoutAssets()->viewAssets()->create())
|
||||
->followingRedirects()
|
||||
->post(route('hardware.bulkcheckout.store'), [
|
||||
'selected_assets' => $this->assets->pluck('id')->toArray(),
|
||||
'checkout_at' => now()->subWeek()->format('Y-m-d'),
|
||||
'expected_checkin' => now()->addWeek()->format('Y-m-d'),
|
||||
'note' => null,
|
||||
] + $assigned)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
private function assertSingularCheckoutEmailNotSent(): static
|
||||
{
|
||||
Mail::assertNotSent(CheckoutAssetMail::class);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications\Webhooks;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CheckoutAssetNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[Group('notifications')]
|
||||
class WebhookNotificationsUponBulkAssetCheckoutTest extends TestCase
|
||||
{
|
||||
public function test_webbook_is_sent_upon_bulk_asset_checkout()
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$this->settings->enableSlackWebhook();
|
||||
|
||||
$assets = Asset::factory()->count(2)->create();
|
||||
|
||||
$this->actingAs(User::factory()->checkoutAssets()->viewAssets()->create())
|
||||
->followingRedirects()
|
||||
->post(route('hardware.bulkcheckout.store'), [
|
||||
'selected_assets' => $assets->pluck('id')->toArray(),
|
||||
'checkout_to_type' => 'user',
|
||||
'assigned_user' => User::factory()->create()->id,
|
||||
'assigned_asset' => null,
|
||||
'checkout_at' => now()->subWeek()->format('Y-m-d'),
|
||||
'expected_checkin' => now()->addWeek()->format('Y-m-d'),
|
||||
'note' => null,
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$this->assertSlackNotificationSent(CheckoutAssetNotification::class);
|
||||
Notification::assertSentTimes(CheckoutAssetNotification::class, 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user