diff --git a/app/Events/AssetsTransferredInBulk.php b/app/Events/AssetsTransferredInBulk.php
new file mode 100644
index 000000000000..60c257e6583b
--- /dev/null
+++ b/app/Events/AssetsTransferredInBulk.php
@@ -0,0 +1,31 @@
+authorize('checkout', Asset::class);
-
$alreadyAssigned = collect();
- if (old('selected_assets') && is_array(old('selected_assets'))) {
- $assets = Asset::findMany(old('selected_assets'));
+ if (!Setting::getSettings()->allow_bulk_asset_transfer) {
- [$assignable, $alreadyAssigned] = $assets->partition(function (Asset $asset) {
- return !$asset->assigned_to;
- });
+ if (old('selected_assets') && is_array(old('selected_assets'))) {
+ $assets = Asset::findMany(old('selected_assets'));
- session()->flashInput(['selected_assets' => $assignable->pluck('id')->values()->toArray()]);
+ [$assignable, $alreadyAssigned] = $assets->partition(function (Asset $asset) {
+ return !$asset->assigned_to;
+ });
+
+ session()->flashInput(['selected_assets' => $assignable->pluck('id')->values()->toArray()]);
+ }
}
$do_not_change = ['' => trans('general.do_not_change')];
@@ -649,6 +653,7 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
{
$this->authorize('checkout', Asset::class);
+
try {
$admin = auth()->user();
@@ -663,8 +668,9 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
$assets = Asset::findOrFail($asset_ids);
- // Prevent checking out assets that are already checked out
- if ($assets->pluck('assigned_to')->unique()->filter()->isNotEmpty()) {
+ [$alreadyAssigned, $unassigned] = $assets->collect()->partition(fn ($asset) => !is_null($asset->assigned_to));
+
+ if ($unassigned->pluck('assigned_to')->unique()->filter()->isNotEmpty()){
// re-add the asset ids so the assets select is re-populated
$request->session()->flashInput(['selected_assets' => $asset_ids]);
@@ -706,8 +712,8 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
}
$errors = [];
- DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
- foreach ($assets as $asset) {
+ DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $unassigned, $alreadyAssigned, $request) { //NOTE: $errors is passsed by reference!
+ foreach ($unassigned as $asset) {
$this->authorize('checkout', $asset);
// See if there is a status label passed
@@ -730,6 +736,32 @@ public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse
$errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
}
}
+ if ($alreadyAssigned->isNotEmpty() && Setting::getSettings()->allow_bulk_asset_transfer) {
+ Context::add('action', 'bulk_transfer');
+ foreach ($alreadyAssigned as $asset) {
+ $this->authorize('checkout', $asset);
+ $transferredFrom = $asset->assignedTo;
+ // See if there is a status label passed
+ if ($request->filled('status_id')) {
+ $asset->status_id = $request->get('status_id');
+ }
+
+ $checkout_success = $asset->transfer($alreadyAssigned, $target, $transferredFrom, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
+
+ //TODO - I think this logic is duplicated in the checkOut method?
+ if ($target->location_id != '') {
+ $asset->location_id = $target->location_id;
+ // TODO - I don't know why this is being saved without events
+ $asset::withoutEvents(function () use ($asset) {
+ $asset->save();
+ });
+ }
+
+ if (!$checkout_success) {
+ $errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
+ }
+ }
+ }
});
if (! $errors) {
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index 8c57efa5eb20..ecba2db289eb 100644
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -352,6 +352,7 @@ public function postSettings(Request $request) : RedirectResponse
$setting->dash_chart_type = $request->input('dash_chart_type');
$setting->profile_edit = $request->input('profile_edit', 0);
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
+ $setting->allow_bulk_asset_transfer = $request->input('allow_bulk_asset_transfer', 0);
$setting->manager_view_enabled = $request->input('manager_view_enabled', 0);
diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php
index 908dd58dfded..6f3d8333ccb5 100644
--- a/app/Listeners/CheckoutableListener.php
+++ b/app/Listeners/CheckoutableListener.php
@@ -33,6 +33,7 @@
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;
@@ -434,6 +435,10 @@ private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool
* 3. The item should send an email at check-in/check-out
*/
+ if (Context::get('action') === 'transfer') {
+ return true;
+ }
+
if ($checkoutable->requireAcceptance()) {
return true;
}
diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php
index d7973e2103e7..013b9e5b047b 100644
--- a/app/Listeners/LogListener.php
+++ b/app/Listeners/LogListener.php
@@ -6,6 +6,7 @@
use App\Events\AccessoryCheckedOut;
use App\Events\AssetCheckedIn;
use App\Events\AssetCheckedOut;
+use App\Events\AssetsTransferredInBulk;
use App\Events\CheckoutableCheckedIn;
use App\Events\CheckoutableCheckedOut;
use App\Events\CheckoutAccepted;
@@ -75,6 +76,15 @@ public function onCheckoutAccepted(CheckoutAccepted $event)
$logaction->save();
}
+ /**
+ * @throws \Exception
+ */
+ public function onAssetsTransferredInBulk(AssetsTransferredInBulk $event): void
+ {
+ Log::debug('event passed to the listener:');
+ $event->transferable->logCheckout($event->note, $event->transferredTo, $event->transferable->last_checkout, $event->originalValues, true);
+
+ }
public function onCheckoutDeclined(CheckoutDeclined $event)
{
$logaction = new Actionlog();
@@ -140,6 +150,7 @@ public function subscribe($events)
$list = [
'CheckoutableCheckedIn',
'CheckoutableCheckedOut',
+ 'AssetsTransferredInBulk',
'CheckoutAccepted',
'CheckoutDeclined',
'UserMerged',
diff --git a/app/Listeners/TransferableListener.php b/app/Listeners/TransferableListener.php
new file mode 100644
index 000000000000..16b041ef5594
--- /dev/null
+++ b/app/Listeners/TransferableListener.php
@@ -0,0 +1,222 @@
+listen(
+ AssetsTransferredInBulk::class,
+ 'App\Listeners\TransferableListener@onTransfer'
+ );
+ }
+ public function onTransfer($event){
+
+ $acceptance = $this->getTransferAcceptance($event);
+
+ $shouldSendEmailToUser = $this->shouldSendTransferEmailToUser($event->transferable);
+ $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance);
+ $shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
+
+ if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
+ return;
+ }
+ if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
+ $mailable = new TransferredMail($event->transferable, $event->transferredTo, $event->admin, $acceptance, $event->transferred_at, $event->expected_checkin, $event->note);
+ $notifiable = $this->getNotifiableUser($event);
+ $notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
+ $shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
+
+ [$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
+
+ if (!empty($to)) {
+ try {
+ $toMail = (clone $mailable)->locale($notifiable->locale);
+ Mail::to(array_flatten($to))->send($toMail);
+ Log::info('Transfer Mail sent to transfer target');
+ } catch (ClientException $e) {
+ Log::debug("Exception caught during transfer email: " . $e->getMessage());
+ } catch (Exception $e) {
+ Log::debug("Exception caught during transfer email: " . $e->getMessage());
+ }
+ }
+ if(!empty($cc)) {
+ try {
+ $ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
+ Mail::to(array_flatten($cc))->send($ccMail);
+ } catch (ClientException $e) {
+ Log::debug("Exception caught during transfer email: " . $e->getMessage());
+ }
+ catch (Exception $e) {
+ Log::debug("Exception caught during transfer email: " . $e->getMessage());
+ }
+ }
+ }
+ }
+ /**
+ * Generates a checkout acceptance
+ * @param Event $event
+ * @return mixed
+ */
+ private function getTransferAcceptance($event)
+ {
+ $transferredToType = get_class($event->transferredTo);
+ if ($transferredToType != "App\Models\User") {
+ return null;
+ }
+
+ if (!$event->transferable->requireAcceptance()) {
+ return null;
+ }
+
+ $acceptance = new CheckoutAcceptance;
+ $acceptance->checkoutable()->associate($event->transferable);
+ $acceptance->assignedTo()->associate($event->transferredTo);
+
+ $acceptance->qty = 1;
+
+ if (isset($event->trasnferable->checkout_qty)) {
+ $acceptance->qty = $event->trasnferable->checkout_qty;
+ }
+
+ $category = $event->transferable->model->category;
+
+ if ($category?->alert_on_response) {
+ $acceptance->alert_on_response_id = auth()->id();
+ }
+
+ $acceptance->save();
+
+ return $acceptance;
+ }
+
+ private function shouldSendWebhookNotification(): bool
+ {
+ return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
+ }
+
+ private function shouldSendTransferEmailToUser(Model $transferable): bool
+ {
+ /**
+ * Send an email if 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') === 'transfer') {
+ return true;
+ }
+
+ if ($transferable->requireAcceptance()) {
+ return true;
+ }
+
+ if ($transferable->getEula()) {
+ return true;
+ }
+
+ if ($this->checkoutableCategoryShouldSendEmail($transferable)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private function shouldSendEmailToAlertAddress($acceptance = null): bool
+ {
+ $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
+ {
+ $alertAddresses = Setting::getSettings()->admin_cc_email;
+
+ if ($alertAddresses !== '') {
+ return array_filter(array_map('trim', explode(',', $alertAddresses)));
+ }
+
+ return [];
+ }
+ /**
+ * This gets the recipient objects based on the type of checkoutable.
+ * The 'name' property for users is set in the boot method in the User model.
+ *
+ * @see \App\Models\User::boot()
+ * @param $event
+ * @return mixed
+ */
+ private function getNotifiableUser($event)
+ {
+
+ // If it's assigned to an asset, get that asset's assignedTo object
+ if ($event->transferredTo instanceof Asset){
+ $event->transferredTo->load('assignedTo');
+ return $event->transferredTo->assignedto;
+
+ // If it's assigned to a location, get that location's manager object
+ } elseif ($event->transferredTo instanceof Location) {
+ return $event->transferredTo->manager;
+
+ // Otherwise just return the assigned to object
+ } else {
+ return $event->transferredTo;
+ }
+ }
+ private function generateEmailRecipients(
+ bool $shouldSendEmailToUser,
+ bool $shouldSendEmailToAlertAddress,
+ mixed $notifiable
+ ): array {
+ $to = [];
+ $cc = [];
+
+ // if user && cc: to user, cc admin
+ if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
+ $to[] = $notifiable;
+ $cc[] = $this->getFormattedAlertAddresses();
+ }
+
+ // if user && no cc: to user
+ if ($shouldSendEmailToUser && !$shouldSendEmailToAlertAddress) {
+ $to[] = $notifiable;
+ }
+
+ // if no user && cc: to admin
+ if (!$shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
+ $to[] = $this->getFormattedAlertAddresses();
+ }
+
+ return array($to, $cc);
+ }
+}
\ No newline at end of file
diff --git a/app/Mail/TransferredMail.php b/app/Mail/TransferredMail.php
new file mode 100644
index 000000000000..0905fa2b1ee7
--- /dev/null
+++ b/app/Mail/TransferredMail.php
@@ -0,0 +1,108 @@
+require_acceptance = $this->requireAcceptance();
+ }
+
+ public function envelope() : Envelope
+ {
+ $from = new Address(config('mail.from.address'), config('mail.from.name'));
+
+ return new Envelope(
+ from: $from,
+ subject: $this->getSubject(),
+ );
+ }
+
+ public function content(): Content
+ {
+ return new Content(
+ markdown: 'mail.markdown.transfer-items',
+ with: [
+ 'introduction' => $this->getIntroduction(),
+ 'requires_acceptance' => $this->requireAcceptance(),
+ 'acceptance_url' => $this->acceptanceUrl(),
+ 'eula' => $this->getEula(),
+ ]
+ );
+ }
+ public function attachments(): array
+ {
+ return [];
+ }
+
+ private function getSubject(): string
+ {
+ return trans('mail.Asset_Transferred_Notification', $this->items->count());
+ }
+
+ private function getIntroduction(): string
+ {
+ if ($this->items->count() > 1) {
+ // @todo: translate
+ return 'Assets have been checked out to you.';
+ }
+
+ // @todo: translate
+ return 'An asset has been checked out to you.';
+ }
+
+ private function requiresAcceptance(): bool
+ {
+ return (bool) $this->assets->reduce(
+ fn($count, $asset) => $count + $asset->requireAcceptance()
+ );
+ }
+
+ private function acceptanceUrl()
+ {
+ if ($this->assets->count() > 1) {
+ return route('account.accept');
+ }
+
+ return route('account.accept.item', $this->assets->first());
+ }
+
+ private function getEula()
+ {
+ // if assets do not have the same category then return early...
+ $categories = $this->assets->pluck('model.category.id')->unique();
+
+ if ($categories->count() > 1) {
+ return;
+ }
+
+ // if assets do have the same category then return the shared EULA
+ if ($categories->count() === 1) {
+ return $this->assets->first()->getEula();
+ }
+
+ // @todo: if the categories use the default eula then return that
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Asset.php b/app/Models/Asset.php
index 70941f62e9f2..0739bc173058 100644
--- a/app/Models/Asset.php
+++ b/app/Models/Asset.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use App\Events\AssetsTransferredInBulk;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed;
use App\Helpers\Helper;
@@ -17,6 +18,7 @@
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
@@ -510,6 +512,63 @@ public function checkOut($target, $admin = null, $checkout_at = null, $expected_
return false;
}
+ public function transfer($alreadyAssigned, $target, $transferredFrom, $admin= null, $transferred_at = null, $expected_checkin = null, $note = null, $name = null, $location = null ){
+ if (! $target) {
+ return false;
+ }
+ if ($this->is($target)) {
+ throw new CheckoutNotAllowed('You cannot transfer an asset to itself.');
+ }
+ if ($expected_checkin) {
+ $this->expected_checkin = $expected_checkin;
+ }
+
+ $this->last_checkout = $transferred_at;
+ $this->name = $name;
+
+ $this->assignedTo()->associate($target);
+
+ if ($location != null) {
+ $this->location_id = $location;
+ } else {
+ if (isset($target->location)) {
+ $this->location_id = $target->location->id;
+ }
+ if ($target instanceof Location) {
+ $this->location_id = $target->id;
+ }
+ }
+
+ $originalValues = $this->getRawOriginal();
+ // attempt to detect change in value if different from today's date
+ if ($transferred_at && strpos($transferred_at, date('Y-m-d')) === false) {
+ $originalValues['action_date'] = date('Y-m-d H:i:s');
+ }
+
+ if ($this->save()) {
+ if (is_int($admin)) {
+ $transferredBy = User::findOrFail($admin);
+ } elseif ($admin && get_class($admin) === \App\Models\User::class) {
+ $transferredBy = $admin;
+ } else {
+ $transferredBy = auth()->user();
+ }
+ event(new AssetsTransferredInBulk(
+ transferable: $alreadyAssigned,
+ transferredTo: $target,
+ transferredFrom: $transferredFrom,
+ admin: $transferredBy,
+ transferred_at: (string) $transferred_at,
+ expected_checkin: (string) $expected_checkin?? '',
+ note: $note,
+ originalValues: $originalValues,
+ ));
+ $this->increment('checkout_counter', 1);
+
+ return true;
+ }
+ return false;
+ }
/**
* Sets the detailedNameAttribute
*
diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php
index f912d159270a..bbbe4fb61e18 100644
--- a/app/Models/Loggable.php
+++ b/app/Models/Loggable.php
@@ -34,7 +34,7 @@ public function setImported(bool $bool): void
* @since [v3.4]
* @return \App\Models\Actionlog
*/
- public function logCheckout($note, $target, $action_date = null, $originalValues = [])
+ public function logCheckout($note, $target, $action_date = null, $originalValues = [], $transfer = null)
{
$log = new Actionlog;
@@ -95,7 +95,6 @@ public function logCheckout($note, $target, $action_date = null, $originalValues
$array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
-
foreach ($originalValues as $key => $value) {
// TODO - action_date isn't a valid attribute of any first-class object, so we might want to remove this?
if ($key == 'action_date' && $value != $action_date) {
@@ -111,9 +110,12 @@ public function logCheckout($note, $target, $action_date = null, $originalValues
if (!empty($changed)) {
$log->log_meta = json_encode($changed);
}
-
- $log->logaction('checkout');
-
+ if($transfer){
+ $log->logaction('transferred');
+ }
+ else {
+ $log->logaction('checkout');
+ }
return $log;
}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index 1f08b445c9ec..1e0b5f43a27f 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -4,6 +4,7 @@
use App\Listeners\CheckoutableListener;
use App\Listeners\LogListener;
+use App\Listeners\TransferableListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
@@ -31,5 +32,6 @@ class EventServiceProvider extends ServiceProvider
protected $subscribe = [
LogListener::class,
CheckoutableListener::class,
+ TransferableListener::class,
];
}
diff --git a/database/migrations/2025_10_08_195012_add_bulk_transfer_boolean_to_settings_table.php b/database/migrations/2025_10_08_195012_add_bulk_transfer_boolean_to_settings_table.php
new file mode 100644
index 000000000000..0080893ec320
--- /dev/null
+++ b/database/migrations/2025_10_08_195012_add_bulk_transfer_boolean_to_settings_table.php
@@ -0,0 +1,28 @@
+boolean('allow_bulk_asset_transfer')->after('require_checkinout_notes')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('settings', function (Blueprint $table) {
+ $table->dropColumn('allow_bulk_asset_transfer');
+ });
+ }
+};
diff --git a/resources/lang/en-US/admin/settings/general.php b/resources/lang/en-US/admin/settings/general.php
index 6d13f62e7937..0cfc3d9943cb 100644
--- a/resources/lang/en-US/admin/settings/general.php
+++ b/resources/lang/en-US/admin/settings/general.php
@@ -22,6 +22,8 @@
'alert_inv_threshold' => 'Inventory Alert Threshold',
'allow_user_skin' => 'Allow User Skin',
'allow_user_skin_help_text' => 'Checking this box will allow a user to override the UI skin with a different one.',
+ 'allow_bulk_asset_transfer' => 'Allow transfer of assets in Bulk Checkout',
+ 'allow_bulk_asset_transfer_help_text' => 'Allow checked out assets to be transferred to another user during bulk checkout.',
'asset_ids' => 'Asset IDs',
'audit_interval' => 'Audit Interval',
'audit_interval_help' => 'If you are required to regularly physically audit your assets, enter the interval in months that you use. If you update this value, all of the "next audit dates" for assets with an upcoming audit date will be updated.',
diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php
index 3c6738bbdc02..d351be115353 100644
--- a/resources/lang/en-US/general.php
+++ b/resources/lang/en-US/general.php
@@ -310,6 +310,7 @@
'total_accessories' => 'total accessories',
'total_consumables' => 'total consumables',
'total_cost' => 'Total Cost',
+ 'transferred' => 'Transferred',
'type' => 'Type',
'undeployable' => 'Un-deployable',
'unknown_admin' => 'Unknown Admin',
diff --git a/resources/lang/en-US/mail.php b/resources/lang/en-US/mail.php
index 70ee6ba42fea..3f569c9712db 100644
--- a/resources/lang/en-US/mail.php
+++ b/resources/lang/en-US/mail.php
@@ -6,6 +6,7 @@
'Accessory_Checkout_Notification' => 'Accessory checked out|:count Accessories checked out',
'Asset_Checkin_Notification' => 'Asset checked in: :tag',
'Asset_Checkout_Notification' => 'Asset checked out: :tag',
+ 'Asset_Transferred_Notification' => '{1} Asset transferred|[2,*] Assets transferred',
'Confirm_Accessory_Checkin' => 'Accessory checkin confirmation',
'Confirm_Asset_Checkin' => 'Asset checkin confirmation',
'Confirm_component_checkin' => 'Component checkin confirmation',
diff --git a/resources/views/mail/markdown/transfer-items.blade.php b/resources/views/mail/markdown/transfer-items.blade.php
new file mode 100644
index 000000000000..721e4bd91532
--- /dev/null
+++ b/resources/views/mail/markdown/transfer-items.blade.php
@@ -0,0 +1,69 @@
+
+ **[✔ Click here to review the terms of use and accept the items]({{ $acceptance_url }})**
+ @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
+
+ @if ($eula)
+
{{trans('mail.serial').': '.$item->serial}} |
+ @if (isset($item->model?->category))
+ | **{{ trans('general.category') }}** | {{ $item->model->category->name }} |
+ @endif
+ @if (isset($item->manufacturer))
+ | **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} |
+ @endif
+ @if (isset($item->model))
+ | **{{ trans('general.asset_model') }}** | {{ $item->model->name }} |
+ @endif
+ @if ((isset($asset->model?->model_number)))
+ | **{{ trans('general.model_no') }}** | {{ $item->model->model_number }} |
+ @endif
+ @if (isset($item->assetstatus))
+ | **{{ trans('general.status') }}** | {{ $item->assetstatus->name }} |
+ @endif
+ |
|
|
+ @endforeach
+
+
+ {{ $snipeSettings->site_name }}
+
{{ trans('admin/settings/general.allow_bulk_asset_transfer_help_text') }}
+