Merge branch 'fix-factory-auto-gen-action-logs' into add_types_to_unaccepted_asset_report
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
], [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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([
|
||||
|
||||
@@ -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
@@ -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
Vendored
+6
@@ -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*/
|
||||
@@ -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",
|
||||
|
||||
@@ -1260,4 +1260,8 @@ caption.tableCaption {
|
||||
clip: rect(0,0,0,0);
|
||||
white-space: preserve;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[name="columnsSearch"] {
|
||||
width: 120px;
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;'])
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]) }}"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user