Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 240196c3a1 | |||
| 409ce69e32 | |||
| 0bdd3034d1 | |||
| f04a41408d | |||
| 15effb1974 | |||
| ccd60eb6d0 | |||
| 2f54f5b051 | |||
| 509584d1ff | |||
| 95ac268046 | |||
| f4b9736862 | |||
| a0bf7a018c | |||
| ac4975e1d1 | |||
| c7c3b04c5f | |||
| 88f87db4fd | |||
| ad0199f662 | |||
| 75a276d9fa | |||
| ead0047629 | |||
| 46f6766bb4 | |||
| 8a470d3ef9 | |||
| 5506245959 | |||
| 0fc175581a | |||
| cd9005f82b | |||
| 24237d4259 | |||
| b2d707aaab | |||
| a432f23692 | |||
| a63b9ec627 | |||
| a35820d612 | |||
| ecf8ce3ec1 | |||
| 1908beb671 |
@@ -6,6 +6,7 @@ use App\Events\CheckoutAccepted;
|
|||||||
use App\Events\CheckoutDeclined;
|
use App\Events\CheckoutDeclined;
|
||||||
use App\Helpers\Helper;
|
use App\Helpers\Helper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\AcceptSignatureRequest;
|
||||||
use App\Mail\CheckoutAcceptanceResponseMail;
|
use App\Mail\CheckoutAcceptanceResponseMail;
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
@@ -36,7 +37,8 @@ class AcceptanceController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
$acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get();
|
$user = auth()->user();
|
||||||
|
$acceptances = CheckoutAcceptance::forUser($user)->pending()->get();
|
||||||
|
|
||||||
return view('account/accept.index', compact('acceptances'));
|
return view('account/accept.index', compact('acceptances'));
|
||||||
}
|
}
|
||||||
@@ -51,13 +53,13 @@ class AcceptanceController extends Controller
|
|||||||
$currentUser = auth()->user();
|
$currentUser = auth()->user();
|
||||||
|
|
||||||
if (! $currentUser instanceof User) {
|
if (! $currentUser instanceof User) {
|
||||||
abort(403, trans('general.insufficient_permissions'));
|
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$acceptance = CheckoutAcceptance::find($id);
|
$acceptance = CheckoutAcceptance::find($id);
|
||||||
|
|
||||||
if (! $acceptance) {
|
if (! $acceptance) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
return redirect()->route('account.accept')->with('error', trans('general.generic_model_not_found', ['model' => trans('general.accept_eula')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $acceptance->isPending()) {
|
if (! $acceptance->isPending()) {
|
||||||
@@ -76,7 +78,7 @@ class AcceptanceController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
|
||||||
return redirect()->route('account.accept')->with('error', trans('general.error_user_company'));
|
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkedOutAt = Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false);
|
$checkedOutAt = Helper::getFormattedDateObject($acceptance->created_at, 'datetime', false);
|
||||||
@@ -90,18 +92,12 @@ class AcceptanceController extends Controller
|
|||||||
*
|
*
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function store(Request $request, $id): RedirectResponse
|
public function store(AcceptSignatureRequest $request, CheckoutAcceptance $acceptance): RedirectResponse
|
||||||
{
|
{
|
||||||
$currentUser = auth()->user();
|
$currentUser = auth()->user();
|
||||||
|
|
||||||
if (! $currentUser instanceof User) {
|
if (! $currentUser instanceof User) {
|
||||||
abort(403, trans('general.insufficient_permissions'));
|
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
|
||||||
}
|
|
||||||
|
|
||||||
$acceptance = CheckoutAcceptance::find($id);
|
|
||||||
|
|
||||||
if (! $acceptance) {
|
|
||||||
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$assignedUser = User::find($acceptance->assigned_to_id);
|
$assignedUser = User::find($acceptance->assigned_to_id);
|
||||||
@@ -185,6 +181,33 @@ class AcceptanceController extends Controller
|
|||||||
$encoded_logo = base64_encode(file_get_contents(public_path().'/uploads/'.basename($settings->acceptance_pdf_logo)));
|
$encoded_logo = base64_encode(file_get_contents(public_path().'/uploads/'.basename($settings->acceptance_pdf_logo)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($isSignInPlaceAdminFlow && (! $acceptance->signed_in_place || (int) $acceptance->signed_in_place_admin !== (int) $currentUser->id)) {
|
||||||
|
$acceptance->forceFill([
|
||||||
|
'signed_in_place' => true,
|
||||||
|
'signed_in_place_admin' => $currentUser->id,
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine signed_in_place and admin for PDF/email output
|
||||||
|
$signedInPlace = $isSignInPlaceAdminFlow ? true : (bool) $acceptance->signed_in_place;
|
||||||
|
$signedInPlaceAdmin = null;
|
||||||
|
if ($isSignInPlaceAdminFlow) {
|
||||||
|
$signedInPlaceAdmin = [
|
||||||
|
'name' => $currentUser->display_name,
|
||||||
|
'username' => $currentUser->username,
|
||||||
|
'email' => $currentUser->email,
|
||||||
|
];
|
||||||
|
} elseif ($acceptance->signed_in_place && $acceptance->signed_in_place_admin) {
|
||||||
|
$admin = User::find($acceptance->signed_in_place_admin);
|
||||||
|
if ($admin) {
|
||||||
|
$signedInPlaceAdmin = [
|
||||||
|
'name' => $admin->display_name,
|
||||||
|
'username' => $admin->username,
|
||||||
|
'email' => $admin->email,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the data array ready for the notifications and PDF generation
|
// Get the data array ready for the notifications and PDF generation
|
||||||
$data = [
|
$data = [
|
||||||
'item_tag' => $item->asset_tag,
|
'item_tag' => $item->asset_tag,
|
||||||
@@ -206,21 +229,46 @@ class AcceptanceController extends Controller
|
|||||||
'logo' => ($encoded_logo) ?? null,
|
'logo' => ($encoded_logo) ?? null,
|
||||||
'date_settings' => $settings->date_display_format,
|
'date_settings' => $settings->date_display_format,
|
||||||
'qty' => $acceptance->qty ?? 1,
|
'qty' => $acceptance->qty ?? 1,
|
||||||
|
'signed_in_place' => $signedInPlace,
|
||||||
];
|
];
|
||||||
|
if ($signedInPlaceAdmin) {
|
||||||
|
$data['signed_in_place_admin'] = $signedInPlaceAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom fields for asset (show_in_email = 1, field_encrypted = 0)
|
||||||
|
$customFields = [];
|
||||||
|
if ($item instanceof Asset && $item->model && $item->model->fieldset) {
|
||||||
|
$fields = $item->model->fieldset->fields->where('show_in_email', true)->where('field_encrypted', false);
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$label = $field->name;
|
||||||
|
$dbColumn = $field->db_column;
|
||||||
|
$value = $item->$dbColumn;
|
||||||
|
if (! is_null($value) && $value !== '') {
|
||||||
|
$customFields[] = [
|
||||||
|
'label' => $label,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! empty($customFields)) {
|
||||||
|
$data['custom_fields'] = $customFields;
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->input('asset_acceptance') === 'accepted') {
|
if ($request->input('asset_acceptance') === 'accepted') {
|
||||||
|
|
||||||
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
|
||||||
|
|
||||||
// Generate the PDF content
|
// Generate the PDF content
|
||||||
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
|
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
|
||||||
Storage::put('private_uploads/eula-pdfs/'.$pdf_filename, $pdf_content);
|
Storage::put('private_uploads/eula-pdfs/'.$pdf_filename, $pdf_content);
|
||||||
|
|
||||||
// Log the acceptance
|
// Log the acceptance
|
||||||
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
|
$accept_qty = $request->input('accept_qty', $acceptance->qty ?? 1);
|
||||||
|
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'), $accept_qty);
|
||||||
|
|
||||||
|
$alwaysSendAcceptanceCopy = (bool) (config('app.always_send_email') || config('app.always_send_eula'));
|
||||||
|
|
||||||
// Send the PDF to the signing user
|
// Send the PDF to the signing user
|
||||||
if (($request->input('send_copy') === '1') && ($assignedUser->email !== '')) {
|
if (($alwaysSendAcceptanceCopy || ($request->input('send_copy') === '1')) && ($assignedUser->email !== '')) {
|
||||||
|
|
||||||
// Add the attachment for the signing user into the $data array
|
// Add the attachment for the signing user into the $data array
|
||||||
$data['file'] = $pdf_filename;
|
$data['file'] = $pdf_filename;
|
||||||
|
|||||||
@@ -25,33 +25,27 @@ class ConsumableCheckoutController extends Controller
|
|||||||
*
|
*
|
||||||
* @param int $id
|
* @param int $id
|
||||||
*/
|
*/
|
||||||
public function create($id): View|RedirectResponse
|
public function create(Consumable $consumable): View|RedirectResponse
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($consumable = Consumable::find($id)) {
|
$this->authorize('checkout', $consumable);
|
||||||
|
|
||||||
$this->authorize('checkout', $consumable);
|
// Make sure the category is valid
|
||||||
|
if ($consumable->category) {
|
||||||
|
|
||||||
// Make sure the category is valid
|
// Make sure there is at least one available to checkout
|
||||||
if ($consumable->category) {
|
if ($consumable->numRemaining() <= 0) {
|
||||||
|
return redirect()->route('consumables.index')
|
||||||
// Make sure there is at least one available to checkout
|
->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
|
||||||
if ($consumable->numRemaining() <= 0) {
|
|
||||||
return redirect()->route('consumables.index')
|
|
||||||
->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the checkout view
|
|
||||||
return view('consumables/checkout', compact('consumable'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid category
|
// Return the checkout view
|
||||||
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])
|
return view('consumables/checkout', compact('consumable'));
|
||||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not found
|
// Invalid category
|
||||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])
|
||||||
|
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +62,8 @@ class ConsumableCheckoutController extends Controller
|
|||||||
*
|
*
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request, $consumableId)
|
public function store(Request $request, Consumable $consumable)
|
||||||
{
|
{
|
||||||
if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
|
|
||||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->authorize('checkout', $consumable);
|
$this->authorize('checkout', $consumable);
|
||||||
|
|
||||||
// If the quantity is not present in the request or is not a positive integer, set it to 1
|
// If the quantity is not present in the request or is not a positive integer, set it to 1
|
||||||
@@ -99,14 +89,14 @@ class ConsumableCheckoutController extends Controller
|
|||||||
// Update the consumable data
|
// Update the consumable data
|
||||||
$consumable->assigned_to = e($request->input('assigned_to'));
|
$consumable->assigned_to = e($request->input('assigned_to'));
|
||||||
|
|
||||||
for ($i = 0; $i < $quantity; $i++) {
|
// Attach the consumable to the user ONCE with the correct qty and note
|
||||||
$consumable->users()->attach($consumable->id, [
|
$consumable->users()->attach($consumable->id, [
|
||||||
'consumable_id' => $consumable->id,
|
'consumable_id' => $consumable->id,
|
||||||
'created_by' => $admin_user->id,
|
'created_by' => $admin_user->id,
|
||||||
'assigned_to' => e($request->input('assigned_to')),
|
'assigned_to' => $assigned_to,
|
||||||
'note' => $request->input('note'),
|
'note' => $request->input('note'),
|
||||||
]);
|
'qty' => $quantity,
|
||||||
}
|
]);
|
||||||
|
|
||||||
$consumable->checkout_qty = $quantity;
|
$consumable->checkout_qty = $quantity;
|
||||||
|
|
||||||
|
|||||||
@@ -1218,7 +1218,7 @@ class ReportsController extends Controller
|
|||||||
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
|
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
|
||||||
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
|
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
|
||||||
|
|
||||||
return view('reports/unaccepted_assets', compact('itemsForReport', 'showDeleted'));
|
return view('reports/unaccepted_items', compact('itemsForReport', 'showDeleted'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1230,6 +1230,10 @@ class ReportsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function sentAssetAcceptanceReminder(Request $request): RedirectResponse
|
public function sentAssetAcceptanceReminder(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
if (! ($user?->isAdmin() || $user?->isSuperUser())) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
$id = $request->input('acceptance_id');
|
$id = $request->input('acceptance_id');
|
||||||
$query = CheckoutAcceptance::query()
|
$query = CheckoutAcceptance::query()
|
||||||
@@ -1251,7 +1255,7 @@ class ReportsController extends Controller
|
|||||||
Log::debug('No pending acceptances');
|
Log::debug('No pending acceptances');
|
||||||
|
|
||||||
// Redirect to the unaccepted items report page with error
|
// Redirect to the unaccepted items report page with error
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.bad_data_or_already_accepted'));
|
||||||
}
|
}
|
||||||
$item = $acceptance->checkoutable;
|
$item = $acceptance->checkoutable;
|
||||||
$assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null;
|
$assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null;
|
||||||
@@ -1263,7 +1267,7 @@ class ReportsController extends Controller
|
|||||||
if (is_null($acceptance->created_at)) {
|
if (is_null($acceptance->created_at)) {
|
||||||
Log::debug('No acceptance created_at');
|
Log::debug('No acceptance created_at');
|
||||||
|
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.bad_data_or_already_accepted'));
|
||||||
} else {
|
} else {
|
||||||
if ($item instanceof LicenseSeat) {
|
if ($item instanceof LicenseSeat) {
|
||||||
$logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
|
$logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
|
||||||
@@ -1273,18 +1277,18 @@ class ReportsController extends Controller
|
|||||||
if ($logItem_res->isEmpty()) {
|
if ($logItem_res->isEmpty()) {
|
||||||
Log::debug('Acceptance date mismatch');
|
Log::debug('Acceptance date mismatch');
|
||||||
|
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.bad_data_or_already_accepted'));
|
||||||
}
|
}
|
||||||
$logItem = $logItem_res[0];
|
$logItem = $logItem_res[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($email) || $email === '') {
|
if (is_null($email) || $email === '') {
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.no_email'));
|
||||||
}
|
}
|
||||||
$mailable = $this->getCheckoutMailType($acceptance, $logItem);
|
$mailable = $this->getCheckoutMailType($acceptance, $logItem);
|
||||||
Mail::to($email)->send($mailable->locale($locale));
|
Mail::to($email)->send($mailable->locale($locale));
|
||||||
|
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
|
return redirect()->route('reports/unaccepted_items')->with('success', trans('admin/reports/general.reminder_sent'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem): Mailable
|
private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem): Mailable
|
||||||
@@ -1317,17 +1321,21 @@ class ReportsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function deleteAssetAcceptance($acceptanceId = null): RedirectResponse
|
public function deleteAssetAcceptance($acceptanceId = null): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
if (! ($user?->isAdmin() || $user?->isSuperUser())) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
$this->authorize('reports.view');
|
$this->authorize('reports.view');
|
||||||
|
|
||||||
if (! $acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
|
if (! $acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
|
||||||
// Redirect to the unaccepted assets report page with error
|
// Redirect to the unaccepted assets report page with error
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.bad_data_or_already_accepted'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($acceptance->delete()) {
|
if ($acceptance->delete()) {
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.acceptance_deleted'));
|
return redirect()->route('reports/unaccepted_items')->with('success', trans('admin/reports/general.acceptance_deleted'));
|
||||||
} else {
|
} else {
|
||||||
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.deletion_failed'));
|
return redirect()->route('reports/unaccepted_items')->with('error', trans('general.deletion_failed'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\CheckoutAcceptance;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class AcceptSignatureRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
$acceptance = $this->route('acceptance');
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if (! $acceptance || ! $user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($acceptance)) {
|
||||||
|
$acceptance = CheckoutAcceptance::find($acceptance);
|
||||||
|
if (! $acceptance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $user instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow if the user is the assigned user or sign-in-place admin
|
||||||
|
$assignedToId = $acceptance->assigned_to_id ?? null;
|
||||||
|
$isSignInPlaceAdmin = session('sign_in_place_acceptance_id') === $acceptance->id && $user->can('checkout', $acceptance->checkoutable);
|
||||||
|
|
||||||
|
return $user->id === $assignedToId || $isSignInPlaceAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// ...existing validation rules...
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function failedAuthorization()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$acceptance = $this->route('acceptance');
|
||||||
|
// If user is logged in and acceptance exists, treat as business logic error
|
||||||
|
if ($user && $acceptance) {
|
||||||
|
$redirectResponse = redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
|
||||||
|
throw new ValidationException($this->getValidatorInstance(), $redirectResponse);
|
||||||
|
}
|
||||||
|
// Otherwise, use default 403
|
||||||
|
parent::failedAuthorization();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ class CheckoutableListener
|
|||||||
$acceptance = $this->getCheckoutAcceptance($event);
|
$acceptance = $this->getCheckoutAcceptance($event);
|
||||||
|
|
||||||
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
|
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
|
||||||
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance);
|
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance, $event->checkoutable);
|
||||||
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
|
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
|
||||||
|
|
||||||
if ($this->shouldSkipInitialAcceptanceEmail($event, $acceptance)) {
|
if ($this->shouldSkipInitialAcceptanceEmail($event, $acceptance)) {
|
||||||
@@ -175,7 +175,7 @@ class CheckoutableListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
$shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable);
|
$shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable);
|
||||||
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
|
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress(null, $event->checkoutable);
|
||||||
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
|
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
|
||||||
if (! $shouldSendEmailToUser && ! $shouldSendEmailToAlertAddress && ! $shouldSendWebhookNotification) {
|
if (! $shouldSendEmailToUser && ! $shouldSendEmailToAlertAddress && ! $shouldSendWebhookNotification) {
|
||||||
return;
|
return;
|
||||||
@@ -198,33 +198,39 @@ class CheckoutableListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mailable = $this->getCheckinMailType($event);
|
$mailable = $this->getCheckinMailType($event);
|
||||||
$notifiable = $this->getNotifiableUser($event);
|
|
||||||
|
|
||||||
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
|
if (! $mailable) {
|
||||||
|
Log::debug('No checkin mail type available for checkoutable class: '.get_class($event->checkoutable));
|
||||||
|
} else {
|
||||||
|
$notifiable = $this->getNotifiableUser($event);
|
||||||
|
|
||||||
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
|
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
|
||||||
|
|
||||||
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
|
||||||
|
|
||||||
if (! empty($to)) {
|
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
|
||||||
try {
|
|
||||||
$toMail = (clone $mailable)->locale($notifiable->locale);
|
if (! empty($to)) {
|
||||||
Mail::to(array_flatten($to))->send($toMail);
|
try {
|
||||||
Log::info('Checkin Mail sent to checkin target');
|
$toMail = (clone $mailable)->locale($notifiable->locale);
|
||||||
} catch (ClientException $e) {
|
Mail::to(array_flatten($to))->send($toMail);
|
||||||
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
Log::info('Checkin Mail sent to checkin target');
|
||||||
} catch (Exception $e) {
|
} catch (ClientException $e) {
|
||||||
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (! empty($cc)) {
|
if (! empty($cc)) {
|
||||||
try {
|
try {
|
||||||
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
$ccMail = (clone $mailable)->locale(Setting::getSettings()->locale);
|
||||||
Mail::cc(array_flatten($cc))->send($ccMail);
|
Mail::cc(array_flatten($cc))->send($ccMail);
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
Log::debug('Exception caught during checkin email: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,7 +398,14 @@ class CheckoutableListener
|
|||||||
LicenseSeat::class => CheckinLicenseMail::class,
|
LicenseSeat::class => CheckinLicenseMail::class,
|
||||||
Component::class => CheckinComponentMail::class,
|
Component::class => CheckinComponentMail::class,
|
||||||
];
|
];
|
||||||
$mailable = $lookup[get_class($event->checkoutable)];
|
|
||||||
|
$checkoutableClass = get_class($event->checkoutable);
|
||||||
|
|
||||||
|
if (! array_key_exists($checkoutableClass, $lookup)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailable = $lookup[$checkoutableClass];
|
||||||
|
|
||||||
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
|
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
|
||||||
|
|
||||||
@@ -466,11 +479,16 @@ class CheckoutableListener
|
|||||||
* 1. The asset requires acceptance
|
* 1. The asset requires acceptance
|
||||||
* 2. The item has a EULA
|
* 2. The item has a EULA
|
||||||
* 3. The item should send an email at check-in/check-out
|
* 3. The item should send an email at check-in/check-out
|
||||||
|
* 4. The config('app.always_send_email') is true
|
||||||
*/
|
*/
|
||||||
if (Context::get('action') === 'bulk_asset_checkout') {
|
if (Context::get('action') === 'bulk_asset_checkout') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config('app.always_send_email')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ($checkoutable->requireAcceptance()) {
|
if ($checkoutable->requireAcceptance()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -495,7 +513,7 @@ class CheckoutableListener
|
|||||||
return ($acceptance instanceof CheckoutAcceptance) || ! empty($event->checkoutable->getEula());
|
return ($acceptance instanceof CheckoutAcceptance) || ! empty($event->checkoutable->getEula());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldSendEmailToAlertAddress($acceptance = null): bool
|
private function shouldSendEmailToAlertAddress($acceptance = null, ?Model $checkoutable = null): bool
|
||||||
{
|
{
|
||||||
if (Context::get('action') === 'bulk_asset_checkout') {
|
if (Context::get('action') === 'bulk_asset_checkout') {
|
||||||
return false;
|
return false;
|
||||||
@@ -507,22 +525,39 @@ class CheckoutableListener
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($acceptance) && ! $setting->admin_cc_always) {
|
$alertRecipients = $this->getFormattedAlertAddresses((bool) $setting->admin_cc_always);
|
||||||
|
|
||||||
|
if (empty($alertRecipients)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (bool) $setting->admin_cc_email;
|
if (is_null($acceptance) && ! $setting->admin_cc_always) {
|
||||||
}
|
if (! $checkoutable || ! $this->checkoutableCategoryShouldSendEmail($checkoutable)) {
|
||||||
|
return false;
|
||||||
private function getFormattedAlertAddresses(): array
|
}
|
||||||
{
|
|
||||||
$alertAddresses = Setting::getSettings()->admin_cc_email;
|
|
||||||
|
|
||||||
if ($alertAddresses !== '') {
|
|
||||||
return array_filter(array_map('trim', explode(',', $alertAddresses)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFormattedAlertAddresses(bool $allowAlertEmailFallback = false): array
|
||||||
|
{
|
||||||
|
$setting = Setting::getSettings();
|
||||||
|
|
||||||
|
if (! $setting) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$adminCcAddresses = $setting->admin_cc_email;
|
||||||
|
$fallbackAlertAddresses = $allowAlertEmailFallback ? $setting->alert_email : null;
|
||||||
|
|
||||||
|
$rawAddresses = $adminCcAddresses ?: $fallbackAlertAddresses;
|
||||||
|
|
||||||
|
if ($rawAddresses === null || $rawAddresses === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter(array_map('trim', explode(',', $rawAddresses)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateEmailRecipients(
|
private function generateEmailRecipients(
|
||||||
@@ -536,7 +571,7 @@ class CheckoutableListener
|
|||||||
// if user && cc: to user, cc admin
|
// if user && cc: to user, cc admin
|
||||||
if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
|
if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
|
||||||
$to[] = $notifiable;
|
$to[] = $notifiable;
|
||||||
$cc[] = $this->getFormattedAlertAddresses();
|
$cc[] = $this->getFormattedAlertAddresses(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user && no cc: to user
|
// if user && no cc: to user
|
||||||
@@ -546,7 +581,7 @@ class CheckoutableListener
|
|||||||
|
|
||||||
// if no user && cc: to admin
|
// if no user && cc: to admin
|
||||||
if (! $shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
|
if (! $shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
|
||||||
$to[] = $this->getFormattedAlertAddresses();
|
$to[] = $this->getFormattedAlertAddresses(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$to, $cc];
|
return [$to, $cc];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Events\UserMerged;
|
|||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\LicenseSeat;
|
use App\Models\LicenseSeat;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Events\Dispatcher;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class LogListener
|
class LogListener
|
||||||
@@ -59,7 +60,10 @@ class LogListener
|
|||||||
$logaction->action_type = 'accepted';
|
$logaction->action_type = 'accepted';
|
||||||
$logaction->action_date = $event->acceptance->accepted_at;
|
$logaction->action_date = $event->acceptance->accepted_at;
|
||||||
$logaction->quantity = $event->acceptance->qty ?? 1;
|
$logaction->quantity = $event->acceptance->qty ?? 1;
|
||||||
$logaction->created_by = auth()->user()->id;
|
$logaction->created_by = auth()->user()?->getAuthIdentifier();
|
||||||
|
$logaction->remote_ip = request()->ip();
|
||||||
|
$logaction->user_agent = request()->header('User-Agent');
|
||||||
|
$logaction->action_source = 'gui';
|
||||||
|
|
||||||
// TODO: log the actual license seat that was checked out
|
// TODO: log the actual license seat that was checked out
|
||||||
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
|
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
|
||||||
@@ -79,7 +83,10 @@ class LogListener
|
|||||||
$logaction->action_type = 'declined';
|
$logaction->action_type = 'declined';
|
||||||
$logaction->action_date = $event->acceptance->declined_at;
|
$logaction->action_date = $event->acceptance->declined_at;
|
||||||
$logaction->quantity = $event->acceptance->qty ?? 1;
|
$logaction->quantity = $event->acceptance->qty ?? 1;
|
||||||
$logaction->created_by = auth()->user()->id;
|
$logaction->created_by = auth()->user()?->getAuthIdentifier();
|
||||||
|
$logaction->remote_ip = request()->ip();
|
||||||
|
$logaction->user_agent = request()->header('User-Agent');
|
||||||
|
$logaction->action_source = 'gui';
|
||||||
|
|
||||||
// TODO: log the actual license seat that was checked out
|
// TODO: log the actual license seat that was checked out
|
||||||
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
|
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
|
||||||
@@ -127,7 +134,7 @@ class LogListener
|
|||||||
/**
|
/**
|
||||||
* Register the listeners for the subscriber.
|
* Register the listeners for the subscriber.
|
||||||
*
|
*
|
||||||
* @param Illuminate\Events\Dispatcher $events
|
* @param Dispatcher $events
|
||||||
*/
|
*/
|
||||||
public function subscribe($events)
|
public function subscribe($events)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,21 @@ class CheckoutAcceptance extends Model
|
|||||||
'alert_on_response_id' => 'integer',
|
'alert_on_response_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'assigned_to_id',
|
||||||
|
'checkoutable_type',
|
||||||
|
'checkoutable_id',
|
||||||
|
'accepted_at',
|
||||||
|
'declined_at',
|
||||||
|
'note',
|
||||||
|
'signature_filename',
|
||||||
|
'stored_eula',
|
||||||
|
'stored_eula_file',
|
||||||
|
'qty',
|
||||||
|
'signed_in_place',
|
||||||
|
'signed_in_place_admin',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the mail recipient from the config
|
* Get the mail recipient from the config
|
||||||
*
|
*
|
||||||
@@ -112,7 +127,7 @@ class CheckoutAcceptance extends Model
|
|||||||
*/
|
*/
|
||||||
public function isCheckedOutTo(User $user)
|
public function isCheckedOutTo(User $user)
|
||||||
{
|
{
|
||||||
return $this->assignedTo?->is($user);
|
return $this->assigned_to_id === $user->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,20 +136,27 @@ class CheckoutAcceptance extends Model
|
|||||||
* checkout_acceptances table or you'll get an error.
|
* checkout_acceptances table or you'll get an error.
|
||||||
*
|
*
|
||||||
* @param string $signature_filename
|
* @param string $signature_filename
|
||||||
|
* @param string|null $eula
|
||||||
|
* @param string|null $filename
|
||||||
|
* @param string|null $note
|
||||||
|
* @param int|null $qty
|
||||||
*/
|
*/
|
||||||
public function accept($signature_filename, $eula = null, $filename = null, $note = null)
|
public function accept($signature_filename, $eula = null, $filename = null, $note = null, $qty = null)
|
||||||
{
|
{
|
||||||
$this->accepted_at = now();
|
$this->accepted_at = now();
|
||||||
$this->signature_filename = $signature_filename;
|
$this->signature_filename = $signature_filename;
|
||||||
$this->stored_eula = $eula;
|
$this->stored_eula = $eula;
|
||||||
$this->stored_eula_file = $filename;
|
$this->stored_eula_file = $filename;
|
||||||
$this->note = $note;
|
$this->note = $note;
|
||||||
|
if ($qty !== null) {
|
||||||
|
$this->qty = $qty;
|
||||||
|
}
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state for the checked out item
|
* Update state for the checked out item
|
||||||
*/
|
*/
|
||||||
$this->checkoutable->acceptedCheckout($this->assignedTo, $signature_filename, $filename);
|
$this->checkoutable->acceptedCheckout($this->assignedTo, $qty, $note, $signature_filename, $filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,7 +230,7 @@ class CheckoutAcceptance extends Model
|
|||||||
$pdf->SetAuthor($data['assigned_to']);
|
$pdf->SetAuthor($data['assigned_to']);
|
||||||
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
|
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
|
||||||
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
|
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
|
||||||
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula, tos');
|
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula, tos,'.$data['item_tag'] ?? null.', '.$data['item_name'] ?? null.', '.$data['assigned_to'] ?? null);
|
||||||
$pdf->SetFont('dejavusans', '', 8, '', true);
|
$pdf->SetFont('dejavusans', '', 8, '', true);
|
||||||
$pdf->SetPrintHeader(false);
|
$pdf->SetPrintHeader(false);
|
||||||
$pdf->SetPrintFooter(false);
|
$pdf->SetPrintFooter(false);
|
||||||
@@ -243,6 +265,17 @@ class CheckoutAcceptance extends Model
|
|||||||
if ($data['item_serial'] != null) {
|
if ($data['item_serial'] != null) {
|
||||||
$pdf->writeHTML(trans('admin/hardware/form.serial').': '.e($data['item_serial']), true, 0, true, 0, '');
|
$pdf->writeHTML(trans('admin/hardware/form.serial').': '.e($data['item_serial']), true, 0, true, 0, '');
|
||||||
}
|
}
|
||||||
|
// Render custom fields if present
|
||||||
|
if (! empty($data['custom_fields']) && is_array($data['custom_fields'])) {
|
||||||
|
foreach ($data['custom_fields'] as $customField) {
|
||||||
|
$label = $customField['label'] ?? '';
|
||||||
|
$value = $customField['value'] ?? '';
|
||||||
|
if ($label !== '' && $value !== '') {
|
||||||
|
$pdf->writeHTML(e($label).': '.e($value), true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($data['qty'] != null) && ($data['qty'] > 1)) {
|
if (($data['qty'] != null) && ($data['qty'] > 1)) {
|
||||||
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
|
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
|
||||||
}
|
}
|
||||||
@@ -250,6 +283,35 @@ class CheckoutAcceptance extends Model
|
|||||||
if ($data['email'] != null) {
|
if ($data['email'] != null) {
|
||||||
$pdf->writeHTML(trans('general.email').': '.e($data['email']), true, 0, true, 0, '');
|
$pdf->writeHTML(trans('general.email').': '.e($data['email']), true, 0, true, 0, '');
|
||||||
}
|
}
|
||||||
|
// Add assigning user if present
|
||||||
|
if (! empty($data['assigning_user'])) {
|
||||||
|
$assigningUser = $data['assigning_user'];
|
||||||
|
$assigningUserLine = trans('general.assigned_by').': '.e($assigningUser['name'] ?? $assigningUser['email'] ?? '');
|
||||||
|
if (! empty($assigningUser['employee_num'])) {
|
||||||
|
$assigningUserLine .= ' ('.e($assigningUser['employee_num']).')';
|
||||||
|
}
|
||||||
|
$pdf->writeHTML($assigningUserLine, true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
// Add signed in place row (always show)
|
||||||
|
$signedInPlace = ! empty($data['signed_in_place']) && filter_var($data['signed_in_place'], FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$pdf->writeHTML(trans('general.signed_in_place').': '.($signedInPlace ? trans('general.yes') : trans('general.no')), true, 0, true, 0, '');
|
||||||
|
// If signed in place, show admin info
|
||||||
|
if ($signedInPlace && ! empty($data['signed_in_place_admin'])) {
|
||||||
|
$admin = $data['signed_in_place_admin'];
|
||||||
|
$adminName = $admin['name'] ?? '';
|
||||||
|
$adminUsername = $admin['username'] ?? '';
|
||||||
|
$adminEmail = $admin['email'] ?? '';
|
||||||
|
$adminDetails = $adminName;
|
||||||
|
if (! empty($adminUsername)) {
|
||||||
|
$adminDetails .= ' ('.$adminUsername.')';
|
||||||
|
}
|
||||||
|
if (! empty($adminEmail)) {
|
||||||
|
$adminDetails .= ' <'.$adminEmail.'>';
|
||||||
|
}
|
||||||
|
$adminLine = trans('general.signed_in_place_admin', ['admin' => $adminDetails]);
|
||||||
|
$pdf->writeHTML($adminLine, true, 0, true, 0, '');
|
||||||
|
}
|
||||||
|
|
||||||
$pdf->Ln();
|
$pdf->Ln();
|
||||||
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
|
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
|
||||||
|
|
||||||
|
|||||||
@@ -495,4 +495,83 @@ class Consumable extends SnipeModel
|
|||||||
{
|
{
|
||||||
return $query->leftJoin('users as users_sort', 'consumables.created_by', '=', 'users_sort.id')->select('consumables.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order);
|
return $query->leftJoin('users as users_sort', 'consumables.created_by', '=', 'users_sort.id')->select('consumables.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle logic after a consumable checkout is accepted by the user.
|
||||||
|
*
|
||||||
|
* @param string|null $signature
|
||||||
|
* @param string|null $filename
|
||||||
|
*/
|
||||||
|
public function acceptedCheckout(User $acceptedBy, ?int $qty = null, ?string $note = null, $signature = null, $filename = null): void
|
||||||
|
{
|
||||||
|
// Find the pending acceptance for this user and consumable
|
||||||
|
$acceptance = $acceptedBy->getAssignedItemsWithPendingAcceptance()
|
||||||
|
->where('item_id', $this->id)
|
||||||
|
->where('qty', $qty)
|
||||||
|
->where('item_type', self::class)
|
||||||
|
->whereNull('declined_at')
|
||||||
|
->sortByDesc('created_at')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($acceptance) {
|
||||||
|
if ($qty !== null) {
|
||||||
|
$acceptance->qty = $qty;
|
||||||
|
}
|
||||||
|
if ($note !== null) {
|
||||||
|
$acceptance->note = $note;
|
||||||
|
}
|
||||||
|
$acceptance->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the consumable to the user if not already attached
|
||||||
|
$pivot = $acceptedBy->consumables()->where('consumable_id', $this->id)->first();
|
||||||
|
if (! $pivot) {
|
||||||
|
$acceptedBy->consumables()->attach($this->id, [
|
||||||
|
'created_by' => $acceptance?->created_by ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging handled by event listener; do not log here to avoid duplicates.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle logic after a consumable checkout is declined by the user.
|
||||||
|
*
|
||||||
|
* @param string|null $signature
|
||||||
|
*/
|
||||||
|
public function declinedCheckout(User $declinedBy, $signature = null): void
|
||||||
|
{
|
||||||
|
// Find the pending acceptance for this user and consumable
|
||||||
|
$acceptance = $declinedBy->acceptances()
|
||||||
|
->where('item_id', $this->id)
|
||||||
|
->where('item_type', self::class)
|
||||||
|
->whereNull('accepted_at')
|
||||||
|
->latest('created_at')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$qty = $acceptance?->qty ?? 1;
|
||||||
|
$note = $acceptance?->note;
|
||||||
|
|
||||||
|
// Detach the consumable from the user (if present)
|
||||||
|
$declinedBy->consumables()->detach($this->id);
|
||||||
|
|
||||||
|
// Logging handled by event listener; do not log here to avoid duplicates.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an acceptance or decline action for this consumable.
|
||||||
|
*/
|
||||||
|
protected function logActionAcceptance(string $actionType, User $user, int $qty, ?string $note = null): void
|
||||||
|
{
|
||||||
|
$this->assetlog()->create([
|
||||||
|
'action_type' => $actionType,
|
||||||
|
'target_id' => $user->id,
|
||||||
|
'target_type' => User::class,
|
||||||
|
'item_id' => $this->id,
|
||||||
|
'item_type' => self::class,
|
||||||
|
'quantity' => $qty,
|
||||||
|
'note' => $note,
|
||||||
|
'created_by' => auth()->id() ?? $user->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class AcceptanceItemAcceptedNotification extends Notification
|
|||||||
$this->file = $params['file'] ?? null;
|
$this->file = $params['file'] ?? null;
|
||||||
$this->qty = $params['qty'] ?? null;
|
$this->qty = $params['qty'] ?? null;
|
||||||
$this->note = $params['note'] ?? null;
|
$this->note = $params['note'] ?? null;
|
||||||
|
$this->signed_in_place = $params['signed_in_place'] ?? false;
|
||||||
|
$this->signed_in_place_admin = $params['signed_in_place_admin'] ?? null;
|
||||||
|
$this->custom_fields = $params['custom_fields'] ?? [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +79,9 @@ class AcceptanceItemAcceptedNotification extends Notification
|
|||||||
'assigned_to' => $this->assigned_to,
|
'assigned_to' => $this->assigned_to,
|
||||||
'company_name' => $this->company_name,
|
'company_name' => $this->company_name,
|
||||||
'qty' => $this->qty,
|
'qty' => $this->qty,
|
||||||
|
'signed_in_place' => $this->signed_in_place,
|
||||||
|
'signed_in_place_admin' => $this->signed_in_place_admin,
|
||||||
|
'custom_fields' => $this->custom_fields,
|
||||||
'intro_text' => trans('mail.acceptance_accepted_greeting', ['user' => $this->assigned_to, 'item' => $this->item_name]),
|
'intro_text' => trans('mail.acceptance_accepted_greeting', ['user' => $this->assigned_to, 'item' => $this->item_name]),
|
||||||
])
|
])
|
||||||
->subject('✅ '.trans('mail.acceptance_accepted', ['user' => $this->assigned_to, 'item' => $this->item_name]))
|
->subject('✅ '.trans('mail.acceptance_accepted', ['user' => $this->assigned_to, 'item' => $this->item_name]))
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class AcceptanceItemAcceptedToUserNotification extends Notification
|
|||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
$this->file = $params['file'] ?? null;
|
$this->file = $params['file'] ?? null;
|
||||||
$this->qty = $params['qty'] ?? null;
|
$this->qty = $params['qty'] ?? null;
|
||||||
|
$this->custom_fields = $params['custom_fields'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,6 +73,7 @@ class AcceptanceItemAcceptedToUserNotification extends Notification
|
|||||||
'assigned_to' => $this->assigned_to,
|
'assigned_to' => $this->assigned_to,
|
||||||
'company_name' => $this->company_name,
|
'company_name' => $this->company_name,
|
||||||
'qty' => $this->qty,
|
'qty' => $this->qty,
|
||||||
|
'custom_fields' => $this->custom_fields,
|
||||||
'intro_text' => trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_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)
|
->attach($pdf_path)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class AcceptanceItemDeclinedNotification extends Notification
|
|||||||
$this->settings = Setting::getSettings();
|
$this->settings = Setting::getSettings();
|
||||||
$this->qty = $params['qty'] ?? null;
|
$this->qty = $params['qty'] ?? null;
|
||||||
$this->admin = $params['admin'] ?? null;
|
$this->admin = $params['admin'] ?? null;
|
||||||
|
$this->custom_fields = $params['custom_fields'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,6 +75,7 @@ class AcceptanceItemDeclinedNotification extends Notification
|
|||||||
'company_name' => $this->company_name,
|
'company_name' => $this->company_name,
|
||||||
'qty' => $this->qty,
|
'qty' => $this->qty,
|
||||||
'admin' => $this->admin,
|
'admin' => $this->admin,
|
||||||
|
'custom_fields' => $this->custom_fields,
|
||||||
'user' => $this->assigned_to,
|
'user' => $this->assigned_to,
|
||||||
'intro_text' => trans('mail.acceptance_declined_greeting', ['user' => $this->assigned_to]),
|
'intro_text' => trans('mail.acceptance_declined_greeting', ['user' => $this->assigned_to]),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Presenters;
|
||||||
|
|
||||||
|
use App\Models\CustomField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CustomFieldPresenter
|
||||||
|
* Handles presentation logic for CustomField, including visibility icons.
|
||||||
|
*/
|
||||||
|
final class CustomFieldPresenter extends Presenter
|
||||||
|
{
|
||||||
|
private CustomField $field;
|
||||||
|
|
||||||
|
public function __construct(CustomField $field)
|
||||||
|
{
|
||||||
|
$this->field = $field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of icon HTML for where the field is visible.
|
||||||
|
*
|
||||||
|
* @return string[] Array of HTML icon strings
|
||||||
|
*/
|
||||||
|
public function visibilityIconsArray(): array
|
||||||
|
{
|
||||||
|
$icons = [];
|
||||||
|
if ($this->field->display_checkout) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.display_checkout')).'" data-tooltip="true"><i class="fa-solid fa-rotate-left text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->display_checkin) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.display_checkin')).'" data-tooltip="true"><i class="fa-solid fa-rotate-right text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->display_audit) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.display_audit')).'" data-tooltip="true"><i class="fas fa-clipboard-check text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->display_in_user_view) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.display_in_user_view_table')).'" data-tooltip="true"><i class="fas fa-user text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->show_in_listview) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.show_in_listview_short')).'" data-tooltip="true"><i class="fas fa-list text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->show_in_email) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.show_in_email_short')).'" data-tooltip="true"><i class="fas fa-envelope text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
if ($this->field->show_in_requestable_list) {
|
||||||
|
$icons[] = '<span title="'.e(trans('admin/custom_fields/general.show_in_requestable_list_short')).'" data-tooltip="true"><i class="fa-solid fa-bell-concierge text-muted"></i></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the icons as a single HTML string (for backward compatibility)
|
||||||
|
*/
|
||||||
|
public function visibilityIcons(): string
|
||||||
|
{
|
||||||
|
return implode(' ', $this->visibilityIconsArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -537,4 +537,21 @@ return [
|
|||||||
|
|
||||||
'max_unpaginated_records' => env('MAX_UNPAGINATED', '5000'),
|
'max_unpaginated_records' => env('MAX_UNPAGINATED', '5000'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Always send emails on acceptance/EULA
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This setting allows you to bypass the "email me a copy" checkbox on EULA/item acceptance,
|
||||||
|
| and forces Snipe-IT to always send email to the accepting user if they have an email address.
|
||||||
|
*/
|
||||||
|
'always_send_email' => env('ALWAYS_SEND_EMAIL', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Always Send EULA
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| If true, the EULA will always be sent and the checkbox will be hidden.
|
||||||
|
*/
|
||||||
|
'always_send_eula' => env('ALWAYS_SEND_EULA', false),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('consumables_users', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('qty')->nullable()->default(1)->after('created_by');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('consumables_users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('qty');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('checkout_acceptances', function (Blueprint $table) {
|
||||||
|
$table->boolean('signed_in_place')->default(false)->after('declined_at');
|
||||||
|
$table->unsignedBigInteger('signed_in_place_admin')->nullable()->after('signed_in_place');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('checkout_acceptances', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['signed_in_place', 'signed_in_place_admin']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ return [
|
|||||||
'asset_already_accepted' => 'This item has already been accepted.',
|
'asset_already_accepted' => 'This item has already been accepted.',
|
||||||
'accept_or_decline' => 'You must either accept or decline this asset.',
|
'accept_or_decline' => 'You must either accept or decline this asset.',
|
||||||
'cannot_delete_yourself' => 'We would feel really bad if you deleted yourself, please reconsider.',
|
'cannot_delete_yourself' => 'We would feel really bad if you deleted yourself, please reconsider.',
|
||||||
'incorrect_user_accepted' => 'The asset you have attempted to accept was not checked out to you.',
|
'incorrect_user_accepted' => 'The item you are attempting to accept was not checked out to you.',
|
||||||
'ldap_could_not_connect' => 'Could not connect to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
|
'ldap_could_not_connect' => 'Could not connect to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
|
||||||
'ldap_could_not_bind' => 'Could not bind to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server: ',
|
'ldap_could_not_bind' => 'Could not bind to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server: ',
|
||||||
'ldap_could_not_search' => 'Could not search the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
|
'ldap_could_not_search' => 'Could not search the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ return [
|
|||||||
'avatar_upload' => 'Upload Avatar',
|
'avatar_upload' => 'Upload Avatar',
|
||||||
'back' => 'Back',
|
'back' => 'Back',
|
||||||
'bad_data' => 'Nothing found. Maybe bad data?',
|
'bad_data' => 'Nothing found. Maybe bad data?',
|
||||||
|
'bad_data_or_already_accepted' => 'No corresponding acceptance was found. You might not have permission to perform this action, or it may have already been accepted',
|
||||||
'bulkaudit' => 'Scanner Bulk Audit',
|
'bulkaudit' => 'Scanner Bulk Audit',
|
||||||
'bulkaudit_status' => 'Audit Status',
|
'bulkaudit_status' => 'Audit Status',
|
||||||
'bulk_checkout' => 'Bulk Checkout',
|
'bulk_checkout' => 'Bulk Checkout',
|
||||||
@@ -590,6 +591,7 @@ return [
|
|||||||
'address2' => 'Address Line 2',
|
'address2' => 'Address Line 2',
|
||||||
'import_note' => 'Imported using csv importer',
|
'import_note' => 'Imported using csv importer',
|
||||||
],
|
],
|
||||||
|
'acceptance_email_always_sent' => 'An email will be sent to the accepting user automatically.',
|
||||||
'remove_customfield_association' => 'Remove this field from the fieldset. This will not delete the custom field, only this field\'s association with this fieldset.',
|
'remove_customfield_association' => 'Remove this field from the fieldset. This will not delete the custom field, only this field\'s association with this fieldset.',
|
||||||
'checked_out_to_fields' => 'Checked Out To Fields',
|
'checked_out_to_fields' => 'Checked Out To Fields',
|
||||||
'percent_complete' => '% complete',
|
'percent_complete' => '% complete',
|
||||||
@@ -748,8 +750,9 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'months_plural' => '1 month|:count months',
|
'months_plural' => '1 month|:count months',
|
||||||
|
|
||||||
'token_unrevoked' => 'API token reinstated',
|
'token_unrevoked' => 'API token reinstated',
|
||||||
'token_revoked' => 'API token revoked',
|
'assigned_by' => 'Assigned by',
|
||||||
|
'signed_in_place' => 'Signed in place',
|
||||||
|
'signed_in_place_admin' => 'Signed in place by :admin',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -125,12 +125,18 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
@if ($acceptance->assignedTo?->email)
|
@if ($acceptance->assignedTo?->email)
|
||||||
<div class="col-md-12" style="display: none;" id="showEmailBox">
|
@if (config('app.always_send_email') || config('app.always_send_eula'))
|
||||||
<label class="form-control">
|
<div class="col-md-12" id="emailInfoBox">
|
||||||
<input type="checkbox" value="1" name="send_copy" id="send_copy" checked="checked" aria-label="send_copy">
|
{{ trans('general.acceptance_email_always_sent') }} ({{ $acceptance->assignedTo->email }})
|
||||||
{{ trans('mail.send_pdf_copy') }} ({{ $acceptance->assignedTo->email }})
|
</div>
|
||||||
</label>
|
@else
|
||||||
</div>
|
<div class="col-md-12" 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') }} ({{ $acceptance->assignedTo->email }})
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5 text-right">
|
<div class="col-md-5 text-right">
|
||||||
@@ -195,16 +201,16 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
$('[name="asset_acceptance"]').on('change', function() {
|
$('[name="asset_acceptance"]').on('change', function() {
|
||||||
|
|
||||||
if ($(this).is(':checked') && $(this).attr('id') === 'declined') {
|
if ($(this).is(':checked') && $(this).attr('id') === 'declined') {
|
||||||
$("#showEmailBox").hide();
|
$("#showEmailBox").hide();
|
||||||
|
$("#emailInfoBox").hide();
|
||||||
$("#showSubmit").show();
|
$("#showSubmit").show();
|
||||||
$("#submit-button").removeClass("btn-success").addClass("btn-danger").show();
|
$("#submit-button").removeClass("btn-success").addClass("btn-danger").show();
|
||||||
$("#submitIcon").removeClass("fa-check").addClass("fa-times");
|
$("#submitIcon").removeClass("fa-check").addClass("fa-times");
|
||||||
$("#buttonText").text('{{ trans_choice('general.i_decline_item', $acceptance->qty ?? 1) }}');
|
$("#buttonText").text('{{ trans_choice('general.i_decline_item', $acceptance->qty ?? 1) }}');
|
||||||
$("#note").prop('required', true);
|
$("#note").prop('required', true);
|
||||||
|
|
||||||
} else if ($(this).is(':checked') && $(this).attr('id') === 'accepted') {
|
} else if ($(this).is(':checked') && $(this).attr('id') === 'accepted') {
|
||||||
|
$("#emailInfoBox").show();
|
||||||
$("#showEmailBox").show();
|
$("#showEmailBox").show();
|
||||||
$("#showSubmit").show();
|
$("#showSubmit").show();
|
||||||
$("#submit-button").removeClass("btn-danger").addClass("btn-success").show();
|
$("#submit-button").removeClass("btn-danger").addClass("btn-success").show();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
|
||||||
<form class="form-horizontal" id="checkout_form" method="post" action="" autocomplete="off">
|
<form class="form-horizontal" id="checkout_form" method="post" action="" autocomplete="off">
|
||||||
<!-- CSRF Token -->
|
<!-- CSRF Token -->
|
||||||
@@ -81,71 +81,83 @@
|
|||||||
<!-- User -->
|
<!-- User -->
|
||||||
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_to', 'required'=> 'true'])
|
@include ('partials.forms.edit.user-select', ['translated_name' => trans('general.select_user'), 'fieldname' => 'assigned_to', 'required'=> 'true'])
|
||||||
|
|
||||||
|
<!-- Checkout QTY -->
|
||||||
@if ($consumable->requireAcceptance() || (string) $snipeSettings->require_accept_signature === '1' || $consumable->getEula() || ($snipeSettings->webhook_endpoint!=''))
|
<div class="form-group {{ $errors->has('qty') ? 'error' : '' }} ">
|
||||||
<div class="form-group notification-callout">
|
<label for="qty" class="col-md-3 control-label">{{ trans('general.qty') }}</label>
|
||||||
<div class="col-md-8 col-md-offset-3">
|
<div class="col-md-7 col-sm-12 required">
|
||||||
<div class="callout callout-info">
|
<div class="col-md-2" style="padding-left:0px">
|
||||||
|
<input class="form-control" type="number" name="checkout_qty" id="checkout_qty" value="1" min="1" max="{{$consumable->numRemaining()}}" maxlength="999999"/>
|
||||||
@if ($consumable->category->require_acceptance=='1')
|
</div>
|
||||||
<i class="far fa-envelope"></i>
|
|
||||||
{{ trans('admin/categories/general.required_acceptance') }}
|
|
||||||
<br>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if ($consumable->getEula())
|
|
||||||
<i class="far fa-envelope"></i>
|
|
||||||
{{ trans('admin/categories/general.required_eula') }}
|
|
||||||
<br>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if (($consumable->category) && ($consumable->category->checkin_email))
|
|
||||||
<i class="far fa-envelope"></i>
|
|
||||||
{{ trans('admin/categories/general.checkin_email_notification') }}
|
|
||||||
<br>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if ($snipeSettings->webhook_endpoint!='')
|
|
||||||
<i class="fab fa-slack"></i>
|
|
||||||
{{ trans('general.webhook_msg_note') }}
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{!! $errors->first('qty', '<div class="col-md-8 col-md-offset-3"><span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span></div>') !!}
|
||||||
|
|
||||||
<!-- Sign in place checkbox -->
|
|
||||||
@if ($consumable->requireAcceptance() || (string) $snipeSettings->require_accept_signature === '1')
|
|
||||||
<div id="sign_in_place_div" class="col-md-7 col-md-offset-3">
|
|
||||||
<label class="form-control">
|
|
||||||
<input type="checkbox" value="1" name="sign_in_place" @checked(old('sign_in_place', session('sign_in_place', false))) aria-label="sign_in_place">
|
|
||||||
{{ trans('general.sign_in_place') }}
|
|
||||||
</label>
|
|
||||||
<p class="help-block">
|
|
||||||
{{ trans('general.sign_in_place_help') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
|
|
||||||
<!-- Checkout QTY -->
|
<!-- Note -->
|
||||||
<div class="form-group {{ $errors->has('qty') ? 'error' : '' }} ">
|
<div class="form-group {{ $errors->has('note') ? 'error' : '' }}">
|
||||||
<label for="qty" class="col-md-3 control-label">{{ trans('general.qty') }}</label>
|
<label for="note" class="col-md-3 control-label">{{ trans('admin/hardware/form.notes') }}</label>
|
||||||
<div class="col-md-7 col-sm-12 required">
|
<div class="col-md-7">
|
||||||
<div class="col-md-2" style="padding-left:0px">
|
<textarea class="col-md-6 form-control" name="note">{{ old('note') }}</textarea>
|
||||||
<input class="form-control" type="number" name="checkout_qty" id="checkout_qty" value="1" min="1" max="{{$consumable->numRemaining()}}" maxlength="999999" />
|
{!! $errors->first('note', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!! $errors->first('qty', '<div class="col-md-8 col-md-offset-3"><span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span></div>') !!}
|
|
||||||
</div>
|
|
||||||
|
@if ($consumable->requireAcceptance() || (string) $snipeSettings->require_accept_signature === '1' || $consumable->getEula() || ($snipeSettings->webhook_endpoint!=''))
|
||||||
<!-- Note -->
|
<div class="form-group notification-callout">
|
||||||
<div class="form-group {{ $errors->has('note') ? 'error' : '' }}">
|
<div class="col-md-8 col-md-offset-3">
|
||||||
<label for="note" class="col-md-3 control-label">{{ trans('admin/hardware/form.notes') }}</label>
|
<div class="callout callout-info">
|
||||||
<div class="col-md-7">
|
|
||||||
<textarea class="col-md-6 form-control" name="note">{{ old('note') }}</textarea>
|
@if ($consumable->category->require_acceptance=='1')
|
||||||
{!! $errors->first('note', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
|
<i class="far fa-envelope"></i>
|
||||||
</div>
|
{{ trans('admin/categories/general.required_acceptance') }}
|
||||||
</div>
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($consumable->getEula())
|
||||||
|
<i class="far fa-envelope"></i>
|
||||||
|
{{ trans('admin/categories/general.required_eula') }}
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (($consumable->category) && ($consumable->category->checkin_email))
|
||||||
|
<i class="far fa-envelope"></i>
|
||||||
|
{{ trans('admin/categories/general.checkin_email_notification') }}
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($snipeSettings->webhook_endpoint!='')
|
||||||
|
<i class="fab fa-slack"></i>
|
||||||
|
{{ trans('general.webhook_msg_note') }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EULA/email checkbox or info message -->
|
||||||
|
@if ($consumable->getEula())
|
||||||
|
<div class="col-md-8 col-md-offset-3">
|
||||||
|
<label class="form-control">
|
||||||
|
<input type="checkbox" value="1" name="send_eula_copy" id="send_eula_copy" checked="checked" aria-label="send_eula_copy">
|
||||||
|
{{ trans('mail.send_pdf_copy') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Sign in place checkbox -->
|
||||||
|
@if ($consumable->requireAcceptance() || (string) $snipeSettings->require_accept_signature === '1')
|
||||||
|
<div id="sign_in_place_div" class="col-md-7 col-md-offset-3">
|
||||||
|
<label class="form-control">
|
||||||
|
<input type="checkbox" value="1" name="sign_in_place" @checked(old('sign_in_place', session('sign_in_place', false))) aria-label="sign_in_place">
|
||||||
|
{{ trans('general.sign_in_place') }}
|
||||||
|
</label>
|
||||||
|
<p class="help-block">
|
||||||
|
{{ trans('general.sign_in_place_help') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
</div> <!-- .box-body -->
|
</div> <!-- .box-body -->
|
||||||
<x-redirect_submit_options
|
<x-redirect_submit_options
|
||||||
index_route="consumables.index"
|
index_route="consumables.index"
|
||||||
|
|||||||
@@ -1857,8 +1857,8 @@
|
|||||||
{{ trans('general.asset_maintenance_report') }}
|
{{ trans('general.asset_maintenance_report') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li {{!! (request()->is('reports/unaccepted_assets') ? ' class="active"' : '') !!}}>
|
<li {{!! (request()->is('reports/unaccepted_items') ? ' class="active"' : '') !!}}>
|
||||||
<a href="{{ url('reports/unaccepted_assets') }}">
|
<a href="{{ url('reports/unaccepted_items') }}">
|
||||||
{{ trans('general.unaccepted_asset_report') }}
|
{{ trans('general.unaccepted_asset_report') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@if (($model) && ($model->fieldset) && $model->fieldset->displayAnyFieldsInForm($show_custom_fields_type ?? ''))
|
@if (($model) && ($model->fieldset) && $model->fieldset->displayAnyFieldsInForm($show_custom_fields_type ?? ''))
|
||||||
<div class="col-md-12 col-sm-12">
|
|
||||||
|
|
||||||
|
|
||||||
|
@if(isset($show_fieldset) && ($show_fieldset=='true'))
|
||||||
<fieldset name="custom-fields">
|
<fieldset name="custom-fields">
|
||||||
<x-form.legend
|
<x-form.legend
|
||||||
help_text="{!! trans('admin/custom_fields/general.general_help_text') !!}">
|
help_text="{!! trans('admin/custom_fields/general.general_help_text') !!}">
|
||||||
@@ -8,6 +9,9 @@
|
|||||||
{{ trans('admin/custom_fields/general.custom_fields') }}
|
{{ trans('admin/custom_fields/general.custom_fields') }}
|
||||||
</x-form.legend>
|
</x-form.legend>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
@foreach($model->fieldset->fields as $field)
|
@foreach($model->fieldset->fields as $field)
|
||||||
@if (!isset($show_custom_fields_type) || ($field->displayFieldInCurrentForm($show_custom_fields_type)))
|
@if (!isset($show_custom_fields_type) || ($field->displayFieldInCurrentForm($show_custom_fields_type)))
|
||||||
|
|
||||||
@@ -15,12 +19,12 @@
|
|||||||
<div class="form-group{{ $errors->has($field->db_column_name()) ? ' has-error' : '' }}">
|
<div class="form-group{{ $errors->has($field->db_column_name()) ? ' has-error' : '' }}">
|
||||||
|
|
||||||
|
|
||||||
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">
|
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">
|
||||||
{{ $field->name }}
|
{{ $field->name }}
|
||||||
|
<br>
|
||||||
|
</label>
|
||||||
|
|
||||||
</label>
|
<div class="col-md-8 col-sm-12">
|
||||||
|
|
||||||
<div class="col-md-7 col-sm-12">
|
|
||||||
|
|
||||||
@if ($field->element!='text')
|
@if ($field->element!='text')
|
||||||
|
|
||||||
@@ -36,7 +40,7 @@
|
|||||||
|
|
||||||
@elseif ($field->element=='textarea')
|
@elseif ($field->element=='textarea')
|
||||||
<!-- Textarea -->
|
<!-- Textarea -->
|
||||||
<textarea class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}"{{ ($field->pivot->required=='1') ? ' required' : '' }}>{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
|
<textarea rows="6" class="form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}"{{ ($field->pivot->required=='1') ? ' required' : '' }}>{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
|
||||||
|
|
||||||
@elseif ($field->element=='checkbox')
|
@elseif ($field->element=='checkbox')
|
||||||
<!-- Checkbox -->
|
<!-- Checkbox -->
|
||||||
@@ -66,9 +70,10 @@
|
|||||||
<!-- Date field -->
|
<!-- Date field -->
|
||||||
@if ($field->format=='DATE')
|
@if ($field->format=='DATE')
|
||||||
|
|
||||||
<div class="input-group col-md-5" style="padding-left: 0px;">
|
<div class="input-group col-md-7">
|
||||||
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
|
|
||||||
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="{{ $field->db_column_name() }}" id="{{ $field->db_column_name() }}" readonly value="{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" style="background-color:inherit"{{ ($field->pivot->required=='1') ? ' required' : '' }}>
|
<div class="input-group date" data-provide="datepicker" data-date-format="yyyy-mm-dd" data-autoclose="true" data-date-clear-btn="true">
|
||||||
|
<input type="text" class="form-control" placeholder="{{ trans('general.select_date') }}" name="{{ $field->db_column_name() }}" id="{{ $field->db_column_name() }}" value="{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}" style="background-color:inherit"{{ ($field->pivot->required=='1') ? ' required' : '' }}>
|
||||||
<span class="input-group-addon"><x-icon type="calendar" /></span>
|
<span class="input-group-addon"><x-icon type="calendar" /></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +89,16 @@
|
|||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($field->help_text!='')
|
{{-- Visibility icons below the field, before help text --}}
|
||||||
|
@php $presenter = new \App\Presenters\CustomFieldPresenter($field); @endphp
|
||||||
|
@if (count($presenter->visibilityIconsArray()))
|
||||||
|
@if ($field->help_text != '')
|
||||||
|
<p class="help-block">{{ $field->help_text }} <span class="custom-field-visibility-icons"><br>{!! $presenter->visibilityIcons() !!}</span>
|
||||||
|
</p>
|
||||||
|
@else
|
||||||
|
<div class="custom-field-visibility-icons" style="margin-bottom:7px;">{!! $presenter->visibilityIcons() !!}</div>
|
||||||
|
@endif
|
||||||
|
@elseif ($field->help_text != '')
|
||||||
<p class="help-block">{{ $field->help_text }}</p>
|
<p class="help-block">{{ $field->help_text }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@@ -107,8 +121,6 @@
|
|||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
|
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,29 @@
|
|||||||
@if (isset($qty))
|
@if (isset($qty))
|
||||||
| **{{ trans('general.qty') }}** | {{ $qty }} |
|
| **{{ trans('general.qty') }}** | {{ $qty }} |
|
||||||
@endif
|
@endif
|
||||||
|
@if (!empty($custom_fields) && is_iterable($custom_fields))
|
||||||
|
@foreach ($custom_fields as $customField)
|
||||||
|
@if (!empty($customField['label']) && array_key_exists('value', $customField) && $customField['value'] !== '')
|
||||||
|
| **{{ $customField['label'] }}** | {{ $customField['value'] }} |
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
| **{{ trans('general.signed_in_place') }}** | {{ isset($signed_in_place) && $signed_in_place ? trans('general.yes') : trans('general.no') }} |
|
||||||
|
@if(isset($signed_in_place) && $signed_in_place && isset($signed_in_place_admin) && is_array($signed_in_place_admin))
|
||||||
|
@php
|
||||||
|
$adminName = $signed_in_place_admin['name'] ?? '';
|
||||||
|
$adminUsername = $signed_in_place_admin['username'] ?? '';
|
||||||
|
$adminEmail = $signed_in_place_admin['email'] ?? '';
|
||||||
|
$adminDetails = $adminName;
|
||||||
|
if (!empty($adminUsername)) {
|
||||||
|
$adminDetails .= ' (' . $adminUsername . ')';
|
||||||
|
}
|
||||||
|
if (!empty($adminEmail)) {
|
||||||
|
$adminDetails .= ' <' . $adminEmail . '>';
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
| **{{ trans('general.signed_in_place_admin', ['admin' => $adminDetails]) }}** | {{ trans('general.signed_in_place_admin', ['admin' => $adminDetails]) }} |
|
||||||
|
@endif
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
{{ trans('mail.best_regards') }}
|
{{ trans('mail.best_regards') }}
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
<?php
|
|
||||||
?>
|
|
||||||
@extends('layouts/default')
|
|
||||||
|
|
||||||
{{-- Page title --}}
|
|
||||||
@section('title')
|
|
||||||
{{ trans('general.unaccepted_asset_report') }}
|
|
||||||
@parent
|
|
||||||
@stop
|
|
||||||
|
|
||||||
@section('header_right')
|
|
||||||
|
|
||||||
<div class="btn-toolbar" role="toolbar">
|
|
||||||
<div class="btn-group mr-2" role="group">
|
|
||||||
@if($showDeleted)
|
|
||||||
<a href="{{ route('reports/unaccepted_assets') }}" class="btn btn-default" ><i class="fa fa-trash icon-white" aria-hidden="true"></i> {{ trans('general.hide_deleted') }}</a>
|
|
||||||
@else
|
|
||||||
<a href="{{ route('reports/unaccepted_assets', ['deleted' => 'deleted']) }}" class="btn btn-default" ><i class="fa fa-trash icon-white" aria-hidden="true"></i> {{ trans('general.show_deleted') }}</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div class="btn-group mr-2" role="group">
|
|
||||||
<form method="POST" action="{{ route('reports/export/unaccepted_assets') }}" accept-charset="UTF-8" class="form-horizontal">
|
|
||||||
{{csrf_field()}}
|
|
||||||
<button type="submit" class="btn btn-default"><i class="fa fa-download icon-white" aria-hidden="true"></i> {{ trans('general.download_all') }}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@stop
|
|
||||||
|
|
||||||
{{-- Page content --}}
|
|
||||||
@section('content')
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="box box-default">
|
|
||||||
<div class="box-body">
|
|
||||||
<table
|
|
||||||
data-cookie-id-table="unacceptedAssetsReport"
|
|
||||||
data-id-table="unacceptedAssetsReport"
|
|
||||||
data-side-pagination="client"
|
|
||||||
data-sort-order="asc"
|
|
||||||
data-sort-name="created_at"
|
|
||||||
data-advanced-search="false"
|
|
||||||
id="unacceptedAssetsReport"
|
|
||||||
data-fixed-number="false"
|
|
||||||
data-fixed-right-number="false"
|
|
||||||
class="table table-striped snipe-table"
|
|
||||||
data-export-options='{
|
|
||||||
"fileName": "maintenance-report-{{ date('Y-m-d') }}",
|
|
||||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
|
||||||
}'>
|
|
||||||
<thead>
|
|
||||||
<tr role="row">
|
|
||||||
<th class="col-sm-1" data-field="created_at" data-searchable="false" data-sortable="true">{{ trans('general.date') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('general.type') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/companies/table.title') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('general.category') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/hardware/form.model') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('general.name') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/hardware/table.asset_tag') }}</th>
|
|
||||||
<th class="col-sm-1" data-sortable="true" >{{ trans('admin/hardware/table.checkoutto') }}</th>
|
|
||||||
<th class="col-md-1"><span class="line"></span>{{ trans('table.actions') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@if ($itemsForReport)
|
|
||||||
@foreach ($itemsForReport as $item)
|
|
||||||
<tr @if($item->acceptance->trashed()) style="text-decoration: line-through" @endif>
|
|
||||||
{{-- Created date --}}
|
|
||||||
<td>
|
|
||||||
{{ Helper::getFormattedDateObject($item->acceptance->created_at, 'datetime', false) }}
|
|
||||||
</td>
|
|
||||||
{{-- Item Type --}}
|
|
||||||
<td>{{ $item->type }}</td>
|
|
||||||
{{-- Company name --}}
|
|
||||||
<td>{{ $item->plain_text_company }}</td>
|
|
||||||
|
|
||||||
{{-- Category --}}
|
|
||||||
<td>{{ $item->plain_text_category }}</td>
|
|
||||||
|
|
||||||
{{-- Model --}}
|
|
||||||
<td>{{ $item->plain_text_model }}</td>
|
|
||||||
|
|
||||||
{{-- Name --}}
|
|
||||||
<td>{{ $item->plain_text_name }}</td>
|
|
||||||
|
|
||||||
{{-- Asset tag or blank --}}
|
|
||||||
<td>{{ $item->asset_tag }}</td>
|
|
||||||
|
|
||||||
{{-- Assigned To (with soft-delete strike if needed) --}}
|
|
||||||
<td @if(!$item->assignee || (method_exists($item->assignee, 'trashed') && $item->assignee->trashed())) style="text-decoration: line-through" @endif>
|
|
||||||
{!! $item->assignee
|
|
||||||
? optional($item->assignee->present())->nameUrl() ?? e($item->assignee->name)
|
|
||||||
: trans('admin/reports/general.deleted_user') !!}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{{-- Actions: send reminder / delete --}}
|
|
||||||
<td class="text-nowrap">
|
|
||||||
|
|
||||||
@unless($item->acceptance->trashed())
|
|
||||||
<form method="post" class="white-space: nowrap;" action="{{ route('reports/unaccepted_assets_sent_reminder') }}">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="acceptance_id" value="{{ $item->acceptance_id }}">
|
|
||||||
@if ($item->assignee && $item->assignee->email)
|
|
||||||
<button class="btn btn-sm btn-warning" data-tooltip="true" data-title="{{ trans('admin/reports/general.send_reminder') }}">
|
|
||||||
<i class="fa fa-repeat" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
@else
|
|
||||||
<span data-tooltip="true" data-title="{{ trans('admin/reports/general.cannot_send_reminder') }}">
|
|
||||||
<a class="btn btn-sm btn-warning disabled" href="#">
|
|
||||||
<i class="fa fa-repeat" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
<a href="{{ route('reports/unaccepted_assets_delete', ['acceptanceId' => $item->acceptance_id]) }}"
|
|
||||||
class="btn btn-sm btn-danger delete-asset"
|
|
||||||
data-tooltip="true"
|
|
||||||
data-toggle="modal"
|
|
||||||
data-content="{{ trans('general.delete_confirm', ['item' => trans('admin/reports/general.acceptance_request')]) }}"
|
|
||||||
data-title="{{ trans('general.delete') }}"
|
|
||||||
onClick="return false;">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</form>
|
|
||||||
@endunless
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
@endif
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@stop
|
|
||||||
|
|
||||||
@section('moar_scripts')
|
|
||||||
@include ('partials.bootstrap-table')
|
|
||||||
@stop
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
?>
|
||||||
|
@extends('layouts/default')
|
||||||
|
|
||||||
|
{{-- Page title --}}
|
||||||
|
@section('title')
|
||||||
|
{{ trans('general.unaccepted_asset_report') }}
|
||||||
|
@parent
|
||||||
|
@stop
|
||||||
|
|
||||||
|
@section('header_right')
|
||||||
|
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<div class="btn-group mr-2" role="group">
|
||||||
|
@if($showDeleted)
|
||||||
|
<a href="{{ route('reports/unaccepted_items') }}" class="btn btn-default"><i class="fa fa-trash icon-white" aria-hidden="true"></i> {{ trans('general.hide_deleted') }}
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<a href="{{ route('reports/unaccepted_items', ['deleted' => 'deleted']) }}" class="btn btn-default"><i class="fa fa-trash icon-white" aria-hidden="true"></i> {{ trans('general.show_deleted') }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mr-2" role="group">
|
||||||
|
<form method="POST" action="{{ route('reports/export/unaccepted_items') }}" accept-charset="UTF-8" class="form-horizontal">
|
||||||
|
{{csrf_field()}}
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
<i class="fa fa-download icon-white" aria-hidden="true"></i> {{ trans('general.download_all') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
||||||
|
|
||||||
|
{{-- Page content --}}
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="box box-default">
|
||||||
|
<div class="box-body">
|
||||||
|
<table
|
||||||
|
data-cookie-id-table="unacceptedItemsReport"
|
||||||
|
data-id-table="unacceptedItemsReport"
|
||||||
|
data-side-pagination="client"
|
||||||
|
data-sort-order="asc"
|
||||||
|
data-sort-name="created_at"
|
||||||
|
data-advanced-search="false"
|
||||||
|
id="unacceptedItemsReport"
|
||||||
|
data-fixed-number="false"
|
||||||
|
data-fixed-right-number="false"
|
||||||
|
class="table table-striped snipe-table"
|
||||||
|
data-export-options='{
|
||||||
|
"fileName": "unaccepted-items-report-{{ date('Y-m-d') }}",
|
||||||
|
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||||
|
}'>
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
<th class="col-sm-1" data-field="created_at" data-searchable="false" data-sortable="true">{{ trans('general.date') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('general.type') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('admin/companies/table.title') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('general.category') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('admin/hardware/form.model') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('general.name') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('admin/hardware/table.asset_tag') }}</th>
|
||||||
|
<th class="col-sm-1" data-sortable="true">{{ trans('admin/hardware/table.checkoutto') }}</th>
|
||||||
|
@if(auth()->user()?->isAdmin() || auth()->user()?->isSuperUser())
|
||||||
|
<th class="col-md-1"><span class="line"></span>{{ trans('table.actions') }}</th>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@if ($itemsForReport)
|
||||||
|
@foreach ($itemsForReport as $item)
|
||||||
|
<tr @if($item->acceptance->trashed()) style="text-decoration: line-through" @endif>
|
||||||
|
{{-- Created date --}}
|
||||||
|
<td>
|
||||||
|
{{ Helper::getFormattedDateObject($item->acceptance->created_at, 'datetime', false) }}
|
||||||
|
</td>
|
||||||
|
{{-- Item Type --}}
|
||||||
|
<td>{{ $item->type }}</td>
|
||||||
|
{{-- Company name --}}
|
||||||
|
<td>{{ $item->plain_text_company }}</td>
|
||||||
|
|
||||||
|
{{-- Category --}}
|
||||||
|
<td>{{ $item->plain_text_category }}</td>
|
||||||
|
|
||||||
|
{{-- Model --}}
|
||||||
|
<td>{{ $item->plain_text_model }}</td>
|
||||||
|
|
||||||
|
{{-- Name --}}
|
||||||
|
<td>{{ $item->plain_text_name }}</td>
|
||||||
|
|
||||||
|
{{-- Asset tag or blank --}}
|
||||||
|
<td>{{ $item->asset_tag }}</td>
|
||||||
|
|
||||||
|
{{-- Assigned To (with soft-delete strike if needed) --}}
|
||||||
|
<td @if(!$item->assignee || (method_exists($item->assignee, 'trashed') && $item->assignee->trashed())) style="text-decoration: line-through" @endif>
|
||||||
|
{!! $item->assignee
|
||||||
|
? optional($item->assignee->present())->nameUrl() ?? e($item->assignee->name)
|
||||||
|
: trans('general.deleted') !!}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{{-- Actions: send reminder / delete --}}
|
||||||
|
@if(auth()->user()?->isAdmin() || auth()->user()?->isSuperUser())
|
||||||
|
<td class="text-nowrap">
|
||||||
|
@unless($item->acceptance->trashed())
|
||||||
|
<form method="post" class="white-space: nowrap;" action="{{ route('reports/unaccepted_items_sent_reminder') }}">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="acceptance_id" value="{{ $item->acceptance_id }}">
|
||||||
|
@if ($item->assignee && $item->assignee->email)
|
||||||
|
<button class="btn btn-sm btn-warning" data-tooltip="true" data-title="{{ trans('admin/reports/general.send_reminder') }}">
|
||||||
|
<i class="fa fa-repeat" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
<span data-tooltip="true" data-title="{{ trans('admin/reports/general.cannot_send_reminder') }}">
|
||||||
|
<a class="btn btn-sm btn-warning disabled" href="#">
|
||||||
|
<i class="fa fa-repeat" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
<a href="{{ route('reports/unaccepted_items_delete', ['acceptanceId' => $item->acceptance_id]) }}"
|
||||||
|
class="btn btn-sm btn-danger delete-asset"
|
||||||
|
data-tooltip="true"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-content="{{ trans('general.delete_confirm', ['item' => trans('admin/reports/general.acceptance_request')]) }}"
|
||||||
|
data-title="{{ trans('general.delete') }}"
|
||||||
|
onClick="return false;">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
@endunless
|
||||||
|
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
||||||
|
|
||||||
|
@section('moar_scripts')
|
||||||
|
@include ('partials.bootstrap-table')
|
||||||
|
@stop
|
||||||
|
|
||||||
+5
-5
@@ -545,19 +545,19 @@ Route::group(['prefix' => 'reports', 'middleware' => ['auth']], function () {
|
|||||||
->name('reports.activity.post');
|
->name('reports.activity.post');
|
||||||
|
|
||||||
Route::get('unaccepted_assets/{deleted?}', [ReportsController::class, 'getAssetAcceptanceReport'])
|
Route::get('unaccepted_assets/{deleted?}', [ReportsController::class, 'getAssetAcceptanceReport'])
|
||||||
->name('reports/unaccepted_assets')
|
->name('reports/unaccepted_items')
|
||||||
->breadcrumbs(fn (Trail $trail) => $trail->parent('home')
|
->breadcrumbs(fn (Trail $trail) => $trail->parent('home')
|
||||||
->push(trans('general.unaccepted_asset_report'), route('reports/unaccepted_assets')));
|
->push(trans('general.unaccepted_asset_report'), route('reports/unaccepted_items')));
|
||||||
|
|
||||||
Route::post('unaccepted_assets/sent_reminder', [ReportsController::class, 'sentAssetAcceptanceReminder'])
|
Route::post('unaccepted_assets/sent_reminder', [ReportsController::class, 'sentAssetAcceptanceReminder'])
|
||||||
->name('reports/unaccepted_assets_sent_reminder');
|
->name('reports/unaccepted_items_sent_reminder');
|
||||||
|
|
||||||
Route::delete('unaccepted_assets/{acceptanceId}/delete', [ReportsController::class, 'deleteAssetAcceptance'])
|
Route::delete('unaccepted_assets/{acceptanceId}/delete', [ReportsController::class, 'deleteAssetAcceptance'])
|
||||||
->name('reports/unaccepted_assets_delete');
|
->name('reports/unaccepted_items_delete');
|
||||||
|
|
||||||
Route::post(
|
Route::post(
|
||||||
'unaccepted_assets/{deleted?}', [ReportsController::class, 'postAssetAcceptanceReport'])
|
'unaccepted_assets/{deleted?}', [ReportsController::class, 'postAssetAcceptanceReport'])
|
||||||
->name('reports/export/unaccepted_assets');
|
->name('reports/export/unaccepted_items');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Consumables;
|
use App\Http\Controllers\Consumables;
|
||||||
|
use App\Models\Consumable;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Tabuna\Breadcrumbs\Trail;
|
||||||
|
|
||||||
|
|
||||||
Route::group(['prefix' => 'consumables', 'middleware' => ['auth']], function () {
|
Route::group(['prefix' => 'consumables', 'middleware' => ['auth']], function () {
|
||||||
Route::get(
|
|
||||||
'{consumablesID}/checkout',
|
Route::get('{consumable}/checkout', [Consumables\ConsumableCheckoutController::class, 'create'])
|
||||||
[Consumables\ConsumableCheckoutController::class, 'create']
|
->breadcrumbs(fn (Trail $trail, Consumable $consumable) => $trail->parent('consumables.show', $consumable)
|
||||||
)->name('consumables.checkout.show');
|
->push(trans('general.checkout'), route('consumables.index'))
|
||||||
|
)->name('consumables.checkout.show');
|
||||||
|
|
||||||
Route::post(
|
Route::post(
|
||||||
'{consumablesID}/checkout',
|
'{consumable}/checkout',
|
||||||
[Consumables\ConsumableCheckoutController::class, 'store']
|
[Consumables\ConsumableCheckoutController::class, 'store']
|
||||||
)->name('consumables.checkout.store');
|
)->name('consumables.checkout.store');
|
||||||
|
|
||||||
|
|
||||||
Route::get('{consumable}/clone',
|
Route::get('{consumable}/clone',
|
||||||
[Consumables\ConsumablesController::class, 'clone']
|
[Consumables\ConsumablesController::class, 'clone']
|
||||||
)->name('consumables.clone.create');
|
)->name('consumables.clone.create');
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::resource('consumables', Consumables\ConsumablesController::class, [
|
Route::resource('consumables', Consumables\ConsumablesController::class, [
|
||||||
'middleware' => ['auth'],
|
'middleware' => ['auth'],
|
||||||
'parameters' => ['consumable' => 'consumable_id'],
|
'parameters' => ['consumable' => 'consumable_id'],
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\CheckoutAcceptances\Ui;
|
||||||
|
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\CheckoutAcceptance;
|
||||||
|
use App\Models\User;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AcceptanceAuthorizationTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_assigned_user_can_accept()
|
||||||
|
{
|
||||||
|
$assignee = User::factory()->create();
|
||||||
|
$asset = Asset::factory()->create();
|
||||||
|
$acceptance = CheckoutAcceptance::factory()->pending()->for($assignee, 'assignedTo')->for($asset, 'checkoutable')->create();
|
||||||
|
|
||||||
|
$this->actingAs($assignee)
|
||||||
|
->post(route('account.store-acceptance', $acceptance), [
|
||||||
|
'asset_acceptance' => 'accepted',
|
||||||
|
'note' => 'ok',
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
$this->assertNotNull($acceptance->fresh()->accepted_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_other_user_cannot_accept()
|
||||||
|
{
|
||||||
|
$other = User::factory()->create();
|
||||||
|
$assignee = User::factory()->create();
|
||||||
|
$asset = Asset::factory()->create();
|
||||||
|
$acceptance = CheckoutAcceptance::factory()->pending()->for($assignee, 'assignedTo')->for($asset, 'checkoutable')->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($other)
|
||||||
|
->post(route('account.store-acceptance', $acceptance), [
|
||||||
|
'asset_acceptance' => 'accepted',
|
||||||
|
'note' => 'no',
|
||||||
|
]);
|
||||||
|
$response->assertRedirectToRoute('account.accept');
|
||||||
|
$response->assertSessionHas('error');
|
||||||
|
$this->assertNull($acceptance->fresh()->accepted_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ namespace Tests\Feature\CheckoutAcceptances\Ui;
|
|||||||
|
|
||||||
use App\Models\Accessory;
|
use App\Models\Accessory;
|
||||||
use App\Models\AccessoryCheckout;
|
use App\Models\AccessoryCheckout;
|
||||||
use App\Models\Asset;
|
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\AcceptanceItemAcceptedNotification;
|
use App\Notifications\AcceptanceItemAcceptedNotification;
|
||||||
@@ -150,11 +149,15 @@ class AccessoryAcceptanceTest extends TestCase
|
|||||||
{
|
{
|
||||||
Notification::fake();
|
Notification::fake();
|
||||||
|
|
||||||
|
$assignee = User::factory()->create();
|
||||||
$otherUser = User::factory()->create();
|
$otherUser = User::factory()->create();
|
||||||
|
|
||||||
|
$accessory = Accessory::factory()->create();
|
||||||
|
|
||||||
$acceptance = CheckoutAcceptance::factory()
|
$acceptance = CheckoutAcceptance::factory()
|
||||||
->pending()
|
->pending()
|
||||||
->for(Asset::factory()->laptopMbp(), 'checkoutable')
|
->for($assignee, 'assignedTo')
|
||||||
|
->for($accessory, 'checkoutable')
|
||||||
->create();
|
->create();
|
||||||
|
|
||||||
$this->actingAs($otherUser)
|
$this->actingAs($otherUser)
|
||||||
@@ -235,4 +238,30 @@ class AccessoryAcceptanceTest extends TestCase
|
|||||||
|
|
||||||
$this->assertNotNull($checkoutAcceptance->refresh()->accepted_at);
|
$this->assertNotNull($checkoutAcceptance->refresh()->accepted_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_email_info_when_always_send_email_enabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => true]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
$response->assertDontSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_checkbox_when_always_send_email_disabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => false]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
$response->assertDontSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ use App\Events\CheckoutAccepted;
|
|||||||
use App\Models\Actionlog;
|
use App\Models\Actionlog;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\CheckoutAcceptance;
|
use App\Models\CheckoutAcceptance;
|
||||||
|
use App\Models\CustomField;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Notifications\AcceptanceItemAcceptedNotification;
|
||||||
|
use App\Notifications\AcceptanceItemAcceptedToUserNotification;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class AssetAcceptanceTest extends TestCase
|
class AssetAcceptanceTest extends TestCase
|
||||||
@@ -180,6 +184,84 @@ class AssetAcceptanceTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_admin_acceptance_email_contains_custom_fields_marked_show_in_email(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
$this->settings->enableAlertEmail();
|
||||||
|
|
||||||
|
$customField = CustomField::factory()->create([
|
||||||
|
'name' => 'Cost Center',
|
||||||
|
'show_in_email' => '1',
|
||||||
|
'field_encrypted' => '0',
|
||||||
|
])->fresh();
|
||||||
|
|
||||||
|
$asset = Asset::factory()->hasMultipleCustomFields([$customField])->create();
|
||||||
|
$asset->{$customField->db_column} = 'ENG-42';
|
||||||
|
$asset->save();
|
||||||
|
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||||
|
->pending()
|
||||||
|
->for($asset, 'checkoutable')
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||||
|
'asset_acceptance' => 'accepted',
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
Notification::assertSentTo(
|
||||||
|
$checkoutAcceptance,
|
||||||
|
function (AcceptanceItemAcceptedNotification $notification) {
|
||||||
|
$rendered = $notification->toMail()->render();
|
||||||
|
|
||||||
|
$this->assertStringContainsString('Cost Center', $rendered);
|
||||||
|
$this->assertStringContainsString('ENG-42', $rendered);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_acceptance_email_does_not_contain_encrypted_custom_fields(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
$this->settings->enableAlertEmail();
|
||||||
|
|
||||||
|
$customField = CustomField::factory()->create([
|
||||||
|
'name' => 'SSN',
|
||||||
|
'show_in_email' => '1',
|
||||||
|
'field_encrypted' => '1',
|
||||||
|
])->fresh();
|
||||||
|
|
||||||
|
$asset = Asset::factory()->hasMultipleCustomFields([$customField])->create();
|
||||||
|
$asset->{$customField->db_column} = '123-45-6789';
|
||||||
|
$asset->save();
|
||||||
|
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()
|
||||||
|
->pending()
|
||||||
|
->for($asset, 'checkoutable')
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||||
|
'asset_acceptance' => 'accepted',
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
Notification::assertSentTo(
|
||||||
|
$checkoutAcceptance,
|
||||||
|
function (AcceptanceItemAcceptedNotification $notification) {
|
||||||
|
$rendered = $notification->toMail()->render();
|
||||||
|
|
||||||
|
$this->assertStringNotContainsString('SSN', $rendered);
|
||||||
|
$this->assertStringNotContainsString('123-45-6789', $rendered);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_action_logged_when_declining_asset()
|
public function test_action_logged_when_declining_asset()
|
||||||
{
|
{
|
||||||
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
@@ -232,7 +314,11 @@ class AssetAcceptanceTest extends TestCase
|
|||||||
])
|
])
|
||||||
->assertRedirect(route('users.show', $assignee));
|
->assertRedirect(route('users.show', $assignee));
|
||||||
|
|
||||||
$this->assertNotNull($checkoutAcceptance->refresh()->accepted_at);
|
$checkoutAcceptance->refresh();
|
||||||
|
|
||||||
|
$this->assertNotNull($checkoutAcceptance->accepted_at);
|
||||||
|
$this->assertTrue((bool) $checkoutAcceptance->signed_in_place);
|
||||||
|
$this->assertSame($admin->id, $checkoutAcceptance->signed_in_place_admin);
|
||||||
Event::assertDispatched(CheckoutAccepted::class);
|
Event::assertDispatched(CheckoutAccepted::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +343,8 @@ class AssetAcceptanceTest extends TestCase
|
|||||||
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||||
'asset_acceptance' => 'accepted',
|
'asset_acceptance' => 'accepted',
|
||||||
])
|
])
|
||||||
->assertRedirect(route('users.show', $assignee));
|
->assertRedirectToRoute('account.accept')
|
||||||
|
->assertSessionHas('error');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_stale_sign_in_place_post_with_missing_assignee_does_not_throw_route_error()
|
public function test_stale_sign_in_place_post_with_missing_assignee_does_not_throw_route_error()
|
||||||
@@ -320,4 +407,68 @@ class AssetAcceptanceTest extends TestCase
|
|||||||
->assertDontSee(route('users.show', $assignee), false)
|
->assertDontSee(route('users.show', $assignee), false)
|
||||||
->assertDontSee(route('hardware.checkout.create', $asset), false);
|
->assertDontSee(route('hardware.checkout.create', $asset), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_email_info_when_always_send_email_enabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => true]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
$response->assertDontSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_checkbox_when_always_send_email_disabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => false]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
$response->assertDontSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_hides_checkbox_when_always_send_eula_enabled(): void
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config([
|
||||||
|
'app.always_send_email' => false,
|
||||||
|
'app.always_send_eula' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
$response->assertDontSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_always_sends_copy_when_always_send_eula_enabled(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config([
|
||||||
|
'app.always_send_email' => false,
|
||||||
|
'app.always_send_eula' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->post(route('account.store-acceptance', $checkoutAcceptance), [
|
||||||
|
'asset_acceptance' => 'accepted',
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
Notification::assertSentTo(
|
||||||
|
$checkoutAcceptance->assignedTo,
|
||||||
|
AcceptanceItemAcceptedToUserNotification::class
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,30 @@ class ConsumableAcceptanceTest extends TestCase
|
|||||||
'quantity' => 2,
|
'quantity' => 2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_email_info_when_always_send_email_enabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => true]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
$response->assertDontSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_acceptance_create_page_shows_checkbox_when_always_send_email_disabled()
|
||||||
|
{
|
||||||
|
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
config(['app.always_send_email' => false]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($checkoutAcceptance->assignedTo)
|
||||||
|
->get(route('account.accept.item', $checkoutAcceptance));
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
$response->assertSee(trans('mail.send_pdf_copy'), false);
|
||||||
|
$response->assertDontSee(trans('general.acceptance_email_always_sent'), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
|
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
|
|
||||||
$this->admin = User::factory()->canViewReports()->create();
|
$this->admin = User::factory()->admin()->canViewReports()->create();
|
||||||
$this->assignee = User::factory()->create();
|
$this->assignee = User::factory()->create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$userWithoutPermission = User::factory()->create();
|
$userWithoutPermission = User::factory()->create();
|
||||||
|
|
||||||
$this->actingAs($userWithoutPermission)
|
$this->actingAs($userWithoutPermission)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $checkoutAcceptance->id,
|
'acceptance_id' => $checkoutAcceptance->id,
|
||||||
]))
|
]))
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
@@ -54,7 +54,7 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
public function test_reminder_not_sent_if_acceptance_does_not_exist()
|
public function test_reminder_not_sent_if_acceptance_does_not_exist()
|
||||||
{
|
{
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => 999999,
|
'acceptance_id' => 999999,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$checkoutAcceptanceAlreadyAccepted = CheckoutAcceptance::factory()->accepted()->create();
|
$checkoutAcceptanceAlreadyAccepted = CheckoutAcceptance::factory()->accepted()->create();
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $checkoutAcceptanceAlreadyAccepted->id,
|
'acceptance_id' => $checkoutAcceptanceAlreadyAccepted->id,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$checkoutAcceptance = $callback();
|
$checkoutAcceptance = $callback();
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $checkoutAcceptance->id,
|
'acceptance_id' => $checkoutAcceptance->id,
|
||||||
]))
|
]))
|
||||||
// check we didn't crash...
|
// check we didn't crash...
|
||||||
@@ -124,10 +124,10 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
->create();
|
->create();
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $acceptance->id,
|
'acceptance_id' => $acceptance->id,
|
||||||
]))
|
]))
|
||||||
->assertRedirect(route('reports/unaccepted_assets'));
|
->assertRedirect(route('reports/unaccepted_items'));
|
||||||
|
|
||||||
Mail::assertSent(CheckoutAccessoryMail::class, 1);
|
Mail::assertSent(CheckoutAccessoryMail::class, 1);
|
||||||
|
|
||||||
@@ -153,10 +153,10 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$this->createActionLogEntry($asset, $this->admin, $this->assignee, $acceptance);
|
$this->createActionLogEntry($asset, $this->admin, $this->assignee, $acceptance);
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $acceptance->id,
|
'acceptance_id' => $acceptance->id,
|
||||||
]))
|
]))
|
||||||
->assertRedirect(route('reports/unaccepted_assets'));
|
->assertRedirect(route('reports/unaccepted_items'));
|
||||||
|
|
||||||
Mail::assertSent(CheckoutAssetMail::class, 1);
|
Mail::assertSent(CheckoutAssetMail::class, 1);
|
||||||
|
|
||||||
@@ -182,10 +182,10 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$this->createActionLogEntry($consumable, $this->admin, $this->assignee, $acceptance);
|
$this->createActionLogEntry($consumable, $this->admin, $this->assignee, $acceptance);
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $acceptance->id,
|
'acceptance_id' => $acceptance->id,
|
||||||
]))
|
]))
|
||||||
->assertRedirect(route('reports/unaccepted_assets'));
|
->assertRedirect(route('reports/unaccepted_items'));
|
||||||
|
|
||||||
Mail::assertSent(CheckoutConsumableMail::class, 1);
|
Mail::assertSent(CheckoutConsumableMail::class, 1);
|
||||||
|
|
||||||
@@ -211,10 +211,10 @@ class AcceptanceReminderTest extends TestCase
|
|||||||
$this->createActionLogEntry($licenseSeat, $this->admin, $this->assignee, $acceptance);
|
$this->createActionLogEntry($licenseSeat, $this->admin, $this->assignee, $acceptance);
|
||||||
|
|
||||||
$this->actingAs($this->admin)
|
$this->actingAs($this->admin)
|
||||||
->post(route('reports/unaccepted_assets_sent_reminder', [
|
->post(route('reports/unaccepted_items_sent_reminder', [
|
||||||
'acceptance_id' => $acceptance->id,
|
'acceptance_id' => $acceptance->id,
|
||||||
]))
|
]))
|
||||||
->assertRedirect(route('reports/unaccepted_assets'));
|
->assertRedirect(route('reports/unaccepted_items'));
|
||||||
|
|
||||||
Mail::assertSent(CheckoutLicenseMail::class, 1);
|
Mail::assertSent(CheckoutLicenseMail::class, 1);
|
||||||
|
|
||||||
|
|||||||
+31
@@ -76,6 +76,21 @@ class EmailNotificationsToAdminAlertEmailUponCheckinTest extends TestCase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_admin_alert_email_sent_when_category_sends_email_and_admin_cc_always_disabled()
|
||||||
|
{
|
||||||
|
$this->settings
|
||||||
|
->enableAdminCC('cc@example.com')
|
||||||
|
->disableAdminCCAlways();
|
||||||
|
|
||||||
|
$this->category->update(['checkin_email' => true]);
|
||||||
|
|
||||||
|
$this->fireCheckInEvent($this->asset, $this->user);
|
||||||
|
|
||||||
|
Mail::assertSent(CheckinAssetMail::class, function (CheckinAssetMail $mail) {
|
||||||
|
return $mail->hasCc('cc@example.com') || $mail->hasTo('cc@example.com');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
|
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
|
||||||
{
|
{
|
||||||
$this->settings->enableAdminCC('cc@example.com');
|
$this->settings->enableAdminCC('cc@example.com');
|
||||||
@@ -121,6 +136,22 @@ class EmailNotificationsToAdminAlertEmailUponCheckinTest extends TestCase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_alert_email_receives_checkin_notification_when_admin_cc_always_enabled_and_admin_cc_email_empty()
|
||||||
|
{
|
||||||
|
$this->settings
|
||||||
|
->disableAdminCC()
|
||||||
|
->enableAlertEmail('alerts@example.com')
|
||||||
|
->enableAdminCCAlways();
|
||||||
|
|
||||||
|
$this->category->update(['checkin_email' => false]);
|
||||||
|
|
||||||
|
$this->fireCheckInEvent($this->asset, $this->user);
|
||||||
|
|
||||||
|
Mail::assertSent(CheckinAssetMail::class, function (CheckinAssetMail $mail) {
|
||||||
|
return $mail->hasTo('alerts@example.com') || $mail->hasCc('alerts@example.com');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private function fireCheckInEvent($asset, $user): void
|
private function fireCheckInEvent($asset, $user): void
|
||||||
{
|
{
|
||||||
event(new CheckoutableCheckedIn(
|
event(new CheckoutableCheckedIn(
|
||||||
|
|||||||
+31
@@ -66,6 +66,21 @@ class EmailNotificationsToAdminAlertEmailUponCheckoutTest extends TestCase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_admin_alert_email_sent_when_category_sends_email_and_admin_cc_always_disabled()
|
||||||
|
{
|
||||||
|
$this->settings
|
||||||
|
->enableAdminCC('cc@example.com')
|
||||||
|
->disableAdminCCAlways();
|
||||||
|
|
||||||
|
$this->category->update(['checkin_email' => true]);
|
||||||
|
|
||||||
|
$this->fireCheckoutEvent();
|
||||||
|
|
||||||
|
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
|
||||||
|
return $mail->hasCc('cc@example.com') || $mail->hasTo('cc@example.com');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
|
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
|
||||||
{
|
{
|
||||||
$this->settings->enableAdminCC('cc@example.com');
|
$this->settings->enableAdminCC('cc@example.com');
|
||||||
@@ -110,6 +125,22 @@ class EmailNotificationsToAdminAlertEmailUponCheckoutTest extends TestCase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_alert_email_receives_checkout_notification_when_admin_cc_always_enabled_and_admin_cc_email_empty()
|
||||||
|
{
|
||||||
|
$this->settings
|
||||||
|
->disableAdminCC()
|
||||||
|
->enableAlertEmail('alerts@example.com')
|
||||||
|
->enableAdminCCAlways();
|
||||||
|
|
||||||
|
$this->category->update(['checkin_email' => false]);
|
||||||
|
|
||||||
|
$this->fireCheckoutEvent();
|
||||||
|
|
||||||
|
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
|
||||||
|
return $mail->hasTo('alerts@example.com') || $mail->hasCc('alerts@example.com');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private function fireCheckoutEvent(): void
|
private function fireCheckoutEvent(): void
|
||||||
{
|
{
|
||||||
event(new CheckoutableCheckedOut(
|
event(new CheckoutableCheckedOut(
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ class EmailNotificationsToUserUponCheckinTest extends TestCase
|
|||||||
|
|
||||||
public function test_handles_user_not_having_email_address_set()
|
public function test_handles_user_not_having_email_address_set()
|
||||||
{
|
{
|
||||||
|
$this->settings
|
||||||
|
->disableAdminCC()
|
||||||
|
->disableAdminCCAlways()
|
||||||
|
->disableAlertEmail();
|
||||||
|
|
||||||
$user = User::factory()->create(['email' => null]);
|
$user = User::factory()->create(['email' => null]);
|
||||||
$asset = Asset::factory()->assignedToUser($user)->create();
|
$asset = Asset::factory()->assignedToUser($user)->create();
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ class EmailNotificationsToUserUponCheckoutTest extends TestCase
|
|||||||
|
|
||||||
public function test_handles_user_not_having_email_address_set()
|
public function test_handles_user_not_having_email_address_set()
|
||||||
{
|
{
|
||||||
|
$this->settings
|
||||||
|
->disableAdminCC()
|
||||||
|
->disableAdminCCAlways()
|
||||||
|
->disableAlertEmail();
|
||||||
|
|
||||||
$this->category->update(['checkin_email' => true]);
|
$this->category->update(['checkin_email' => true]);
|
||||||
$this->user->update(['email' => null]);
|
$this->user->update(['email' => null]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Reporting;
|
namespace Tests\Feature\Reporting;
|
||||||
|
|
||||||
|
use App\Models\CheckoutAcceptance;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Testing\TestResponse;
|
use Illuminate\Testing\TestResponse;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
@@ -44,14 +45,51 @@ class UnacceptedAssetReportTest extends TestCase
|
|||||||
public function test_permission_required_to_view_unaccepted_asset_report()
|
public function test_permission_required_to_view_unaccepted_asset_report()
|
||||||
{
|
{
|
||||||
$this->actingAs(User::factory()->create())
|
$this->actingAs(User::factory()->create())
|
||||||
->get(route('reports/unaccepted_assets'))
|
->get(route('reports/unaccepted_items'))
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_user_can_list_unaccepted_assets()
|
public function test_user_can_list_unaccepted_assets()
|
||||||
{
|
{
|
||||||
$this->actingAs(User::factory()->superuser()->create())
|
$this->actingAs(User::factory()->canViewReports()->create())
|
||||||
->get(route('reports/unaccepted_assets'))
|
->get(route('reports/unaccepted_items'))
|
||||||
->assertOk();
|
->assertOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_regular_user_cannot_perform_reminder_or_delete()
|
||||||
|
{
|
||||||
|
$user = User::factory()->canViewReports()->create();
|
||||||
|
$acceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('reports/unaccepted_items_sent_reminder'), ['acceptance_id' => $acceptance->id])
|
||||||
|
->assertForbidden();
|
||||||
|
$this->actingAs($user)
|
||||||
|
->delete(route('reports/unaccepted_items_delete', $acceptance->id))
|
||||||
|
->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_perform_reminder_and_delete()
|
||||||
|
{
|
||||||
|
$admin = User::factory()->admin()->canViewReports()->create();
|
||||||
|
$acceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->post(route('reports/unaccepted_items_sent_reminder'), ['acceptance_id' => $acceptance->id])
|
||||||
|
->assertStatus(302); // Or whatever is appropriate (redirect, etc)
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->delete(route('reports/unaccepted_items_delete', $acceptance->id))
|
||||||
|
->assertStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_superuser_can_perform_reminder_and_delete()
|
||||||
|
{
|
||||||
|
$superuser = User::factory()->superuser()->canViewReports()->create();
|
||||||
|
$acceptance = CheckoutAcceptance::factory()->pending()->create();
|
||||||
|
$this->actingAs($superuser)
|
||||||
|
->post(route('reports/unaccepted_items_sent_reminder'), ['acceptance_id' => $acceptance->id])
|
||||||
|
->assertStatus(302);
|
||||||
|
$this->actingAs($superuser)
|
||||||
|
->delete(route('reports/unaccepted_items_delete', $acceptance->id))
|
||||||
|
->assertStatus(302);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user