diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index 747f7b7284..b652455a57 100644
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -650,6 +650,7 @@ class SettingsController extends Controller
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
+ $setting->admin_cc_always = $request->validated('admin_cc_always');
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
diff --git a/app/Http/Requests/StoreNotificationSettings.php b/app/Http/Requests/StoreNotificationSettings.php
index bf5f5b3d4e..f58d014c76 100644
--- a/app/Http/Requests/StoreNotificationSettings.php
+++ b/app/Http/Requests/StoreNotificationSettings.php
@@ -5,6 +5,7 @@ namespace App\Http\Requests;
use App\Models\Accessory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
+use Illuminate\Validation\Rule;
class StoreNotificationSettings extends FormRequest
{
@@ -26,6 +27,9 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
+ 'admin_cc_always' => [
+ Rule::in('0', '1'),
+ ],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php
index e647bcd9e0..ec843b980b 100644
--- a/app/Listeners/CheckoutableListener.php
+++ b/app/Listeners/CheckoutableListener.php
@@ -69,16 +69,16 @@ class CheckoutableListener
return;
}
+ $acceptance = $this->getCheckoutAcceptance($event);
+
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
- $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
+ $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance);
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
- $acceptance = $this->getCheckoutAcceptance($event);
-
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUser($event);
@@ -419,9 +419,19 @@ class CheckoutableListener
return false;
}
- private function shouldSendEmailToAlertAddress(): bool
+ private function shouldSendEmailToAlertAddress($acceptance = null): bool
{
- return Setting::getSettings() && Setting::getSettings()->admin_cc_email;
+ $setting = Setting::getSettings();
+
+ if (!$setting) {
+ return false;
+ }
+
+ if (is_null($acceptance) && !$setting->admin_cc_always) {
+ return false;
+ }
+
+ return (bool) $setting->admin_cc_email;
}
private function getFormattedAlertAddresses(): array
diff --git a/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php
new file mode 100644
index 0000000000..d28115a6d2
--- /dev/null
+++ b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php
@@ -0,0 +1,27 @@
+boolean('admin_cc_always')->after('admin_cc_email')->default(1);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('settings', function (Blueprint $table) {
+ $table->dropColumn('admin_cc_always');
+ });
+ }
+};
diff --git a/resources/lang/en-US/admin/settings/general.php b/resources/lang/en-US/admin/settings/general.php
index 9f0fa2c9e6..8c4ae35365 100644
--- a/resources/lang/en-US/admin/settings/general.php
+++ b/resources/lang/en-US/admin/settings/general.php
@@ -9,6 +9,8 @@ return [
'ad_append_domain_help' => 'User isn\'t required to write "username@domain.local", they can just type "username".',
'admin_cc_email' => 'CC Email',
'admin_cc_email_help' => 'Send a copy of checkin/checkout emails to this address.',
+ 'admin_cc_always' => 'Always send copy upon checkin/checkout',
+ 'admin_cc_when_acceptance_required' => 'Only send copy upon checkout if acceptance is required',
'admin_settings' => 'Admin Settings',
'is_ad' => 'This is an Active Directory server',
'alerts' => 'Alerts',
diff --git a/resources/views/settings/alerts.blade.php b/resources/views/settings/alerts.blade.php
index 9f31206452..c861cc63bf 100644
--- a/resources/views/settings/alerts.blade.php
+++ b/resources/views/settings/alerts.blade.php
@@ -96,8 +96,28 @@
{!! $errors->first('admin_cc_email', ':message
') !!}
{{ trans('admin/settings/general.admin_cc_email_help') }}
-
-
+
+
+
diff --git a/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckinTest.php b/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckinTest.php
new file mode 100644
index 0000000000..1bdbf0b673
--- /dev/null
+++ b/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckinTest.php
@@ -0,0 +1,126 @@
+user = User::factory()->create();
+
+ $this->category = Category::factory()->create([
+ 'checkin_email' => false,
+ 'eula_text' => null,
+ 'require_acceptance' => false,
+ 'use_default_eula' => false,
+ ]);
+
+ $this->assetModel = AssetModel::factory()->for($this->category)->create();
+
+ $this->asset = Asset::factory()
+ ->for($this->assetModel, 'model')
+ ->assignedToUser($this->user)
+ ->create();
+ }
+
+ public function test_admin_alert_email_sends()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->category->update(['checkin_email' => true]);
+
+ $this->fireCheckInEvent($this->asset, $this->user);
+
+ Mail::assertSent(CheckinAssetMail::class, function ($mail) {
+ return $mail->hasTo($this->user->email) && $mail->hasCc('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_still_sent_when_category_email_is_not_set_to_send_email_to_user()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->category->update(['checkin_email' => false]);
+
+ $this->fireCheckInEvent($this->asset, $this->user);
+
+ Mail::assertSent(CheckinAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->user->update(['email' => null]);
+
+ $this->category->update(['checkin_email' => true]);
+
+ $this->fireCheckInEvent($this->asset, $this->user);
+
+ Mail::assertSent(CheckinAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_sent_when_always_send_is_true_and_asset_does_not_require_acceptance()
+ {
+ $this->settings
+ ->enableAdminCC('cc@example.com')
+ ->enableAdminCCAlways();
+
+ $this->category->update(['checkin_email' => false]);
+
+ $this->fireCheckInEvent($this->asset, $this->user);
+
+ Mail::assertSent(CheckinAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_not_sent_when_always_send_is_false_and_asset_does_not_require_acceptance()
+ {
+ $this->settings
+ ->enableAdminCC('cc@example.com')
+ ->disableAdminCCAlways();
+
+ $this->category->update(['checkin_email' => false]);
+
+ $this->fireCheckInEvent($this->asset, $this->user);
+
+ Mail::assertNotSent(CheckinAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
+ });
+ }
+
+ private function fireCheckInEvent($asset, $user): void
+ {
+ event(new CheckoutableCheckedIn(
+ $asset,
+ $user,
+ User::factory()->checkinAssets()->create(),
+ ''
+ ));
+ }
+}
diff --git a/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckoutTest.php b/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckoutTest.php
new file mode 100644
index 0000000000..d881ae7dab
--- /dev/null
+++ b/tests/Feature/Notifications/Email/EmailNotificationsToAdminAlertEmailUponCheckoutTest.php
@@ -0,0 +1,119 @@
+category = Category::factory()->create([
+ 'checkin_email' => false,
+ 'eula_text' => null,
+ 'require_acceptance' => false,
+ 'use_default_eula' => false,
+ ]);
+
+ $this->assetModel = AssetModel::factory()->for($this->category)->create();
+ $this->asset = Asset::factory()->for($this->assetModel, 'model')->create();
+
+ $this->user = User::factory()->create();
+ }
+
+ public function test_admin_alert_email_sends()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->category->update(['checkin_email' => true]);
+
+ $this->fireCheckoutEvent();
+
+ Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
+ return $mail->hasCc('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_still_sent_when_category_is_not_set_to_send_email_to_user()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->fireCheckoutEvent();
+
+ Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
+ {
+ $this->settings->enableAdminCC('cc@example.com');
+
+ $this->category->update(['checkin_email' => true]);
+ $this->user->update(['email' => null]);
+
+ $this->fireCheckoutEvent();
+
+ Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
+ return $mail->hasTo('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_sent_when_always_send_is_true_and_asset_does_not_require_acceptance()
+ {
+ $this->settings
+ ->enableAdminCC('cc@example.com')
+ ->enableAdminCCAlways();
+
+ $this->category->update(['checkin_email' => false]);
+
+ $this->fireCheckoutEvent();
+
+ Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
+ return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
+ });
+ }
+
+ public function test_admin_alert_email_not_sent_when_always_send_is_false_and_asset_does_not_require_acceptance()
+ {
+ $this->settings
+ ->enableAdminCC('cc@example.com')
+ ->disableAdminCCAlways();
+
+ $this->category->update(['checkin_email' => false]);
+
+ $this->fireCheckoutEvent();
+
+ Mail::assertNotSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
+ return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
+ });
+ }
+
+ private function fireCheckoutEvent(): void
+ {
+ event(new CheckoutableCheckedOut(
+ $this->asset,
+ $this->user,
+ User::factory()->superuser()->create(),
+ '',
+ ));
+ }
+}
diff --git a/tests/Feature/Notifications/Email/EmailNotificationsUponCheckinTest.php b/tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckinTest.php
similarity index 64%
rename from tests/Feature/Notifications/Email/EmailNotificationsUponCheckinTest.php
rename to tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckinTest.php
index 254e2e09ca..432ce88b4b 100644
--- a/tests/Feature/Notifications/Email/EmailNotificationsUponCheckinTest.php
+++ b/tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckinTest.php
@@ -4,8 +4,6 @@ namespace Tests\Feature\Notifications\Email;
use App\Mail\CheckinAssetMail;
use App\Models\Accessory;
-use App\Models\AssetModel;
-use App\Models\Category;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use Illuminate\Support\Facades\Mail;
@@ -16,7 +14,7 @@ use App\Models\User;
use Tests\TestCase;
#[Group('notifications')]
-class EmailNotificationsUponCheckinTest extends TestCase
+class EmailNotificationsToUserUponCheckinTest extends TestCase
{
protected function setUp(): void
{
@@ -101,57 +99,6 @@ class EmailNotificationsUponCheckinTest extends TestCase
Mail::assertNothingSent();
}
- public function test_admin_alert_email_sends()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $user = User::factory()->create();
- $asset = Asset::factory()->assignedToUser($user)->create();
-
- $asset->model->category->update(['checkin_email' => true]);
-
- $this->fireCheckInEvent($asset, $user);
-
- Mail::assertSent(CheckinAssetMail::class, function ($mail) use ($user) {
- return $mail->hasTo($user->email) && $mail->hasCc('cc@example.com');
- });
- }
-
- public function test_admin_alert_email_still_sent_when_category_email_is_not_set_to_send_email_to_user()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $category = Category::factory()->create([
- 'checkin_email' => false,
- 'eula_text' => null,
- 'use_default_eula' => false,
- ]);
- $assetModel = AssetModel::factory()->create(['category_id' => $category->id]);
- $asset = Asset::factory()->create(['model_id' => $assetModel->id]);
-
- $this->fireCheckInEvent($asset, User::factory()->create());
-
- Mail::assertSent(CheckinAssetMail::class, function ($mail) {
- return $mail->hasTo('cc@example.com');
- });
- }
-
- public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $user = User::factory()->create(['email' => null]);
- $asset = Asset::factory()->assignedToUser($user)->create();
-
- $asset->model->category->update(['checkin_email' => true]);
-
- $this->fireCheckInEvent($asset, $user);
-
- Mail::assertSent(CheckinAssetMail::class, function ($mail) {
- return $mail->hasTo('cc@example.com');
- });
- }
-
private function fireCheckInEvent($asset, $user): void
{
event(new CheckoutableCheckedIn(
diff --git a/tests/Feature/Notifications/Email/EmailNotificationsUponCheckoutTest.php b/tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckoutTest.php
similarity index 68%
rename from tests/Feature/Notifications/Email/EmailNotificationsUponCheckoutTest.php
rename to tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckoutTest.php
index e5a1a66cb6..e5741da8ec 100644
--- a/tests/Feature/Notifications/Email/EmailNotificationsUponCheckoutTest.php
+++ b/tests/Feature/Notifications/Email/EmailNotificationsToUserUponCheckoutTest.php
@@ -13,7 +13,7 @@ use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;
#[Group('notifications')]
-class EmailNotificationsUponCheckoutTest extends TestCase
+class EmailNotificationsToUserUponCheckoutTest extends TestCase
{
private Asset $asset;
private AssetModel $assetModel;
@@ -87,44 +87,6 @@ class EmailNotificationsUponCheckoutTest extends TestCase
Mail::assertNothingSent();
}
- public function test_admin_alert_email_sends()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $this->category->update(['checkin_email' => true]);
-
- $this->fireCheckoutEvent();
-
- Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
- return $mail->hasCc('cc@example.com');
- });
- }
-
- public function test_admin_alert_email_still_sent_when_category_is_not_set_to_send_email_to_user()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $this->fireCheckoutEvent();
-
- Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
- return $mail->hasTo('cc@example.com');
- });
- }
-
- public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
- {
- $this->settings->enableAdminCC('cc@example.com');
-
- $this->category->update(['checkin_email' => true]);
- $this->user->update(['email' => null]);
-
- $this->fireCheckoutEvent();
-
- Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
- return $mail->hasTo('cc@example.com');
- });
- }
-
private function fireCheckoutEvent(): void
{
event(new CheckoutableCheckedOut(
diff --git a/tests/Feature/Settings/AlertsSettingTest.php b/tests/Feature/Settings/AlertsSettingTest.php
index d79bd1cf21..f8829d3825 100644
--- a/tests/Feature/Settings/AlertsSettingTest.php
+++ b/tests/Feature/Settings/AlertsSettingTest.php
@@ -18,7 +18,10 @@ class AlertsSettingTest extends TestCase
public function testAdminCCEmailArrayCanBeSaved()
{
$response = $this->actingAs(User::factory()->superuser()->create())
- ->post(route('settings.alerts.save', ['alert_email' => 'me@example.com,you@example.com']))
+ ->post(route('settings.alerts.save', [
+ 'alert_email' => 'me@example.com,you@example.com',
+ 'admin_cc_always' => '1',
+ ]))
->assertStatus(302)
->assertValid('alert_email')
->assertRedirect(route('settings.index'))
@@ -26,4 +29,23 @@ class AlertsSettingTest extends TestCase
$this->followRedirects($response)->assertSee('alert-success');
}
+ public function test_can_update_admin_cc_always_to_true()
+ {
+ $this->settings->disableAdminCCAlways();
+
+ $this->actingAs(User::factory()->superuser()->create())
+ ->post(route('settings.alerts.save', ['admin_cc_always' => '1']));
+
+ $this->assertDatabaseHas('settings', ['admin_cc_always' => '1']);
+ }
+
+ public function test_can_update_admin_cc_always_to_false()
+ {
+ $this->settings->enableAdminCC()->enableAdminCCAlways();
+
+ $this->actingAs(User::factory()->superuser()->create())
+ ->post(route('settings.alerts.save', ['admin_cc_always' => '0']));
+
+ $this->assertDatabaseHas('settings', ['admin_cc_always' => '0']);
+ }
}
diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php
index bcbd83d8b6..8ff8ba6945 100644
--- a/tests/Support/Settings.php
+++ b/tests/Support/Settings.php
@@ -60,6 +60,20 @@ class Settings
]);
}
+ public function enableAdminCCAlways(): Settings
+ {
+ return $this->update([
+ 'admin_cc_always' => 1,
+ ]);
+ }
+
+ public function disableAdminCCAlways(): Settings
+ {
+ return $this->update([
+ 'admin_cc_always' => 0,
+ ]);
+ }
+
public function enableMultipleFullCompanySupport(): Settings
{
return $this->update(['full_multiple_companies_support' => 1]);