Merge branch 'develop' into snipeit_v7_laravel10

This commit is contained in:
Brady Wetherington 2024-05-06 18:44:03 +01:00
commit 5b02a43957
43 changed files with 2079 additions and 1851 deletions

View file

@ -3018,6 +3018,87 @@
"contributions": [
"code"
]
},
{
"login": "koiakoia",
"name": "koiakoia",
"avatar_url": "https://avatars.githubusercontent.com/u/60405354?v=4",
"profile": "https://github.com/koiakoia",
"contributions": [
"code"
]
},
{
"login": "mustafa-online",
"name": "Mustafa Online",
"avatar_url": "https://avatars.githubusercontent.com/u/5323832?v=4",
"profile": "https://github.com/mustafa-online",
"contributions": [
"code"
]
},
{
"login": "franceslui",
"name": "franceslui",
"avatar_url": "https://avatars.githubusercontent.com/u/104601439?v=4",
"profile": "https://github.com/franceslui",
"contributions": [
"code"
]
},
{
"login": "Q4kK",
"name": "Q4kK",
"avatar_url": "https://avatars.githubusercontent.com/u/125313163?v=4",
"profile": "https://github.com/Q4kK",
"contributions": [
"code"
]
},
{
"login": "squintfox",
"name": "squintfox",
"avatar_url": "https://avatars.githubusercontent.com/u/55590532?v=4",
"profile": "https://github.com/squintfox",
"contributions": [
"code"
]
},
{
"login": "jeffclay",
"name": "Jeff Clay",
"avatar_url": "https://avatars.githubusercontent.com/u/1380084?v=4",
"profile": "https://github.com/jeffclay",
"contributions": [
"code"
]
},
{
"login": "PP-JN-RL",
"name": "Phil J R",
"avatar_url": "https://avatars.githubusercontent.com/u/52716446?v=4",
"profile": "https://github.com/PP-JN-RL",
"contributions": [
"code"
]
},
{
"login": "chandanchowdhury",
"name": "i_virus",
"avatar_url": "https://avatars.githubusercontent.com/u/1496725?v=4",
"profile": "https://www.corelight.com/",
"contributions": [
"code"
]
},
{
"login": "gitgrimbo",
"name": "Paul Grime",
"avatar_url": "https://avatars.githubusercontent.com/u/1020541?v=4",
"profile": "https://github.com/gitgrimbo",
"contributions": [
"code"
]
}
]
}

View file

@ -432,6 +432,17 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bilias"><img src="https://avatars.githubusercontent.com/u/47315739?v=4?s=110" width="110px;" alt="bilias"/><br /><sub><b>bilias</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bilias" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coach1988"><img src="https://avatars.githubusercontent.com/u/2565989?v=4?s=110" width="110px;" alt="coach1988"/><br /><sub><b>coach1988</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=coach1988" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mauro-miatello"><img src="https://avatars.githubusercontent.com/u/11910225?v=4?s=110" width="110px;" alt="MrM"/><br /><sub><b>MrM</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mauro-miatello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/koiakoia"><img src="https://avatars.githubusercontent.com/u/60405354?v=4?s=110" width="110px;" alt="koiakoia"/><br /><sub><b>koiakoia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koiakoia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mustafa-online"><img src="https://avatars.githubusercontent.com/u/5323832?v=4?s=110" width="110px;" alt="Mustafa Online"/><br /><sub><b>Mustafa Online</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mustafa-online" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franceslui"><img src="https://avatars.githubusercontent.com/u/104601439?v=4?s=110" width="110px;" alt="franceslui"/><br /><sub><b>franceslui</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=franceslui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Q4kK"><img src="https://avatars.githubusercontent.com/u/125313163?v=4?s=110" width="110px;" alt="Q4kK"/><br /><sub><b>Q4kK</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Q4kK" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/squintfox"><img src="https://avatars.githubusercontent.com/u/55590532?v=4?s=110" width="110px;" alt="squintfox"/><br /><sub><b>squintfox</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=squintfox" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeffclay"><img src="https://avatars.githubusercontent.com/u/1380084?v=4?s=110" width="110px;" alt="Jeff Clay"/><br /><sub><b>Jeff Clay</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jeffclay" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PP-JN-RL"><img src="https://avatars.githubusercontent.com/u/52716446?v=4?s=110" width="110px;" alt="Phil J R"/><br /><sub><b>Phil J R</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PP-JN-RL" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.corelight.com/"><img src="https://avatars.githubusercontent.com/u/1496725?v=4?s=110" width="110px;" alt="i_virus"/><br /><sub><b>i_virus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chandanchowdhury" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gitgrimbo"><img src="https://avatars.githubusercontent.com/u/1020541?v=4?s=110" width="110px;" alt="Paul Grime"/><br /><sub><b>Paul Grime</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gitgrimbo" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View file

@ -12,7 +12,7 @@ It is built on [Laravel 8](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
> [!TIP]
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
-----

View file

@ -9,7 +9,6 @@ use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class SendExpectedCheckinAlerts extends Command
{
@ -43,25 +42,31 @@ class SendExpectedCheckinAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
$whenNotify = Carbon::now();
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$interval = $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
$this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
foreach ($assets as $asset) {
if ($asset->assigned && $asset->checkedOutToUser()) {
Log::info('Sending ExpectedCheckinNotification to ' . $asset->assigned->email);
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
$this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
}
}
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
}
}

View file

@ -3,10 +3,8 @@
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\License;
use App\Models\Recipients;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\ExpiringAssetsNotification;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
use DB;
@ -45,40 +43,26 @@ class SendUpcomingAuditReport extends Command
*/
public function handle()
{
$interval = $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$settings = Setting::getSettings();
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) {
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
// Assets due for auditing
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$assets = Asset::whereNotNull('next_audit_date')
->DueOrOverdueForAudit($settings)
->orderBy('last_audit_date', 'asc')->get();
if ($assets->count() > 0) {
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Audit report sent to '.$settings->alert_email);
} else {
$this->info('No assets to be audited. No report sent.');
}
} elseif ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (! $settings->audit_warning_days) {
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
} elseif ($settings->alerts_enabled != 1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
} else {
$this->error('Something went wrong. :( ');
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
}
}
}

View file

@ -59,7 +59,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $audit = null)
public function index(Request $request, $action = null, $upcoming_status = null)
{
$filter_non_deprecable_assets = false;
@ -155,17 +155,44 @@ class AssetsController extends Controller
$assets->TextSearch($request->input('search'));
}
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
/**
* Handle due and overdue audits and checkin dates
*/
switch ($action) {
case 'audits':
switch ($upcoming_status) {
case 'due':
$assets->DueForAudit($settings);
break;
case 'overdue':
$assets->OverdueForAudit();
break;
case 'due-or-overdue':
$assets->DueOrOverdueForAudit($settings);
break;
}
break;
case 'checkins':
switch ($upcoming_status) {
case 'due':
$assets->DueForCheckin($settings);
break;
case 'overdue':
$assets->OverdueForCheckin();
break;
case 'due-or-overdue':
$assets->DueOrOverdueForCheckin($settings);
break;
}
break;
}
}
/**
* End handling due and overdue audits and checkin dates
*/
// This is used by the sidenav, mostly

View file

@ -854,11 +854,11 @@ class AssetsController extends Controller
return view('hardware/audit-due');
}
public function overdueForAudit()
public function dueForCheckin()
{
$this->authorize('audit', Asset::class);
$this->authorize('checkin', Asset::class);
return view('hardware/audit-overdue');
return view('hardware/checkin-due');
}

View file

@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use App\Models\Asset;
use Auth;
use Closure;
use App\Models\Setting;
class AssetCountForSidebar
{
@ -24,6 +25,13 @@ class AssetCountForSidebar
\Log::debug($e);
}
try {
$total_assets = Asset::RTD()->count();
view()->share('total_assets', $total_assets);
} catch (\Exception $e) {
\Log::debug($e);
}
try {
$total_deployed_sidebar = Asset::Deployed()->count();
view()->share('total_deployed_sidebar', $total_deployed_sidebar);
@ -59,6 +67,44 @@ class AssetCountForSidebar
\Log::debug($e);
}
try {
$settings = Setting::getSettings();
view()->share('settings', $settings);
} catch (\Exception $e) {
\Log::debug($e);
}
try {
$total_due_for_audit = Asset::DueForAudit($settings)->count();
view()->share('total_due_for_audit', $total_due_for_audit);
} catch (\Exception $e) {
\Log::debug($e);
}
try {
$total_overdue_for_audit = Asset::OverdueForAudit()->count();
view()->share('total_overdue_for_audit', $total_overdue_for_audit);
} catch (\Exception $e) {
\Log::debug($e);
}
try {
$total_due_for_checkin = Asset::DueForCheckin($settings)->count();
view()->share('total_due_for_checkin', $total_due_for_checkin);
} catch (\Exception $e) {
\Log::debug($e);
}
try {
$total_overdue_for_checkin = Asset::OverdueForCheckin()->count();
view()->share('total_overdue_for_checkin', $total_overdue_for_checkin);
} catch (\Exception $e) {
\Log::debug($e);
}
view()->share('total_due_and_overdue_for_checkin', ($total_due_for_checkin + $total_overdue_for_checkin));
view()->share('total_due_and_overdue_for_audit', ($total_due_for_audit + $total_overdue_for_audit));
return $next($request);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Requests;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Support\Facades\Gate;
@ -45,12 +46,21 @@ class StoreAssetRequest extends ImageUploadRequest
*/
public function rules(): array
{
$rules = array_merge(
(new Asset)->getRules(),
$modelRules = (new Asset)->getRules();
if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) {
// If purchase_cost was submitted as a string with a comma separator
// then we need to ignore the normal numeric rules.
// Since the original rules still live on the model they will be run
// right before saving (and after purchase_cost has been
// converted to a float via setPurchaseCostAttribute).
$modelRules = $this->removeNumericRulesFromPurchaseCost($modelRules);
}
return array_merge(
$modelRules,
parent::rules(),
);
return $rules;
}
private function parseLastAuditDate(): void
@ -69,4 +79,20 @@ class StoreAssetRequest extends ImageUploadRequest
}
}
}
private function removeNumericRulesFromPurchaseCost(array $rules): array
{
$purchaseCost = $rules['purchase_cost'];
// If rule is in "|" format then turn it into an array
if (is_string($purchaseCost)) {
$purchaseCost = explode('|', $purchaseCost);
}
$rules['purchase_cost'] = array_filter($purchaseCost, function ($rule) {
return $rule !== 'numeric' && $rule !== 'gte:0';
});
return $rules;
}
}

View file

@ -74,9 +74,9 @@ class Asset extends Depreciable
'eol_explicit' => 'boolean',
'last_checkout' => 'datetime',
'last_checkin' => 'datetime',
'expected_checkin' => 'date',
'expected_checkin' => 'datetime:m-d-Y',
'last_audit_date' => 'datetime',
'next_audit_date' => 'date',
'next_audit_date' => 'datetime:m-d-Y',
'model_id' => 'integer',
'status_id' => 'integer',
'company_id' => 'integer',
@ -1163,10 +1163,11 @@ class Asset extends Depreciable
public function scopeDueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
return $query->whereNotNull('assets.next_audit_date')
->where('assets.next_audit_date', '>=', Carbon::now())
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
->whereBetween('assets.next_audit_date', [$today->format('Y-m-d'), $interval_date])
->where('assets.archived', '=', 0)
->NotArchived();
}
@ -1188,7 +1189,7 @@ class Asset extends Depreciable
public function scopeOverdueForAudit($query)
{
return $query->whereNotNull('assets.next_audit_date')
->where('assets.next_audit_date', '<', Carbon::now())
->where('assets.next_audit_date', '<', Carbon::now()->format('Y-m-d'))
->where('assets.archived', '=', 0)
->NotArchived();
}
@ -1209,14 +1210,69 @@ class Asset extends Depreciable
public function scopeDueOrOverdueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
return $query->whereNotNull('assets.next_audit_date')
->whereRaw('DATE_SUB('.DB::getTablePrefix()."assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
return $query->where(function ($query) {
$query->OverdueForAudit();
})->orWhere(function ($query) use ($settings) {
$query->DueForAudit($settings);
});
}
/**
* Query builder scope for Assets that are DUE for checkin, based on the assets.expected_checkin
* and settings.audit_warning_days. It checks to see if assets.expected_checkin is now
*
* @author A. Gianotto <snipe@snipe.net>
* @since v6.4.0
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueForCheckin($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
return $query->whereNotNull('assets.expected_checkin')
->whereBetween('assets.expected_checkin', [$today->format('Y-m-d'), $interval_date])
->where('assets.archived', '=', 0)
->whereNotNull('assets.assigned_to')
->NotArchived();
}
/**
* Query builder scope for Assets that are overdue for checkin OR overdue
*
* @author A. Gianotto <snipe@snipe.net>
* @since v6.4.0
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOverdueForCheckin($query)
{
return $query->whereNotNull('assets.expected_checkin')
->where('assets.expected_checkin', '<', Carbon::now()->format('Y-m-d'))
->where('assets.archived', '=', 0)
->whereNotNull('assets.assigned_to')
->NotArchived();
}
/**
* Query builder scope for Assets that are due for checkin OR overdue
*
* @author A. Gianotto <snipe@snipe.net>
* @since v6.4.0
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueOrOverdueForCheckin($query, $settings)
{
return $query->where(function ($query) {
$query->OverdueForCheckin();
})->orWhere(function ($query) use ($settings) {
$query->DueForCheckin($settings);
});
}
/**
* Query builder scope for Archived assets counting

View file

@ -0,0 +1,19 @@
<?php
namespace App\Models\Labels\Tapes\Brother;
use App\Helpers\Helper;
use App\Models\Labels\Label;
abstract class TZe_18mm extends Label
{
private const HEIGHT = 18.00;
private const MARGIN_SIDES = 3.20;
private const MARGIN_ENDS = 3.20;
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); }
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Models\Labels\Tapes\Brother;
class TZe_18mm_A extends TZe_18mm
{
private const BARCODE_SIZE = 3.20;
private const BARCODE_MARGIN = 0.30;
private const TEXT_SIZE_MOD = 1.00;
public function getUnit() { return 'mm'; }
public function getWidth() { return 50.0; }
public function getSupportAssetTag() { return true; }
public function getSupport1DBarcode() { return true; }
public function getSupport2DBarcode() { return false; }
public function getSupportFields() { return 1; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return false; }
public function preparePDF($pdf) {}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
if ($record->has('barcode1d')) {
static::write1DBarcode(
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
$pa->x1, $pa->y1, $pa->w, self::BARCODE_SIZE
);
}
$currentY = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN;
$usableHeight = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN;
$fontSize = $usableHeight + self::TEXT_SIZE_MOD;
$tagWidth = $pa->w / 3;
$fieldWidth = $pa->w / 3 * 2;
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $currentY,
'freemono', 'b', $fontSize, 'L',
$tagWidth, $usableHeight, true, 0, 0
);
if ($record->get('fields')->count() >= 1) {
static::writeText(
$pdf, $record->get('fields')->values()->get(0)['value'],
$pa->x1 + ($tagWidth), $currentY,
'freemono', 'b', $fontSize, 'R',
$fieldWidth, $usableHeight, true, 0, 0
);
}
}
}

12
composer.lock generated
View file

@ -11010,16 +11010,16 @@
},
{
"name": "tecnickcom/tcpdf",
"version": "6.7.4",
"version": "6.7.5",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
"reference": "d4adef47ca21c90e6483d59dcb9e5b1023696937"
"reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/d4adef47ca21c90e6483d59dcb9e5b1023696937",
"reference": "d4adef47ca21c90e6483d59dcb9e5b1023696937",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36",
"reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36",
"shasum": ""
},
"require": {
@ -11070,7 +11070,7 @@
],
"support": {
"issues": "https://github.com/tecnickcom/TCPDF/issues",
"source": "https://github.com/tecnickcom/TCPDF/tree/6.7.4"
"source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5"
},
"funding": [
{
@ -11078,7 +11078,7 @@
"type": "custom"
}
],
"time": "2024-03-25T23:56:24+00:00"
"time": "2024-04-20T17:25:10+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",

2793
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@
"postcss": "^8.4.5"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.0",
"@fortawesome/fontawesome-free": "^6.5.2",
"acorn": "^8.11.2",
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
@ -55,7 +55,7 @@
"select2": "4.0.13",
"sheetjs": "^2.0.0",
"signature_pad": "^4.2.0",
"tableexport.jquery.plugin": "1.28.0",
"tableexport.jquery.plugin": "1.30.0",
"tether": "^1.4.0",
"webpack": "^5.90.2"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,52 +1,52 @@
{
"/js/build/app.js": "/js/build/app.js?id=a05df3d0d95cb1cb86b26e858563009f",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=fbe33c09aabd4c4441bd0c7df51dae27",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=81c1f603db10885e65c582b6a4d9990a",
"/css/build/overrides.css": "/css/build/overrides.css?id=33450d0bef7d27f0f626ba360088c00c",
"/css/build/app.css": "/css/build/app.css?id=2b8033f07b6bddad5ab3c2cf944e2092",
"/js/build/app.js": "/js/build/app.js?id=ceb28a75cb946bd1ca5b42002e222142",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/build/overrides.css": "/css/build/overrides.css?id=59b5942be1960a787f9d1bb58e7dc068",
"/css/build/app.css": "/css/build/app.css?id=bd609c74fd0071b6f93af1869e9482b6",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=a67bd93bed52e6a29967fe472de66d6c",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=ff8ee9591f9689de2ceff34df3abf693",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e1a8f357e3f9f2fe2ca3aa73028d7660",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=ac9d06b3a2273c86e96f3d518556dcc6",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=9d579e001f4d9f61a63c801ee2da1570",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=eaa06b4be5365eeb47df9d78dc8d3f7b",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=9dc2632270ea725008c979ba0c3e5e6e",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=51e57ecb88994262fd444f8805ef8dc5",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=164148ec02f4c537f70cc1390ea35fca",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=f67a07ab14cd87fe782aa8b1558dbe51",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=875c9e6fdb26015a4eaad7c34abeba3d",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=a9cf77c9b9a7b1c9be035df00294d453",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=2c79e570b79bae4d510cce7e99032359",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=dc03c84f501f028349540a19cff67ac5",
"/css/dist/all.css": "/css/dist/all.css?id=d950c2d932c847f4dd554dc14fb87957",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=268041e902b019730c23ee3875838005",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=03075904b967308132b810bc0205ab1c",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=47ab28abd019c2b1f9aae60a3d44824a",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=af8c7daf7e9a2c784eafb76f65c418f7",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7fb8cf2421ad272b41393fdf5844559f",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=cf6c8c340420724b02d6e787ef9bded5",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=122d3df19d2c0552d7ef388e69f7d71f",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=5414c37b1403f41e051ad7b3aac112b4",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=bd61fefb56b30ed6d8c946f02bc956fb",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
"/css/dist/all.css": "/css/dist/all.css?id=90098fdcbf06c943723c1857f7c31d5d",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
"/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=189b85e9c72c6f75e464c3f58a6707cf",
"/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=ed4c23399d1013809882e90bfe396d1b",
"/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=be75b1958ae0da55e1eed562d9b7713d",
"/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=dfdc7801582dd0d20ea75faa3b96c296",
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=0141634c24336be626e05c8b77d1fa27",
"/css/webfonts/fa-brands-400.woff2": "/css/webfonts/fa-brands-400.woff2?id=b3cf7a6dd618bd392f3ddcc61343a463",
"/css/webfonts/fa-regular-400.ttf": "/css/webfonts/fa-regular-400.ttf?id=9cf69d99de9d83f82466a647f5cb1f94",
"/css/webfonts/fa-regular-400.woff2": "/css/webfonts/fa-regular-400.woff2?id=f0549181a126fe40849a53792bb0e077",
"/css/webfonts/fa-solid-900.ttf": "/css/webfonts/fa-solid-900.ttf?id=509c0e46de844df754d10179cf03c953",
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=96d16b1bdb177fd796c810b9e706c780",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=8994b282f9f3b7a00380bb1e2731a4bf",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=111e341dba724e1df946e8d1f406a7bd",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=99c395f0bab5966f32f63f4e55899e64",
"/js/build/vendor.js": "/js/build/vendor.js?id=db2e005808d5a2d2e7f4a82059e5d16f",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=857da5daffd13e0553510e5ccd410c79",
"/js/dist/all.js": "/js/dist/all.js?id=5f4bdd1b17a98eb4b59085823cf63972",
"/js/build/vendor.js": "/js/build/vendor.js?id=179bfe20e8767f7df32658c6b5a10ca3",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=e3bde6c62806c5ae510c964de17cd610",
"/js/dist/all.js": "/js/dist/all.js?id=99df559d106d7c1da6beff663646d76f",
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=19ccc62a8f1ea103dede4808837384d4",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=2c79e570b79bae4d510cce7e99032359",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=a9cf77c9b9a7b1c9be035df00294d453",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=875c9e6fdb26015a4eaad7c34abeba3d",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=f67a07ab14cd87fe782aa8b1558dbe51",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=81c1f603db10885e65c582b6a4d9990a",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=ac9d06b3a2273c86e96f3d518556dcc6",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=eaa06b4be5365eeb47df9d78dc8d3f7b",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=9d579e001f4d9f61a63c801ee2da1570",
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=fbe33c09aabd4c4441bd0c7df51dae27",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=164148ec02f4c537f70cc1390ea35fca",
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=51e57ecb88994262fd444f8805ef8dc5",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=9dc2632270ea725008c979ba0c3e5e6e",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=ff8ee9591f9689de2ceff34df3abf693",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=e1a8f357e3f9f2fe2ca3aa73028d7660",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=dc03c84f501f028349540a19cff67ac5"
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=bd61fefb56b30ed6d8c946f02bc956fb",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=5414c37b1403f41e051ad7b3aac112b4",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=47ab28abd019c2b1f9aae60a3d44824a",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=af8c7daf7e9a2c784eafb76f65c418f7",
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=122d3df19d2c0552d7ef388e69f7d71f",
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=cf6c8c340420724b02d6e787ef9bded5",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=7fb8cf2421ad272b41393fdf5844559f",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=268041e902b019730c23ee3875838005",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=03075904b967308132b810bc0205ab1c",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=f0fbbb0ac729ea092578fb05ca615460"
}

View file

@ -131,7 +131,7 @@
//Hover and active states
&:hover > a, &.active > a {
color: @sidebar-dark-hover-color;
background: @link-hover-border-color;
background: @sidebar-dark-hover-bg;
border-left-color: @link-hover-border-color;
}
//First Level Submenu

View file

@ -900,4 +900,10 @@ input[type="radio"]:checked::before {
}
.datepicker.dropdown-menu {
z-index: 1030 !important;
}
}
.sidebar-menu > li .badge {
margin-top: 0px;
filter: brightness(70%);
font-size: 70%;
}

View file

@ -313,6 +313,10 @@ return [
'token_expired' => 'Your form session has expired. Please try again.',
'login_enabled' => 'Login Enabled',
'audit_due' => 'Due for Audit',
'audit_due_days' => 'Assets Due for Audit Within :days Day|Assets Due for Audit Within :days Days',
'checkin_due' => 'Due for Checkin',
'checkin_overdue' => 'Overdue for Checkin',
'checkin_due_days' => 'Assets Due for Checkin Within :days Day|Assets Due for Checkin Within :days Days',
'audit_overdue' => 'Overdue for Audit',
'accept' => 'Accept :asset',
'i_accept' => 'I accept',

View file

@ -1,40 +1,59 @@
@extends('layouts/default')
@section('title0')
@if ((Request::get('company_id')) && ($company))
{{ $company->name }}
@endif
{{ trans('general.audit_due') }}
@stop
{{-- Page title --}}
@section('title')
@yield('title0') @parent
{{ trans_choice('general.audit_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }}
@stop
{{-- Page content --}}
@section('content')
{{-- Page content --}}
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-body">
@include('partials.asset-bulk-actions')
<!-- Custom Tabs -->
<div class="nav-tabs-custom">
<ul class="nav nav-tabs hidden-print">
<li class="active">
<a href="#due" data-toggle="tab">{{ trans('general.audit_due') }}
<span class="hidden-lg hidden-md">
<i class="far fa-file fa-2x" aria-hidden="true"></i>
</span>
<span class="badge">{{ (isset($total_due_for_audit)) ? $total_due_for_audit : '' }}</span>
</a>
</li>
<li>
<a href="#overdue" data-toggle="tab">{{ trans('general.audit_overdue') }}
<span class="hidden-lg hidden-md">
<i class="far fa-file fa-2x" aria-hidden="true"></i>
</span>
<span class="badge">{{ (isset($total_overdue_for_audit)) ? $total_overdue_for_audit : '' }}</span>
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="due">
@include('partials.asset-bulk-actions',
[
'id_divname' => 'dueAssetEditToolbar',
'id_formname' => 'dueAssetEditForm',
'id_button' => 'dueAssetEditButton'])
<div class="row">
<div class="table table-responsive">
<div class="col-md-12">
<table
data-click-to-select="true"
data-columns="{{ \App\Presenters\AssetAuditPresenter::dataTableLayout() }}"
data-cookie-id-table="assetsAuditListingTable"
data-cookie-id-table="dueAssetAuditListing"
data-pagination="true"
data-id-table="assetsAuditListingTable"
data-id-table="dueAssetAuditListing"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
@ -44,28 +63,71 @@
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
data-bulk-form-id="#assetsBulkForm"
id="assetsAuditListingTable"
data-toolbar="#dueAssetEditToolbar"
data-bulk-button-id="#dueAssetEditButton"
data-bulk-form-id="#dueAssetEditForm"
id="#dueAssetAuditListing"
class="table table-striped snipe-table"
data-url="{{ route('api.asset.to-audit', ['audit' => 'due']) }}"
data-url="{{ route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due']) }}"
data-export-options='{
"fileName": "export-assets-due-audit-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- end col-md-12 -->
</div><!-- end table-responsive -->
</div><!-- end row -->
</div><!-- end tab-pane -->
<div class="tab-pane" id="overdue">
@include('partials.asset-bulk-actions',
[
'id_divname' => 'overdueAssetEditToolbar',
'id_formname' => 'overdueAssetEditForm',
'id_button' => 'overdueAssetEditButton'])
<div class="row">
<div class="table table-responsive">
<div class="col-md-12">
<table
data-click-to-select="true"
data-columns="{{ \App\Presenters\AssetAuditPresenter::dataTableLayout() }}"
data-cookie-id-table="overdueAssetAuditListing"
data-pagination="true"
data-id-table="overdueAssetAuditListing"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
data-toolbar="#overdueAssetEditToolbar"
data-bulk-button-id="#overdueAssetEditButton"
data-bulk-form-id="#overdueAssetEditForm"
id="#overdueAssetAuditListing"
class="table table-striped snipe-table"
data-url="{{ route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'overdue']) }}"
data-export-options='{
"fileName": "export-assets-overdue-audit-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- end col-md-12 -->
</div><!-- end table-responsive -->
</div><!-- end row -->
</div><!-- end tab-pane -->
</div><!-- end tab-content -->
</div><!-- end nav-tabs-custom -->
</div><!-- /.col -->
</div><!-- /.row -->
{{ Form::close() }}
</div><!-- ./box-body -->
</div><!-- /.box -->
</div>
</div>
@stop
@section('moar_scripts')
@include('partials.bootstrap-table')
@stop

View file

@ -1,69 +0,0 @@
@extends('layouts/default')
@section('title0')
@if ((Request::get('company_id')) && ($company))
{{ $company->name }}
@endif
{{ trans('general.audit_overdue') }}
@stop
{{-- Page title --}}
@section('title')
@yield('title0') @parent
@stop
{{-- Page content --}}
@section('content')
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-body">
@include('partials.asset-bulk-actions')
<div class="row">
<div class="col-md-12">
<table
data-click-to-select="true"
data-columns="{{ \App\Presenters\AssetAuditPresenter::dataTableLayout() }}"
data-cookie-id-table="assetsOverdueAuditListingTable"
data-pagination="true"
data-id-table="assetsOverdueAuditListingTable"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
data-toolbar="#assetsBulkEditToolbar"
data-bulk-button-id="#bulkAssetEditButton"
data-bulk-form-id="#assetsBulkForm"
id="assetsAuditListingTable"
class="table table-striped snipe-table"
data-url="{{ route('api.asset.to-audit', ['audit' => 'overdue']) }}"
data-export-options='{
"fileName": "export-assets-due-audit-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div><!-- /.col -->
</div><!-- /.row -->
{{ Form::close() }}
</div><!-- ./box-body -->
</div><!-- /.box -->
</div>
</div>
@stop
@section('moar_scripts')
@include('partials.bootstrap-table')
@stop

View file

@ -0,0 +1,131 @@
@extends('layouts/default')
{{-- Page title --}}
@section('title')
{{ trans_choice('general.checkin_due_days', $settings->audit_warning_days, ['days' => $settings->audit_warning_days]) }}
@stop
{{-- Page content --}}
@section('content')
{{-- Page content --}}
<div class="row">
<div class="col-md-12">
<!-- Custom Tabs -->
<div class="nav-tabs-custom">
<ul class="nav nav-tabs hidden-print">
<li class="active">
<a href="#due" data-toggle="tab">{{ trans('general.checkin_due') }}
<span class="hidden-lg hidden-md">
<i class="far fa-file fa-2x" aria-hidden="true"></i>
</span>
<span class="badge">{{ (isset($total_due_for_checkin)) ? $total_due_for_checkin : '' }}</span>
</a>
</li>
<li>
<a href="#overdue" data-toggle="tab">{{ trans('general.checkin_overdue') }}
<span class="hidden-lg hidden-md">
<i class="far fa-file fa-2x" aria-hidden="true"></i>
</span>
<span class="badge">{{ (isset($total_overdue_for_checkin)) ? $total_overdue_for_checkin : '' }}</span>
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="due">
@include('partials.asset-bulk-actions',
[
'id_divname' => 'dueAssetEditToolbar',
'id_formname' => 'dueAssetEditForm',
'id_button' => 'dueAssetEditButton'])
<div class="row">
<div class="table table-responsive">
<div class="col-md-12">
<table
data-click-to-select="true"
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-cookie-id-table="dueAssetcheckinListing"
data-pagination="true"
data-id-table="dueAssetcheckinListing"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
data-toolbar="#dueAssetEditToolbar"
data-bulk-button-id="#dueAssetEditButton"
data-bulk-form-id="#dueAssetEditForm"
id="#dueAssetcheckinListing"
class="table table-striped snipe-table"
data-url="{{ route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due']) }}"
data-export-options='{
"fileName": "export-assets-due-checkin-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- end col-md-12 -->
</div><!-- end table-responsive -->
</div><!-- end row -->
</div><!-- end tab-pane -->
<div class="tab-pane" id="overdue">
@include('partials.asset-bulk-actions',
[
'id_divname' => 'overdueAssetEditToolbar',
'id_formname' => 'overdueAssetEditForm',
'id_button' => 'overdueAssetEditButton'])
<div class="row">
<div class="table table-responsive">
<div class="col-md-12">
<table
data-click-to-select="true"
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
data-cookie-id-table="overdueAssetcheckinListing"
data-pagination="true"
data-id-table="overdueAssetcheckinListing"
data-search="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
data-toolbar="#overdueAssetEditToolbar"
data-bulk-button-id="#overdueAssetEditButton"
data-bulk-form-id="#overdueAssetEditForm"
id="#overdueAssetcheckinListing"
class="table table-striped snipe-table"
data-url="{{ route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'overdue']) }}"
data-export-options='{
"fileName": "export-assets-overdue-checkin-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- end col-md-12 -->
</div><!-- end table-responsive -->
</div><!-- end row -->
</div><!-- end tab-pane -->
</div><!-- end tab-content -->
</div><!-- end nav-tabs-custom -->
</div><!-- /.col -->
</div><!-- /.row -->
@stop
@section('moar_scripts')
@include('partials.bootstrap-table')
@stop

View file

@ -51,7 +51,7 @@
<i class="far fa-save fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.licenses') }}
{!! ($asset->licenses->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->licenses->count()).'</badge>' : '' !!}
{!! ($asset->licenses->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->licenses->count()).'</span>' : '' !!}
</span>
</a>
</li>
@ -62,7 +62,7 @@
<i class="far fa-hdd fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.components') }}
{!! ($asset->components->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->components->count()).'</badge>' : '' !!}
{!! ($asset->components->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->components->count()).'</span>' : '' !!}
</span>
</a>
</li>
@ -73,7 +73,7 @@
<i class="fas fa-barcode fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.assets') }}
{!! ($asset->assignedAssets()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->assignedAssets()->count()).'</badge>' : '' !!}
{!! ($asset->assignedAssets()->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->assignedAssets()->count()).'</span>' : '' !!}
</span>
</a>
@ -96,7 +96,7 @@
<i class="fas fa-wrench fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.maintenances') }}
{!! ($asset->assetmaintenances()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->assetmaintenances()->count()).'</badge>' : '' !!}
{!! ($asset->assetmaintenances()->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->assetmaintenances()->count()).'</span>' : '' !!}
</span>
</a>
</li>
@ -107,7 +107,7 @@
<i class="far fa-file fa-2x" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.files') }}
{!! ($asset->uploads->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->uploads->count()).'</badge>' : '' !!}
{!! ($asset->uploads->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->uploads->count()).'</span>' : '' !!}
</span>
</a>
</li>
@ -119,7 +119,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.additional_files') }}
{!! ($asset->model) && ($asset->model->uploads->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($asset->model->uploads->count()).'</badge>' : '' !!}
{!! ($asset->model) && ($asset->model->uploads->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->model->uploads->count()).'</span>' : '' !!}
</span>
</a>
</li>

View file

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"
dir="{{ in_array(app()->getLocale(),['ar-SA','fa-IR', 'he-IL']) ? 'rtl' : 'ltr' }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -437,6 +438,9 @@
<a href="{{ url('hardware') }}">
<i class="far fa-circle text-grey fa-fw" aria-hidden="true"></i>
{{ trans('general.list_all') }}
<span class="badge">
{{ (isset($total_assets)) ? $total_assets : '' }}
</span>
</a>
</li>
@ -447,7 +451,8 @@
<a href="{{ route('statuslabels.show', ['statuslabel' => $status_nav->id]) }}">
<i class="fas fa-circle text-grey fa-fw"
aria-hidden="true"{!! ($status_nav->color!='' ? ' style="color: '.e($status_nav->color).'"' : '') !!}></i>
{{ $status_nav->name }} ({{ $status_nav->asset_count }})</a></li>
{{ $status_nav->name }}
<span class="badge badge-secondary">{{ $status_nav->asset_count }})</span></a></li>
@endforeach
@endif
@ -455,49 +460,43 @@
<li{!! (Request::query('status') == 'Deployed' ? ' class="active"' : '') !!}>
<a href="{{ url('hardware?status=Deployed') }}">
<i class="far fa-circle text-blue fa-fw"></i>
{{ trans('general.all') }}
{{ trans('general.deployed') }}
({{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }})
<span class="badge">{{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'RTD' ? ' class="active"' : '') !!}>
<a href="{{ url('hardware?status=RTD') }}">
<i class="far fa-circle text-green fa-fw"></i>
{{ trans('general.all') }}
{{ trans('general.ready_to_deploy') }}
({{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }})
<span class="badge">{{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'Pending' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=Pending') }}"><i
class="far fa-circle text-orange fa-fw"></i>
{{ trans('general.all') }}
{{ trans('general.pending') }}
({{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }})
<span class="badge">{{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'Undeployable' ? ' class="active"' : '') !!} ><a
href="{{ url('hardware?status=Undeployable') }}"><i
class="fas fa-times text-red fa-fw"></i>
{{ trans('general.all') }}
{{ trans('general.undeployable') }}
({{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }})
<span class="badge">{{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'byod' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=byod') }}"><i
class="fas fa-times text-red fa-fw"></i>
{{ trans('general.all') }}
{{ trans('general.byod') }}
({{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }})
<span class="badge">{{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'Archived' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=Archived') }}"><i
class="fas fa-times text-red fa-fw"></i>
{{ trans('general.all') }}
{{ trans('admin/hardware/general.archived') }}
({{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }})
<span class="badge">{{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }}</span>
</a>
</li>
<li{!! (Request::query('status') == 'Requestable' ? ' class="active"' : '') !!}><a
@ -511,13 +510,18 @@
<li{!! (Request::is('hardware/audit/due') ? ' class="active"' : '') !!}>
<a href="{{ route('assets.audit.due') }}">
<i class="fas fa-history text-yellow fa-fw"></i> {{ trans('general.audit_due') }}
<span class="badge">{{ (isset($total_due_and_overdue_for_audit)) ? $total_due_and_overdue_for_audit : '' }}</span>
</a>
</li>
<li{!! (Request::is('hardware/audit/overdue') ? ' class="active"' : '') !!}>
<a href="{{ route('assets.audit.overdue') }}">
<i class="fas fa-exclamation-triangle text-red fa-fw"></i> {{ trans('general.audit_overdue') }}
</a>
</li>
@endcan
@can('checkin', \App\Models\Asset::class)
<li{!! (Request::is('hardware/checkins/due') ? ' class="active"' : '') !!}>
<a href="{{ route('assets.checkins.due') }}">
<i class="fas fa-history text-yellow fa-fw"></i> {{ trans('general.checkin_due') }}
<span class="badge">{{ (isset($total_due_and_overdue_for_checkin)) ? $total_due_and_overdue_for_checkin : '' }}</span>
</a>
</li>
@endcan
<li class="divider">&nbsp;</li>

View file

@ -10,7 +10,7 @@
@php
$checkin = Helper::getFormattedDateObject($asset->expected_checkin, 'date');
@endphp
| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assigned->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assigned->id]) }}) | {{ $checkin['formatted'] }}
| [{{ $asset->present()->name }}]({{ route('hardware.show', ['hardware' => $asset->id]) }}) | [{{ $asset->assignedTo->present()->fullName }}]({{ route($asset->targetShowRoute().'.show', [$asset->assignedTo->id]) }}) | {{ $checkin['formatted'] }}
@endforeach
@endcomponent

View file

@ -496,13 +496,27 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
)->name('api.assets.show.byserial')
->where('any', '.*');
Route::get('audit/{audit}',
// LEGACY URL - Get assets that are due or overdue for audit
Route::get('audit/{status}',
[
Api\AssetsController::class,
'index'
]
)->name('api.asset.to-audit');
// This gets the "due or overdue" API endpoints for audits and checkins
Route::get('{action}/{upcoming_status}',
[
Api\AssetsController::class,
'index'
]
)->name('api.assets.list-upcoming')
->where(['action' => 'audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']);
Route::post('audit',
[
Api\AssetsController::class,

View file

@ -50,26 +50,10 @@ Route::group(
[AssetsController::class, 'dueForAudit']
)->name('assets.audit.due');
Route::get('audit/overdue',
[AssetsController::class, 'overdueForAudit']
)->name('assets.audit.overdue');
Route::get('audit/due',
[AssetsController::class, 'dueForAudit']
)->name('assets.audit.due');
Route::get('audit/overdue',
[AssetsController::class, 'overdueForAudit']
)->name('assets.audit.overdue');
Route::get('audit/due',
[AssetsController::class, 'dueForAudit']
)->name('assets.audit.due');
Route::get('audit/overdue',
[AssetsController::class, 'overdueForAudit']
)->name('assets.audit.overdue');
Route::get('checkins/due',
[AssetsController::class, 'dueForCheckin']
)->name('assets.checkins.due');
Route::get('audit/{id}',
[AssetsController::class, 'audit']
)->name('asset.audit.create');

View file

@ -7,10 +7,10 @@ use App\Models\Company;
use App\Models\User;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;
use Carbon\Carbon;
class AssetIndexTest extends TestCase
{
public function testAssetIndexReturnsExpectedAssets()
public function testAssetApiIndexReturnsExpectedAssets()
{
Asset::factory()->count(3)->create();
@ -30,7 +30,102 @@ class AssetIndexTest extends TestCase
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetIndexAdheresToCompanyScoping()
public function testAssetApiIndexReturnsDisplayUpcomingAuditsDue()
{
Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due']))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetApiIndexReturnsOverdueForAudit()
{
Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'overdue']))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetApiIndexReturnsDueOrOverdueForAudit()
{
Asset::factory()->count(3)->create(['next_audit_date' => Carbon::now()->format('Y-m-d')]);
Asset::factory()->count(2)->create(['next_audit_date' => Carbon::now()->subDays(1)->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.list-upcoming', ['action' => 'audits', 'upcoming_status' => 'due-or-overdue']))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc());
}
public function testAssetApiIndexReturnsDueForExpectedCheckin()
{
Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due'])
)
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetApiIndexReturnsOverdueForExpectedCheckin()
{
Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'overdue']))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
}
public function testAssetApiIndexReturnsDueOrOverdueForExpectedCheckin()
{
Asset::factory()->count(3)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->subDays(1)->format('Y-m-d')]);
Asset::factory()->count(2)->create(['assigned_to' => '1', 'expected_checkin' => Carbon::now()->format('Y-m-d')]);
$this->actingAsForApi(User::factory()->superuser()->create())
->getJson(route('api.assets.list-upcoming', ['action' => 'checkins', 'upcoming_status' => 'due-or-overdue']))
->assertOk()
->assertJsonStructure([
'total',
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 5)->etc());
}
public function testAssetApiIndexAdheresToCompanyScoping()
{
[$companyA, $companyB] = Company::factory()->count(2)->create();

View file

@ -2,6 +2,7 @@
namespace Tests\Feature\Api\Assets;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
@ -12,6 +13,7 @@ use App\Models\Supplier;
use App\Models\User;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Testing\Fluent\AssertableJson;
use Illuminate\Testing\TestResponse;
use Tests\TestCase;
class AssetStoreTest extends TestCase
@ -278,6 +280,50 @@ class AssetStoreTest extends TestCase
->assertStatusMessageIs('error');
}
public function testStoresPeriodAsDecimalSeparatorForPurchaseCost()
{
$this->settings->set([
'default_currency' => 'USD',
'digit_separator' => '1,234.56',
]);
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => 'random-string',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
// API accepts float
'purchase_cost' => 12.34,
])
->assertStatusMessageIs('success');
$asset = Asset::find($response['payload']['id']);
$this->assertEquals(12.34, $asset->purchase_cost);
}
public function testStoresPeriodAsCommaSeparatorForPurchaseCost()
{
$this->settings->set([
'default_currency' => 'EUR',
'digit_separator' => '1.234,56',
]);
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.assets.store'), [
'asset_tag' => 'random-string',
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
// API also accepts string for comma separated values
'purchase_cost' => '12,34',
])
->assertStatusMessageIs('success');
$asset = Asset::find($response['payload']['id']);
$this->assertEquals(12.34, $asset->purchase_cost);
}
public function testUniqueSerialNumbersIsEnforcedWhenEnabled()
{
$model = AssetModel::factory()->create();

View file

@ -16,4 +16,13 @@ class HelperTest extends TestCase
{
$this->assertIsString(Helper::defaultChartColors(-1));
}
public function testParseCurrencyMethod()
{
$this->settings->set(['default_currency' => 'USD']);
$this->assertSame(12.34, Helper::ParseCurrency('USD 12.34'));
$this->settings->set(['digit_separator' => '1.234,56']);
$this->assertSame(12.34, Helper::ParseCurrency('12,34'));
}
}

View file

@ -87,7 +87,7 @@ if($upgrade_requirements){
// done fetching requirements
if (!$no_interactive) {
$yesno = readline("\nProceed with upgrade? [Y/n]: ");
$yesno = readline("\nProceed with upgrade? [y/N]: ");
} else {
$yesno = "yes";
}