Merge branch 'fix-factory-auto-gen-action-logs' into add_types_to_unaccepted_asset_report

This commit is contained in:
Godfrey M
2025-09-24 15:43:27 -07:00
51 changed files with 546 additions and 275 deletions
+2 -2
View File
@@ -95,7 +95,7 @@ class Helper
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->text($str);
return $Parsedown->text(strip_tags($str));
}
}
@@ -105,7 +105,7 @@ class Helper
$Parsedown->setSafeMode(true);
if ($str) {
return $Parsedown->line($str);
return $Parsedown->line(strip_tags($str));
}
}
@@ -13,11 +13,6 @@ use App\Models\Company;
use App\Models\Contracts\Acceptable;
use App\Models\Setting;
use App\Models\User;
use App\Models\AssetModel;
use App\Models\Accessory;
use App\Models\License;
use App\Models\Component;
use App\Models\Consumable;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
@@ -26,12 +21,9 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
use Carbon\Carbon;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use TCPDF;
use App\Helpers\Helper;
class AcceptanceController extends Controller
@@ -83,6 +75,11 @@ class AcceptanceController extends Controller
public function store(Request $request, $id) : RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
$assigned_user = User::find($acceptance->assigned_to_id);
$settings = Setting::getSettings();
$path_logo = '';
$sig_filename='';
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -118,135 +115,62 @@ class AcceptanceController extends Controller
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
}
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
$checkout_type_shortname = strtolower(str_replace('App\Models\\', '', $acceptance->checkoutable_type));
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$checkout_type_shortname.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
$sig_filename='';
// If signatures are required, make sure we have one
if (Setting::getSettings()->require_accept_signature == '1') {
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
// Get the data array ready for the notifications and PDF generation
$data = [
'item_tag' => $item->asset_tag,
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
'item_model' => $item->model?->name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false),
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'assigned_to' => $assigned_user->display_name,
'site_name' => $settings->site_name,
'company_name' => $item->company?->name?? $settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => ($settings->acceptance_pdf_logo) ? public_path() . '/uploads/' . $settings->acceptance_pdf_logo : null,
'date_settings' => $settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
'qty' => $acceptance->qty ?? 1,
];
if ($request->input('asset_acceptance') == 'accepted') {
if (Setting::getSettings()->require_accept_signature == '1') {
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
$assigned_user = User::find($acceptance->assigned_to_id);
/**
* Gather the data for the PDF. We fire this whether there is a signature required or not,
* since we want the moment-in-time proof of what the EULA was when they accepted it.
*/
$branding_settings = SettingsController::getPDFBranding();
$path_logo = "";
// Check for the PDF logo path and use that, otherwise use the regular logo path
if (!is_null($branding_settings->acceptance_pdf_logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
} elseif (!is_null($branding_settings->logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
}
$data = [
'item_tag' => $item->asset_tag,
'item_model' => $item->model ? $item->model->name : $item->display_name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(),
'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d H:i:s'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d H:i:s'),
'assigned_to' => $assigned_user->display_name,
'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => $path_logo,
'date_settings' => $branding_settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
'qty' => $acceptance->qty ?? 1,
];
// set some language dependent data:
$lg = Array();
$lg['a_meta_charset'] = 'UTF-8';
$lg['w_page'] = 'page';
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->setRTL(false);
$pdf->setLanguageArray($lg);
$pdf->SetFontSubsetting(true);
$pdf->SetCreator('Snipe-IT');
$pdf->SetAuthor($data['assigned_to']);
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula', 'tos');
$pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->SetPrintHeader(false);
$pdf->SetPrintFooter(false);
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
$pdf->AddPage();
$pdf->writeHTML('<img src="'.$path_logo.'" height="30">', true, 0, true, 0, '');
if ($data['item_tag']) {
$pdf->writeHTML("<strong>" . trans('general.asset_tag') . '</strong>: ' . $data['item_tag'], true, 0, true, 0, '');
}
$pdf->writeHTML("<strong>".trans('general.asset_model').'</strong>: '.$data['item_model'], true, 0, true, 0, '');
if ($data['item_serial']) {
$pdf->writeHTML("<strong>".trans('admin/hardware/form.serial').'</strong>: '.$data['item_serial'], true, 0, true, 0, '');
}
$pdf->writeHTML("<strong>".trans('general.assigned_date').'</strong>: '.$data['check_out_date'], true, 0, true, 0, '');
$pdf->writeHTML("<strong>".trans('general.assignee').'</strong>: '.$data['assigned_to'], true, 0, true, 0, '');
$pdf->Ln();
// Break the EULA into lines based on newlines, and check each line for RTL or CJK characters
$eula_lines = preg_split("/\r\n|\n|\r/", $item->getEula());
foreach ($eula_lines as $eula_line) {
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
}
$pdf->Ln();
$pdf->Ln();
$pdf->setRTL(false);
$pdf->writeHTML('<br><br>', true, 0, true, 0, '');
if ($data['note'] != null) {
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML("<strong>".trans('general.notes') . '</strong>: ' . $data['note'], true, 0, true, 0, '');
$pdf->Ln();
}
if ($data['signature'] != null) {
$pdf->writeHTML('<img src="'.$data['signature'].'" style="max-width: 600px;">', true, 0, true, 0, '');
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
}
$pdf->writeHTML("<strong>".trans('general.accepted_date').'</strong>: '.$data['accepted_date'], true, 0, true, 0, '');
$pdf_content = $pdf->Output($pdf_filename, 'S');
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
// Generate the PDF content
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
// Log the acceptance
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
// Send the PDF to the signing user
@@ -269,45 +193,9 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.accepted');
// Item was not accepted
// Item was declined
} else {
if (Setting::getSettings()->require_accept_signature == '1') {
// The item was declined, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
// Format the data to send the declined notification
$branding_settings = SettingsController::getPDFBranding();
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
$data = [
'item_tag' => $item->asset_tag,
'item_model' => $item->model ? $item->model->name : $item->display_name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'note' => $request->input('note'),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format,
'qty' => $acceptance->qty ?? 1,
];
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
$acceptance->decline($sig_filename, $request->input('note'));
}
@@ -318,6 +206,8 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.declined');
}
// Send an email notification if one is requested
if ($acceptance->alert_on_response_id) {
try {
$recipient = User::find($acceptance->alert_on_response_id);
@@ -336,9 +226,10 @@ class AcceptanceController extends Controller
Log::warning($e);
}
}
return redirect()->to('account/accept')->with('success', $return_msg);
}
}
@@ -647,6 +647,30 @@ class BulkAssetsController extends Controller
$assets = Asset::findOrFail($asset_ids);
// Prevent checking out assets that are already checked out
if ($assets->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]);
return redirect(route('hardware.bulkcheckout.show'))
->with('error', trans('general.error_assets_already_checked_out'));
}
// Prevent checking out assets across companies if FMCS enabled
if (Setting::getSettings()->full_multiple_companies_support && $target->company_id) {
$company_ids = $assets->pluck('company_id')->unique();
// if there is more than one unique company id or the singular company id does not match
// then the checkout is invalid
if ($company_ids->count() > 1 || $company_ids->first() != $target->company_id) {
// re-add the asset ids so the assets select is re-populated
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect(route('hardware.bulkcheckout.show'))
->with('error', trans('general.error_user_company_multiple'));
}
}
if (request('checkout_to_type') == 'asset') {
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
@@ -36,6 +36,7 @@ class AccessoriesTransformer
'qty' => ($accessory->qty) ? (int) $accessory->qty : null,
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($accessory->totalCostSum()),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
@@ -43,6 +43,7 @@ class ComponentsTransformer
'order_number' => e($component->order_number),
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($component->totalCostSum()),
'remaining' => (int) $component->numRemaining(),
'company' => ($component->company) ? [
'id' => (int) $component->company->id,
@@ -37,6 +37,7 @@ class ConsumablesTransformer
'remaining' => $consumable->numRemaining(),
'order_number' => e($consumable->order_number),
'purchase_cost' => Helper::formatCurrencyOutput($consumable->purchase_cost),
'total_cost' => Helper::formatCurrencyOutput($consumable->totalCostSum()),
'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'),
'qty' => (int) $consumable->qty,
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
+4
View File
@@ -357,6 +357,10 @@ class Accessory extends SnipeModel
$accessory_checkout->limit(1)->delete();
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* -----------------------------------------------
+100 -3
View File
@@ -2,11 +2,14 @@
namespace App\Models;
use App\Helpers\Helper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Notifications\Notifiable;
use TCPDF;
class CheckoutAcceptance extends Model
{
@@ -129,8 +132,7 @@ class CheckoutAcceptance extends Model
/**
* Filter checkout acceptences by the user
*
* @param Illuminate\Database\Eloquent\Builder $query
* @param User $user
* @param User $user
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForUser(Builder $query, User $user)
@@ -141,7 +143,6 @@ class CheckoutAcceptance extends Model
/**
* Filter to only get pending acceptances
*
* @param Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePending(Builder $query)
@@ -153,4 +154,100 @@ class CheckoutAcceptance extends Model
{
return $query->whereNull('accepted_at')->whereNotNull('declined_at');
}
protected function displayCheckoutableType(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => strtolower(str_replace('App\Models\\', '', $this->checkoutable_type)),
);
}
public function generateAcceptancePdf($data, $pdf_filename) {
// set some language dependent data:
$lg = Array();
$lg['a_meta_charset'] = 'UTF-8';
$lg['w_page'] = 'page';
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->setRTL(false);
$pdf->setLanguageArray($lg);
$pdf->SetFontSubsetting(true);
$pdf->SetCreator('Snipe-IT Asset Management System');
$pdf->SetAuthor($data['assigned_to']);
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula, tos');
$pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->SetPrintHeader(false);
$pdf->SetPrintFooter(false);
$pdf->AddPage();
if ($data['logo'] != null) {
$pdf->writeHTML('<img src="'.$data['logo'].'">', true, 0, true, 0, '');
} else {
$pdf->writeHTML('<h3>'.$data['site_name'].'</h3><br /><br />', true, 0, true, 0, 'C');
}
$pdf->Ln();
$pdf->writeHTML(trans('general.date') . ': ' . Helper::getFormattedDateObject(now(), 'datetime', false), true, 0, true, 0, '');
if ($data['company_name'] != null) {
$pdf->writeHTML(trans('general.company') . ': ' . e($data['company_name']), true, 0, true, 0, '');
}
if ($data['item_tag'] != null) {
$pdf->writeHTML(trans('general.asset_tag') . ': ' . e($data['item_tag']), true, 0, true, 0, '');
}
if ($data['item_name'] != null) {
$pdf->writeHTML(trans('general.name') . ': ' . e($data['item_name']), true, 0, true, 0, '');
}
if ($data['item_model'] != null) {
$pdf->writeHTML(trans('general.asset_model') . ': ' . e($data['item_model']), true, 0, true, 0, '');
}
if ($data['item_serial'] != null) {
$pdf->writeHTML(trans('admin/hardware/form.serial').': '.e($data['item_serial']), true, 0, true, 0, '');
}
if (($data['qty'] != null) && ($data['qty'] > 1)) {
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
}
$pdf->writeHTML(trans('general.assignee').': '.e($data['assigned_to']), true, 0, true, 0, '');
$pdf->Ln();
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
// Break the EULA into lines based on newlines, and check each line for RTL or CJK characters
$eula_lines = preg_split("/\r\n|\n|\r/", $data['eula']);
foreach ($eula_lines as $eula_line) {
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
}
$pdf->Ln();
$pdf->Ln();
$pdf->setRTL(false);
$pdf->Ln();
if ($data['signature'] != null) {
$pdf->writeHTML('<img src="'.$data['signature'].'">', true, 0, true, 0, '');
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
$pdf->writeHTML(e($data['assigned_to']), true, 0, true, 0, 'C');
$pdf->Ln();
}
if ($data['note'] != null) {
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(trans('general.notes') . ': ' . e($data['note']), true, 0, true, 0, '');
$pdf->Ln();
}
$pdf->writeHTML(trans('general.assigned_date').': '.e($data['check_out_date']), true, 0, true, 0, '');
$pdf->writeHTML(trans('general.accepted_date').': '.e($data['accepted_date']), true, 0, true, 0, '');
return $pdf->Output($pdf_filename, 'S');
}
}
+3
View File
@@ -300,7 +300,10 @@ class Component extends SnipeModel
return $this->qty - $this->numCheckedOut();
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS
+3
View File
@@ -312,7 +312,10 @@ class Consumable extends SnipeModel
return $remaining;
}
public function totalCostSum() {
return $this->purchase_cost !== null ? $this->qty * $this->purchase_cost : null;
}
/**
* Get the list of checkouts for this consumable
*
+3 -3
View File
@@ -729,7 +729,7 @@ class License extends Depreciable
public static function getExpiringLicenses($days = 60)
{
return self::whereNull('deleted_at')
return self::whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) use ($days) {
@@ -757,7 +757,7 @@ class License extends Depreciable
public function scopeActiveLicenses($query)
{
return $query->whereNull('deleted_at')
return $query->whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) {
@@ -773,7 +773,7 @@ class License extends Depreciable
public function scopeExpiredLicenses($query)
{
return $query->whereNull('deleted_at')
return $query->whereNull('licenses.deleted_at')
// The termination date is null or within range
->where(function ($query) {
+16
View File
@@ -7,6 +7,7 @@ use App\Models\Traits\CompanyableChildTrait;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -64,6 +65,21 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
return $this->license->getEula();
}
protected function name(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $this->license->name,
);
}
protected function displayName(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $this->license->name,
);
}
/**
* Establishes the seat -> license relationship
*
@@ -2,6 +2,7 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Helpers\Helper;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
@@ -10,7 +11,7 @@ use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class AcceptanceAssetAcceptedNotification extends Notification
#[AllowDynamicProperties] class AcceptanceAssetAcceptedNotification extends Notification
{
use Queueable;
@@ -22,16 +23,18 @@ class AcceptanceAssetAcceptedNotification extends Notification
public function __construct($params)
{
$this->item_tag = $params['item_tag'];
$this->item_name = $params['item_name'];
$this->item_model = $params['item_model'];
$this->item_serial = $params['item_serial'];
$this->item_status = $params['item_status'];
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'datetime', false);
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
$this->company_name = $params['company_name'];
$this->admin = $params['admin'] ?? null;
$this->settings = Setting::getSettings();
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->note = $params['note'] ?? null;
$this->admin = $params['admin'] ?? null;
}
@@ -66,6 +69,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
[
'item_tag' => $this->item_tag,
'item_name' => $this->item_name,
'item_model' => $this->item_model,
'item_serial' => $this->item_serial,
'item_status' => $this->item_status,
@@ -73,9 +77,9 @@ class AcceptanceAssetAcceptedNotification extends Notification
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans('mail.acceptance_asset_accepted'),
'admin' => $this->admin,
])
->subject(trans('mail.acceptance_asset_accepted'));
@@ -2,13 +2,14 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Helpers\Helper;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AcceptanceAssetAcceptedToUserNotification extends Notification
#[AllowDynamicProperties] class AcceptanceAssetAcceptedToUserNotification extends Notification
{
use Queueable;
@@ -20,16 +21,18 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
public function __construct($params)
{
$this->item_tag = $params['item_tag'];
$this->item_name = $params['item_name'];
$this->item_model = $params['item_model'];
$this->item_serial = $params['item_serial'];
$this->item_status = $params['item_status'];
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false);
$this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'datetime', false);
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
$this->note = $params['note'] ?? null;
$this->company_name = $params['company_name'];
$this->settings = Setting::getSettings();
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->admin = $params['admin'] ?? null;
}
/**
@@ -59,6 +62,7 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
$message = (new MailMessage)->markdown('notifications.markdown.asset-acceptance',
[
'item_tag' => $this->item_tag,
'item_name' => $this->item_name,
'item_model' => $this->item_model,
'item_serial' => $this->item_serial,
'item_status' => $this->item_status,
@@ -66,11 +70,12 @@ class AcceptanceAssetAcceptedToUserNotification extends Notification
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->company_name ?? $this->settings->site_name]),
'intro_text' => trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_name' => $this->settings->site_name]),
])
->attach($pdf_path)
->subject(trans('mail.acceptance_asset_accepted_to_user', ['site_name' => $this->settings->site_name]));
->subject(trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_name' => $this->settings->site_name]));
return $message;
}
+6
View File
@@ -121,6 +121,12 @@ class AccessoryPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('general.unit_cost'),
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
+26 -20
View File
@@ -79,6 +79,25 @@ class ComponentPresenter extends Presenter
'title' => trans('general.manufacturer'),
'visible' => false,
'formatter' => 'manufacturersLinkObjFormatter',
], [
'field' => 'location',
'searchable' => true,
'sortable' => true,
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'order_number',
'searchable' => true,
'sortable' => true,
'title' => trans('general.order_number'),
'visible' => true,
], [
'field' => 'purchase_date',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
@@ -103,33 +122,20 @@ class ComponentPresenter extends Presenter
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'location',
'searchable' => true,
'sortable' => true,
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'order_number',
'searchable' => true,
'sortable' => true,
'title' => trans('general.order_number'),
'visible' => true,
], [
'field' => 'purchase_date',
'searchable' => true,
'sortable' => true,
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'purchase_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.unit_cost'),
'visible' => true,
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'notes',
'searchable' => true,
+36 -29
View File
@@ -67,35 +67,6 @@ class ConsumablePresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('general.model_no'),
], [
'field' => 'item_no',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/consumables/general.item_no'),
], [
'field' => 'qty',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.total'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'remaining',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.remaining'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
'visible' => true,
'formatter' => 'minAmtFormatter',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'location',
'searchable' => true,
@@ -103,6 +74,12 @@ class ConsumablePresenter extends Presenter
'title' => trans('general.location'),
'formatter' => 'locationsLinkObjFormatter',
], [
'field' => 'item_no',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/consumables/general.item_no'),
], [
'field' => 'manufacturer',
'searchable' => true,
'sortable' => true,
@@ -122,12 +99,42 @@ class ConsumablePresenter extends Presenter
'title' => trans('general.purchase_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
'visible' => true,
'formatter' => 'minAmtFormatter',
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'qty',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.total'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'remaining',
'searchable' => false,
'sortable' => true,
'title' => trans('admin/components/general.remaining'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
'footerFormatter' => 'qtySumFormatter',
], [
'field' => 'purchase_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.unit_cost'),
'visible' => true,
'class' => 'text-right text-padding-number-cell',
], [
'field' => 'total_cost',
'searchable' => true,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',
'class' => 'text-right text-padding-number-cell',
], [
+5 -5
View File
@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v8.3.1',
'full_app_version' => 'v8.3.1 - build 19577-g7dd493da3',
'build_version' => '19577',
'app_version' => 'v8.3.2',
'full_app_version' => 'v8.3.2 - build 19905-g028b4e7b7',
'build_version' => '19905',
'prerelease_version' => '',
'hash_version' => 'g7dd493da3',
'full_hash' => 'v8.3.1-15-g7dd493da3',
'hash_version' => 'g028b4e7b7',
'full_hash' => 'v8.3.2-319-g028b4e7b7',
'branch' => 'develop',
);
@@ -23,23 +23,39 @@ class CheckoutAcceptanceFactory extends Factory
'assigned_to_id' => User::factory(),
];
}
protected static bool $skipActionLog = false;
public function withoutActionLog(): static
{
// turn off for this create() call
static::$skipActionLog = true;
// ensure it turns back on AFTER creating
return $this->afterCreating(function () {
static::$skipActionLog = false;
});
}
public function configure(): static
{
return $this->afterCreating(function (CheckoutAcceptance $acceptance) {
if (static::$skipActionLog) {
return; // short-circuit
}
if ($acceptance->checkoutable instanceof Asset) {
$this->createdAssociatedActionLogEntry($acceptance);
}
if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) {
$acceptance->checkoutable->update([
'assigned_to' => $acceptance->assigned_to_id,
'assigned_type' => get_class($acceptance->assignedTo),
'assigned_to' => $acceptance->assigned_to_id,
'assigned_type'=> get_class($acceptance->assignedTo),
]);
}
});
}
public function forAccessory()
{
return $this->state([
+3
View File
@@ -1501,6 +1501,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=app.css.map*/
File diff suppressed because one or more lines are too long
+3
View File
@@ -1125,6 +1125,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=overrides.css.map*/
File diff suppressed because one or more lines are too long
+6
View File
@@ -22837,6 +22837,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=app.css.map*/
@@ -24449,6 +24452,9 @@ caption.tableCaption {
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
/*# sourceMappingURL=overrides.css.map*/
+3 -3
View File
@@ -2,8 +2,8 @@
"/js/dist/all.js": "/js/dist/all.js?id=76d88f0f91b852f7eecbce357ab5858b",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=42f97cd5b9ee7521b04a448e7fc16ac9",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=d81a7ed323f68a7c5e3e9115f7fb5404",
"/css/build/overrides.css": "/css/build/overrides.css?id=d8bef2b8ef03ee8dbb120749211eafc0",
"/css/build/app.css": "/css/build/app.css?id=1bf6a5e78cbccff6e6d32640c28c54b8",
"/css/build/overrides.css": "/css/build/overrides.css?id=d6ec1f1e36c57f8cd96218d2a59a2580",
"/css/build/app.css": "/css/build/app.css?id=d591faf82795dc8151a6d3f84c26f5a4",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=ee0ed88465dd878588ed044eefb67723",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=3d8a3d2035ea28aaad4a703c2646f515",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=3979929a3423ff35b96b1fc84299fdf3",
@@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=b2cd9f59d7e8587939ce27b2d3363d82",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=7277edd636cf46aa7786a4449ce0ead7",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=cbd06cc1d58197ccc81d4376bbaf0d28",
"/css/dist/all.css": "/css/dist/all.css?id=dd5f7ab27ec80569b90d63a883718ff9",
"/css/dist/all.css": "/css/dist/all.css?id=ff957e6cef08b72d32cf28fe50da645a",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
+4
View File
@@ -1260,4 +1260,8 @@ caption.tableCaption {
clip: rect(0,0,0,0);
white-space: preserve;
display: inline-block;
}
input[name="columnsSearch"] {
width: 120px;
}
+3
View File
@@ -309,6 +309,7 @@ return [
'total_licenses' => 'total licenses',
'total_accessories' => 'total accessories',
'total_consumables' => 'total consumables',
'total_cost' => 'Total Cost',
'type' => 'Type',
'undeployable' => 'Un-deployable',
'unknown_admin' => 'Unknown Admin',
@@ -518,7 +519,9 @@ return [
'item_notes' => ':item Notes',
'item_name_var' => ':item Name',
'error_user_company' => 'Checkout target company and asset company do not match',
'error_user_company_multiple' => 'One or more of the checkout target company and asset company do not match',
'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager',
'error_assets_already_checked_out' => 'One or more of the assets are already checked out',
'importer' => [
'checked_out_to_fullname' => 'Checked Out to: Full Name',
'checked_out_to_first_name' => 'Checked Out to: First Name',
+1 -1
View File
@@ -31,7 +31,7 @@ return [
'Low_Inventory_Report' => 'Low Inventory Report',
'a_user_canceled' => 'A user has canceled an item request on the website',
'a_user_requested' => 'A user has requested an item on the website',
'acceptance_asset_accepted_to_user' => 'You have accepted an item assigned to you by :site_name',
'acceptance_asset_accepted_to_user' => 'You have accepted an item assigned to you by :site_name|You have accepted :qty items assigned to you by :site_name',
'acceptance_asset_accepted' => 'A user has accepted an item',
'acceptance_asset_declined' => 'A user has declined an item',
'send_pdf_copy' => 'Send a copy of this acceptance to my email address',
@@ -244,6 +244,18 @@
</div>
</div>
@endif
@if ($accessory->purchase_cost)
<div class="row">
<div class="col-md-3" style="padding-bottom: 10px;">
<strong>
{{ trans('general.total_cost') }}
</strong>
</div>
<div class="col-md-9" style="word-wrap: break-word;">
{{ Helper::formatCurrencyOutput($accessory->totalCostSum()) }}
</div>
</div>
@endif
<div class="row">
<div class="col-md-3" style="padding-bottom: 10px;">
+33 -16
View File
@@ -20,11 +20,21 @@
}
.m-signature-pad--body {
border-style: solid;
border-style: dashed;
border-color: grey;
border-width: thin;
border-width: thick;
padding-top: 0px;
}
.m-signature-pad {
box-shadow: none;
background-color: inherit;
border: none;
}
</style>
@@ -90,30 +100,37 @@
<canvas style="width:100%;"></canvas>
<input type="hidden" name="signature_output" id="signature_output">
</div>
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-center">
<div class="col-md-12 col-sm-12 col-lg-12 col-xs-12 text-left">
<button type="button" class="btn btn-sm btn-default clear" data-action="clear" id="clear_button">{{trans('general.clear_signature')}}</button>
</div>
</div>
</div>
@endif
@if (auth()->user()->email!='')
<div class="col-md-12" style="padding-top: 20px; display: none;" id="showEmailBox">
<label class="form-control">
<input type="checkbox" value="1" name="send_copy" id="send_copy" checked="checked" aria-label="send_copy">
{{ trans('mail.send_pdf_copy') }} ({{ auth()->user()->email }})
</label>
</div>
@endif
</div> <!-- / box-body -->
<div class="box-footer text-right" style="display: none;" id="showSubmit">
<button type="submit" class="btn btn-success" id="submit-button">
<i class="fa fa-check icon-white" aria-hidden="true" id="submitIcon"></i>
<span id="buttonText">
<div class="box-footer" style="display: none;" id="showSubmit">
<div class="row">
<div class="col-md-7">
@if (auth()->user()->email!='')
<div class="col-md-12" style="display: none;" id="showEmailBox">
<label class="form-control">
<input type="checkbox" value="1" name="send_copy" id="send_copy" checked="checked" aria-label="send_copy">
{{ trans('mail.send_pdf_copy') }} ({{ auth()->user()->email }})
</label>
</div>
@endif
</div>
<div class="col-md-5 text-right">
<button type="submit" class="btn btn-success" id="submit-button">
<i class="fa fa-check icon-white" aria-hidden="true" id="submitIcon"></i>
<span id="buttonText">
{{ trans_choice('general.i_accept_item', $acceptance->qty ?? null) }}
</span>
</button>
</button>
</div>
</div>
</div><!-- /.box-footer -->
</div> <!-- / box-default -->
</div> <!-- / col -->
@@ -61,6 +61,7 @@
@if ($category->category_type=='asset')
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="categoryAssetsTable"
id="categoryAssetsTable"
data-buttons="assetButtons"
+1
View File
@@ -94,6 +94,7 @@
data-cookie-id-table="assetsListingTable"
data-id-table="assetsListingTable"
data-side-pagination="server"
data-show-columns-search="true"
data-sort-order="asc"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
@@ -207,6 +207,13 @@
{{ Helper::formatCurrencyOutput($component->purchase_cost) }} </div>
@endif
@if ($component->purchase_cost)
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('general.total_cost') }}:</strong>
{{ $snipeSettings->default_currency }}
{{ Helper::formatCurrencyOutput($component->totalCostSum()) }} </div>
@endif
@if ($component->order_number)
<div class="col-md-12" style="padding-bottom: 5px;"><strong>{{ trans('general.order_number') }}:</strong>
{{ $component->order_number }} </div>
@@ -281,6 +281,18 @@
</div>
@endif
@if ($consumable->purchase_cost)
<div class="row">
<div class="col-md-3">
{{ trans('general.total_cost') }}
</div>
<div class="col-md-9">
{{ $snipeSettings->default_currency }}
{{ Helper::formatCurrencyOutput($consumable->totalCostSum()) }}
</div>
</div>
@endif
@if ($consumable->order_number)
<div class="row">
<div class="col-md-3">
@@ -65,6 +65,7 @@
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="depreciationsAssetTable"
data-id-table="depreciationsAssetTable"
id="depreciationsAssetTable"
@@ -49,6 +49,7 @@
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="dueAssetcheckinListing"
data-id-table="dueAssetcheckinListing"
data-side-pagination="server"
+1 -1
View File
@@ -94,7 +94,7 @@
</div>
@include ('partials.forms.checkout-selector', ['user_select' => 'true','asset_select' => 'true', 'location_select' => 'true'])
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.user'), 'fieldname' => 'assigned_user', 'style' => session('checkout_to_type') == 'user' ? '' : 'display: none;'])
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.user'), 'fieldname' => 'assigned_user', 'style' => (session('checkout_to_type') ?: 'user') == 'user' ? '' : 'display: none;'])
<!-- We have to pass unselect here so that we don't default to the asset that's being checked out. We want that asset to be pre-selected everywhere else. -->
@include ('partials.forms.edit.asset-select', ['translated_name' => trans('general.select_asset'), 'fieldname' => 'assigned_asset', 'company_id' => $asset->company_id, 'unselect' => 'true', 'style' => session('checkout_to_type') == 'asset' ? '' : 'display: none;'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'assigned_location', 'style' => session('checkout_to_type') == 'location' ? '' : 'display: none;'])
+3
View File
@@ -47,6 +47,8 @@
{{-- Page content --}}
@section('content')
<div class="row">
<div class="col-md-12">
<div class="box">
@@ -66,6 +68,7 @@
data-show-footer="true"
data-sort-order="asc"
data-sort-name="name"
data-show-columns-search="true"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
data-bulk-form-id="#assetsBulkForm"
+1
View File
@@ -1285,6 +1285,7 @@
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="assetsTable"
data-id-table="assetsTable"
data-side-pagination="server"
+3
View File
@@ -210,6 +210,7 @@
@include('partials.asset-bulk-actions')
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="assetsListingTable"
data-id-table="assetsListingTable"
data-side-pagination="server"
@@ -237,6 +238,7 @@
<table
role="table"
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="assetsAssignedListingTable"
data-id-table="assetsAssignedListingTable"
data-side-pagination="server"
@@ -262,6 +264,7 @@
<table
role="table"
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="RTDassetsListingTable"
data-id-table="RTDassetsListingTable"
data-side-pagination="server"
@@ -104,6 +104,7 @@
<div class="table table-responsive">
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="assetsListingTable"
data-id-table="assetsListingTable"
data-toolbar="#assetsBulkEditToolbar"
+2
View File
@@ -87,9 +87,11 @@
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="assetListingTable"
data-id-table="assetListingTable"
data-side-pagination="server"
data-show-footer="true"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
data-bulk-form-id="#assetsBulkForm"
@@ -6,6 +6,9 @@
@component('mail::table')
| | |
| ------------- | ------------- |
@if (isset($item_name))
| **{{ trans('general.name') }}** | {{ $item_name }} |
@endif
| **{{ trans('mail.user') }}** | {{ $assigned_to }} |
@if (isset($user->location))
| **{{ trans('general.location') }}** | {{ $user->location->name }} |
@@ -4,22 +4,25 @@
<div class="btn-group" data-toggle="buttons">
@if ((isset($user_select)) && ($user_select!='false'))
<label class="btn btn-default{{ session('checkout_to_type') == 'user' ? ' active' : '' }}">
<input name="checkout_to_type" value="user" aria-label="checkout_to_type" type="radio" checked="checked">
<label class="btn btn-default{{ (session('checkout_to_type') ?: 'user') == 'user' ? ' active' : '' }}">
<input name="checkout_to_type" value="user" aria-label="checkout_to_type"
type="radio" {{ (session('checkout_to_type') ?: 'user') == 'user' ? 'checked' : '' }}>
<x-icon type="user" />
{{ trans('general.user') }}
</label>
@endif
@if ((isset($asset_select)) && ($asset_select!='false'))
<label class="btn btn-default{{ session('checkout_to_type') == 'asset' ? ' active' : '' }}">
<input name="checkout_to_type" value="asset" aria-label="checkout_to_type" type="radio">
<label class="btn btn-default{{ session('checkout_to_type') == 'asset' ? ' active' : '' }}">
<input name="checkout_to_type" value="asset" aria-label="checkout_to_type"
type="radio" {{ session('checkout_to_type') == 'asset' ? 'checked': '' }}>
<i class="fas fa-barcode" aria-hidden="true"></i>
{{ trans('general.asset') }}
</label>
@endif
@if ((isset($location_select)) && ($location_select!='false'))
<label class="btn btn-default{{ session('checkout_to_type') == 'location' ? ' active' : '' }}">
<input name="checkout_to_type" value="location" aria-label="checkout_to_type" class="active" type="radio">
<label class="btn btn-default{{ session('checkout_to_type') == 'location' ? ' active' : '' }}">
<input name="checkout_to_type" value="location" aria-label="checkout_to_type"
type="radio" {{ session('checkout_to_type') == 'location' ? 'checked' : '' }}>
<i class="fas fa-map-marker-alt" aria-hidden="true"></i>
{{ trans('general.location') }}
</label>
+1 -1
View File
@@ -150,7 +150,7 @@
<div class="box-body">
<p>
{!! trans('admin/settings/general.backups_path', ['path'=> 'storage/app/backup']) !!}
{!! trans('admin/settings/general.backups_path', ['path'=> 'storage/app/backups']) !!}
</p>
@if (config('app.lock_passwords')===true)
@@ -24,6 +24,7 @@
data-bulk-button-id="#bulkAssetEditButton"
data-bulk-form-id="#assetsBulkForm"
id="assetsListingTable"
data-show-columns-search="true"
data-buttons="assetButtons"
class="table table-striped snipe-table"
data-url="{{route('api.assets.index', ['status_id' => $statuslabel->id]) }}"
+1
View File
@@ -114,6 +114,7 @@
<table
data-cookie-id-table="suppliersAssetsTable"
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-id-table="suppliersAssetsTable"
data-show-footer="true"
data-side-pagination="server"
+1
View File
@@ -836,6 +836,7 @@
<table
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-show-columns-search="true"
data-cookie-id-table="userAssetsListingTable"
data-id-table="userAssetsListingTable"
data-side-pagination="server"
+1 -3
View File
@@ -583,9 +583,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
// the model name to be the parameter - and i think it's a good differentiation in the code while we convert the others.
Route::patch('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.update');
Route::put('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.put-update');
Route::put('/hardware/{asset}', [Api\AssetsController::class, 'update'])->name('api.assets.put-update');
Route::resource('hardware',
Api\AssetsController::class,
['names' => [
+1 -1
View File
@@ -725,7 +725,7 @@ Route::group(['middleware' => 'web'], function () {
'destroy'
]
)->name('ui.files.destroy')
->where(['object_type' => 'assets|hardware|models|users|locations|accessories|consumables|licenses|components']);
->where(['object_type' => 'assets|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']);
});
@@ -4,8 +4,11 @@ namespace Tests\Feature\Checkouts\Ui;
use App\Mail\CheckoutAssetMail;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\ExpectationFailedException;
use Tests\TestCase;
@@ -89,4 +92,99 @@ class BulkAssetCheckoutTest extends TestCase
$this->fail('Asset checkout email was sent when the entire checkout failed.');
}
}
public static function checkoutTargets()
{
yield 'Checkout to user' => [
function () {
return [
'type' => 'user',
'target' => User::factory()->forCompany()->create(),
];
}
];
yield 'Checkout to asset' => [
function () {
return [
'type' => 'asset',
'target' => Asset::factory()->forCompany()->create(),
];
}
];
yield 'Checkout to location' => [
function () {
return [
'type' => 'location',
'target' => Location::factory()->forCompany()->create(),
];
}
];
}
#[DataProvider('checkoutTargets')]
public function test_adheres_to_full_multiple_company_support($data)
{
['type' => $type, 'target' => $target] = $data();
$this->settings->enableMultipleFullCompanySupport();
// create two companies
[$companyA, $companyB] = Company::factory()->count(2)->create();
// create an asset for each company
$assetForCompanyA = Asset::factory()->for($companyA)->create();
$assetForCompanyB = Asset::factory()->for($companyB)->create();
$this->assertNull($assetForCompanyA->assigned_to, 'Asset should not be assigned before attempting this test case.');
$this->assertNull($assetForCompanyB->assigned_to, 'Asset should not be assigned before attempting this test case.');
// attempt to bulk checkout both items to the target
$response = $this->actingAs(User::factory()->superuser()->create())
->post(route('hardware.bulkcheckout.store'), [
'selected_assets' => [
$assetForCompanyA->id,
$assetForCompanyB->id,
],
'checkout_to_type' => $type,
"assigned_$type" => $target->id,
]);
// ensure bulk checkout is blocked
$this->assertNull($assetForCompanyA->fresh()->assigned_to, 'Asset was checked out across companies.');
$this->assertNull($assetForCompanyB->fresh()->assigned_to, 'Asset was checked out across companies.');
// ensure redirected back
$response->assertRedirectToRoute('hardware.bulkcheckout.show');
}
#[DataProvider('checkoutTargets')]
public function test_prevents_checkouts_of_checked_out_items($data)
{
['type' => $type, 'target' => $target] = $data();
$asset = Asset::factory()->create();
$checkedOutAsset = Asset::factory()->assignedToUser()->create();
$existingUserId = $checkedOutAsset->assigned_to;
$response = $this->actingAs(User::factory()->superuser()->create())
->post(route('hardware.bulkcheckout.store'), [
'selected_assets' => [
$asset->id,
$checkedOutAsset->id,
],
'checkout_to_type' => $type,
"assigned_$type" => $target->id,
]);
$this->assertEquals(
$existingUserId,
$checkedOutAsset->fresh()->assigned_to,
'Asset was checked out when it should have been prevented.'
);
// ensure redirected back
$response->assertRedirectToRoute('hardware.bulkcheckout.show');
}
}