Merge remote-tracking branch 'origin/develop'
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Events\CheckoutDeclined;
|
||||
use App\Events\ItemAccepted;
|
||||
use App\Events\ItemDeclined;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
@@ -21,8 +22,10 @@ use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
||||
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Http\Controllers\SettingsController;
|
||||
@@ -337,6 +340,21 @@ class AcceptanceController extends Controller
|
||||
$return_msg = trans('admin/users/message.declined');
|
||||
}
|
||||
|
||||
if ($acceptance->alert_on_response_id) {
|
||||
try {
|
||||
$recipient = User::find($acceptance->alert_on_response_id);
|
||||
|
||||
if ($recipient) {
|
||||
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
|
||||
$acceptance,
|
||||
$recipient,
|
||||
$request->input('asset_acceptance') === 'accepted',
|
||||
));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::warning($e);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('account/accept')->with('success', $return_msg);
|
||||
|
||||
|
||||
@@ -677,7 +677,6 @@ class UsersController extends Controller
|
||||
$this->authorize('view', License::class);
|
||||
|
||||
if ($user = User::where('id', $id)->withTrashed()->first()) {
|
||||
$this->authorize('update', $user);
|
||||
$licenses = $user->licenses()->get();
|
||||
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ class CategoriesController extends Controller
|
||||
$category->eula_text = $request->input('eula_text');
|
||||
$category->use_default_eula = $request->input('use_default_eula', '0');
|
||||
$category->require_acceptance = $request->input('require_acceptance', '0');
|
||||
$category->alert_on_response = $request->input('alert_on_response', '0');
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
$category->notes = $request->input('notes');
|
||||
$category->created_by = auth()->id();
|
||||
@@ -121,6 +122,7 @@ class CategoriesController extends Controller
|
||||
$category->eula_text = $request->input('eula_text');
|
||||
$category->use_default_eula = $request->input('use_default_eula', '0');
|
||||
$category->require_acceptance = $request->input('require_acceptance', '0');
|
||||
$category->alert_on_response = $request->input('alert_on_response', '0');
|
||||
$category->checkin_email = $request->input('checkin_email', '0');
|
||||
$category->notes = $request->input('notes');
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Mail\CheckoutConsumableMail;
|
||||
use App\Mail\CheckoutLicenseMail;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Category;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
@@ -227,6 +228,7 @@ class CheckoutableListener
|
||||
if ($checkedOutToType != "App\Models\User") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$event->checkoutable->requireAcceptance()) {
|
||||
return null;
|
||||
}
|
||||
@@ -234,6 +236,13 @@ class CheckoutableListener
|
||||
$acceptance = new CheckoutAcceptance;
|
||||
$acceptance->checkoutable()->associate($event->checkoutable);
|
||||
$acceptance->assignedTo()->associate($event->checkedOutTo);
|
||||
|
||||
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
|
||||
|
||||
if ($category?->alert_on_response) {
|
||||
$acceptance->alert_on_response_id = auth()->id();
|
||||
}
|
||||
|
||||
$acceptance->save();
|
||||
|
||||
return $acceptance;
|
||||
@@ -360,23 +369,6 @@ class CheckoutableListener
|
||||
return in_array(get_class($checkoutable), $this->skipNotificationsFor);
|
||||
}
|
||||
|
||||
private function shouldSendEmailNotifications(Model $checkoutable): bool
|
||||
{
|
||||
//runs a check if the category wants to send checkin/checkout emails to users
|
||||
$category = match (true) {
|
||||
$checkoutable instanceof Asset => $checkoutable->model->category,
|
||||
$checkoutable instanceof Accessory,
|
||||
$checkoutable instanceof Consumable => $checkoutable->category,
|
||||
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$category?->checkin_email) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function shouldSendWebhookNotification(): bool
|
||||
{
|
||||
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
|
||||
@@ -471,4 +463,14 @@ class CheckoutableListener
|
||||
|
||||
return array($to, $cc);
|
||||
}
|
||||
|
||||
private function getCategoryFromCheckoutable(Model $checkoutable): ?Category
|
||||
{
|
||||
return match (true) {
|
||||
$checkoutable instanceof Asset => $checkoutable->model->category,
|
||||
$checkoutable instanceof Accessory,
|
||||
$checkoutable instanceof Consumable => $checkoutable->category,
|
||||
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use Livewire\Component;
|
||||
|
||||
class CategoryEditForm extends Component
|
||||
{
|
||||
public bool $alertOnResponse;
|
||||
|
||||
public $defaultEulaText;
|
||||
|
||||
public $eulaText;
|
||||
|
||||
79
app/Mail/CheckoutAcceptanceResponseMail.php
Normal file
79
app/Mail/CheckoutAcceptanceResponseMail.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutAcceptanceResponseMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public CheckoutAcceptance $acceptance;
|
||||
public User $recipient;
|
||||
public bool $wasAccepted;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(CheckoutAcceptance $acceptance, User $recipient, bool $wasAccepted)
|
||||
{
|
||||
$this->acceptance = $acceptance;
|
||||
$this->recipient = $recipient;
|
||||
$this->wasAccepted = $wasAccepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$subject = $this->wasAccepted
|
||||
? trans('mail.initiated_accepted')
|
||||
: trans('mail.initiated_declined');
|
||||
|
||||
return new Envelope(
|
||||
subject: $subject,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.markdown.checkout-acceptance-response',
|
||||
with: [
|
||||
'assignedTo' => $this->acceptance->assignedTo,
|
||||
'introduction' => $this->introduction(),
|
||||
'item' => $this->acceptance->checkoutable,
|
||||
'note' => $this->acceptance->note,
|
||||
'recipient' => $this->recipient,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function introduction(): string
|
||||
{
|
||||
return $this->wasAccepted
|
||||
? trans('mail.following_accepted')
|
||||
: trans('mail.following_declined');
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class Category extends SnipeModel
|
||||
protected $hidden = ['created_by', 'deleted_at'];
|
||||
|
||||
protected $casts = [
|
||||
'alert_on_response' => 'boolean',
|
||||
'created_by' => 'integer',
|
||||
];
|
||||
|
||||
@@ -69,6 +70,7 @@ class Category extends SnipeModel
|
||||
'eula_text',
|
||||
'name',
|
||||
'require_acceptance',
|
||||
'alert_on_response',
|
||||
'use_default_eula',
|
||||
'created_by',
|
||||
'notes',
|
||||
|
||||
@@ -15,6 +15,7 @@ class CheckoutAcceptance extends Model
|
||||
protected $casts = [
|
||||
'accepted_at' => 'datetime',
|
||||
'declined_at' => 'datetime',
|
||||
'alert_on_response_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ class License extends Depreciable
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'seats' => 'required|min:1|integer',
|
||||
'seats' => 'required|min:1|integer|limit_change:10000', // limit_change is a "pseudo-rule" that translates into 'between', see prepareLimitChangeRule() below
|
||||
'license_email' => 'email|nullable|max:120',
|
||||
'license_name' => 'string|nullable|max:100',
|
||||
'notes' => 'string|nullable',
|
||||
@@ -148,6 +148,14 @@ class License extends Depreciable
|
||||
});
|
||||
}
|
||||
|
||||
public function prepareLimitChangeRule($parameters, $field)
|
||||
{
|
||||
$actual_seat_count = $this->licenseseats()->count(); //we use the *actual* seat count here, in case your license has gone wonky
|
||||
$lower_bound = $actual_seat_count - $parameters[0];
|
||||
$upper_bound = $actual_seat_count + $parameters[0];
|
||||
return ["between", ($lower_bound <= 0 ? 1 : $lower_bound), $upper_bound];
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance seat counts
|
||||
*
|
||||
@@ -164,21 +172,17 @@ class License extends Depreciable
|
||||
// On Create, we just make one for each of the seats.
|
||||
$change = abs($oldSeats - $newSeats);
|
||||
if ($oldSeats > $newSeats) {
|
||||
$license->load('licenseseats.user');
|
||||
|
||||
// Need to delete seats... lets see if if we have enough.
|
||||
$seatsAvailableForDelete = $license->licenseseats->reject(function ($seat) {
|
||||
return ((bool) $seat->assigned_to) || ((bool) $seat->asset_id);
|
||||
});
|
||||
$seatsAvailableForDelete = $license->licenseseats()->whereNull('assigned_to')->whereNull('asset_id')->limit($change);
|
||||
|
||||
if ($change > $seatsAvailableForDelete->count()) {
|
||||
Session::flash('error', trans('admin/licenses/message.assoc_users'));
|
||||
|
||||
return false;
|
||||
}
|
||||
for ($i = 1; $i <= $change; $i++) {
|
||||
$seatsAvailableForDelete->pop()->delete();
|
||||
}
|
||||
$seatsAvailableForDelete->delete();
|
||||
|
||||
// Log Deletion of seats.
|
||||
$logAction = new Actionlog;
|
||||
$logAction->item_type = self::class;
|
||||
|
||||
@@ -64,6 +64,24 @@ class CheckoutAcceptanceFactory extends Factory
|
||||
]);
|
||||
}
|
||||
|
||||
public function withoutAlerting()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'alert_on_response_id' => null,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function withAlertingTo(User $user)
|
||||
{
|
||||
return $this->state(function () use ($user) {
|
||||
return [
|
||||
'alert_on_response_id' => $user->id,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private function createdAssociatedActionLogEntry(CheckoutAcceptance $acceptance): void
|
||||
{
|
||||
$acceptance->checkoutable->assetlog()->create([
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('checkout_acceptances', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('alert_on_response_id')->nullable()->after('note');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('checkout_acceptances', function (Blueprint $table) {
|
||||
$table->dropColumn('alert_on_response_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->boolean('alert_on_response')->default(0)->after('require_acceptance');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropColumn('alert_on_response');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,7 @@ return array(
|
||||
'asset_categories' => 'Asset Categories',
|
||||
'category_name' => 'Category Name',
|
||||
'checkin_email' => 'Send email to user on checkin/checkout.',
|
||||
'email_to_initiator' => 'Send email to you when user accepts or declines checkout.',
|
||||
'checkin_email_notification' => 'This user will be sent an email on checkin/checkout.',
|
||||
'clone' => 'Clone Category',
|
||||
'create' => 'Create Category',
|
||||
|
||||
@@ -52,9 +52,13 @@ return [
|
||||
'days' => 'Days',
|
||||
'expecting_checkin_date' => 'Expected Checkin Date',
|
||||
'expires' => 'Expires',
|
||||
'following_accepted' => 'The following was accepted',
|
||||
'following_declined' => 'The following was declined',
|
||||
'hello' => 'Hello',
|
||||
'hi' => 'Hi',
|
||||
'i_have_read' => 'I have read and agree to the terms of use, and have received this item.',
|
||||
'initiated_accepted' => 'A checkout you initiated was accepted',
|
||||
'initiated_declined' => 'A checkout you initiated was declined',
|
||||
'inventory_report' => 'Inventory Report',
|
||||
'item' => 'Item',
|
||||
'item_checked_reminder' => 'This is a reminder that you currently have :count items checked out to you that you have not accepted or declined. Please click the link below to confirm your decision.',
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</div>
|
||||
|
||||
<livewire:category-edit-form
|
||||
:alert-on-response="(bool) old('alert_on_response', $item->alert_on_response)"
|
||||
:default-eula-text="$snipeSettings->default_eula_text"
|
||||
:eula-text="old('eula_text', $item->eula_text)"
|
||||
:require-acceptance="(bool) old('require_acceptance', $item->require_acceptance)"
|
||||
|
||||
@@ -64,6 +64,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($requireAcceptance)
|
||||
<div class="form-group">
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
<label class="form-control">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="alert_on_response"
|
||||
value="1"
|
||||
wire:model="alertOnResponse"
|
||||
/>
|
||||
{{ trans('admin/categories/general.email_to_initiator') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Email on Checkin -->
|
||||
<div class="form-group">
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@component('mail::message')
|
||||
# {{ trans('mail.hello') }} {{ $recipient->present()->fullName() }},
|
||||
|
||||
{{ $introduction }}:
|
||||
|
||||
@if (($snipeSettings->show_images_in_email =='1') && (method_exists($item, 'getImageUrl') && $item->getImageUrl()))
|
||||
<center><img src="{{ $item->getImageUrl() }}" alt="Asset" style="max-width: 570px;"></center>
|
||||
@endif
|
||||
|
||||
@component('mail::table')
|
||||
| | |
|
||||
| ------------- | ------------- |
|
||||
| **{{ trans('mail.user') }}** | {{ $assignedTo->present()->fullName() }} |
|
||||
| **{{ trans('mail.name') }}** | {{ $item->present()->name() }} |
|
||||
@if (isset($item->asset_tag))
|
||||
| **{{ trans('mail.asset_tag') }}** | {{ $item->asset_tag }} |
|
||||
@endif
|
||||
@if ($note != '')
|
||||
| **{{ trans('mail.notes') }}** | {{ $note }} |
|
||||
@endif
|
||||
@endcomponent
|
||||
|
||||
{{ trans('mail.best_regards') }}
|
||||
|
||||
{{ $snipeSettings->site_name }}
|
||||
|
||||
@endcomponent
|
||||
@@ -11,8 +11,6 @@ use Tests\TestCase;
|
||||
|
||||
class CreateCategoriesTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
public function testRequiresPermissionToCreateCategory()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->create())
|
||||
@@ -28,6 +26,8 @@ class CreateCategoriesTest extends TestCase
|
||||
'eula_text' => 'Test EULA',
|
||||
'category_type' => 'accessory',
|
||||
'notes' => 'Test Note',
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => true,
|
||||
])
|
||||
->assertOk()
|
||||
->assertStatusMessageIs('success')
|
||||
@@ -41,6 +41,8 @@ class CreateCategoriesTest extends TestCase
|
||||
$this->assertEquals('Test EULA', $category->eula_text);
|
||||
$this->assertEquals('Test Note', $category->notes);
|
||||
$this->assertEquals('accessory', $category->category_type);
|
||||
$this->assertEquals(1, $category->require_acceptance);
|
||||
$this->assertEquals(1, $category->alert_on_response);
|
||||
}
|
||||
|
||||
public function testCannotCreateCategoryWithoutCategoryType()
|
||||
@@ -81,5 +83,4 @@ class CreateCategoriesTest extends TestCase
|
||||
$this->assertFalse(Category::where('name', 'Test Category')->exists());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,40 @@ use Tests\TestCase;
|
||||
|
||||
class UpdateCategoriesTest extends TestCase
|
||||
{
|
||||
public function test_requires_permission_to_update_category()
|
||||
{
|
||||
$category = Category::factory()->create();
|
||||
|
||||
$this->actingAsForApi(User::factory()->create())
|
||||
->patchJson(route('api.categories.update', $category))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_can_update_category()
|
||||
{
|
||||
$category = Category::factory()->forAssets()->create([
|
||||
'name' => 'Test Category',
|
||||
'require_acceptance' => false,
|
||||
'alert_on_response' => false,
|
||||
]);
|
||||
|
||||
$this->actingAsForApi(User::factory()->superuser()->create())
|
||||
->patchJson(route('api.categories.update', $category), [
|
||||
'name' => 'Test Category Edited',
|
||||
'notes' => 'Test Note Edited',
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => true,
|
||||
])
|
||||
->assertOk()
|
||||
->assertStatusMessageIs('success')
|
||||
->assertStatus(200);
|
||||
|
||||
$category->refresh();
|
||||
$this->assertEquals('Test Category Edited', $category->name, 'Name was not updated');
|
||||
$this->assertEquals('Test Note Edited', $category->notes, 'Note was not updated');
|
||||
$this->assertEquals(1, $category->require_acceptance, 'Require acceptance was not updated');
|
||||
$this->assertTrue($category->alert_on_response, 'Alert on response was not updated');
|
||||
}
|
||||
|
||||
public function testCanUpdateCategoryViaPatchWithoutCategoryType()
|
||||
{
|
||||
@@ -55,5 +89,4 @@ class UpdateCategoriesTest extends TestCase
|
||||
$this->assertNotEquals('accessory', $category->category_type, 'EULA was not updated');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,11 +34,20 @@ class CreateCategoriesTest extends TestCase
|
||||
->post(route('categories.store'), [
|
||||
'name' => 'Test Category',
|
||||
'category_type' => 'asset',
|
||||
'notes' => 'Test Note',
|
||||
'eula_text' => 'Sample text',
|
||||
'require_acceptance' => '1',
|
||||
'notes' => 'My Note',
|
||||
])
|
||||
->assertRedirect(route('categories.index'));
|
||||
|
||||
$this->assertTrue(Category::where('name', 'Test Category')->where('notes', 'Test Note')->exists());
|
||||
$this->assertDatabaseHas('categories', [
|
||||
'name' => 'Test Category',
|
||||
'category_type' => 'asset',
|
||||
'eula_text' => 'Sample text',
|
||||
'notes' => 'My Note',
|
||||
'require_acceptance' => 1,
|
||||
'alert_on_response' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUserCannotCreateCategoriesWithInvalidType()
|
||||
|
||||
@@ -43,21 +43,33 @@ class UpdateCategoriesTest extends TestCase
|
||||
|
||||
public function testUserCanEditAssetCategory()
|
||||
{
|
||||
$category = Category::factory()->forAssets()->create(['name' => 'Test Category']);
|
||||
$category = Category::factory()->forAssets()->create([
|
||||
'name' => 'Test Category',
|
||||
'require_acceptance' => false,
|
||||
'alert_on_response' => false,
|
||||
]);
|
||||
|
||||
$this->assertTrue(Category::where('name', 'Test Category')->exists());
|
||||
|
||||
$response = $this->actingAs(User::factory()->superuser()->create())
|
||||
->put(route('categories.update', $category), [
|
||||
'name' => 'Test Category Edited',
|
||||
'notes' => 'Test Note Edited',
|
||||
'require_acceptance' => '1',
|
||||
'alert_on_response' => '1',
|
||||
])
|
||||
->assertStatus(302)
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('categories.index'));
|
||||
|
||||
$this->followRedirects($response)->assertSee('Success');
|
||||
$this->assertTrue(Category::where('name', 'Test Category Edited')->where('notes', 'Test Note Edited')->exists());
|
||||
|
||||
$this->assertDatabaseHas('categories', [
|
||||
'name' => 'Test Category Edited',
|
||||
'notes' => 'Test Note Edited',
|
||||
'require_acceptance' => 1,
|
||||
'alert_on_response' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUserCanChangeCategoryTypeIfNoAssetsAssociated()
|
||||
@@ -102,5 +114,4 @@ class UpdateCategoriesTest extends TestCase
|
||||
$this->assertFalse(Category::where('name', 'Test Category Edited')->where('notes', 'Test Note Edited')->exists());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
163
tests/Feature/Checkouts/General/SettingAlertOnResponseTest.php
Normal file
163
tests/Feature/Checkouts/General/SettingAlertOnResponseTest.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Checkouts\General;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SettingAlertOnResponseTest extends TestCase
|
||||
{
|
||||
private User $actor;
|
||||
private User $assignedUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->actor = User::factory()->superuser()->create();
|
||||
$this->assignedUser = User::factory()->create();
|
||||
}
|
||||
|
||||
public function test_sets_alert_on_response_if_enabled_by_category_for_accessory()
|
||||
{
|
||||
$accessory = Accessory::factory()->create();
|
||||
$accessory->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => true,
|
||||
]);
|
||||
|
||||
$this->postAccessoryCheckout($accessory);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => Accessory::class,
|
||||
'checkoutable_id' => $accessory->id,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => $this->actor->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_does_not_set_alert_on_response_if_disabled_by_category_for_accessory()
|
||||
{
|
||||
$accessory = Accessory::factory()->create();
|
||||
$accessory->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => false,
|
||||
]);
|
||||
|
||||
$this->postAccessoryCheckout($accessory);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => Accessory::class,
|
||||
'checkoutable_id' => $accessory->id,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sets_alert_on_response_if_enabled_by_category_for_asset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$asset->model->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => true,
|
||||
]);
|
||||
|
||||
$this->postAssetCheckout($asset);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => Asset::class,
|
||||
'checkoutable_id' => $asset->id,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => $this->actor->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_does_not_set_alert_on_response_if_disabled_by_category_for_asset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$asset->model->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => false,
|
||||
]);
|
||||
|
||||
$this->postAssetCheckout($asset);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => Asset::class,
|
||||
'checkoutable_id' => $asset->id,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sets_alert_on_response_if_enabled_by_category_for_license()
|
||||
{
|
||||
$license = License::factory()->create();
|
||||
|
||||
$license->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => true,
|
||||
]);
|
||||
|
||||
$this->postLicenseCheckout($license);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => LicenseSeat::class,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => $this->actor->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_does_not_set_alert_on_response_if_disabled_by_category_for_license()
|
||||
{
|
||||
$license = License::factory()->create();
|
||||
|
||||
$license->category->update([
|
||||
'require_acceptance' => true,
|
||||
'alert_on_response' => false,
|
||||
]);
|
||||
|
||||
$this->postLicenseCheckout($license);
|
||||
|
||||
$this->assertDatabaseHas('checkout_acceptances', [
|
||||
'checkoutable_type' => LicenseSeat::class,
|
||||
'assigned_to_id' => $this->assignedUser->id,
|
||||
'alert_on_response_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
private function postAssetCheckout(Asset $asset): void
|
||||
{
|
||||
$this->actingAs($this->actor)
|
||||
->post(route('hardware.checkout.store', $asset), [
|
||||
'checkout_to_type' => 'user',
|
||||
'status_id' => (string) Statuslabel::factory()->readyToDeploy()->create()->id,
|
||||
'assigned_user' => $this->assignedUser->id,
|
||||
]);
|
||||
}
|
||||
|
||||
private function postAccessoryCheckout(Accessory $accessory): void
|
||||
{
|
||||
$this->actingAs($this->actor)
|
||||
->post(route('accessories.checkout.store', $accessory), [
|
||||
'checkout_to_type' => 'user',
|
||||
'status_id' => (string) Statuslabel::factory()->readyToDeploy()->create()->id,
|
||||
'assigned_user' => $this->assignedUser->id,
|
||||
'checkout_qty' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
private function postLicenseCheckout(License $license): void
|
||||
{
|
||||
$this->actingAs($this->actor)
|
||||
->post("/licenses/{$license->id}/checkout/", [
|
||||
'checkout_to_type' => 'user',
|
||||
'assigned_to' => $this->assignedUser->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Tests\Feature\Licenses\Ui;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Category;
|
||||
use App\Models\License;
|
||||
use App\Models\Depreciation;
|
||||
@@ -41,7 +40,48 @@ class CreateLicenseTest extends TestCase
|
||||
$response->assertInvalid(['purchase_date']);
|
||||
$response->assertSessionHasErrors(['purchase_date']);
|
||||
$this->followRedirects($response)->assertSee(trans('general.error'));
|
||||
$this->assertFalse(AssetModel::where('name', 'Test Invalid License')->exists());
|
||||
$this->assertFalse(License::where('name', 'Test Invalid License')->exists());
|
||||
|
||||
}
|
||||
|
||||
public function testLicenseCreate()
|
||||
{
|
||||
$response = $this->actingAs(User::factory()->superuser()->create())
|
||||
->from(route('licenses.create'))
|
||||
->post(route('licenses.store'), [
|
||||
'name' => 'Test Valid License',
|
||||
'seats' => '10',
|
||||
'category_id' => Category::factory()->forLicenses()->create()->id,
|
||||
]);
|
||||
$response->assertStatus(302);
|
||||
$license = License::where('name', 'Test Valid License')->sole();
|
||||
$this->assertNotNull($license);
|
||||
//$license->assetlog()->has_one_of_();
|
||||
$this->assertDatabaseHas('action_logs', ['action_type' => 'create', 'item_id' => $license->id, 'item_type' => License::class]);
|
||||
$this->assertDatabaseHas('action_logs', ['action_type' => 'add seats', 'item_id' => $license->id, 'item_type' => License::class]);
|
||||
$this->assertEquals($license->licenseseats()->count(), 10);
|
||||
//test log entries? Sure.
|
||||
|
||||
}
|
||||
|
||||
public function testTooManySeatsLicenseCreate()
|
||||
{
|
||||
$response = $this->actingAs(User::factory()->superuser()->create())
|
||||
->from(route('licenses.create'))
|
||||
->post(route('licenses.store'), [
|
||||
'name' => 'Test Valid License',
|
||||
'seats' => '100000',
|
||||
'category_id' => Category::factory()->forLicenses()->create()->id,
|
||||
]);
|
||||
$response->assertStatus(302);
|
||||
$license = License::where('name', 'Test Valid License')->first();
|
||||
$this->assertNull($license);
|
||||
//$license->assetlog()->has_one_of_();
|
||||
// $this->assertDatabaseMissing('action_logs', ['action_type' => 'create', 'item_id' => $license->id, 'item_type' => License::class]);
|
||||
// $this->assertDatabaseMissing('action_logs', ['action_type' => 'add seats', 'item_id' => $license->id, 'item_type' => License::class]);
|
||||
//test log entries? Sure.
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Feature\Licenses\Ui;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
@@ -14,4 +15,90 @@ class UpdateLicenseTest extends TestCase
|
||||
->get(route('licenses.edit', License::factory()->create()->id))
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function testCanUpdateLicenseSeats()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$license_category = Category::factory()->forLicenses()->create()->id;
|
||||
$response = $this->actingAs($admin)
|
||||
->from(route('licenses.create'))
|
||||
->post(route('licenses.store'), [
|
||||
'name' => 'Test Update License',
|
||||
'seats' => '9999',
|
||||
'category_id' => $license_category,
|
||||
]);
|
||||
$response->assertStatus(302);
|
||||
$license = License::where('name', 'Test Update License')->sole();
|
||||
$this->assertNotNull($license);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('licenses.update', $license->id), [
|
||||
'name' => 'Test Update License',
|
||||
'seats' => '19999',
|
||||
'category_id' => $license_category,
|
||||
])
|
||||
->assertStatus(302);
|
||||
|
||||
$license->refresh();
|
||||
$this->assertEquals($license->licenseseats()->count(), $license->seats);
|
||||
$this->assertEquals($license->licenseseats()->count(), 19999);
|
||||
}
|
||||
|
||||
public function testCannotUpdateLicenseSeatsTooMuch()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$license_category = Category::factory()->forLicenses()->create()->id;
|
||||
$response = $this->actingAs($admin)
|
||||
->from(route('licenses.create'))
|
||||
->post(route('licenses.store'), [
|
||||
'name' => 'Test Update License',
|
||||
'seats' => '9999',
|
||||
'category_id' => $license_category,
|
||||
]);
|
||||
$response->assertStatus(302);
|
||||
$license = License::where('name', 'Test Update License')->sole();
|
||||
$this->assertNotNull($license);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('licenses.update', $license->id), [
|
||||
'name' => 'Test Update License',
|
||||
'seats' => '29999',
|
||||
'category_id' => $license_category,
|
||||
])
|
||||
->assertStatus(302);
|
||||
|
||||
$license->refresh();
|
||||
$this->assertEquals($license->licenseseats()->count(), $license->seats);
|
||||
$this->assertEquals($license->licenseseats()->count(), 9999);
|
||||
}
|
||||
|
||||
public function testCanRemoveLicenseSeats()
|
||||
{
|
||||
$admin = User::factory()->superuser()->create();
|
||||
$license_category = Category::factory()->forLicenses()->create()->id;
|
||||
$response = $this->actingAs($admin)
|
||||
->from(route('licenses.create'))
|
||||
->post(route('licenses.store'), [
|
||||
'name' => 'Test Remove License Seats',
|
||||
'seats' => '9999',
|
||||
'category_id' => $license_category,
|
||||
]);
|
||||
$response->assertStatus(302);
|
||||
$license = License::where('name', 'Test Remove License Seats')->sole();
|
||||
$this->assertNotNull($license);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->put(route('licenses.update', $license->id), [
|
||||
'name' => 'Test Remove License Seats',
|
||||
'seats' => '5000',
|
||||
'category_id' => $license_category,
|
||||
])
|
||||
->assertStatus(302);
|
||||
|
||||
$license->refresh();
|
||||
$this->assertEquals($license->licenseseats()->count(), $license->seats);
|
||||
$this->assertEquals($license->licenseseats()->count(), 5000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
107
tests/Feature/Notifications/Email/CheckoutResponseEmailTest.php
Normal file
107
tests/Feature/Notifications/Email/CheckoutResponseEmailTest.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications\Email;
|
||||
|
||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||
use App\Models\CheckoutAcceptance;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CheckoutResponseEmailTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Mail::fake();
|
||||
}
|
||||
|
||||
public function test_accepting_checkout_acceptance_configured_to_send_alert()
|
||||
{
|
||||
$initiator = User::factory()->create();
|
||||
|
||||
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||
->pending()
|
||||
->withAlertingTo($initiator)
|
||||
->create();
|
||||
|
||||
$this->acceptCheckout($checkoutAcceptance);
|
||||
|
||||
$this->assertEmailSentTo($initiator, 'accepted');
|
||||
}
|
||||
|
||||
public function test_declining_checkout_acceptance_configured_to_send_alert()
|
||||
{
|
||||
$initiator = User::factory()->create();
|
||||
|
||||
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||
->pending()
|
||||
->withAlertingTo($initiator)
|
||||
->create();
|
||||
|
||||
$this->declineCheckout($checkoutAcceptance);
|
||||
|
||||
$this->assertEmailSentTo($initiator, 'declined');
|
||||
}
|
||||
|
||||
public function test_accepting_checkout_acceptance_not_configured_to_send_alert()
|
||||
{
|
||||
$initiator = User::factory()->create();
|
||||
|
||||
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||
->pending()
|
||||
->withoutAlerting()
|
||||
->create();
|
||||
|
||||
$this->acceptCheckout($checkoutAcceptance);
|
||||
|
||||
$this->assertEmailNotSentTo($initiator);
|
||||
}
|
||||
|
||||
public function test_declining_checkout_acceptance_not_configured_to_send_alert()
|
||||
{
|
||||
$initiator = User::factory()->create();
|
||||
|
||||
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||
->pending()
|
||||
->withoutAlerting()
|
||||
->create();
|
||||
|
||||
$this->declineCheckout($checkoutAcceptance);
|
||||
|
||||
$this->assertEmailNotSentTo($initiator);
|
||||
}
|
||||
|
||||
private function assertEmailSentTo(User $user, string $type): void
|
||||
{
|
||||
Mail::assertSent(CheckoutAcceptanceResponseMail::class, function (CheckoutAcceptanceResponseMail $mail) use ($type, $user) {
|
||||
return $mail->hasTo($user->email) && $mail->assertHasSubject('A checkout you initiated was ' . $type);
|
||||
});
|
||||
}
|
||||
|
||||
private function assertEmailNotSentTo(User $user): void
|
||||
{
|
||||
Mail::assertNotSent(CheckoutAcceptanceResponseMail::class, function ($mail) use ($user) {
|
||||
return $mail->hasTo($user->email);
|
||||
});
|
||||
}
|
||||
|
||||
private function acceptCheckout(CheckoutAcceptance $checkoutAcceptance): void
|
||||
{
|
||||
$this->actingAs($checkoutAcceptance->assignedTo)
|
||||
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||
'asset_acceptance' => 'accepted',
|
||||
'note' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
private function declineCheckout(CheckoutAcceptance $checkoutAcceptance): void
|
||||
{
|
||||
$this->actingAs($checkoutAcceptance->assignedTo)
|
||||
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||
'asset_acceptance' => 'declined',
|
||||
'note' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user