2021-06-10 13:15:52 -07:00
|
|
|
<?php
|
|
|
|
|
2018-07-27 16:15:32 -07:00
|
|
|
namespace App\Http\Controllers\Account;
|
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
use App\Events\CheckoutAccepted;
|
|
|
|
use App\Events\CheckoutDeclined;
|
2018-07-27 16:15:32 -07:00
|
|
|
use App\Events\ItemAccepted;
|
|
|
|
use App\Events\ItemDeclined;
|
|
|
|
use App\Http\Controllers\Controller;
|
2022-03-10 12:15:50 -08:00
|
|
|
use App\Models\Actionlog;
|
|
|
|
use App\Models\Asset;
|
2018-07-28 03:45:33 -07:00
|
|
|
use App\Models\CheckoutAcceptance;
|
2018-07-27 16:15:32 -07:00
|
|
|
use App\Models\Company;
|
|
|
|
use App\Models\Contracts\Acceptable;
|
2022-07-04 05:29:01 -07:00
|
|
|
use App\Models\Setting;
|
2022-03-10 12:15:50 -08:00
|
|
|
use App\Models\User;
|
2022-03-21 09:18:29 -07:00
|
|
|
use App\Models\AssetModel;
|
|
|
|
use App\Models\Accessory;
|
2022-06-23 11:33:36 -07:00
|
|
|
use App\Models\License;
|
2022-06-23 11:52:35 -07:00
|
|
|
use App\Models\Component;
|
|
|
|
use App\Models\Consumable;
|
2022-08-11 12:01:11 -07:00
|
|
|
use App\Notifications\AcceptanceAssetAcceptedNotification;
|
2022-08-11 14:00:21 -07:00
|
|
|
use App\Notifications\AcceptanceAssetDeclinedNotification;
|
2018-07-27 16:15:32 -07:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
2018-09-29 21:33:52 -07:00
|
|
|
use Illuminate\Support\Facades\Storage;
|
2019-03-13 20:12:03 -07:00
|
|
|
use Illuminate\Support\Str;
|
2022-03-16 15:53:32 -07:00
|
|
|
use App\Http\Controllers\SettingsController;
|
2022-03-08 16:42:27 -08:00
|
|
|
use Barryvdh\DomPDF\Facade\Pdf;
|
2022-03-21 09:18:29 -07:00
|
|
|
use Carbon\Carbon;
|
2024-07-04 16:54:50 -07:00
|
|
|
use \Illuminate\Contracts\View\View;
|
|
|
|
use \Illuminate\Http\RedirectResponse;
|
2024-05-29 04:38:15 -07:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2021-06-10 13:15:52 -07:00
|
|
|
class AcceptanceController extends Controller
|
|
|
|
{
|
2018-07-28 04:32:29 -07:00
|
|
|
/**
|
|
|
|
* Show a listing of pending checkout acceptances for the current user
|
|
|
|
*/
|
2024-07-04 16:54:50 -07:00
|
|
|
public function index() : View
|
2021-06-10 13:15:52 -07:00
|
|
|
{
|
2024-07-04 12:48:35 -07:00
|
|
|
$acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get();
|
2018-07-28 03:45:33 -07:00
|
|
|
return view('account/accept.index', compact('acceptances'));
|
2018-07-27 16:15:32 -07:00
|
|
|
}
|
2018-07-28 04:32:29 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows a form to either accept or decline the checkout acceptance
|
2021-06-10 13:15:52 -07:00
|
|
|
*
|
2018-07-28 04:32:29 -07:00
|
|
|
* @param int $id
|
|
|
|
*/
|
2024-07-04 16:54:50 -07:00
|
|
|
public function create($id) : View | RedirectResponse
|
2021-06-10 13:15:52 -07:00
|
|
|
{
|
2018-07-28 03:45:33 -07:00
|
|
|
$acceptance = CheckoutAcceptance::find($id);
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2022-03-08 16:42:27 -08:00
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
if (is_null($acceptance)) {
|
2018-09-29 21:33:52 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
2018-07-27 16:15:32 -07:00
|
|
|
}
|
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
if (! $acceptance->isPending()) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
2021-06-10 13:15:52 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2024-07-04 12:48:35 -07:00
|
|
|
if (! $acceptance->isCheckedOutTo(auth()->user())) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
|
|
|
}
|
|
|
|
|
2021-06-10 13:15:52 -07:00
|
|
|
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
2023-08-02 18:22:35 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('general.error_user_company'));
|
2021-06-10 13:15:52 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
return view('account/accept.create', compact('acceptance'));
|
2021-06-10 13:15:52 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2018-07-28 04:32:29 -07:00
|
|
|
/**
|
|
|
|
* Stores the accept/decline of the checkout acceptance
|
2021-06-10 13:15:52 -07:00
|
|
|
*
|
2018-07-28 04:32:29 -07:00
|
|
|
* @param Request $request
|
|
|
|
* @param int $id
|
|
|
|
*/
|
2024-07-04 16:54:50 -07:00
|
|
|
public function store(Request $request, $id) : RedirectResponse
|
2021-06-10 13:15:52 -07:00
|
|
|
{
|
2018-07-28 03:45:33 -07:00
|
|
|
$acceptance = CheckoutAcceptance::find($id);
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
if (is_null($acceptance)) {
|
2018-09-29 21:33:52 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
2018-07-27 16:15:32 -07:00
|
|
|
}
|
|
|
|
|
2018-07-28 03:45:33 -07:00
|
|
|
if (! $acceptance->isPending()) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
|
2021-06-10 13:15:52 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2024-07-04 12:48:35 -07:00
|
|
|
if (! $acceptance->isCheckedOutTo(auth()->user())) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
|
|
|
}
|
|
|
|
|
2021-06-10 13:15:52 -07:00
|
|
|
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
2021-06-10 13:15:52 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2021-06-10 13:15:52 -07:00
|
|
|
if (! $request->filled('asset_acceptance')) {
|
2018-07-27 16:15:32 -07:00
|
|
|
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the signature and save it
|
|
|
|
*/
|
2021-06-10 13:15:52 -07:00
|
|
|
if (! Storage::exists('private_uploads/signatures')) {
|
|
|
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
|
|
}
|
2022-03-08 16:42:27 -08:00
|
|
|
|
2022-07-01 11:21:02 -07:00
|
|
|
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2022-05-17 07:08:37 -07:00
|
|
|
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
|
|
|
|
$display_model = '';
|
|
|
|
$pdf_view_route = '';
|
|
|
|
$pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
2022-05-19 17:53:30 -07:00
|
|
|
$sig_filename='';
|
|
|
|
|
2018-07-27 16:15:32 -07:00
|
|
|
if ($request->input('asset_acceptance') == 'accepted') {
|
2022-03-08 16:42:27 -08:00
|
|
|
|
2022-07-01 11:21:02 -07:00
|
|
|
/**
|
|
|
|
* Check for the eula-pdfs directory
|
|
|
|
*/
|
|
|
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
|
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
|
|
}
|
2022-06-30 21:01:58 -07:00
|
|
|
|
2022-07-01 11:21:02 -07:00
|
|
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
|
|
|
|
|
|
|
// Check if the signature directory exists, if not create it
|
|
|
|
if (!Storage::exists('private_uploads/signatures')) {
|
|
|
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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'));
|
|
|
|
}
|
2022-05-17 07:08:37 -07:00
|
|
|
}
|
2018-07-27 16:15:32 -07:00
|
|
|
|
2022-05-17 07:08:37 -07:00
|
|
|
// this is horrible
|
2022-06-23 11:15:15 -07:00
|
|
|
switch($acceptance->checkoutable_type){
|
|
|
|
case 'App\Models\Asset':
|
|
|
|
$pdf_view_route ='account.accept.accept-asset-eula';
|
|
|
|
$asset_model = AssetModel::find($item->model_id);
|
2023-05-01 15:50:18 -07:00
|
|
|
if (!$asset_model) {
|
|
|
|
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
|
|
|
}
|
2022-06-23 11:15:15 -07:00
|
|
|
$display_model = $asset_model->name;
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
2022-06-23 11:52:35 -07:00
|
|
|
|
2022-06-23 11:15:15 -07:00
|
|
|
case 'App\Models\Accessory':
|
|
|
|
$pdf_view_route ='account.accept.accept-accessory-eula';
|
|
|
|
$accessory = Accessory::find($item->id);
|
|
|
|
$display_model = $accessory->name;
|
2023-05-03 14:22:05 -07:00
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
2022-06-23 11:15:15 -07:00
|
|
|
break;
|
2022-06-23 11:52:35 -07:00
|
|
|
|
2022-06-23 11:15:15 -07:00
|
|
|
case 'App\Models\LicenseSeat':
|
|
|
|
$pdf_view_route ='account.accept.accept-license-eula';
|
2022-06-23 11:33:36 -07:00
|
|
|
$license = License::find($item->license_id);
|
2022-06-23 11:15:15 -07:00
|
|
|
$display_model = $license->name;
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
2022-06-23 11:52:35 -07:00
|
|
|
|
|
|
|
case 'App\Models\Component':
|
|
|
|
$pdf_view_route ='account.accept.accept-component-eula';
|
|
|
|
$component = Component::find($item->id);
|
|
|
|
$display_model = $component->name;
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'App\Models\Consumable':
|
|
|
|
$pdf_view_route ='account.accept.accept-consumable-eula';
|
|
|
|
$consumable = Consumable::find($item->id);
|
|
|
|
$display_model = $consumable->name;
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
2022-05-17 07:08:37 -07:00
|
|
|
}
|
2022-06-23 11:15:15 -07:00
|
|
|
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
|
|
|
|
// $pdf_view_route ='account.accept.accept-asset-eula';
|
|
|
|
// $asset_model = AssetModel::find($item->model_id);
|
|
|
|
// $display_model = $asset_model->name;
|
|
|
|
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
|
|
|
|
//
|
|
|
|
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
|
|
|
|
// $pdf_view_route ='account.accept.accept-accessory-eula';
|
|
|
|
// $accessory = Accessory::find($item->id);
|
|
|
|
// $display_model = $accessory->name;
|
|
|
|
// $assigned_to = User::find($item->assignedTo);
|
|
|
|
//
|
|
|
|
// }
|
2022-03-21 09:18:29 -07:00
|
|
|
|
2022-05-17 07:08:37 -07:00
|
|
|
/**
|
2022-05-19 11:28:14 -07:00
|
|
|
* 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.
|
2022-05-17 07:08:37 -07:00
|
|
|
*/
|
2022-03-21 09:18:29 -07:00
|
|
|
$branding_settings = SettingsController::getPDFBranding();
|
2022-05-31 13:59:01 -07:00
|
|
|
|
|
|
|
if (is_null($branding_settings->logo)){
|
|
|
|
$path_logo = "";
|
|
|
|
} else {
|
|
|
|
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
|
|
|
|
}
|
|
|
|
|
2022-03-21 09:18:29 -07:00
|
|
|
$data = [
|
|
|
|
'item_tag' => $item->asset_tag,
|
2022-05-17 07:08:37 -07:00
|
|
|
'item_model' => $display_model,
|
2022-03-21 09:18:29 -07:00
|
|
|
'item_serial' => $item->serial,
|
2024-07-24 11:24:30 -07:00
|
|
|
'item_status' => $item->assetstatus->name ?: '',
|
2022-03-21 09:18:29 -07:00
|
|
|
'eula' => $item->getEula(),
|
2024-04-03 11:59:47 -07:00
|
|
|
'note' => $request->input('note'),
|
2023-01-30 13:11:41 -08:00
|
|
|
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
|
|
|
|
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
|
2022-05-17 07:08:37 -07:00
|
|
|
'assigned_to' => $assigned_to,
|
2022-03-21 09:18:29 -07:00
|
|
|
'company_name' => $branding_settings->site_name,
|
2022-05-19 17:53:30 -07:00
|
|
|
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
2022-05-31 13:59:01 -07:00
|
|
|
'logo' => $path_logo,
|
2022-03-21 09:18:29 -07:00
|
|
|
'date_settings' => $branding_settings->date_display_format,
|
|
|
|
];
|
|
|
|
|
2022-05-17 07:08:37 -07:00
|
|
|
if ($pdf_view_route!='') {
|
2024-05-29 04:38:15 -07:00
|
|
|
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
2022-05-17 07:08:37 -07:00
|
|
|
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
|
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
|
|
}
|
|
|
|
|
2024-04-03 11:36:03 -07:00
|
|
|
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
2022-08-11 12:01:11 -07:00
|
|
|
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
|
2022-05-17 07:08:37 -07:00
|
|
|
event(new CheckoutAccepted($acceptance));
|
|
|
|
|
|
|
|
$return_msg = trans('admin/users/message.accepted');
|
2022-03-21 09:18:29 -07:00
|
|
|
|
2022-05-17 07:08:37 -07:00
|
|
|
} else {
|
2023-08-14 13:58:10 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check for the eula-pdfs directory
|
|
|
|
*/
|
|
|
|
if (! Storage::exists('private_uploads/eula-pdfs')) {
|
|
|
|
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Setting::getSettings()->require_accept_signature == '1') {
|
|
|
|
|
|
|
|
// Check if the signature directory exists, if not create it
|
|
|
|
if (!Storage::exists('private_uploads/signatures')) {
|
|
|
|
Storage::makeDirectory('private_uploads/signatures', 775);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-11 14:00:21 -07:00
|
|
|
// Format the data to send the declined notification
|
|
|
|
$branding_settings = SettingsController::getPDFBranding();
|
|
|
|
|
|
|
|
// This is the most horriblest
|
|
|
|
switch($acceptance->checkoutable_type){
|
|
|
|
case 'App\Models\Asset':
|
2023-05-03 17:04:01 -07:00
|
|
|
$asset_model = AssetModel::find($item->model_id);
|
|
|
|
$display_model = $asset_model->name;
|
2022-08-11 14:00:21 -07:00
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'App\Models\Accessory':
|
2023-05-03 17:04:01 -07:00
|
|
|
$accessory = Accessory::find($item->id);
|
|
|
|
$display_model = $accessory->name;
|
2023-05-03 14:22:05 -07:00
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
2022-08-11 14:00:21 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'App\Models\LicenseSeat':
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'App\Models\Component':
|
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'App\Models\Consumable':
|
2023-05-03 17:04:01 -07:00
|
|
|
$consumable = Consumable::find($item->id);
|
|
|
|
$display_model = $consumable->name;
|
2022-08-11 14:00:21 -07:00
|
|
|
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
|
|
|
|
break;
|
|
|
|
}
|
2024-03-18 10:25:55 -07:00
|
|
|
|
2022-08-11 14:00:21 -07:00
|
|
|
$data = [
|
|
|
|
'item_tag' => $item->asset_tag,
|
|
|
|
'item_model' => $display_model,
|
|
|
|
'item_serial' => $item->serial,
|
2024-07-24 11:24:30 -07:00
|
|
|
'item_status' => $item->assetstatus->name ?: '',
|
2024-03-21 14:03:25 -07:00
|
|
|
'note' => $request->input('note'),
|
2023-01-30 13:11:41 -08:00
|
|
|
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
|
2023-08-14 13:58:10 -07:00
|
|
|
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
|
2022-08-11 14:00:21 -07:00
|
|
|
'assigned_to' => $assigned_to,
|
|
|
|
'company_name' => $branding_settings->site_name,
|
|
|
|
'date_settings' => $branding_settings->date_display_format,
|
|
|
|
];
|
|
|
|
|
2023-08-14 13:58:10 -07:00
|
|
|
if ($pdf_view_route!='') {
|
2024-05-29 04:38:15 -07:00
|
|
|
Log::debug($pdf_filename.' is the filename, and the route was specified.');
|
2023-08-14 13:58:10 -07:00
|
|
|
$pdf = Pdf::loadView($pdf_view_route, $data);
|
|
|
|
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
|
|
|
|
}
|
|
|
|
|
2024-03-21 14:03:25 -07:00
|
|
|
$acceptance->decline($sig_filename, $request->input('note'));
|
2022-08-11 14:00:21 -07:00
|
|
|
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
|
2022-05-17 07:08:37 -07:00
|
|
|
event(new CheckoutDeclined($acceptance));
|
|
|
|
$return_msg = trans('admin/users/message.declined');
|
2022-03-21 09:18:29 -07:00
|
|
|
}
|
2022-05-17 07:08:37 -07:00
|
|
|
|
2022-03-08 16:42:27 -08:00
|
|
|
|
|
|
|
return redirect()->to('account/accept')->with('success', $return_msg);
|
2022-05-17 07:08:37 -07:00
|
|
|
|
2022-02-24 14:50:16 -08:00
|
|
|
}
|
2023-05-03 14:22:05 -07:00
|
|
|
}
|