Merge branch 'develop' into lastnameemail
This commit is contained in:
commit
f659b7631d
85 changed files with 1689 additions and 320 deletions
19
README.md
19
README.md
|
@ -76,26 +76,37 @@ Since the release of the JSON REST API, several third-party developers have been
|
|||
> [!NOTE]
|
||||
> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
|
||||
|
||||
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
#### Libraries & Modules
|
||||
|
||||
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
|
||||
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
|
||||
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
|
||||
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
|
||||
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
|
||||
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
|
||||
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
|
||||
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
|
||||
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
|
||||
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
|
||||
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [UniFi to Snipe-IT](https://www.edtechirl.com/p/snipe-it-and-azure-asset-management) originally by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
|
||||
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
|
||||
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
|
||||
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
|
||||
- [InQRy (archived)](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
|
||||
- [Marksman (archived)](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
|
||||
- [Python Module (archived)](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
|
||||
|
||||
We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks.
|
||||
|
||||
#### Mobile Apps
|
||||
|
||||
We're currently working on our own mobile app, but in the meantime, check out these third-party apps that work with Snipe-IT:
|
||||
|
||||
- [SnipeMate](https://snipemate.app/) (iOS, Google Play, Huawei AppGallery) by Mars Technology
|
||||
- [Snipe-Scan](https://apps.apple.com/do/app/snipe-scan/id6744179400?uo=2) (iOS) by Nicolas Maton
|
||||
- [Snipe-IT Assets Management](https://play.google.com/store/apps/details?id=com.diegogarciadev.assetsmanager.snipeit&hl=en&pli=1) (Google Play) by DiegoGarciaDEV
|
||||
- [AssetX](https://apps.apple.com/my/app/assetx-for-snipe-it/id6741996196?uo=2) (iOS) for Snipe-IT by Rishi Gupta
|
||||
|
||||
-----
|
||||
|
||||
### Join the Community!
|
||||
|
|
151
app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php
Normal file
151
app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Actionlog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FixBulkAccessoryCheckinActionLogEntries extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:fix-bulk-accessory-action-log-entries {--dry-run : Run the sync process but don\'t update the database} {--skip-backup : Skip pre-execution backup}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This script attempts to fix timestamps and missing created_by values for bulk checkin entries in the log table';
|
||||
|
||||
private bool $dryrun = false;
|
||||
private bool $skipBackup = false;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->skipBackup = $this->option('skip-backup');
|
||||
$this->dryrun = $this->option('dry-run');
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('This is a DRY RUN - no changes will be saved.');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$logs = Actionlog::query()
|
||||
// only look for accessory checkin logs
|
||||
->where('item_type', Accessory::class)
|
||||
// that were part of a bulk checkin
|
||||
->where('note', 'Bulk checkin items')
|
||||
// logs that were improperly timestamped should have created_at in the 1970s
|
||||
->whereYear('created_at', '1970')
|
||||
->get();
|
||||
|
||||
if ($logs->isEmpty()) {
|
||||
$this->info('No logs found with incorrect timestamps.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:');
|
||||
|
||||
$this->table(
|
||||
['ID', 'Created By', 'Created At', 'Updated At'],
|
||||
$logs->map(function ($log) {
|
||||
return [
|
||||
$log->id,
|
||||
$log->created_by,
|
||||
$log->created_at,
|
||||
$log->updated_at,
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
if (!$this->dryrun && !$this->confirm('Update these logs?')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$this->dryrun && !$this->skipBackup) {
|
||||
$this->info('Backing up the database before making changes...');
|
||||
$this->call('snipeit:backup');
|
||||
}
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->newLine();
|
||||
$this->info('DRY RUN. NOT ACTUALLY UPDATING LOGS.');
|
||||
}
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$this->newLine();
|
||||
$this->info('Processing log id:' . $log->id);
|
||||
|
||||
// created_by was not being set for accessory bulk checkins
|
||||
// so let's see if there was another bulk checkin log
|
||||
// with the same timestamp and a created_by value we can use.
|
||||
if (is_null($log->created_by)) {
|
||||
$createdByFromSimilarLog = $this->getCreatedByAttributeFromSimilarLog($log);
|
||||
|
||||
if ($createdByFromSimilarLog) {
|
||||
$this->line(vsprintf('Updating log id:%s created_by to %s', [$log->id, $createdByFromSimilarLog]));
|
||||
$log->created_by = $createdByFromSimilarLog;
|
||||
} else {
|
||||
$this->warn(vsprintf('No created_by found for log id:%s', [$log->id]));
|
||||
$this->warn('Skipping updating this log since no similar log was found to update created_by from.');
|
||||
|
||||
// If we can't find a similar log then let's skip updating it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at]));
|
||||
$log->created_at = $log->updated_at;
|
||||
|
||||
if (!$this->dryrun) {
|
||||
Model::withoutTimestamps(function () use ($log) {
|
||||
$log->saveQuietly();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($this->dryrun) {
|
||||
$this->info('DRY RUN. NO CHANGES WERE ACTUALLY MADE.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hopefully the bulk checkin included other items like assets or licenses
|
||||
* so we can use one of those logs to get the correct created_by value.
|
||||
*
|
||||
* This method attempts to find a bulk check in log that was
|
||||
* created at the same time as the log passed in.
|
||||
*/
|
||||
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int
|
||||
{
|
||||
$similarLog = Actionlog::query()
|
||||
->whereNotNull('created_by')
|
||||
->where([
|
||||
'action_type' => 'checkin from',
|
||||
'note' => 'Bulk checkin items',
|
||||
'target_id' => $log->target_id,
|
||||
'target_type' => $log->target_type,
|
||||
'created_at' => $log->updated_at,
|
||||
])
|
||||
->first();
|
||||
|
||||
if ($similarLog) {
|
||||
return $similarLog->created_by;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
51
app/Console/Commands/TestLocationsFMCS.php
Normal file
51
app/Console/Commands/TestLocationsFMCS.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class TestLocationsFMCS extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'snipeit:test-locations-fmcs {--location_id=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Test for company ID inconsistencies if FullMultipleCompanySupport with scoped locations will be used.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('This script checks for company ID inconsistencies if Full Multiple Company Support with scoped locations will be used.');
|
||||
$this->info('This could take few moments if have a very large dataset.');
|
||||
$this->newLine();
|
||||
|
||||
// if parameter location_id is set, only test this location
|
||||
$location_id = null;
|
||||
if ($this->option('location_id')) {
|
||||
$location_id = $this->option('location_id');
|
||||
}
|
||||
|
||||
$mismatched = Helper::test_locations_fmcs(true, $location_id);
|
||||
$this->warn(trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)));
|
||||
$this->newLine();
|
||||
$this->info('Edit your locations to associate them with the correct company.');
|
||||
|
||||
$header = ['Type', 'ID', 'Name', 'Checkout Type', 'Company ID', 'Item Company', 'Item Location', 'Location Company', 'Location Company ID'];
|
||||
sort($mismatched);
|
||||
|
||||
$this->table($header, $mismatched);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ use App\Models\Depreciation;
|
|||
use App\Models\Setting;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\License;
|
||||
use App\Models\Location;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Carbon\Carbon;
|
||||
|
@ -1529,4 +1530,93 @@ class Helper
|
|||
}
|
||||
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for inconsistencies before activating scoped locations with FullMultipleCompanySupport
|
||||
* If there are locations with different companies than related objects unforseen problems could arise
|
||||
*
|
||||
* @author T. Regnery <tobias.regnery@gmail.com>
|
||||
* @since 7.0
|
||||
*
|
||||
* @param $artisan when false, bail out on first inconsistent entry
|
||||
* @param $location_id when set, only test this specific location
|
||||
* @param $new_company_id in case of updating a location, this is the newly requested company_id
|
||||
* @return string []
|
||||
*/
|
||||
static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) {
|
||||
$mismatched = [];
|
||||
|
||||
if ($location_id) {
|
||||
$location = Location::find($location_id);
|
||||
if ($location) {
|
||||
$locations = collect([])->push(Location::find($location_id));
|
||||
}
|
||||
} else {
|
||||
$locations = Location::all();
|
||||
}
|
||||
|
||||
foreach($locations as $location) {
|
||||
// in case of an update of a single location, use the newly requested company_id
|
||||
if ($new_company_id) {
|
||||
$location_company = $new_company_id;
|
||||
} else {
|
||||
$location_company = $location->company_id;
|
||||
}
|
||||
|
||||
// Depending on the relationship, we must use different operations to retrieve the objects
|
||||
$keywords_relation = [
|
||||
'many' => [
|
||||
'accessories',
|
||||
'assets',
|
||||
'assignedAccessories',
|
||||
'assignedAssets',
|
||||
'components',
|
||||
'consumables',
|
||||
'rtd_assets',
|
||||
'users',
|
||||
],
|
||||
'one' => [
|
||||
'manager',
|
||||
'parent',
|
||||
]];
|
||||
|
||||
// In case of a single location, the children must be checked as well, because we don't walk every location
|
||||
if ($location_id) {
|
||||
$keywords_relation['many'][] = 'children';
|
||||
}
|
||||
|
||||
foreach ($keywords_relation as $relation => $keywords) {
|
||||
foreach($keywords as $keyword) {
|
||||
if ($relation == 'many') {
|
||||
$items = $location->{$keyword}->all();
|
||||
} else {
|
||||
$items = collect([])->push($location->$keyword);
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
|
||||
if ($item && $item->company_id != $location_company) {
|
||||
$mismatched[] = [
|
||||
class_basename(get_class($item)),
|
||||
$item->id,
|
||||
$item->name ?? $item->asset_tag ?? $item->serial ?? $item->username,
|
||||
str_replace('App\\Models\\', '', $item->assigned_type) ?? null,
|
||||
$item->company_id ?? null,
|
||||
$item->company->name ?? null,
|
||||
// $item->defaultLoc->id ?? null,
|
||||
// $item->defaultLoc->name ?? null,
|
||||
// $item->defaultLoc->company->id ?? null,
|
||||
// $item->defaultLoc->company->name ?? null,
|
||||
$item->location->name ?? null,
|
||||
$item->location->company->name ?? null,
|
||||
$location_company ?? null,
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $mismatched;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ use Illuminate\Support\Facades\Log;
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use App\View\Label;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1064,7 +1065,7 @@ class AssetsController extends Controller
|
|||
* @param int $id
|
||||
* @since [v4.0]
|
||||
*/
|
||||
public function audit(Request $request): JsonResponse
|
||||
public function audit(Request $request, Asset $asset): JsonResponse
|
||||
|
||||
{
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
@ -1072,36 +1073,15 @@ class AssetsController extends Controller
|
|||
$settings = Setting::getSettings();
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
|
||||
// No tag passed - return an error
|
||||
if (!$request->filled('asset_tag')) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => '',
|
||||
'error' => trans('admin/hardware/message.no_tag'),
|
||||
], trans('admin/hardware/message.no_tag')), 200);
|
||||
// Allow the asset tag to be passed in the payload (legacy method)
|
||||
if ($request->filled('asset_tag')) {
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
}
|
||||
|
||||
|
||||
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
|
||||
|
||||
|
||||
if ($asset) {
|
||||
|
||||
/**
|
||||
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
|
||||
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
|
||||
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
|
||||
* the audit log entry we're creating through this controller.
|
||||
*
|
||||
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
|
||||
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
|
||||
* will bypass normal model-level validation that's usually handled at the observer )
|
||||
*
|
||||
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
|
||||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
*/
|
||||
$asset->unsetEventDispatcher();
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
$asset->next_audit_date = $dt;
|
||||
|
||||
if ($request->filled('next_audit_date')) {
|
||||
|
@ -1116,33 +1096,89 @@ class AssetsController extends Controller
|
|||
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// Set up the payload for re-display in the API response
|
||||
$payload = [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => $request->input('note'),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Update custom fields in the database.
|
||||
* Validation for these fields is handled through the AssetRequest form request
|
||||
* $model = AssetModel::find($request->get('model_id'));
|
||||
*/
|
||||
if (($asset->model) && ($asset->model->fieldset)) {
|
||||
$payload['custom_fields'] = [];
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Validate custom fields
|
||||
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
|
||||
|
||||
// Validate the rest of the data before we turn off the event dispatcher
|
||||
if ($asset->isInvalid()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
|
||||
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
|
||||
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
|
||||
* the audit log entry we're creating through this controller.
|
||||
*
|
||||
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
|
||||
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
|
||||
* will bypass normal model-level validation that's usually handled at the observer)
|
||||
*
|
||||
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
|
||||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
|
||||
/**
|
||||
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
|
||||
* We have to invoke this manually because of the unsetEventDispatcher() above.)
|
||||
*/
|
||||
if ($asset->isValid() && $asset->save()) {
|
||||
$asset->logAudit(request('note'), request('location_id'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', [
|
||||
'asset_tag' => e($asset->asset_tag),
|
||||
'note' => e($request->input('note')),
|
||||
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
|
||||
], trans('admin/hardware/message.audit.success')));
|
||||
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
|
||||
}
|
||||
|
||||
// Asset failed validation or was not able to be saved
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => e($asset->asset_tag),
|
||||
'error' => $asset->getErrors()->first(),
|
||||
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
|
||||
}
|
||||
|
||||
|
||||
// No matching asset for the asset tag that was passed.
|
||||
return response()->json(Helper::formatStandardApiResponse('error', [
|
||||
'asset_tag' => e($request->input('asset_tag')),
|
||||
'error' => trans('admin/hardware/message.audit.error'),
|
||||
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -136,13 +136,13 @@ class LicenseSeatsController extends Controller
|
|||
if ($licenseSeat->save()) {
|
||||
|
||||
if ($is_checkin) {
|
||||
$licenseSeat->logCheckin($target, $request->input('note'));
|
||||
$licenseSeat->logCheckin($target, $request->input('notes'));
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
||||
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
|
||||
$licenseSeat->logCheckout($request->input('note'), $target);
|
||||
$licenseSeat->logCheckout($request->input('notes'), $target);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ use App\Http\Transformers\SelectlistTransformer;
|
|||
use App\Models\Accessory;
|
||||
use App\Models\AccessoryCheckout;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
@ -46,6 +48,7 @@ class LocationsController extends Controller
|
|||
'id',
|
||||
'image',
|
||||
'ldap_ou',
|
||||
'company_id',
|
||||
'manager_id',
|
||||
'name',
|
||||
'rtd_assets_count',
|
||||
|
@ -74,8 +77,10 @@ class LocationsController extends Controller
|
|||
'locations.image',
|
||||
'locations.ldap_ou',
|
||||
'locations.currency',
|
||||
'locations.company_id',
|
||||
'locations.notes',
|
||||
])
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assignedAssets as assigned_assets_count')
|
||||
->withCount('assets as assets_count')
|
||||
->withCount('assignedAccessories as assigned_accessories_count')
|
||||
|
@ -84,6 +89,11 @@ class LocationsController extends Controller
|
|||
->withCount('children as children_count')
|
||||
->withCount('users as users_count');
|
||||
|
||||
// Only scope locations if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$locations = Company::scopeCompanyables($locations);
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$locations = $locations->TextSearch($request->input('search'));
|
||||
}
|
||||
|
@ -116,6 +126,10 @@ class LocationsController extends Controller
|
|||
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$locations->where('locations.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
|
||||
// Make sure the offset and limit are actually integers and do not exceed system limits
|
||||
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
|
||||
$limit = app('api_limit_value');
|
||||
|
@ -132,6 +146,9 @@ class LocationsController extends Controller
|
|||
case 'manager':
|
||||
$locations->OrderManager($order);
|
||||
break;
|
||||
case 'company':
|
||||
$locations->OrderCompany($order);
|
||||
break;
|
||||
default:
|
||||
$locations->orderBy($sort, $order);
|
||||
break;
|
||||
|
@ -159,6 +176,15 @@ class LocationsController extends Controller
|
|||
$location->fill($request->all());
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
// Only scope location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
|
||||
// check if parent is set and has a different company
|
||||
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
|
||||
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($location->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success')));
|
||||
}
|
||||
|
@ -176,7 +202,7 @@ class LocationsController extends Controller
|
|||
public function show($id) : JsonResponse | array
|
||||
{
|
||||
$this->authorize('view', Location::class);
|
||||
$location = Location::with('parent', 'manager', 'children')
|
||||
$location = Location::with('parent', 'manager', 'children', 'company')
|
||||
->select([
|
||||
'locations.id',
|
||||
'locations.name',
|
||||
|
@ -220,6 +246,19 @@ class LocationsController extends Controller
|
|||
$location->fill($request->all());
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
// Only scope location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
|
||||
// check if there are related objects with different company
|
||||
if (Helper::test_locations_fmcs(false, $id, $location->company_id)) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations'));
|
||||
}
|
||||
} else {
|
||||
$location->company_id = $request->get('company_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($location->isValid()) {
|
||||
|
||||
$location->save();
|
||||
|
@ -340,6 +379,11 @@ class LocationsController extends Controller
|
|||
'locations.image',
|
||||
]);
|
||||
|
||||
// Only scope locations if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$locations = Company::scopeCompanyables($locations);
|
||||
}
|
||||
|
||||
$page = 1;
|
||||
if ($request->filled('page')) {
|
||||
$page = $request->input('page');
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Events\CheckoutableCheckedIn;
|
|||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Requests\UpdateAssetRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Http\Requests\UploadFileRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -390,26 +391,26 @@ class AssetsController extends Controller
|
|||
$asset = $request->handleImages($asset);
|
||||
|
||||
// Update custom fields in the database.
|
||||
// Validation for these fields is handlded through the AssetRequest form request
|
||||
// FIXME: No idea why this is returning a Builder error on db_column_name.
|
||||
// Need to investigate and fix. Using static method for now.
|
||||
$model = AssetModel::find($request->get('model_id'));
|
||||
if (($model) && ($model->fieldset)) {
|
||||
foreach ($model->fieldset->fields as $field) {
|
||||
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
if ($request->has($field->db_column)) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -865,13 +866,6 @@ class AssetsController extends Controller
|
|||
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
|
||||
}
|
||||
|
||||
public function audit(Asset $asset)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
|
||||
}
|
||||
|
||||
public function dueForAudit()
|
||||
{
|
||||
|
@ -888,19 +882,59 @@ class AssetsController extends Controller
|
|||
}
|
||||
|
||||
|
||||
public function audit(Asset $asset)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->authorize('audit', Asset::class);
|
||||
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
|
||||
return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list');
|
||||
}
|
||||
|
||||
public function auditStore(UploadFileRequest $request, Asset $asset)
|
||||
{
|
||||
|
||||
$this->authorize('audit', Asset::class);
|
||||
|
||||
$rules = [
|
||||
'location_id' => 'exists:locations,id|nullable|numeric',
|
||||
'next_audit_date' => 'date|nullable',
|
||||
];
|
||||
$originalValues = $asset->getRawOriginal();
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
|
||||
// Check to see if they checked the box to update the physical location,
|
||||
// not just note it in the audit notes
|
||||
if ($request->input('update_location') == '1') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
// Update custom fields in the database
|
||||
if (($asset->model) && ($asset->model->fieldset)) {
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
|
||||
if ($field->field_encrypted == '1') {
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
|
||||
} else {
|
||||
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (is_array($request->input($field->db_column))) {
|
||||
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
|
||||
} else {
|
||||
$asset->{$field->db_column} = $request->input($field->db_column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate custom fields
|
||||
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
|
||||
|
||||
// Validate the rest of the data before we turn off the event dispatcher
|
||||
if ($asset->isInvalid()) {
|
||||
return redirect()->back()->withInput()->withErrors($asset->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -917,18 +951,11 @@ class AssetsController extends Controller
|
|||
* which manually invokes Watson Validating to make sure the asset's model is valid.
|
||||
*
|
||||
* @see \App\Observers\AssetObserver::updating()
|
||||
* @see \App\Models\Asset::save()
|
||||
*/
|
||||
|
||||
$asset->unsetEventDispatcher();
|
||||
|
||||
$asset->next_audit_date = $request->input('next_audit_date');
|
||||
$asset->last_audit_date = date('Y-m-d H:i:s');
|
||||
|
||||
// Check to see if they checked the box to update the physical location,
|
||||
// not just note it in the audit notes
|
||||
if ($request->input('update_location') == '1') {
|
||||
$asset->location_id = $request->input('location_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
|
||||
|
@ -942,7 +969,7 @@ class AssetsController extends Controller
|
|||
$file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
|
||||
}
|
||||
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
|
||||
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
|
||||
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
|
||||
}
|
||||
|
||||
|
|
|
@ -71,20 +71,28 @@ class BulkAssetModelsController extends Controller
|
|||
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) {
|
||||
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
|
||||
}
|
||||
|
||||
if (($request->filled('category_id') && ($request->input('category_id') != 'NC'))) {
|
||||
$update_array['category_id'] = $request->input('category_id');
|
||||
}
|
||||
|
||||
if ($request->input('fieldset_id') != 'NC') {
|
||||
$update_array['fieldset_id'] = $request->input('fieldset_id');
|
||||
}
|
||||
|
||||
if ($request->input('depreciation_id') != 'NC') {
|
||||
$update_array['depreciation_id'] = $request->input('depreciation_id');
|
||||
}
|
||||
|
||||
if ($request->filled('requestable') != '') {
|
||||
if ($request->input('requestable') != '') {
|
||||
$update_array['requestable'] = $request->input('requestable');
|
||||
}
|
||||
|
||||
if ($request->filled('min_amt')) {
|
||||
$update_array['min_amt'] = $request->input('min_amt');
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (count($update_array) > 0) {
|
||||
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
|
||||
|
|
|
@ -106,6 +106,7 @@ class CustomFieldsController extends Controller
|
|||
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
|
||||
"display_checkin" => $request->get("display_checkin", 0),
|
||||
"display_checkout" => $request->get("display_checkout", 0),
|
||||
"display_audit" => $request->get("display_audit", 0),
|
||||
"created_by" => auth()->id()
|
||||
]);
|
||||
|
||||
|
@ -250,6 +251,7 @@ class CustomFieldsController extends Controller
|
|||
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
|
||||
$field->display_checkin = $request->get("display_checkin", 0);
|
||||
$field->display_checkout = $request->get("display_checkout", 0);
|
||||
$field->display_audit = $request->get("display_audit", 0);
|
||||
|
||||
if ($request->get('format') == 'CUSTOM REGEX') {
|
||||
$field->format = e($request->get('custom_format'));
|
||||
|
|
|
@ -83,6 +83,10 @@ class GroupsController extends Controller
|
|||
{
|
||||
$permissions = config('permissions');
|
||||
$groupPermissions = $group->decodePermissions();
|
||||
|
||||
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
|
||||
$groupPermissions = [];
|
||||
}
|
||||
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
|
||||
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ class LabelsController extends Controller
|
|||
$exampleAsset->order_number = '12345';
|
||||
$exampleAsset->purchase_date = '2023-01-01';
|
||||
$exampleAsset->status_id = 1;
|
||||
$exampleAsset->location_id = 1;
|
||||
|
||||
$exampleAsset->company = new Company([
|
||||
'name' => trans('admin/labels/table.example_company'),
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -79,6 +82,18 @@ class LocationsController extends Controller
|
|||
$location->phone = request('phone');
|
||||
$location->fax = request('fax');
|
||||
$location->notes = $request->input('notes');
|
||||
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
|
||||
// Only scope the location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
// check if parent is set and has a different company
|
||||
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
|
||||
return redirect()->back()->withInput()->withInput()->with('error', 'different company than parent');
|
||||
}
|
||||
} else {
|
||||
$location->company_id = $request->input('company_id');
|
||||
}
|
||||
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
|
@ -131,6 +146,17 @@ class LocationsController extends Controller
|
|||
$location->manager_id = $request->input('manager_id');
|
||||
$location->notes = $request->input('notes');
|
||||
|
||||
// Only scope the location if the setting is enabled
|
||||
if (Setting::getSettings()->scope_locations_fmcs) {
|
||||
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
|
||||
// check if there are related objects with different company
|
||||
if (Helper::test_locations_fmcs(false, $locationId, $location->company_id)) {
|
||||
return redirect()->back()->withInput()->withInput()->with('error', 'error scoped locations');
|
||||
}
|
||||
} else {
|
||||
$location->company_id = $request->input('company_id');
|
||||
}
|
||||
|
||||
$location = $request->handleImages($location);
|
||||
|
||||
if ($location->save()) {
|
||||
|
@ -203,20 +229,22 @@ class LocationsController extends Controller
|
|||
|
||||
public function print_assigned($id) : View | RedirectResponse
|
||||
{
|
||||
|
||||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$company = Company::where('id', $location->company_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
return view('locations/print')
|
||||
->with('assets', $assets)
|
||||
->with('users',$users)
|
||||
->with('location', $location)
|
||||
->with('parent', $parent)
|
||||
->with('manager', $manager)
|
||||
->with('company', $company);
|
||||
}
|
||||
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -288,10 +316,16 @@ class LocationsController extends Controller
|
|||
if ($location = Location::where('id', $id)->first()) {
|
||||
$parent = Location::where('id', $location->parent_id)->first();
|
||||
$manager = User::where('id', $location->manager_id)->first();
|
||||
$company = Company::where('id', $location->company_id)->first();
|
||||
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
|
||||
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
|
||||
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
|
||||
|
||||
return view('locations/print')
|
||||
->with('assets', $assets)
|
||||
->with('users',$users)
|
||||
->with('location', $location)
|
||||
->with('parent', $parent)
|
||||
->with('manager', $manager)
|
||||
->with('company', $company);
|
||||
}
|
||||
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
|
||||
}
|
||||
|
|
|
@ -243,7 +243,7 @@ class ReportsController extends Controller
|
|||
|
||||
$header = [
|
||||
trans('general.date'),
|
||||
trans('general.admin'),
|
||||
trans('general.created_by'),
|
||||
trans('general.action'),
|
||||
trans('general.type'),
|
||||
trans('general.item'),
|
||||
|
|
|
@ -314,7 +314,23 @@ class SettingsController extends Controller
|
|||
$setting->modellist_displays = implode(',', $request->input('show_in_model_list'));
|
||||
}
|
||||
|
||||
$old_locations_fmcs = $setting->scope_locations_fmcs;
|
||||
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
|
||||
$setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0');
|
||||
|
||||
// Backward compatibility for locations makes no sense without FullMultipleCompanySupport
|
||||
if (!$setting->full_multiple_companies_support) {
|
||||
$setting->scope_locations_fmcs = '0';
|
||||
}
|
||||
|
||||
// check for inconsistencies when activating scoped locations
|
||||
if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') {
|
||||
$mismatched = Helper::test_locations_fmcs(false);
|
||||
if (count($mismatched) != 0) {
|
||||
return redirect()->back()->withInput()->with('error', trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)).' '.trans('admin/settings/message.location_scoping.not_saved'));
|
||||
}
|
||||
}
|
||||
|
||||
$setting->unique_serial = $request->input('unique_serial', '0');
|
||||
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
|
||||
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
|
||||
|
|
|
@ -10,19 +10,36 @@ trait MayContainCustomFields
|
|||
// this gets called automatically on a form request
|
||||
public function withValidator($validator)
|
||||
{
|
||||
// find the model
|
||||
if ($this->method() == 'POST') {
|
||||
$asset_model = AssetModel::find($this->model_id);
|
||||
}
|
||||
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
|
||||
$asset_model = $this->asset->model;
|
||||
|
||||
// In case the model is being changed via form
|
||||
if (request()->has('model_id')!='') {
|
||||
|
||||
$asset_model = AssetModel::find(request()->input('model_id'));
|
||||
|
||||
// or if we have it available to route-model-binding
|
||||
} elseif ((request()->route('asset') && (request()->route('asset')->model_id))) {
|
||||
|
||||
$asset_model = AssetModel::find(request()->route('asset')->model_id);
|
||||
|
||||
} else {
|
||||
|
||||
if ($this->method() == 'POST') {
|
||||
$asset_model = AssetModel::find($this->model_id);
|
||||
}
|
||||
|
||||
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
|
||||
$asset_model = $this->asset->model;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// collect the custom fields in the request
|
||||
$validator->after(function ($validator) use ($asset_model) {
|
||||
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
|
||||
return str_starts_with($attributes, '_snipeit_');
|
||||
});
|
||||
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||
|
||||
// if there are custom fields, find the ones that don't exist on the model's fieldset and add an error to the validator's error bag
|
||||
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
|
||||
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
|
||||
->each(function ($request_field_name) use ($request_fields, $validator) {
|
||||
|
|
|
@ -50,6 +50,9 @@ class CustomFieldsTransformer
|
|||
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
|
||||
'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false,
|
||||
'show_in_listview' => ($field->show_in_listview == '1') ? true : false,
|
||||
'display_checkin' => ($field->display_checkin == '1') ? true : false,
|
||||
'display_checkout' => ($field->display_checkout == '1') ? true : false,
|
||||
'display_audit' => ($field->display_audit == '1') ? true : false,
|
||||
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
|
||||
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
|
||||
];
|
||||
|
|
|
@ -63,6 +63,10 @@ class LocationsTransformer
|
|||
'name'=> e($location->parent->name),
|
||||
] : null,
|
||||
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
|
||||
'company' => ($location->company) ? [
|
||||
'id' => (int) $location->company->id,
|
||||
'name'=> e($location->company->name)
|
||||
] : null,
|
||||
|
||||
'children' => $children_arr,
|
||||
];
|
||||
|
|
|
@ -61,6 +61,7 @@ class Accessory extends SnipeModel
|
|||
'qty' => 'required|integer|min:1',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
|
|
|
@ -57,7 +57,9 @@ class Actionlog extends SnipeModel
|
|||
'user_agent',
|
||||
'item_type',
|
||||
'target_type',
|
||||
'action_source'
|
||||
'action_source',
|
||||
'created_at',
|
||||
'action_date',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -69,7 +71,25 @@ class Actionlog extends SnipeModel
|
|||
'company' => ['name'],
|
||||
'adminuser' => ['first_name','last_name','username', 'email'],
|
||||
'user' => ['first_name','last_name','username', 'email'],
|
||||
'assets' => ['asset_tag','name', 'serial'],
|
||||
'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'],
|
||||
'assets.model' => ['name', 'model_number', 'eol', 'notes'],
|
||||
'assets.model.category' => ['name', 'notes'],
|
||||
'assets.model.manufacturer' => ['name', 'notes'],
|
||||
'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'],
|
||||
'licenses.category' => ['name', 'notes'],
|
||||
'licenses.supplier' => ['name'],
|
||||
'consumables' => ['name', 'notes', 'order_number', 'model_number', 'item_no', 'purchase_date'],
|
||||
'consumables.category' => ['name', 'notes'],
|
||||
'consumables.location' => ['name', 'notes'],
|
||||
'consumables.supplier' => ['name', 'notes'],
|
||||
'components' => ['name', 'notes', 'purchase_date'],
|
||||
'components.category' => ['name', 'notes'],
|
||||
'components.location' => ['name', 'notes'],
|
||||
'components.supplier' => ['name', 'notes'],
|
||||
'accessories' => ['name', 'purchase_date'],
|
||||
'accessories.category' => ['name'],
|
||||
'accessories.location' => ['name', 'notes'],
|
||||
'accessories.supplier' => ['name', 'notes'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -134,6 +154,54 @@ class Actionlog extends SnipeModel
|
|||
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> license relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> consumable relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> consumable relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> components relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item type relationship
|
||||
*
|
||||
|
|
|
@ -108,8 +108,8 @@ class Asset extends Depreciable
|
|||
'expected_checkin' => ['nullable', 'date'],
|
||||
'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'next_audit_date' => ['nullable', 'date'],
|
||||
'location_id' => ['nullable', 'exists:locations,id'],
|
||||
'rtd_location_id' => ['nullable', 'exists:locations,id'],
|
||||
'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
|
||||
'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
|
||||
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||
'serial' => ['nullable', 'unique_undeleted:assets,serial'],
|
||||
'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'],
|
||||
|
@ -122,7 +122,7 @@ class Asset extends Depreciable
|
|||
'assigned_to' => ['nullable', 'integer'],
|
||||
'requestable' => ['nullable', 'boolean'],
|
||||
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
|
||||
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'],
|
||||
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'],
|
||||
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
|
||||
];
|
||||
|
||||
|
@ -213,6 +213,31 @@ class Asset extends Depreciable
|
|||
$this->attributes['expected_checkin'] = $value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function customFieldValidationRules()
|
||||
{
|
||||
|
||||
$customFieldValidationRules = [];
|
||||
|
||||
if (($this->model) && ($this->model->fieldset)) {
|
||||
|
||||
foreach ($this->model->fieldset->fields as $field) {
|
||||
|
||||
if ($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
$customFieldValidationRules += $this->model->fieldset->validation_rules();
|
||||
}
|
||||
|
||||
return $customFieldValidationRules;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This handles the custom field validation for assets
|
||||
*
|
||||
|
@ -220,29 +245,7 @@ class Asset extends Depreciable
|
|||
*/
|
||||
public function save(array $params = [])
|
||||
{
|
||||
if ($this->model_id != '') {
|
||||
$model = AssetModel::find($this->model_id);
|
||||
|
||||
if (($model) && ($model->fieldset)) {
|
||||
|
||||
foreach ($model->fieldset->fields as $field){
|
||||
if($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
$this->rules += $model->fieldset->validation_rules();
|
||||
|
||||
if ($this->model->fieldset){
|
||||
foreach ($this->model->fieldset->fields as $field){
|
||||
if($field->format == 'BOOLEAN'){
|
||||
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->rules += $this->customFieldValidationRules();
|
||||
return parent::save($params);
|
||||
}
|
||||
|
||||
|
@ -254,7 +257,7 @@ class Asset extends Depreciable
|
|||
|
||||
/**
|
||||
* Returns the warranty expiration date as Carbon object
|
||||
* @return \Carbon|null
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function getWarrantyExpiresAttribute()
|
||||
{
|
||||
|
@ -687,6 +690,21 @@ class Asset extends Depreciable
|
|||
->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of audits for this asset
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function audits()
|
||||
{
|
||||
return $this->assetlog()->where('action_type', '=', 'audit')
|
||||
->orderBy('created_at', 'desc')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of checkins for this asset
|
||||
*
|
||||
|
|
|
@ -13,6 +13,13 @@ trait CompanyableTrait
|
|||
*/
|
||||
public static function bootCompanyableTrait()
|
||||
{
|
||||
static::addGlobalScope(new CompanyableScope);
|
||||
// In Version 7.0 and before locations weren't scoped by companies, so add a check for the backward compatibility setting
|
||||
if (__CLASS__ != 'App\Models\Location') {
|
||||
static::addGlobalScope(new CompanyableScope);
|
||||
} else {
|
||||
if (Setting::getSettings()->scope_locations_fmcs == 1) {
|
||||
static::addGlobalScope(new CompanyableScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class Component extends SnipeModel
|
|||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'supplier_id' => 'nullable|integer|exists:suppliers,id',
|
||||
'company_id' => 'integer|nullable|exists:companies,id',
|
||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
||||
|
|
|
@ -49,6 +49,7 @@ class Consumable extends SnipeModel
|
|||
'qty' => 'required|integer|min:0|max:99999',
|
||||
'category_id' => 'required|integer',
|
||||
'company_id' => 'integer|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||
'min_amt' => 'integer|min:0|max:99999|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
|
|
|
@ -30,6 +30,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
|
|||
protected $fillable = [
|
||||
'assigned_to',
|
||||
'asset_id',
|
||||
'notes',
|
||||
];
|
||||
|
||||
use Acceptable;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Setting;
|
||||
use App\Models\SnipeModel;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Models\User;
|
||||
|
@ -18,6 +19,7 @@ use Watson\Validating\ValidatingTrait;
|
|||
class Location extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
use CompanyableTrait;
|
||||
|
||||
protected $presenter = \App\Presenters\LocationPresenter::class;
|
||||
use Presentable;
|
||||
|
@ -34,11 +36,13 @@ class Location extends SnipeModel
|
|||
'zip' => 'max:10|nullable',
|
||||
'manager_id' => 'exists:users,id|nullable',
|
||||
'parent_id' => 'nullable|exists:locations,id|non_circular:locations,id',
|
||||
'company_id' => 'integer|nullable|exists:companies,id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'parent_id' => 'integer',
|
||||
'manager_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -72,6 +76,7 @@ class Location extends SnipeModel
|
|||
'currency',
|
||||
'manager_id',
|
||||
'image',
|
||||
'company_id',
|
||||
'notes',
|
||||
];
|
||||
protected $hidden = ['user_id'];
|
||||
|
@ -91,7 +96,8 @@ class Location extends SnipeModel
|
|||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'parent' => ['name'],
|
||||
'parent' => ['name'],
|
||||
'company' => ['name']
|
||||
];
|
||||
|
||||
|
||||
|
@ -215,6 +221,17 @@ class Location extends SnipeModel
|
|||
->with('parent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the locations -> company relationship
|
||||
*
|
||||
* @author [T. Regnery] [<tobias.regnery@gmail.com>]
|
||||
* @since [v7.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the manager of a location
|
||||
|
@ -326,4 +343,17 @@ class Location extends SnipeModel
|
|||
{
|
||||
return $query->leftJoin('users as location_user', 'locations.manager_id', '=', 'location_user.id')->orderBy('location_user.first_name', $order)->orderBy('location_user.last_name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCompany($query, $order)
|
||||
{
|
||||
return $query->leftJoin('companies as company_sort', 'locations.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,9 +220,41 @@ trait Loggable
|
|||
* @since [v4.0]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logAudit($note, $location_id, $filename = null)
|
||||
public function logAudit($note, $location_id, $filename = null, $originalValues = [])
|
||||
{
|
||||
|
||||
$log = new Actionlog;
|
||||
|
||||
if (static::class == Asset::class) {
|
||||
if ($asset = Asset::find($log->item_id)) {
|
||||
// add the custom fields that were changed
|
||||
if ($asset->model->fieldset) {
|
||||
$fields_array = [];
|
||||
foreach ($asset->model->fieldset->fields as $field) {
|
||||
if ($field->display_audit == 1) {
|
||||
$fields_array[$field->db_column] = $asset->{$field->db_column};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
|
||||
unset($originalValues['updated_at'], $originalValues['last_audit_date']);
|
||||
foreach ($originalValues as $key => $value) {
|
||||
|
||||
if ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
|
||||
$location = Location::find($location_id);
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
|
@ -235,6 +267,7 @@ trait Loggable
|
|||
$log->note = $note;
|
||||
$log->created_by = auth()->id();
|
||||
$log->filename = $filename;
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->logaction('audit');
|
||||
|
||||
$params = [
|
||||
|
@ -276,6 +309,7 @@ trait Loggable
|
|||
$log->item_id = $this->id;
|
||||
}
|
||||
$log->location_id = null;
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->note = $note;
|
||||
$log->created_by = $created_by;
|
||||
$log->logaction('create');
|
||||
|
@ -303,6 +337,7 @@ trait Loggable
|
|||
$log->note = $note;
|
||||
$log->target_id = null;
|
||||
$log->created_at = date('Y-m-d H:i:s');
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
$log->filename = $filename;
|
||||
$log->logaction('uploaded');
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
|||
'locale' => 'max:10|nullable',
|
||||
'website' => 'url|nullable|max:191',
|
||||
'manager_id' => 'nullable|exists:users,id|cant_manage_self',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable|fmcs_location',
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
|
||||
'autoassign_licenses' => 'boolean',
|
||||
|
|
|
@ -229,7 +229,7 @@ class AccessoryPresenter extends Presenter
|
|||
'field' => 'created_by',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => trans('general.admin'),
|
||||
'title' => trans('general.created_by'),
|
||||
'visible' => false,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
],
|
||||
|
|
|
@ -102,6 +102,10 @@ class ActionlogPresenter extends Presenter
|
|||
return 'fas fa-sticky-note';
|
||||
}
|
||||
|
||||
if ($this->action_type == 'audit') {
|
||||
return 'fas fa-clipboard-check';
|
||||
}
|
||||
|
||||
return 'fa-solid fa-rotate-right';
|
||||
|
||||
}
|
||||
|
|
|
@ -555,7 +555,7 @@ class AssetPresenter extends Presenter
|
|||
*/
|
||||
public function statusMeta()
|
||||
{
|
||||
if ($this->model->assigned) {
|
||||
if ($this->model->assigned_to) {
|
||||
return 'deployed';
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,17 @@ class LocationPresenter extends Presenter
|
|||
'switchable' => true,
|
||||
'title' => trans('general.id'),
|
||||
'visible' => false,
|
||||
], [
|
||||
],
|
||||
[
|
||||
'field' => 'company',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.company'),
|
||||
'visible' => false,
|
||||
'formatter' => 'locationCompanyObjFilterFormatter'
|
||||
],
|
||||
[
|
||||
'field' => 'name',
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
|
@ -262,7 +272,7 @@ class LocationPresenter extends Presenter
|
|||
'field' => 'created_by',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'title' => trans('general.admin'),
|
||||
'title' => trans('general.created_by'),
|
||||
'visible' => false,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
],
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Providers;
|
|||
|
||||
use App\Models\CustomField;
|
||||
use App\Models\Department;
|
||||
use App\Models\Location;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
@ -353,6 +354,20 @@ class ValidationServiceProvider extends ServiceProvider
|
|||
|
||||
return in_array($value, $options);
|
||||
});
|
||||
|
||||
// Validates that the company of the validated object matches the company of the location in case of scoped locations
|
||||
Validator::extend('fmcs_location', function ($attribute, $value, $parameters, $validator){
|
||||
$settings = Setting::getSettings();
|
||||
if ($settings->full_multiple_companies_support == '1' && $settings->scope_locations_fmcs == '1') {
|
||||
$company_id = array_get($validator->getData(), 'company_id');
|
||||
$location = Location::find($value);
|
||||
|
||||
if ($company_id != $location->company_id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -139,6 +139,9 @@ class Label implements View
|
|||
case 'plain_serial_number':
|
||||
$barcode2DTarget = $asset->serial;
|
||||
break;
|
||||
case 'location':
|
||||
$barcode2DTarget = route('locations.show', $asset->location_id);
|
||||
break;
|
||||
case 'hardware_id':
|
||||
default:
|
||||
$barcode2DTarget = route('hardware.show', $asset);
|
||||
|
|
|
@ -385,6 +385,12 @@ class UserFactory extends Factory
|
|||
return $this->appendPermission(['suppliers.delete' => '1']);
|
||||
}
|
||||
|
||||
public function auditAssets()
|
||||
{
|
||||
return $this->appendPermission(['assets.audit' => '1']);
|
||||
}
|
||||
|
||||
|
||||
private function appendPermission(array $permission)
|
||||
{
|
||||
return $this->state(function ($currentState) use ($permission) {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddCompanyIdToLocations extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('locations', function (Blueprint $table) {
|
||||
$table->integer('company_id')->unsigned()->nullable();
|
||||
$table->index(['company_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('locations', function (Blueprint $table) {
|
||||
$table->dropIndex(['company_id']);
|
||||
$table->dropColumn('company_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddScopeLocationsSetting extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->boolean('scope_locations_fmcs')->default('0')->after('full_multiple_companies_support');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->dropColumn('scope_locations_fmcs');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?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('custom_fields', function (Blueprint $table) {
|
||||
$table->boolean('display_audit')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('custom_fields', function (Blueprint $table) {
|
||||
$table->dropColumn('display_audit');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1175,7 +1175,8 @@ th.css-history > .th-inner::before {
|
|||
padding: 6px 12px;
|
||||
height: 34px;
|
||||
}
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label,
|
||||
.form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
|
@ -1427,6 +1428,7 @@ th.text-right.text-padding-number-footer-cell {
|
|||
white-space: nowrap;
|
||||
}
|
||||
code.single-line {
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
|
@ -806,7 +806,8 @@ th.css-history > .th-inner::before {
|
|||
padding: 6px 12px;
|
||||
height: 34px;
|
||||
}
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label,
|
||||
.form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
|
@ -1058,6 +1059,7 @@ th.text-right.text-padding-number-footer-cell {
|
|||
white-space: nowrap;
|
||||
}
|
||||
code.single-line {
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
8
public/css/dist/all.css
vendored
8
public/css/dist/all.css
vendored
|
@ -22510,7 +22510,8 @@ th.css-history > .th-inner::before {
|
|||
padding: 6px 12px;
|
||||
height: 34px;
|
||||
}
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label,
|
||||
.form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
|
@ -22762,6 +22763,7 @@ th.text-right.text-padding-number-footer-cell {
|
|||
white-space: nowrap;
|
||||
}
|
||||
code.single-line {
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
@ -24060,7 +24062,8 @@ th.css-history > .th-inner::before {
|
|||
padding: 6px 12px;
|
||||
height: 34px;
|
||||
}
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label,
|
||||
.form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
|
@ -24312,6 +24315,7 @@ th.text-right.text-padding-number-footer-cell {
|
|||
white-space: nowrap;
|
||||
}
|
||||
code.single-line {
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
"/js/build/app.js": "/js/build/app.js?id=607de09b70b83ef82a427e4b36341682",
|
||||
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=06c13e817cc022028b3f4a33c0ca303a",
|
||||
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=79aa889a1a6691013be6c342ca7391cd",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=01e77f7a486fd578a14760d045dcd80f",
|
||||
"/css/build/app.css": "/css/build/app.css?id=ad2974ecfed16a76dadd2f4ec34f8dac",
|
||||
"/css/build/overrides.css": "/css/build/overrides.css?id=4d62149a0ee9dc139bdf03ff2f83930d",
|
||||
"/css/build/app.css": "/css/build/app.css?id=d47ce0dc14671bb4e462e111001488e5",
|
||||
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=4ea0068716c1bb2434d87a16d51b98c9",
|
||||
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
|
||||
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=ea22079836a432d7f46a5d390c445e13",
|
||||
|
@ -19,7 +19,7 @@
|
|||
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=a82b065847bf3cd5d713c04ee8dc86c6",
|
||||
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=6ea836d8126de101081c49abbdb89417",
|
||||
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=524d6fe45db04b3258c818dfb391fe2c",
|
||||
"/css/dist/all.css": "/css/dist/all.css?id=7c861c2086473c513fe26c21e3c4d433",
|
||||
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
|
||||
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
|
||||
|
|
|
@ -886,7 +886,7 @@ th.css-history > .th-inner::before {
|
|||
height: 34px;
|
||||
}
|
||||
|
||||
.form-group.has-error label {
|
||||
.form-group.has-error label, .form-group.has-error .help-block {
|
||||
color: #a94442;
|
||||
}
|
||||
|
||||
|
@ -1176,6 +1176,7 @@ th.text-right.text-padding-number-footer-cell {
|
|||
}
|
||||
|
||||
code.single-line {
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
|
@ -147,6 +147,8 @@ return [
|
|||
'logo_print_assets_help' => 'Firmenlogo anzeigen beim Drucken der Asset-Liste ',
|
||||
'full_multiple_companies_support_help_text' => 'Beschränkung von Benutzern (inklusive Administratoren) die einer Firma zugewiesen sind zu den Assets der Firma.',
|
||||
'full_multiple_companies_support_text' => 'Volle Mehrmandanten-Unterstützung für Firmen',
|
||||
'scope_locations_fmcs_support_text' => 'Beschränke Standorte mit voller Mehrmandanten-Unterstützung für Firmen',
|
||||
'scope_locations_fmcs_support_help_text' => 'Bis zu Version 7.0 waren Standorte nicht auf die Firma des Benutzers beschränkt. Wenn diese Einstellung deaktiviert ist, wird die Kompatibilität zu älteren Versionen gewahrt und die Standorte nicht beschränkt. Wenn diese Einstellung aktiviert ist, werden Standorte ebenfalls auf die Firma des Benutzers beschränkt.',
|
||||
'show_in_model_list' => 'In Modell-Dropdown-Liste anzeigen',
|
||||
'optional' => 'optional',
|
||||
'per_page' => 'Ergebnisse pro Seite',
|
||||
|
|
|
@ -59,5 +59,6 @@ return [
|
|||
'encrypted_options' => 'This field is encrypted, so some display options will not be available.',
|
||||
'display_checkin' => 'Display in checkin forms',
|
||||
'display_checkout' => 'Display in checkout forms',
|
||||
'display_audit' => 'Display in audit forms',
|
||||
|
||||
];
|
||||
|
|
|
@ -100,9 +100,10 @@ return [
|
|||
],
|
||||
|
||||
'requests' => [
|
||||
'error' => 'Asset was not requested, please try again',
|
||||
'success' => 'Asset requested successfully.',
|
||||
'canceled' => 'Checkout request successfully canceled',
|
||||
'error' => 'Request was not successful, please try again.',
|
||||
'success' => 'Request successfully submitted.',
|
||||
'canceled' => 'Request successfully canceled.',
|
||||
'cancel' => 'Cancel this item request',
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -149,6 +149,8 @@ return [
|
|||
'logo_print_assets_help' => 'Use branding on printable asset lists ',
|
||||
'full_multiple_companies_support_help_text' => 'Restricting users (including admins) assigned to companies to their company\'s assets.',
|
||||
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
|
||||
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
|
||||
'scope_locations_fmcs_support_help_text' => 'Up until Version 7.0 locations were not restricted to the users company. If this setting is disabled, this preserves backward compatibility with older versions and locations are not restricted. If this setting is enabled, locations are also restricted to the users company',
|
||||
'show_in_model_list' => 'Show in Model Dropdowns',
|
||||
'optional' => 'optional',
|
||||
'per_page' => 'Results Per Page',
|
||||
|
@ -394,6 +396,19 @@ return [
|
|||
'due_checkin_days_help' => 'How many days before the expected checkin of an asset should it be listed in the "Due for checkin" page?',
|
||||
'no_groups' => 'No groups have been created yet. Visit <code>Admin Settings > Permission Groups</code> to add one.',
|
||||
'text' => 'Text',
|
||||
'firstname_lastname_format' => 'First Name Last Name (jane.smith)',
|
||||
'first_name_format' => 'First Name (jane)',
|
||||
'filastname_format' => 'First Initial Last Name (jsmith)',
|
||||
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj)',
|
||||
'firstname_lastname_underscore_format' => 'First Name Last Name (jane_smith)',
|
||||
'firstinitial.lastname' => 'First Initial Last Name (j.smith)',
|
||||
'lastname_firstinitial' => 'Last Name First Initial (smith_j)',
|
||||
'lastname_dot_firstinitial_format' => 'Last Name First Initial (smith.j)',
|
||||
'firstnamelastname' => 'First Name Last Name (janesmith)',
|
||||
'firstnamelastinitial' => 'First Name Last Initial (janes)',
|
||||
'lastnamefirstname' => 'Last Name.First Name (smith.jane)',
|
||||
|
||||
|
||||
|
||||
'logo_labels' => [
|
||||
'acceptance_pdf_logo' => 'PDF Logo',
|
||||
|
|
|
@ -50,5 +50,11 @@ return [
|
|||
'error_misc' => 'Something went wrong. :( ',
|
||||
'webhook_fail' => ' webhook notification failed: Check to make sure the URL is still valid.',
|
||||
'webhook_channel_not_found' => ' webhook channel not found.'
|
||||
]
|
||||
],
|
||||
|
||||
'location_scoping' => [
|
||||
'not_saved' => 'Your settings were not saved.',
|
||||
'mismatch' => 'There is 1 item in the database that need your attention before you can enable location scoping.|There are :count items in the database that need your attention before you can enable location scoping.',
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -31,6 +31,7 @@ return [
|
|||
'accept_assets_menu' => 'Accept Assets',
|
||||
'accept_item' => 'Accept Item',
|
||||
'audit' => 'Audit',
|
||||
'audits' => 'Audits',
|
||||
'audit_report' => 'Audit Log',
|
||||
'assets' => 'Assets',
|
||||
'assets_audited' => 'assets audited',
|
||||
|
|
|
@ -189,7 +189,7 @@ Form::macro('barcode_types', function ($name = 'barcode_type', $selected = null,
|
|||
return $select;
|
||||
});
|
||||
|
||||
Form::macro('username_format', function ($name = 'username_format', $selected = null, $class = null) {
|
||||
Form::macro('email_format', function ($name = 'email_format', $selected = null, $class = null) {
|
||||
$formats = [
|
||||
'firstname.lastname' => trans('general.firstname_lastname_format'),
|
||||
'firstname' => trans('general.first_name_format'),
|
||||
|
@ -215,6 +215,31 @@ Form::macro('username_format', function ($name = 'username_format', $selected =
|
|||
return $select;
|
||||
});
|
||||
|
||||
Form::macro('username_format', function ($name = 'username_format', $selected = null, $class = null) {
|
||||
$formats = [
|
||||
'firstname.lastname' => trans('admin/settings/general.firstname_lastname_format'),
|
||||
'firstname' => trans('admin/settings/general.first_name_format'),
|
||||
'filastname' => trans('admin/settings/general.filastname_format'),
|
||||
'lastnamefirstinitial' => trans('admin/settings/general.lastnamefirstinitial_format'),
|
||||
'firstname_lastname' => trans('admin/settings/general.firstname_lastname_underscore_format'),
|
||||
'firstinitial.lastname' => trans('admin/settings/general.firstinitial.lastname'),
|
||||
'lastname_firstinitial' => trans('admin/settings/general.lastname_firstinitial'),
|
||||
'lastname.firstinitial' => trans('admin/settings/general.lastname_dot_firstinitial_format'),
|
||||
'firstnamelastname' => trans('admin/settings/general.firstnamelastname'),
|
||||
'firstnamelastinitial' => trans('admin/settings/general.firstnamelastinitial'),
|
||||
'lastname.firstname' => trans('admin/settings/general.lastnamefirstname'),
|
||||
];
|
||||
|
||||
$select = '<select name="'.$name.'" class="'.$class.'" style="width: 100%" aria-label="'.$name.'">';
|
||||
foreach ($formats as $format => $label) {
|
||||
$select .= '<option value="'.$format.'"'.($selected == $format ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$label.'</option> '."\n";
|
||||
}
|
||||
|
||||
$select .= '</select>';
|
||||
|
||||
return $select;
|
||||
});
|
||||
|
||||
Form::macro('two_factor_options', function ($name = 'two_factor_enabled', $selected = null, $class = null) {
|
||||
$formats = [
|
||||
'' => trans('admin/settings/general.two_factor_disabled'),
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="col-sm-2" data-visible="false" data-sortable="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.record_created') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -421,7 +421,7 @@
|
|||
{{ trans('general.date') }}
|
||||
</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="note">{{ trans('general.notes') }}</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="admin">{{ trans('general.admin') }}</th>
|
||||
<th data-searchable="false" data-sortable="false" data-field="admin">{{ trans('general.created_by') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
@ -472,7 +472,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="action_date" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -224,6 +224,14 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Show in Audit Form -->
|
||||
<div class="col-md-9 col-md-offset-3" id="display_audit" style="padding-bottom: 10px;">
|
||||
<label class="form-control">
|
||||
<input type="checkbox" name="display_audit" aria-label="display_audit" value="1" {{ (old('display_audit') || $field->display_audit) ? ' checked="checked"' : '' }}>
|
||||
{{ trans('admin/custom_fields/general.display_audit') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Show in View All Assets profile view -->
|
||||
<div class="col-md-9 col-md-offset-3" id="display_in_user_view">
|
||||
|
|
|
@ -196,6 +196,14 @@
|
|||
</span>
|
||||
</th>
|
||||
|
||||
<th data-sortable="true" data-visible="false" data-searchable="false" class="text-center"
|
||||
data-tooltip="{{ trans('admin/custom_fields/general.display_audit') }}">
|
||||
<x-icon type="due" />
|
||||
<span class="sr-only">
|
||||
{{ trans('admin/custom_fields/general.display_audit') }}
|
||||
</span>
|
||||
</th>
|
||||
|
||||
|
||||
<th data-sortable="true" data-searchable="true" class="text-center">{{ trans('admin/custom_fields/general.field_element_short') }}</th>
|
||||
|
||||
|
@ -227,6 +235,7 @@
|
|||
<td class="text-center">{!! ($field->is_unique=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_checkin=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_checkout=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td class="text-center">{!! ($field->display_audit=='1') ? '<i class="fas fa-check text-success" aria-hidden="true"><span class="sr-only">'.trans('general.yes').'</span></i>' : '<i class="fas fa-times text-danger" aria-hidden="true"><span class="sr-only">'.trans('general.no').'</span></i>' !!}</td>
|
||||
<td>{{ $field->element }}</td>
|
||||
<td>
|
||||
@foreach($field->fieldset as $fieldset)
|
||||
|
|
|
@ -115,6 +115,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom fields -->
|
||||
@include("models/custom_fields_form", [
|
||||
'model' => $asset->model,
|
||||
'show_display_checkout_fields' => 'true'
|
||||
])
|
||||
|
||||
|
||||
<!-- Note -->
|
||||
<div class="form-group{{ $errors->has('note') ? ' has-error' : '' }}">
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
var formData = $('#audit-form').serializeArray();
|
||||
|
||||
$.ajax({
|
||||
url: "{{ route('api.asset.audit') }}",
|
||||
url: "{{ route('api.asset.audit.legacy') }}",
|
||||
type : 'POST',
|
||||
headers: {
|
||||
"X-Requested-With": 'XMLHttpRequest',
|
||||
|
|
|
@ -111,6 +111,22 @@
|
|||
@endif
|
||||
|
||||
|
||||
@if ($asset->audits->count() > 0)
|
||||
<li>
|
||||
<a href="#audits" data-toggle="tab" data-tooltip="true">
|
||||
|
||||
<span class="hidden-lg hidden-md">
|
||||
<i class="fas fa-clipboard-check fa-2x"></i>
|
||||
</span>
|
||||
<span class="hidden-xs hidden-sm">
|
||||
{{ trans('general.audits') }}
|
||||
{!! ($asset->audits()->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($asset->audits()->count()).'</span>' : '' !!}
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li>
|
||||
<a href="#history" data-toggle="tab">
|
||||
<span class="hidden-lg hidden-md">
|
||||
|
@ -1390,7 +1406,53 @@
|
|||
</div> <!-- /.row -->
|
||||
</div> <!-- /.tab-pane maintenances -->
|
||||
|
||||
<div class="tab-pane fade" id="history">
|
||||
|
||||
<div class="tab-pane fade" id="audits">
|
||||
<!-- checked out assets table -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table
|
||||
class="table table-striped snipe-table"
|
||||
id="asseAuditHistory"
|
||||
data-pagination="true"
|
||||
data-id-table="asseAuditHistory"
|
||||
data-search="true"
|
||||
data-side-pagination="server"
|
||||
data-show-columns="true"
|
||||
data-show-fullscreen="true"
|
||||
data-show-refresh="true"
|
||||
data-sort-order="desc"
|
||||
data-sort-name="created_at"
|
||||
data-show-export="true"
|
||||
data-export-options='{
|
||||
"fileName": "export-asset-{{ $asset->id }}-audits",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'
|
||||
|
||||
data-url="{{ route('api.activity.index', ['item_id' => $asset->id, 'item_type' => 'asset', 'action_type' => 'audit']) }}"
|
||||
data-cookie-id-table="assetHistory"
|
||||
data-cookie="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="created_at" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-sortable="true" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-field="note">{{ trans('general.notes') }}</th>
|
||||
<th data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
|
||||
<th data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">{{ trans('admin/hardware/table.changed')}}</th>
|
||||
<th data-field="remote_ip" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_ip') }}</th>
|
||||
<th data-field="user_agent" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_user_agent') }}</th>
|
||||
<th data-field="action_source" data-visible="false" data-sortable="true">{{ trans('general.action_source') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div> <!-- /.row -->
|
||||
</div> <!-- /.tab-pane history -->
|
||||
|
||||
|
||||
<div class="tab-pane fade" id="history">
|
||||
<!-- checked out assets table -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
@ -1419,7 +1481,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th data-visible="true" data-field="action_date" data-sortable="true" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -526,7 +526,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
|
|||
@can('audit', \App\Models\Asset::class)
|
||||
<li{!! (Request::is('hardware/audit/due') ? ' class="active"' : '') !!}>
|
||||
<a href="{{ route('assets.audit.due') }}">
|
||||
<x-icon type="due" class="text-yellow fa-fw"/>
|
||||
<x-icon type="audit" class="text-yellow fa-fw"/>
|
||||
{{ trans('general.audit_due') }}
|
||||
<span class="badge">{{ (isset($total_due_and_overdue_for_audit)) ? $total_due_and_overdue_for_audit : '' }}</span>
|
||||
</a>
|
||||
|
|
|
@ -498,7 +498,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="col-sm-2" data-visible="false" data-sortable="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.record_created') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-2"data-visible="true" data-sortable="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-field="file" data-visible="false" data-formatter="fileUploadNameFormatter">{{ trans('general.file_name') }}</th>
|
||||
<th class="col-sm-2" data-sortable="true" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
<!-- Manager-->
|
||||
@include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id'])
|
||||
|
||||
<!-- Company -->
|
||||
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
|
||||
|
||||
@include ('partials.forms.edit.phone')
|
||||
@include ('partials.forms.edit.fax')
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
data-sort-order="asc"
|
||||
id="locationTable"
|
||||
class="table table-striped snipe-table"
|
||||
data-url="{{ route('api.locations.index') }}"
|
||||
data-url="{{ route('api.locations.index', array('company_id'=>e(Request::get('company_id')))) }}"
|
||||
data-export-options='{
|
||||
"fileName": "export-locations-{{ date('Y-m-d') }}",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
|
|
|
@ -53,7 +53,11 @@
|
|||
@if ($parent)
|
||||
{{ $parent->present()->fullName() }}
|
||||
@endif
|
||||
|
||||
<br>
|
||||
@if ($company)
|
||||
<b>{{ trans('admin/companies/table.name') }}:</b> {{ $company->present()->Name() }}</b>
|
||||
<br>
|
||||
@endif
|
||||
@if ($manager)
|
||||
<b>{{ trans('general.manager') }}</b> {{ $manager->present()->fullName() }}<br>
|
||||
@endif
|
||||
|
|
|
@ -157,11 +157,39 @@
|
|||
|
||||
|
||||
<div class="tab-content">
|
||||
@can('view', \App\Models\User::class)
|
||||
<div id="users" @class(['tab-pane','active' => $location->users->count() > 0 ]) >
|
||||
@endcan
|
||||
<h2 class="box-title">{{ trans('general.users') }}</h2>
|
||||
@include('partials.users-bulk-actions')
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\UserPresenter::dataTableLayout() }}"
|
||||
data-cookie-id-table="usersTable"
|
||||
data-pagination="true"
|
||||
data-id-table="usersTable"
|
||||
data-search="true"
|
||||
data-side-pagination="server"
|
||||
data-show-columns="true"
|
||||
data-show-export="true"
|
||||
data-show-refresh="true"
|
||||
data-sort-order="asc"
|
||||
data-toolbar="#userBulkEditToolbar"
|
||||
data-bulk-button-id="#bulkUserEditButton"
|
||||
data-bulk-form-id="#usersBulkForm"
|
||||
data-click-to-select="true"
|
||||
id="usersTable"
|
||||
class="table table-striped snipe-table"
|
||||
data-url="{{route('api.users.index', ['location_id' => $location->id])}}"
|
||||
data-export-options='{
|
||||
"fileName": "export-locations-{{ str_slug($location->name) }}-users-{{ date('Y-m-d') }}",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
</div><!-- /.tab-pane -->
|
||||
<div id="assets" @class(['tab-pane', 'active' => $location->users->count() == 0]) >
|
||||
|
||||
<div class="tab-pane active" id="assets">
|
||||
<h2 class="box-title">{{ trans('admin/locations/message.current_location') }}</h2>
|
||||
|
||||
<div class="table table-responsive">
|
||||
@include('partials.asset-bulk-actions')
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
|
||||
|
@ -186,50 +214,13 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
||||
|
||||
<div class="tab-pane" id="users">
|
||||
<h2 class="box-title">{{ trans('general.users') }}</h2>
|
||||
<div class="table table-responsive">
|
||||
@include('partials.users-bulk-actions')
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\UserPresenter::dataTableLayout() }}"
|
||||
data-cookie-id-table="usersTable"
|
||||
data-pagination="true"
|
||||
data-id-table="usersTable"
|
||||
data-search="true"
|
||||
data-side-pagination="server"
|
||||
data-show-columns="true"
|
||||
data-show-export="true"
|
||||
data-show-refresh="true"
|
||||
data-sort-order="asc"
|
||||
data-toolbar="#userBulkEditToolbar"
|
||||
data-bulk-button-id="#bulkUserEditButton"
|
||||
data-bulk-form-id="#usersBulkForm"
|
||||
data-click-to-select="true"
|
||||
id="usersTable"
|
||||
class="table table-striped snipe-table"
|
||||
data-url="{{route('api.users.index', ['location_id' => $location->id])}}"
|
||||
data-export-options='{
|
||||
"fileName": "export-locations-{{ str_slug($location->name) }}-users-{{ date('Y-m-d') }}",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
|
||||
</table>
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
||||
<div class="tab-pane" id="assets_assigned">
|
||||
<h2 class="box-title">
|
||||
{{ trans('admin/locations/message.assigned_assets') }}
|
||||
</h2>
|
||||
|
||||
<div class="table table-responsive">
|
||||
@include('partials.asset-bulk-actions', ['id_divname' => 'AssignedAssetsBulkEditToolbar', 'id_formname' => 'assignedAssetsBulkForm', 'id_button' => 'AssignedbulkAssetEditButton'])
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
|
||||
|
@ -254,14 +245,11 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
<div class="tab-pane" id="rtd_assets">
|
||||
<h2 class="box-title">{{ trans('admin/hardware/form.default_location') }}</h2>
|
||||
|
||||
<div class="table table-responsive">
|
||||
@include('partials.asset-bulk-actions', ['id_divname' => 'RTDassetsBulkEditToolbar', 'id_formname' => 'RTDassets', 'id_button' => 'RTDbulkAssetEditButton'])
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\AssetPresenter::dataTableLayout() }}"
|
||||
|
@ -286,15 +274,12 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
||||
|
||||
<div class="tab-pane" id="accessories">
|
||||
<h2 class="box-title">{{ trans('general.accessories') }}</h2>
|
||||
<div class="table table-responsive">
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\AccessoryPresenter::dataTableLayout() }}"
|
||||
data-cookie-id-table="accessoriesListingTable"
|
||||
|
@ -314,13 +299,9 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
<div class="tab-pane" id="accessories_assigned">
|
||||
|
||||
<div class="table table-responsive">
|
||||
|
||||
<h2 class="box-title" style="float:left">
|
||||
{{ trans('general.accessories_assigned') }}
|
||||
</h2>
|
||||
|
@ -345,15 +326,11 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
|
||||
<div class="tab-pane" id="consumables">
|
||||
<h2 class="box-title">{{ trans('general.consumables') }}</h2>
|
||||
|
||||
<div class="table table-responsive">
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\ConsumablePresenter::dataTableLayout() }}"
|
||||
data-cookie-id-table="consumablesListingTable"
|
||||
|
@ -373,14 +350,10 @@
|
|||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
</table>
|
||||
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
<div class="tab-pane" id="components">
|
||||
<h2 class="box-title">{{ trans('general.components') }}</h2>
|
||||
<div class="table table-responsive">
|
||||
|
||||
<table
|
||||
data-columns="{{ \App\Presenters\ComponentPresenter::dataTableLayout() }}"
|
||||
data-cookie-id-table="componentsTable"
|
||||
|
@ -399,9 +372,7 @@
|
|||
"fileName": "export-locations-{{ str_slug($location->name) }}-components-{{ date('Y-m-d') }}",
|
||||
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
|
||||
}'>
|
||||
|
||||
</table>
|
||||
</div><!-- /.table-responsive -->
|
||||
</div><!-- /.tab-pane -->
|
||||
|
||||
<div class="tab-pane" id="history">
|
||||
|
@ -434,7 +405,7 @@
|
|||
<tr>
|
||||
<th data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">{{ trans('admin/hardware/table.icon') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="action_date" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
|
||||
<th class="col-sm-2" data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
|
||||
|
@ -490,6 +461,9 @@
|
|||
@if ($location->manager)
|
||||
<li>{{ trans('admin/users/table.manager') }}: {!! $location->manager->present()->nameUrl() !!}</li>
|
||||
@endif
|
||||
@if ($location->company)
|
||||
<li>{{ trans('admin/companies/table.name') }}: {!! $location->company->present()->nameUrl() !!}</li>
|
||||
@endif
|
||||
@if ($location->parent)
|
||||
<li>{{ trans('admin/locations/table.parent') }}: {!! $location->parent->present()->nameUrl() !!}</li>
|
||||
@endif
|
||||
|
|
|
@ -11,6 +11,16 @@
|
|||
</div>
|
||||
@include('modals.partials.name', ['item' => new \App\Models\Location(), 'required' => 'true'])
|
||||
|
||||
<!-- Setup of default company, taken from asset creator if scoped locations are activated in the settings -->
|
||||
@if (($snipeSettings->scope_locations_fmcs == '1') && ($user->company))
|
||||
<input type="hidden" name="company_id" id='modal-company' value='{{ $user->company->id }}' class="form-control">
|
||||
@endif
|
||||
|
||||
<!-- Select company, only for users with multicompany access - replace default company -->
|
||||
<div class="dynamic-form-row">
|
||||
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
|
||||
</div>
|
||||
|
||||
<div class="dynamic-form-row">
|
||||
<div class="col-md-4 col-xs-12"><label for="modal-city">{{ trans('general.city') }}:</label></div>
|
||||
<div class="col-md-8 col-xs-12"><input type='text' name="city" id='modal-city' class="form-control"></div>
|
||||
|
|
|
@ -90,6 +90,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@include ('partials.forms.edit.minimum_quantity')
|
||||
|
||||
|
||||
<!-- requestable -->
|
||||
<div class="form-group{{ $errors->has('requestable') ? ' has-error' : '' }}">
|
||||
<div class="col-md-7 col-md-offset-3">
|
||||
|
|
|
@ -101,10 +101,4 @@
|
|||
@endif
|
||||
|
||||
|
||||
<script nonce="{{ csrf_token() }}">
|
||||
// We have to re-call the tooltip since this is pulled in after the DOM has loaded
|
||||
$('[data-tooltip="true"]').tooltip({
|
||||
container: 'body',
|
||||
animation: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -341,7 +341,7 @@
|
|||
|
||||
|
||||
function hardwareAuditFormatter(value, row) {
|
||||
return '<a href="{{ config('app.url') }}/hardware/audit/' + row.id + '/" class="btn btn-sm bg-yellow" data-tooltip="true" title="Audit this item">{{ trans('general.audit') }}</a>';
|
||||
return '<a href="{{ config('app.url') }}/hardware/' + row.id + '/audit" class="btn btn-sm bg-yellow" data-tooltip="true" title="Audit this item">{{ trans('general.audit') }}</a>';
|
||||
}
|
||||
|
||||
|
||||
|
@ -547,11 +547,11 @@
|
|||
// This is only used by the requestable assets section
|
||||
function assetRequestActionsFormatter (row, value) {
|
||||
if (value.assigned_to_self == true){
|
||||
return '<button class="btn btn-danger btn-sm disabled" data-tooltip="true" title="Cancel this item request">{{ trans('button.cancel') }}</button>';
|
||||
return '<button class="btn btn-danger btn-sm btn-block disabled" data-tooltip="true" title="{{ trans('admin/hardware/message.requests.cancel') }}">{{ trans('button.cancel') }}</button>';
|
||||
} else if (value.available_actions.cancel == true) {
|
||||
return '<form action="{{ config('app.url') }}/account/request-asset/' + value.id + '/cancel" method="POST">@csrf<button class="btn btn-danger btn-sm" data-tooltip="true" title="Cancel this item request">{{ trans('button.cancel') }}</button></form>';
|
||||
return '<form action="{{ config('app.url') }}/account/request-asset/' + value.id + '/cancel" method="POST">@csrf<button class="btn btn-danger btn-block btn-sm" data-tooltip="true" title="{{ trans('admin/hardware/message.requests.cancel') }}">{{ trans('button.cancel') }}</button></form>';
|
||||
} else if (value.available_actions.request == true) {
|
||||
return '<form action="{{ config('app.url') }}/account/request-asset/'+ value.id + '" method="POST">@csrf<button class="btn btn-primary btn-sm" data-tooltip="true" title="{{ trans('general.request_item') }}">{{ trans('button.request') }}</button></form>';
|
||||
return '<form action="{{ config('app.url') }}/account/request-asset/'+ value.id + '" method="POST">@csrf<button class="btn btn-block btn-primary btn-sm" data-tooltip="true" title="{{ trans('general.request_item') }}">{{ trans('button.request') }}</button></form>';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -819,6 +819,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
function locationCompanyObjFilterFormatter(value, row) {
|
||||
if (value) {
|
||||
return '<a href="{{ url('/') }}/locations/?company_id=' + row.company.id + '">' + row.company.name + '</a>';
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function employeeNumFormatter(value, row) {
|
||||
|
||||
if ((row) && (row.assigned_to) && ((row.assigned_to.employee_number))) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<label for="min_amt" class="col-md-3 control-label">{{ trans('general.min_amt') }}</label>
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-2" style="padding-left:0px">
|
||||
<input class="form-control col-md-3" maxlength="5" type="text" name="min_amt" id="min_amt" aria-label="min_amt" value="{{ old('min_amt', $item->min_amt) }}"{{ (Helper::checkIfRequired($item, 'min_amt')) ? ' required' : '' }}/>
|
||||
<input class="form-control col-md-3" maxlength="5" type="text" name="min_amt" id="min_amt" aria-label="min_amt" value="{{ old('min_amt', ($item->min_amt ?? '')) }}"{{ (isset($item) ?? (Helper::checkIfRequired($item, 'min_amt')) ? ' required' : '') }}/>
|
||||
</div>
|
||||
<div class="col-md-7" style="margin-left: -15px;">
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<th data-sortable="true" data-field="location" data-formatter="deployedLocationFormatter" data-visible="false">{{ trans('general.location') }}</th>
|
||||
<th data-sortable="true" data-field="rtd_location" data-formatter="deployedLocationFormatter" data-visible="false">{{ trans('admin/hardware/form.default_location') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="is_warranty" data-formatter="trueFalseFormatter">{{ trans('admin/asset_maintenances/table.is_warranty') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="user_id" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="user_id" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-searchable="true" data-sortable="true" data-field="notes" data-visible="false">{{ trans('admin/asset_maintenances/form.notes') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<div class="box-body">
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-11">
|
||||
|
||||
<!-- Full Multiple Companies Support -->
|
||||
<div class="form-group {{ $errors->has('full_multiple_companies_support') ? 'error' : '' }}">
|
||||
|
@ -54,7 +54,24 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.form-group -->
|
||||
|
||||
<!-- Scope Locations with Full Multiple Companies Support -->
|
||||
<div class="form-group {{ $errors->has('scope_locations_fmcs') ? 'error' : '' }}">
|
||||
<div class="col-md-3">
|
||||
{{ Form::label('scope_locations_fmcs', trans('admin/settings/general.scope_locations_fmcs_support_text')) }}
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<label class="form-control">
|
||||
{{ Form::checkbox('scope_locations_fmcs', '1', old('scope_locations_fmcs', $setting->scope_locations_fmcs),array('class' => 'minimal', 'aria-label'=>'scope_locations_fmcs')) }}
|
||||
{{ trans('admin/settings/general.scope_locations_fmcs_support_text') }}
|
||||
</label>
|
||||
{!! $errors->first('scope_locations_fmcs', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
|
||||
<p class="help-block">
|
||||
{{ trans('admin/settings/general.scope_locations_fmcs_support_help_text') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.form-group -->
|
||||
|
||||
<!-- Require signature for acceptance -->
|
||||
|
@ -93,7 +110,7 @@
|
|||
<label for="email_format">{{ trans('general.email_format') }}</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
{!! Form::username_format('email_format', old('email_format', $setting->email_format), 'select2') !!}
|
||||
{!! Form::email_format('email_format', old('email_format', $setting->email_format), 'select2') !!}
|
||||
{!! $errors->first('email_format', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -506,6 +523,5 @@
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
@stop
|
||||
|
|
|
@ -301,7 +301,10 @@
|
|||
<x-input.select
|
||||
name="label2_2d_target"
|
||||
id="label2_2d_target"
|
||||
:options="['hardware_id'=>'/hardware/{id} ('.trans('admin/settings/general.default').')', 'ht_tag'=>'/ht/{asset_tag}']"
|
||||
:options="['hardware_id'=>'/hardware/{id} ('.trans('admin/settings/general.default').')',
|
||||
'ht_tag'=>'/ht/{asset_tag}',
|
||||
'location' => '/location/{location_id}',
|
||||
]"
|
||||
:selected="old('label2_2d_target', $setting->label2_2d_target)"
|
||||
class="col-md-4"
|
||||
aria-label="label2_2d_target"
|
||||
|
|
|
@ -147,8 +147,8 @@
|
|||
<td>
|
||||
{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}</td>
|
||||
<td>
|
||||
@if (($asset->assetlog->first()) && ($asset->assetlog->first()->accept_signature!=''))
|
||||
<img style="width:auto;height:100px;" src="{{ asset('/') }}display-sig/{{ $asset->assetlog->first()->accept_signature }}">
|
||||
@if (($asset->assetlog->firstWhere('action_type', 'accepted')) && ($asset->assetlog->firstWhere('action_type', 'accepted')->accept_signature!=''))
|
||||
<img style="width:auto;height:100px;" src="{{ asset('/') }}display-sig/{{ $asset->assetlog->firstWhere('action_type', 'accepted')->accept_signature }}">
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -174,8 +174,8 @@
|
|||
<td>
|
||||
{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}</td>
|
||||
<td>
|
||||
@if (($asset->assetlog->first()) && ($asset->assetlog->first()->accept_signature!=''))
|
||||
<img style="width:auto;height:100px;" src="{{ asset('/') }}display-sig/{{ $asset->assetlog->first()->accept_signature }}">
|
||||
@if (($asset->assetlog->firstWhere('action_type', 'accepted')) && ($asset->assetlog->firstWhere('action_type', 'accepted')->accept_signature!=''))
|
||||
<img style="width:auto;height:100px;" src="{{ asset('/') }}display-sig/{{ $asset->assetlog->firstWhere('action_type', 'accepted')->accept_signature }}">
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1003,7 +1003,7 @@
|
|||
<th data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
|
||||
@endif
|
||||
<th data-field="item.serial" data-visible="false">{{ trans('admin/hardware/table.serial') }}</th>
|
||||
<th data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
|
||||
<th data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.created_by') }}</th>
|
||||
<th data-field="remote_ip" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_ip') }}</th>
|
||||
<th data-field="user_agent" data-visible="false" data-sortable="true">{{ trans('admin/settings/general.login_user_agent') }}</th>
|
||||
<th data-field="action_source" data-visible="false" data-sortable="true">{{ trans('general.action_source') }}</th>
|
||||
|
|
|
@ -511,8 +511,17 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
->where(['action' => 'audit|audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']);
|
||||
|
||||
|
||||
// Legacy URL for audit
|
||||
Route::post('audit',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'audit'
|
||||
]
|
||||
)->name('api.asset.audit.legacy');
|
||||
|
||||
Route::post('audit',
|
||||
|
||||
// Newer url for audit
|
||||
Route::post('{asset}/audit',
|
||||
[
|
||||
Api\AssetsController::class,
|
||||
'audit'
|
||||
|
|
|
@ -422,10 +422,6 @@ Route::group(['prefix' => 'account', 'middleware' => ['auth']], function () {
|
|||
$trail->parent('home')
|
||||
->push(trans('general.requestable_items'), route('requestable-assets')));
|
||||
|
||||
Route::post(
|
||||
'request-asset/{assetId}',
|
||||
[ViewAssetsController::class, 'getRequestAsset']
|
||||
)->name('account/request-asset');
|
||||
|
||||
Route::post('request-asset/{asset}', [ViewAssetsController::class, 'store'])
|
||||
->name('account.request-asset');
|
||||
|
|
|
@ -63,14 +63,14 @@ Route::group(
|
|||
->push(trans_choice('general.checkin_due_days', Setting::getSettings()->due_checkin_days, ['days' => Setting::getSettings()->due_checkin_days]), route('assets.audit.due'))
|
||||
);
|
||||
|
||||
Route::get('audit/{asset}', [AssetsController::class, 'audit'])
|
||||
Route::get('{asset}/audit', [AssetsController::class, 'audit'])
|
||||
->name('asset.audit.create')
|
||||
->breadcrumbs(fn (Trail $trail, Asset $asset) =>
|
||||
$trail->parent('hardware.show', $asset)
|
||||
->push(trans('general.audit'))
|
||||
);
|
||||
|
||||
Route::post('audit/{asset}',
|
||||
Route::post('{asset}/audit',
|
||||
[AssetsController::class, 'auditStore']
|
||||
)->name('asset.audit.store');
|
||||
|
||||
|
|
78
tests/Feature/Assets/Api/AuditAssetTest.php
Normal file
78
tests/Feature/Assets/Api/AuditAssetTest.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Api;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\Company;
|
||||
use App\Models\Location;
|
||||
use App\Models\Statuslabel;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\User;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuditAssetTest extends TestCase
|
||||
{
|
||||
public function testThatANonExistentAssetIdReturnsError()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit', 123456789))
|
||||
->assertStatusMessageIs('error');
|
||||
}
|
||||
|
||||
public function testRequiresPermissionToAuditAsset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->create())
|
||||
->postJson(route('api.asset.audit', $asset))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testLegacyAssetAuditIsSaved()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit.legacy'), [
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test',
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertJson(
|
||||
[
|
||||
'messages' =>trans('admin/hardware/message.audit.success'),
|
||||
'payload' => [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test'
|
||||
],
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testAssetAuditIsSaved()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
$this->actingAsForApi(User::factory()->auditAssets()->create())
|
||||
->postJson(route('api.asset.audit', $asset), [
|
||||
'note' => 'test'
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertJson(
|
||||
[
|
||||
'messages' =>trans('admin/hardware/message.audit.success'),
|
||||
'payload' => [
|
||||
'id' => $asset->id,
|
||||
'asset_tag' => $asset->asset_tag,
|
||||
'note' => 'test'
|
||||
],
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
34
tests/Feature/Assets/Ui/AuditAssetTest.php
Normal file
34
tests/Feature/Assets/Ui/AuditAssetTest.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Ui;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuditAssetTest extends TestCase
|
||||
{
|
||||
public function testPermissionRequiredToCreateAssetModel()
|
||||
{
|
||||
$this->actingAs(User::factory()->create())
|
||||
->get(route('clone/hardware', Asset::factory()->create()))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testPageCanBeAccessed(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->auditAssets()->create())
|
||||
->get(route('asset.audit.create', Asset::factory()->create()))
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testAssetCanBeAudited()
|
||||
{
|
||||
$response = $this->actingAs(User::factory()->auditAssets()->create())
|
||||
->post(route('asset.audit.store', Asset::factory()->create()))
|
||||
->assertStatus(302)
|
||||
->assertRedirect(route('assets.audit.due'));
|
||||
|
||||
$this->followRedirects($response)->assertSee('success');
|
||||
}
|
||||
}
|
86
tests/Feature/Assets/Ui/DeleteAssetTest.php
Normal file
86
tests/Feature/Assets/Ui/DeleteAssetTest.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Ui;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DeleteAssetTest extends TestCase
|
||||
{
|
||||
public function testPermissionNeededToDeleteAsset()
|
||||
{
|
||||
$this->actingAs(User::factory()->create())
|
||||
->delete(route('hardware.destroy', Asset::factory()->create()))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testCanDeleteAsset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
|
||||
$this->actingAs(User::factory()->deleteAssets()->create())
|
||||
->delete(route('hardware.destroy', $asset))
|
||||
->assertRedirectToRoute('hardware.index')
|
||||
->assertSessionHas('success');
|
||||
|
||||
$this->assertSoftDeleted($asset);
|
||||
}
|
||||
|
||||
public function testActionLogEntryMadeWhenAssetDeleted()
|
||||
{
|
||||
$actor = User::factory()->deleteAssets()->create();
|
||||
|
||||
$asset = Asset::factory()->create();
|
||||
|
||||
$this->actingAs($actor)->delete(route('hardware.destroy', $asset));
|
||||
|
||||
$this->assertDatabaseHas('action_logs', [
|
||||
'created_by' => $actor->id,
|
||||
'action_type' => 'delete',
|
||||
'target_id' => null,
|
||||
'target_type' => null,
|
||||
'item_type' => Asset::class,
|
||||
'item_id' => $asset->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAssetIsCheckedInWhenDeleted()
|
||||
{
|
||||
Event::fake();
|
||||
|
||||
$assignedUser = User::factory()->create();
|
||||
$asset = Asset::factory()->assignedToUser($assignedUser)->create();
|
||||
|
||||
$this->assertTrue($assignedUser->assets->contains($asset));
|
||||
|
||||
$this->actingAs(User::factory()->deleteAssets()->create())
|
||||
->delete(route('hardware.destroy', $asset));
|
||||
|
||||
$this->assertFalse(
|
||||
$assignedUser->fresh()->assets->contains($asset),
|
||||
'Asset still assigned to user after deletion'
|
||||
);
|
||||
|
||||
Event::assertDispatched(CheckoutableCheckedIn::class);
|
||||
}
|
||||
|
||||
public function testImageIsDeletedWhenAssetDeleted()
|
||||
{
|
||||
Storage::fake('public');
|
||||
|
||||
$asset = Asset::factory()->create(['image' => 'image.jpg']);
|
||||
|
||||
Storage::disk('public')->put('assets/image.jpg', 'content');
|
||||
|
||||
Storage::disk('public')->assertExists('assets/image.jpg');
|
||||
|
||||
$this->actingAs(User::factory()->deleteAssets()->create())
|
||||
->delete(route('hardware.destroy', $asset));
|
||||
|
||||
Storage::disk('public')->assertMissing('assets/image.jpg');
|
||||
}
|
||||
}
|
45
tests/Feature/Checkins/Api/LicenseCheckInTest.php
Normal file
45
tests/Feature/Checkins/Api/LicenseCheckInTest.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
namespace Tests\Feature\Checkins\Api;
|
||||
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LicenseCheckInTest extends TestCase {
|
||||
public function testLicenseCheckin()
|
||||
{
|
||||
$authUser = User::factory()->superuser()->create();
|
||||
$this->actingAsForApi($authUser);
|
||||
|
||||
$license = License::factory()->create();
|
||||
$oldUser = User::factory()->create();
|
||||
|
||||
$licenseSeat = LicenseSeat::factory()->for($license)->create([
|
||||
'assigned_to' => $oldUser->id,
|
||||
'notes' => 'Previously checked out',
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'assigned_to' => null,
|
||||
'asset_id' => null,
|
||||
'notes' => 'Checking in the seat',
|
||||
];
|
||||
|
||||
$response = $this->patchJson(
|
||||
route('api.licenses.seats.update', [$license->id, $licenseSeat->id]),
|
||||
$payload);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
'status' => 'success',
|
||||
]);
|
||||
|
||||
$licenseSeat->refresh();
|
||||
|
||||
$this->assertNull($licenseSeat->assigned_to);
|
||||
$this->assertNull($licenseSeat->asset_id);
|
||||
|
||||
$this->assertEquals('Checking in the seat', $licenseSeat->notes);
|
||||
}
|
||||
}
|
41
tests/Feature/Checkouts/Api/LicenseCheckOutTest.php
Normal file
41
tests/Feature/Checkouts/Api/LicenseCheckOutTest.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
namespace Tests\Feature\Checkouts\Api;
|
||||
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LicenseCheckOutTest extends TestCase {
|
||||
public function testLicenseCheckout()
|
||||
{
|
||||
$authUser = User::factory()->superuser()->create();
|
||||
$this->actingAsForApi($authUser);
|
||||
|
||||
$license = License::factory()->create();
|
||||
$licenseSeat = LicenseSeat::factory()->for($license)->create([
|
||||
'assigned_to' => null,
|
||||
]);
|
||||
|
||||
$targetUser = User::factory()->create();
|
||||
|
||||
$payload = [
|
||||
'assigned_to' => $targetUser->id,
|
||||
'notes' => 'Checking out the seat to a user',
|
||||
];
|
||||
|
||||
$response = $this->patchJson(
|
||||
route('api.licenses.seats.update', [$license->id, $licenseSeat->id]),
|
||||
$payload);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
'status' => 'success',
|
||||
]);
|
||||
|
||||
$licenseSeat->refresh();
|
||||
|
||||
$this->assertEquals($targetUser->id, $licenseSeat->assigned_to);
|
||||
$this->assertEquals('Checking out the seat to a user', $licenseSeat->notes);
|
||||
}
|
||||
}
|
40
tests/Feature/Users/Ui/EmailAssignedToUserTest.php
Normal file
40
tests/Feature/Users/Ui/EmailAssignedToUserTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Users\Ui;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EmailAssignedToUserTest extends TestCase
|
||||
{
|
||||
public function testUserWithoutCompanyPermissionsCannotSendInventory()
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
->assertStatus(403);
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
->assertStatus(302);
|
||||
|
||||
$this->actingAs($superuser)
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
->assertStatus(302);
|
||||
|
||||
Notification::assertSentTo(
|
||||
[$user], CurrentInventory::class
|
||||
);
|
||||
}
|
||||
}
|
41
tests/Feature/Users/Ui/PrintUserInventoryTest.php
Normal file
41
tests/Feature/Users/Ui/PrintUserInventoryTest.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Users\Ui;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PrintUserInventoryTest extends TestCase
|
||||
{
|
||||
public function testPermissionRequiredToPrintUserInventory()
|
||||
{
|
||||
$this->actingAs(User::factory()->create())
|
||||
->get(route('users.print', User::factory()->create()))
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
public function testCanPrintUserInventory()
|
||||
{
|
||||
$actor = User::factory()->viewUsers()->create();
|
||||
|
||||
$this->actingAs($actor)
|
||||
->get(route('users.print', User::factory()->create()))
|
||||
->assertOk()
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testCannotPrintUserInventoryFromAnotherCompany()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$actor = User::factory()->for($companyA)->viewUsers()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs($actor)
|
||||
->get(route('users.print', $user))
|
||||
->assertStatus(302);
|
||||
}
|
||||
}
|
|
@ -4,80 +4,38 @@ namespace Tests\Feature\Users\Ui;
|
|||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ViewUserTest extends TestCase
|
||||
{
|
||||
public function testPermissionsForUserDetailPage()
|
||||
public function testRequiresPermissionToViewUser()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs(User::factory()->editUsers()->for($companyA)->create())
|
||||
->get(route('users.show', $user))
|
||||
->assertStatus(302);
|
||||
|
||||
$this->actingAs($superuser)
|
||||
->get(route('users.show', $user))
|
||||
->assertOk()
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testPermissionsForPrintAllInventoryPage()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
|
||||
->get(route('users.print', ['userId' => $user->id]))
|
||||
->assertStatus(302);
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
|
||||
->get(route('users.print', ['userId' => $user->id]))
|
||||
->assertStatus(200);
|
||||
|
||||
$this->actingAs($superuser)
|
||||
->get(route('users.print', ['userId' => $user->id]))
|
||||
->assertOk()
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testUserWithoutCompanyPermissionsCannotSendInventory()
|
||||
{
|
||||
|
||||
Notification::fake();
|
||||
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$superuser = User::factory()->superuser()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
$this->actingAs(User::factory()->create())
|
||||
->get(route('users.show', User::factory()->create()))
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
$this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
public function testCanViewUser()
|
||||
{
|
||||
$actor = User::factory()->viewUsers()->create();
|
||||
|
||||
$this->actingAs($actor)
|
||||
->get(route('users.show', User::factory()->create()))
|
||||
->assertOk()
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testCannotViewUserFromAnotherCompany()
|
||||
{
|
||||
$this->settings->enableMultipleFullCompanySupport();
|
||||
|
||||
[$companyA, $companyB] = Company::factory()->count(2)->create();
|
||||
|
||||
$actor = User::factory()->for($companyA)->viewUsers()->create();
|
||||
$user = User::factory()->for($companyB)->create();
|
||||
|
||||
$this->actingAs($actor)
|
||||
->get(route('users.show', $user))
|
||||
->assertStatus(302);
|
||||
|
||||
$this->actingAs($superuser)
|
||||
->post(route('users.email', ['userId' => $user->id]))
|
||||
->assertStatus(302);
|
||||
|
||||
Notification::assertSentTo(
|
||||
[$user], CurrentInventory::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,13 @@ class UserTest extends TestCase
|
|||
$expected_username = 'allanovna-romanova-oshostakova';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'lastname');
|
||||
$this->assertEquals($expected_username, $user['username']);
|
||||
|
||||
public function testFirstNameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'natalia@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstNameDotLastName()
|
||||
|
@ -40,6 +47,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstNameDotLastNameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'natalia.allanovna-romanova-oshostakova@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstname.lastname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testLastNameFirstInitial()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -48,6 +63,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testLastNameFirstInitialEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'allanovna-romanova-oshostakovan@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'lastnamefirstinitial');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstInitialLastName()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -56,6 +79,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstInitialLastNameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'nallanovna-romanova-oshostakova@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'filastname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstInitialUnderscoreLastName()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -64,6 +95,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstInitialUnderscoreLastNameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'nallanovna-romanova-oshostakova@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial_lastname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testSingleName()
|
||||
{
|
||||
$fullname = 'Natalia';
|
||||
|
@ -72,6 +111,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testSingleNameEmail()
|
||||
{
|
||||
$fullname = 'Natalia';
|
||||
$expected_email = 'natalia@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstname_lastname',);
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstInitialDotLastname()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -80,6 +127,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstInitialDotLastnameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'nallanovna-romanova-oshostakova@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial.lastname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testLastNameDotFirstInitial()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -88,6 +143,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testLastNameDotFirstInitialEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'allanovna-romanova-oshostakova.n@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'lastname.firstinitial');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testLastNameUnderscoreFirstInitial()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -96,6 +159,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testLastNameUnderscoreFirstInitialEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'allanovna-romanova-oshostakova_n@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'lastname_firstinitial');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstNameLastName()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -104,6 +175,14 @@ class UserTest extends TestCase
|
|||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstNameLastNameEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'nataliaallanovna-romanova-oshostakova@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastname');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
|
||||
public function testFirstNameLastInitial()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
|
@ -111,4 +190,12 @@ class UserTest extends TestCase
|
|||
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
|
||||
$this->assertEquals($expected_username, $user['username']);
|
||||
}
|
||||
|
||||
public function testFirstNameLastInitialEmail()
|
||||
{
|
||||
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
|
||||
$expected_email = 'nataliaa@example.com';
|
||||
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
|
||||
$this->assertEquals($expected_email, $user['username'] . '@example.com');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue