diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 1ac2a90a34..b222bdf5a3 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -651,6 +651,17 @@ class Helper return $customfields; } + /** + * Get all of the different types of custom fields there are + * TODO - how to make this more general? Or more useful? or more dynamic? + * idea - key of classname, *value* of trans? (thus having to make this a method, which is fine) + */ + static $itemtypes_having_custom_fields = [ + 0 => \App\Models\Asset::class, + 1 => \App\Models\User::class, + 2 => \App\Models\Accessory::class + ]; + /** * Get the list of custom field formats in an array to make a dropdown menu * diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index f1fa5369d1..324677223a 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -67,7 +67,7 @@ class AssetModelsController extends Controller 'models.deleted_at', 'models.updated_at', ]) - ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues') + ->with('category', 'depreciation', 'manufacturer') ->withCount('assets as assets_count'); if ($request->input('status')=='deleted') { diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index dc20588b75..ee74169f16 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -120,7 +120,7 @@ class AssetsController extends Controller $filter = json_decode($request->input('filter'), true); } - $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load + $all_custom_fields = CustomField::where('type', Asset::class); //used as a 'cache' of custom fields throughout this page load foreach ($all_custom_fields as $field) { $allowed_columns[] = $field->db_column_name(); } @@ -586,48 +586,8 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); - // Update custom fields in the database. - $model = AssetModel::find($request->input('model_id')); + $asset->customFill($request, Auth::user(), true); - // Check that it's an object and not a collection - // (Sometimes people send arrays here and they shouldn't - if (($model) && ($model instanceof AssetModel) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - - // Set the field value based on what was sent in the request - $field_val = $request->input($field->db_column, null); - - // If input value is null, use custom field's default value - if ($field_val == null) { - Log::debug('Field value for '.$field->db_column.' is null'); - $field_val = $field->defaultValue($request->get('model_id')); - Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id'))); - } - - // if the field is set to encrypted, make sure we encrypt the value - if ($field->field_encrypted == '1') { - Log::debug('This model field is encrypted in this fieldset.'); - - if (Gate::allows('admin')) { - - // If input value is null, use custom field's default value - if (($field_val == null) && ($request->has('model_id') != '')) { - $field_val = Crypt::encrypt($field->defaultValue($request->get('model_id'))); - } else { - $field_val = Crypt::encrypt($request->input($field->db_column)); - } - } - } - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } - } - - - $asset->{$field->db_column} = $field_val; - } - } if ($asset->save()) { if ($request->get('assigned_user')) { @@ -689,32 +649,8 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); $model = AssetModel::find($asset->model_id); - - // Update custom fields - $problems_updating_encrypted_custom_fields = false; - if (($model) && (isset($model->fieldset))) { - foreach ($model->fieldset->fields as $field) { - $field_val = $request->input($field->db_column, null); - - if ($request->has($field->db_column)) { - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } - } - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - $field_val = Crypt::encrypt($field_val); - } else { - $problems_updating_encrypted_custom_fields = true; - continue; - } - } - $asset->{$field->db_column} = $field_val; - } - } - } + $asset->customFill($request, Auth::user()); if ($asset->save()) { if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index 2fa22f5491..17026ccb2d 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -35,7 +35,7 @@ class CustomFieldsetsController extends Controller public function index() { $this->authorize('index', CustomField::class); - $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); + $fieldsets = CustomFieldset::withCount('fields as fields_count')->get(); return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count()); } @@ -125,7 +125,7 @@ class CustomFieldsetsController extends Controller $this->authorize('delete', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); - $modelsCount = $fieldset->models->count(); + $modelsCount = $fieldset->customizables()->count(); $fieldsCount = $fieldset->fields->count(); if (($modelsCount > 0) || ($fieldsCount > 0)) { diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 3dfb28feb3..6b86a26c78 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -9,6 +9,7 @@ use App\Models\Asset; use App\Models\AssetModel; use App\Models\CustomField; use App\Models\User; +use App\Models\DefaultValuesForCustomFields; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\View; @@ -163,7 +164,7 @@ class AssetModelsController extends Controller $model->notes = $request->input('notes'); $model->requestable = $request->input('requestable', '0'); - $this->removeCustomFieldsDefaultValues($model); + DefaultValuesForCustomFields::forPivot($model, Asset::class)->delete(); $model->fieldset_id = $request->input('fieldset_id'); @@ -480,7 +481,7 @@ class AssetModelsController extends Controller } /** - * Adds default values to a model (as long as they are truthy) + * Adds default values to a model (as long as they are truthy) (does this mean I cannot set a default value of 0?) * * @param AssetModel $model * @param array $defaultValues @@ -515,22 +516,12 @@ class AssetModelsController extends Controller } foreach ($defaultValues as $customFieldId => $defaultValue) { - if(is_array($defaultValue)){ - $model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]); - }elseif ($defaultValue) { - $model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]); + if (is_array($defaultValue)) { + $defaultValue = implode(', ', $defaultValue); } + DefaultValuesForCustomFields::updateOrCreate(['custom_field_id' => $customFieldId, 'item_pivot_id' => $model->id], ['default_value' => $defaultValue]); } return true; } - /** - * Removes all default values - * - * @return void - */ - private function removeCustomFieldsDefaultValues(AssetModel $model) - { - $model->defaultValues()->detach(); - } } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index bf3a3ec3a8..0a069e7ba6 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -160,29 +160,7 @@ class AssetsController extends Controller $asset = $request->handleImages($asset); } - // 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 (($model) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - 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); - } - } - } - } + $asset->customFill($request, Auth::user()); // Update custom fields in the database. // Validate the asset before saving if ($asset->isValid() && $asset->save()) { diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index 94b45552fd..1c9513d65d 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -9,6 +9,7 @@ use App\Models\CustomFieldset; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; + /** * This controller handles all actions related to Custom Asset Fields for * the Snipe-IT Asset Management application. @@ -20,6 +21,7 @@ use Illuminate\Http\Request; */ class CustomFieldsController extends Controller { + /** * Returns a view with a listing of custom fields. * @@ -28,12 +30,12 @@ class CustomFieldsController extends Controller * @return \Illuminate\Support\Facades\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index(Request $request) { $this->authorize('view', CustomField::class); - $fieldsets = CustomFieldset::with('fields', 'models')->get(); - $fields = CustomField::with('fieldset')->get(); + $fieldsets = CustomFieldset::with('fields')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab', 0)])->get(); //cannot eager-load 'customizable' because it's not a relation + $fields = CustomField::with('fieldset')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab', 0)])->get(); return view('custom_fields.index')->with('custom_fieldsets', $fieldsets)->with('custom_fields', $fields); } @@ -110,8 +112,10 @@ class CustomFieldsController extends Controller "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "show_in_listview" => $request->get("show_in_listview", 0), "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), - "user_id" => Auth::id() + "user_id" => Auth::id(), ]); + // not mass-assignable; must be manual + $field->type = Helper::$itemtypes_having_custom_fields[$request->get('tab')]; if ($request->filled('custom_format')) { @@ -131,7 +135,7 @@ class CustomFieldsController extends Controller } - return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.create.success')); + return redirect()->route('fields.index', ['tab' => $request->get('tab', 0)])->with('success', trans('admin/custom_fields/message.field.create.success')); } return redirect()->back()->with('selected_fieldsets', $request->input('associate_fieldsets'))->withInput() @@ -188,8 +192,8 @@ class CustomFieldsController extends Controller return redirect()->back()->withErrors(['message' => 'Field is in-use']); } $field->delete(); - return redirect()->route("fields.index") - ->with("success", trans('admin/custom_fields/message.field.delete.success')); + return redirect()->route('fields.index', ['tab' => Request::query('tab', 0)]) + ->with('success', trans('admin/custom_fields/message.field.delete.success')); } return redirect()->back()->withErrors(['message' => 'Field does not exist']); @@ -290,7 +294,7 @@ class CustomFieldsController extends Controller $field->fieldset()->sync([]); } - return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success')); + return redirect()->route('fields.index', ['tab' => $request->get('tab', 0)])->with('success', trans('admin/custom_fields/message.field.update.success')); } return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.update.error')); diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index e4382a31ea..cab5e86f36 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Helpers\Helper; use App\Models\AssetModel; use App\Models\CustomField; use App\Models\CustomFieldset; @@ -96,6 +97,8 @@ class CustomFieldsetsController extends Controller $fieldset = new CustomFieldset([ 'name' => $request->get('name'), 'user_id' => Auth::user()->id, + 'type' => Helper::$itemtypes_having_custom_fields[$request->get('tab')] +// 'sub' => ]); $validator = Validator::make($request->all(), $fieldset->rules); diff --git a/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php b/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php index e1487d2151..1c6a30cb31 100644 --- a/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php +++ b/app/Http/Livewire/CustomFieldSetDefaultValuesForModel.php @@ -2,6 +2,8 @@ namespace App\Http\Livewire; +use App\Models\Asset; +use App\Models\DefaultValuesForCustomFields; use Livewire\Component; use App\Models\CustomFieldset; @@ -30,7 +32,7 @@ class CustomFieldSetDefaultValuesForModel extends Component $this->fields = CustomFieldset::find($this->fieldset_id)->fields; } - $this->add_default_values = ($this->model->defaultValues->count() > 0); + $this->add_default_values = (DefaultValuesForCustomFields::forPivot($this->model, Asset::class)->count() > 0); } public function updatedFieldsetId() diff --git a/app/Http/Livewire/Importer.php b/app/Http/Livewire/Importer.php index 318514af73..81ad14ecd7 100644 --- a/app/Http/Livewire/Importer.php +++ b/app/Http/Livewire/Importer.php @@ -109,7 +109,7 @@ class Importer extends Component if ($type == "asset") { // add Custom Fields after a horizontal line $results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’"; - foreach (CustomField::orderBy('name')->get() as $field) { + foreach (CustomField::where('type', \App\Models\Asset::class)->orderBy('name')->get() as $field) { // TODO - generalize? $results[$field->db_column_name()] = $field->name; } } diff --git a/app/Http/Requests/CustomFieldRequest.php b/app/Http/Requests/CustomFieldRequest.php index 0c2ec0ae60..c8da020803 100644 --- a/app/Http/Requests/CustomFieldRequest.php +++ b/app/Http/Requests/CustomFieldRequest.php @@ -34,12 +34,14 @@ class CustomFieldRequest extends FormRequest case 'POST': { $rules['name'] = 'required|unique:custom_fields'; + $rules['tab'] = 'required'; break; } // Save all fields case 'PUT': $rules['name'] = 'required'; + $rules['tab'] = 'required'; break; // Save only what's passed diff --git a/app/Http/Transformers/CustomFieldsetsTransformer.php b/app/Http/Transformers/CustomFieldsetsTransformer.php index 61e42486ab..18f1f79632 100644 --- a/app/Http/Transformers/CustomFieldsetsTransformer.php +++ b/app/Http/Transformers/CustomFieldsetsTransformer.php @@ -3,6 +3,8 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Models\Asset; +use App\Models\AssetModel; use App\Models\CustomFieldset; use Illuminate\Database\Eloquent\Collection; @@ -21,8 +23,13 @@ class CustomFieldsetsTransformer public function transformCustomFieldset(CustomFieldset $fieldset) { $fields = $fieldset->fields; - $models = $fieldset->models; + $models = []; $modelsArray = []; + if ($fieldset->type == Asset::class) { + \Log::debug("Item pivot id is: ".$fieldset->item_pivot_id); + $models = AssetModel::where('fieldset_id', $fieldset->id)->get(); + \Log::debug("And the models object count is: ".$models->count()); + } foreach ($models as $model) { $modelsArray[] = [ @@ -30,15 +37,21 @@ class CustomFieldsetsTransformer 'name' => e($model->name), ]; } + \Log::debug("Models array is: ".print_r($modelsArray,true)); $array = [ 'id' => (int) $fieldset->id, 'name' => e($fieldset->name), 'fields' => (new CustomFieldsTransformer)->transformCustomFields($fields, $fieldset->fields_count), - 'models' => (new DatatablesTransformer)->transformDatatables($modelsArray, $fieldset->models_count), + 'customizables' => (new DatatablesTransformer)->transformDatatables($fieldset->customizables(),count($fieldset->customizables())), 'created_at' => Helper::getFormattedDateObject($fieldset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($fieldset->updated_at, 'datetime'), + 'type' => $fieldset->type, ]; + if ($fieldset->type == Asset::class) { + // TODO - removeme - legacy column just for Assets? + $array['models'] = (new DatatablesTransformer)->transformDatatables($modelsArray, count($modelsArray)); + } return $array; } diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index 594ca58f0d..c4a0734ca6 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -28,9 +28,10 @@ class AssetImporter extends ItemImporter // ItemImporter handles the general fetching. parent::handle($row); + // FIXME : YUP!!!!! This shit needs to go (?) Yeah? if ($this->customFields) { foreach ($this->customFields as $customField) { - $customFieldValue = $this->array_smart_custom_field_fetch($row, $customField); + $customFieldValue = $this->array_smart_custom_field_fetch($row, $customField); // TODO/FIXME - this might require a new 'mode' on customFill()? if ($customFieldValue) { if ($customField->field_encrypted == 1) { @@ -40,7 +41,7 @@ class AssetImporter extends ItemImporter $this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue; $this->log('Custom Field '.$customField->name.': '.$customFieldValue); } - } else { + } else { // FIXME - think this through? Do we want to blank this? Is that how other stuff works? // Clear out previous data. $this->item['custom_fields'][$customField->db_column_name()] = null; } diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index c7b0e96820..2ea52da927 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -175,7 +175,7 @@ abstract class Importer * @author Daniel Meltzer * @since 5.0 */ - protected function populateCustomFields($headerRow) + protected function populateCustomFields($headerRow) // FIXME - what in the actual fuck is this. { // Stolen From https://adamwathan.me/2016/07/14/customizing-keys-when-mapping-collections/ // This 'inverts' the fields such that we have a collection of fields indexed by name. diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 09dae1a1c3..6ed8568973 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -8,6 +8,8 @@ use App\Exceptions\CheckoutNotAllowed; use App\Helpers\Helper; use App\Http\Traits\UniqueUndeletedTrait; use App\Models\Traits\Acceptable; +use App\Models\Traits\Customizable; +use App\Models\Traits\HasCustomFields; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use AssetPresenter; @@ -17,6 +19,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; @@ -37,8 +40,21 @@ class Asset extends Depreciable public const ASSET = 'asset'; public const USER = 'user'; - use Acceptable; + use Acceptable, HasCustomFields; + public function getFieldsetKey(): object|int|null + { + return $this->model; + } + + public static function getFieldsetUsers(int $fieldset_id): array + { + $models = []; + foreach (AssetModel::where("fieldset_id", $fieldset_id)->get() as $model) { + $models[route('models.show', $model->id)] = $model->name . (($model->model_number) ? ' (' . $model->model_number . ')' : ''); + } + return $models; + } /** * Run after the checkout acceptance was declined by the user * @@ -200,41 +216,7 @@ class Asset extends Depreciable } $this->attributes['expected_checkin'] = $value; } - - /** - * This handles the custom field validation for assets - * - * @var array - */ - 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); - } - } - } - } - } - - return parent::save($params); - } - - + public function getDisplayNameAttribute() { return $this->present()->name(); diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index c5fb9284aa..f7e08d6f25 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -142,6 +142,7 @@ class AssetModel extends SnipeModel */ public function fieldset() { + // this is actually OK - we don't *need* to do this, but it's okay to make references from Model to fieldset return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id'); } @@ -150,18 +151,6 @@ class AssetModel extends SnipeModel return $this->fieldset()->first()->fields(); } - /** - * Establishes the model -> custom field default values relationship - * - * @author hannah tinkler - * @since [v4.3] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function defaultValues() - { - return $this->belongsToMany(\App\Models\CustomField::class, 'models_custom_fields')->withPivot('default_value'); - } - /** * Gets the full url for the image * diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index c1826a94d8..ec6f486a7d 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -16,7 +16,7 @@ class CustomField extends Model UniqueUndeletedTrait; /** - * Custom field predfined formats + * Custom field predefined formats * * @var array */ @@ -82,30 +82,19 @@ class CustomField extends Model 'show_in_requestable_list', ]; - /** - * This is confusing, since it's actually the custom fields table that - * we're usually modifying, but since we alter the assets table, we have to - * say that here, otherwise the new fields get added onto the custom fields - * table instead of the assets table. - * - * @author [Brady Wetherington] [] - * @since [v3.0] - */ - public static $table_name = 'assets'; - /** * Convert the custom field's name property to a db-safe string. * * We could probably have used str_slug() here but not sure what it would * do with previously existing values. - @snipe * - * @author [A. Gianotto] [] - * @since [v3.4] * @return string + * @since [v3.4] + * @author [A. Gianotto] [] */ public static function name_to_db_name($name) { - return '_snipeit_'.preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name)); + return '_snipeit_' . preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name)); } /** @@ -116,23 +105,22 @@ class CustomField extends Model * if they have changed, so we handle that here so that we don't have to remember * to do it in the controllers. * - * @author [A. Gianotto] [] - * @since [v3.4] * @return bool + * @since [v3.4] + * @author [A. Gianotto] [] */ public static function boot() { parent::boot(); self::created(function ($custom_field) { - // Column already exists on the assets table - nothing to do here. // This *shouldn't* happen in the wild. - if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) { + if (Schema::hasColumn($custom_field->getTableName(), $custom_field->db_column)) { return false; } // Update the column name in the assets table - Schema::table(self::$table_name, function ($table) use ($custom_field) { + Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->text($custom_field->convertUnicodeDbSlug())->nullable(); }); @@ -145,7 +133,7 @@ class CustomField extends Model // Column already exists on the assets table - nothing to do here. if ($custom_field->isDirty('name')) { - if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) { + if (Schema::hasColumn($custom_field->getTableName(), $custom_field->convertUnicodeDbSlug())) { return true; } @@ -155,7 +143,7 @@ class CustomField extends Model $platform->registerDoctrineTypeMapping('enum', 'string'); // Rename the field if the name has changed - Schema::table(self::$table_name, function ($table) use ($custom_field) { + Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug()); }); @@ -171,12 +159,19 @@ class CustomField extends Model // Drop the assets column if we've deleted it from custom fields self::deleting(function ($custom_field) { - return Schema::table(self::$table_name, function ($table) use ($custom_field) { + return Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) { $table->dropColumn($custom_field->db_column); }); }); } + public function getTableName() + { + $type = $this->type; + $instance = new $type(); + return $instance->getTable(); + } + /** * Establishes the customfield -> fieldset relationship * @@ -207,31 +202,23 @@ class CustomField extends Model } /** - * Establishes the customfield -> default values relationship - * - * @author Hannah Tinkler - * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function defaultValues() - { - return $this->belongsToMany(\App\Models\AssetModel::class, 'models_custom_fields')->withPivot('default_value'); - } - - /** - * Returns the default value for a given model using the defaultValues + * Returns the default value for a given 'item' using the defaultValues * relationship * * @param int $modelId * @return string */ - public function defaultValue($modelId) + public function defaultValue($pivot_id) { - return $this->defaultValues->filter(function ($item) use ($modelId) { - return $item->pivot->asset_model_id == $modelId; - })->map(function ($item) { - return $item->pivot->default_value; - })->first(); + /* + below, you might think you need to add: + + where('type', $this->type), + + but the type can be inferred from by the custom_field itself (which also has a type) + can't use forPivot() here because we don't have an object yet. (TODO?) + */ + DefaultValuesForCustomFields::where('item_pivot_id', $pivot_id)->where('custom_field_id', $this->id)->first()?->default_value; //TODO - php8-only operator! } /** diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index 71be28e8a3..3a36a417c2 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -22,6 +22,7 @@ class CustomFieldset extends Model */ public $rules = [ 'name' => 'required|unique:custom_fieldsets', + '' ]; /** @@ -50,11 +51,13 @@ class CustomFieldset extends Model * * @author [Brady Wetherington] [] * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation + * @return \Illuminate\Database\Eloquent\Collection */ - public function models() + public function customizables() // TODO - I don't like this name, but I can't think of anything better { - return $this->hasMany(\App\Models\AssetModel::class, 'fieldset_id'); + $customizable_class_name = $this->type; //TODO - copypasta from Customizable trait? + \Log::debug("Customizable Class name is: ".$customizable_class_name); + return $customizable_class_name::getFieldsetUsers($this->id); } /** @@ -79,6 +82,7 @@ class CustomFieldset extends Model */ public function validation_rules() { + \Log::debug("CALLING validation_rules FOR customfiledsets!"); $rules = []; foreach ($this->fields as $field) { $rule = []; @@ -92,7 +96,12 @@ class CustomFieldset extends Model $rule[] = 'unique_undeleted'; } - array_push($rule, $field->attributes['format']); + \Log::debug("Field Format for".$field->name." is: ".$field->format); + if($field->format == 'DATE') { //we do a weird mutator thing, it's confusing - but, yes, it's all-caps + $rule[] = 'date_format:Y-m-d'; + } else { + array_push($rule, $field->attributes['format']); + } $rules[$field->db_column_name()] = $rule; // add not_array to rules for all fields but checkboxes diff --git a/app/Models/DefaultValuesForCustomFields.php b/app/Models/DefaultValuesForCustomFields.php new file mode 100644 index 0000000000..884ff9b3b2 --- /dev/null +++ b/app/Models/DefaultValuesForCustomFields.php @@ -0,0 +1,34 @@ + 'required' + ]; + + public $timestamps = false; + + public function field() { + return $this->belongsTo('custom_fields'); + } + + // There is, effectively, another 'relation' here, but it's weirdly polymorphic + // and impossible to represent in Laravel. + // we have a 'type', and we have an 'item_pivot_id' - + // For example, in Assets the 'type' would be App\Models\Asset, and the 'item_pivot_id' would be a model_id + // I can't come up with any way to represent this in Laravel/Eloquent + + // TODO: might be getting overly-fancy here; maybe just want to do an ID? Instead of an Eloquent Model? + public function scopeForPivot(Builder $query, Model $item, string $class) { + return $query->where('item_pivot_id', $item->id)->where('type', $class); + } +} diff --git a/app/Models/Traits/HasCustomFields.php b/app/Models/Traits/HasCustomFields.php new file mode 100644 index 0000000000..b3f8b52977 --- /dev/null +++ b/app/Models/Traits/HasCustomFields.php @@ -0,0 +1,138 @@ +fieldset` or `->id`. + */ + public function getFieldset(): ?CustomFieldset { + $pivot = $this->getFieldsetKey(); + if(is_int($pivot)) { //why does this look just like the other thing? (below, look for is_int() + return Fieldset::find($pivot); + } + return $pivot->fieldset; + } + + /********************** + * @return Object|int|null + * (if this is in PHP 8.0, can we just put that as the signature?) + * + * This is the main method you have to override. It should either return an + * Object who you can call `->fieldset` on and get a fieldset object, and also + * be able to call `->id` on to get a unique key to be able to show custom fields. + * For example, for Assets, the element that is returned is the 'model' for the Asset. + * For something like Users, which will probably have only one universal set of custom fields, + * it should just return the Fieldset ID for it. Or, if there are no custom fields, it should + * return null + */ + abstract public function getFieldsetKey(): Object|int|null; // php v8 minimum, GOOD. TODO + + /*********************** + * @param int $fieldset_id + * @return Collection + * + * This is the main method you need to override to return a list of things that are *using* this fieldset + * The format is an array with keys: a URL, and values. So, for assets, it might return + * { + * "models/14" => "MacBook Pro 13 (model no: 12345)" + * } + */ + abstract public static function getFieldsetUsers(int $fieldset_id): array; + + public static function augmentValidationRulesForCustomFields($model) { + \Log::debug("Augmenting validation rules for custom fields!!!!!!"); + $fieldset = $model->getFieldset(); + if ($fieldset) { + foreach ($fieldset->fields as $field){ + if($field->format == 'BOOLEAN'){ // TODO - this 'feels' like entanglement of concerns? + $model->{$field->db_column} = filter_var($model->{$model->db_column}, FILTER_VALIDATE_BOOLEAN); + } + } + + if(!$model->rules) { + $model->rules = []; + } + $model->rules += $model->getFieldset()->validation_rules(); + \Log::debug("FINAL RULES ARE: ".print_r($model->rules,true)); + } + + } + + public function getDefaultValue(CustomField $field) + { + $pivot = $this->getFieldsetKey(); // TODO - feels copypasta-ish? + $key_id = null; + + if( is_int($pivot) ) { // TODO: *WHY* does this code repeat?! + $key_id = $pivot; // now we're done + } elseif( is_object($pivot) ) { + $key_id = $pivot?->id; + } + if(is_null($key_id)) { + return; + } + + // TODO - begninng to think my custom scope really should be just an integer :/ + return DefaultValuesForCustomFields::where('type',self::class) + ->where('custom_field_id',$field->id) + ->where('item_pivot_id',$key_id)->first()?->default_value; + } + + public function customFill(Request $request, User $user, bool $shouldSetDefaults = false) { + $this->_filled = true; + if ($this->getFieldset()) { + foreach ($this->getFieldset()->fields as $field) { + if (is_array($request->input($field->db_column))) { + $field_value = implode(', ', $request->input($field->db_column)); + } else { + $field_value = $request->input($field->db_column); + } + + if ($shouldSetDefaults && (is_null($field_value) || $field_value === '')) { + $field_value = $this->getDefaultValue($field); + } + if ($field->field_encrypted == '1') { + if ($user->can('admin')) { + $this->{$field->db_column} = \Crypt::encrypt($field_value); + } + } else { + $this->{$field->db_column} = $request->input($field->db_column); + } + } + } + } +} \ No newline at end of file diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index 163ee1b606..2b40395405 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -2,8 +2,10 @@ namespace App\Presenters; +use App\Models\Asset; use App\Models\CustomField; use Carbon\CarbonImmutable; +use App\Models\CustomFieldset; use DateTime; /** @@ -299,10 +301,16 @@ class AssetPresenter extends Presenter // models. We only pass the fieldsets that pertain to each asset (via their model) so that we // don't junk up the REST API with tons of custom fields that don't apply - $fields = CustomField::whereHas('fieldset', function ($query) { - $query->whereHas('models'); - })->get(); + //only get fieldsets that have fields + $fieldsets = CustomFieldset::where("type", Asset::class)->whereHas('fields')->get(); + $ids = []; + foreach($fieldsets as $fieldset) { + if (count($fieldset->customizables()) > 0) { //only get fieldsets that are 'in use' + $ids[] = $fieldset->id; + } + } + $fields = CustomField::whereIn('id',$ids)->get(); // Note: We do not need to e() escape the field names here, as they are already escaped when // they are presented in the blade view. If we escape them here, custom fields with quotes in their // name can break the listings page. - snipe diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 51e6858c9c..19da367a08 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -87,7 +87,6 @@ class AuthServiceProvider extends ServiceProvider ]); $this->registerPolicies(); - //Passport::routes(); //this is no longer required in newer passport versions Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); diff --git a/config/trustedproxy.php b/config/trustedproxy.php index 106a13d4f5..cf011faa93 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -1,5 +1,7 @@ Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, //this is mostly handled already - + 'headers' => Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB, ]; diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 43845c3075..67470b2be5 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -387,4 +387,15 @@ class AssetFactory extends Factory $asset->setValidating(true); }); } + + public function withComplicatedCustomFields() + { + return $this->state(function () { + return [ + 'model_id' => function () { + return AssetModel::where('name', 'complicated')->first() ?? AssetModel::factory()->complicated(); + } + ]; + }); + } } diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 6790897567..6a50e5d933 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -448,4 +448,13 @@ class AssetModelFactory extends Factory ]; }); } + + public function complicated() + { + return $this->state(function () { + return [ + 'name' => 'Complicated fieldset' + ]; + })->for(CustomFieldSet::factory()->complicated(), 'fieldset'); + } } diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php index 44ab0707e0..05247c013b 100644 --- a/database/factories/CustomFieldFactory.php +++ b/database/factories/CustomFieldFactory.php @@ -27,6 +27,7 @@ class CustomFieldFactory extends Factory 'element' => 'text', 'auto_add_to_fieldsets' => '0', 'show_in_requestable_list' => '0', + 'type' => 'App\\Models\\Asset' ]; } @@ -76,7 +77,7 @@ class CustomFieldFactory extends Factory { return $this->state(function () { return [ - 'name' => 'MAC Address', + 'name' => 'MAC Address EXPLICIT', 'format' => 'regex:/^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/', ]; }); @@ -93,6 +94,15 @@ class CustomFieldFactory extends Factory }); } + public function plainText() + { + return $this->state(function () { + return [ + 'name' => 'plain_text', + ]; + }); + } + public function testCheckbox() { return $this->state(function () { @@ -117,4 +127,13 @@ class CustomFieldFactory extends Factory }); } + public function date() + { + return $this->state(function () { + return [ + 'name' => 'date', + 'format' => 'date' + ]; + }); + } } diff --git a/database/factories/CustomFieldsetFactory.php b/database/factories/CustomFieldsetFactory.php index a9e8b9ae12..7ac31ce38e 100644 --- a/database/factories/CustomFieldsetFactory.php +++ b/database/factories/CustomFieldsetFactory.php @@ -2,8 +2,8 @@ namespace Database\Factories; -use App\Models\CustomFieldset; use App\Models\CustomField; +use App\Models\CustomFieldset; use Illuminate\Database\Eloquent\Factories\Factory; class CustomFieldsetFactory extends Factory @@ -58,13 +58,13 @@ class CustomFieldsetFactory extends Factory { return $this->afterCreating(function (CustomFieldset $fieldset) use ($fields) { if (empty($fields)) { - $mac_address = CustomField::factory()->macAddress()->create(); - $ram = CustomField::factory()->ram()->create(); - $cpu = CustomField::factory()->cpu()->create(); + $mac_address = CustomField::factory()->macAddress()->create(); + $ram = CustomField::factory()->ram()->create(); + $cpu = CustomField::factory()->cpu()->create(); - $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); - $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); - $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); + $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); + $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); + $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); } else { foreach ($fields as $field) { $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); @@ -72,4 +72,16 @@ class CustomFieldsetFactory extends Factory } }); } + + public function complicated() + { + //$mac = CustomField::factory()->macAddress()->create(); + return $this->state(function () { + return [ + 'name' => 'complicated' + ]; + })->hasAttached(CustomField::factory()->macAddress(), ['required' => false, 'order' => 0], 'fields') + ->hasAttached(CustomField::factory()->plainText(), ['required' => true, 'order' => 1], 'fields') + ->hasAttached(CustomField::factory()->date(), ['required' => false, 'order' => 2], 'fields'); + } } diff --git a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php index 4725cccfe1..2aa036bc09 100644 --- a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php +++ b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php @@ -23,8 +23,8 @@ function updateLegacyColumnName($customfield) $name_to_db_name = CustomField::name_to_db_name($customfield->name); //\Log::debug('Trying to rename '.$name_to_db_name." to ".$customfield->convertUnicodeDbSlug()."...\n"); - if (Schema::hasColumn(CustomField::$table_name, $name_to_db_name)) { - return Schema::table(CustomField::$table_name, + if (Schema::hasColumn('assets', $name_to_db_name)) { + return Schema::table('assets', function ($table) use ($name_to_db_name, $customfield) { $table->renameColumn($name_to_db_name, $customfield->convertUnicodeDbSlug()); } @@ -81,8 +81,8 @@ class FixUtf8CustomFieldColumnNames extends Migration // "_snipeit_imei_1" becomes "_snipeit_imei" $legacyColumnName = (string) Str::of($currentColumnName)->replaceMatches('/_(\d)+$/', ''); - if (Schema::hasColumn(CustomField::$table_name, $currentColumnName)) { - Schema::table(CustomField::$table_name, function (Blueprint $table) use ($currentColumnName, $legacyColumnName) { + if (Schema::hasColumn('assets', $currentColumnName)) { + Schema::table('assets', function (Blueprint $table) use ($currentColumnName, $legacyColumnName) { $table->renameColumn( $currentColumnName, $legacyColumnName diff --git a/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php b/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php new file mode 100644 index 0000000000..e583d6d413 --- /dev/null +++ b/database/migrations/2021_06_29_212602_add_type_to_custom_fields.php @@ -0,0 +1,38 @@ +text('type')->default('App\\Models\\Asset'); // TODO this default is needed for a not-nullable column, I guess? I don't like this because it will silently 'fix' errors we should properly 'fix' + }); + CustomField::query()->update(['type' => Asset::class]); // TODO - is this still necessary with that 'default'? + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fields', function (Blueprint $table) { + // + $table->dropColumn('type'); + }); + } +} diff --git a/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php b/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php new file mode 100644 index 0000000000..e9302abb43 --- /dev/null +++ b/database/migrations/2021_06_29_212612_add_type_to_custom_fieldsets.php @@ -0,0 +1,38 @@ +text('type')->default('App\\Models\\Asset'); + }); + CustomFieldset::query()->update(['type' => Asset::class]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fieldsets', function (Blueprint $table) { + // + $table->dropColumn('type'); + }); + } +} diff --git a/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php b/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php new file mode 100644 index 0000000000..46d1ad2a2a --- /dev/null +++ b/database/migrations/2023_08_10_121812_generalize_default_values_for_custom_fields.php @@ -0,0 +1,28 @@ +text('type')->default('App\\Models\\Asset'); + $table->renameColumn('asset_model_id','item_pivot_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('default_values_for_custom_fields', function (Blueprint $table) { + $table->dropColumn('type'); + $table->renameColumn('item_pivot_id','asset_model_id'); + }); + } +} diff --git a/resources/views/custom_fields/fields/edit.blade.php b/resources/views/custom_fields/fields/edit.blade.php index e21e9fba48..b47a73f3fb 100644 --- a/resources/views/custom_fields/fields/edit.blade.php +++ b/resources/views/custom_fields/fields/edit.blade.php @@ -30,6 +30,8 @@ @endif @csrf + +
diff --git a/resources/views/custom_fields/fieldsets/edit.blade.php b/resources/views/custom_fields/fieldsets/edit.blade.php index 7a35ca146f..4520b27776 100644 --- a/resources/views/custom_fields/fieldsets/edit.blade.php +++ b/resources/views/custom_fields/fieldsets/edit.blade.php @@ -12,6 +12,7 @@ @section('inputFields') @include ('partials.forms.edit.name', ['translated_name' => trans('general.name')]) + @stop diff --git a/resources/views/custom_fields/index.blade.php b/resources/views/custom_fields/index.blade.php index d8eeae1d56..9119031d00 100644 --- a/resources/views/custom_fields/index.blade.php +++ b/resources/views/custom_fields/index.blade.php @@ -13,6 +13,26 @@ @section('content') @can('view', \App\Models\CustomFieldset::class) +
+
+
+
+ + +
+
+
+
@@ -21,7 +41,9 @@

{{ trans('admin/custom_fields/general.fieldsets') }}

@@ -47,7 +69,7 @@ {{ trans('general.name') }} {{ trans('admin/custom_fields/general.qty_fields') }} - {{ trans('admin/custom_fields/general.used_by_models') }} + {{ trans('admin/custom_fields/general.used_by_models') }}{{-- FIXME --}} {{ trans('table.actions') }} @@ -63,9 +85,9 @@ {{ $fieldset->fields->count() }} - @foreach($fieldset->models as $model) - {{ $model->name }}{{ ($model->model_number) ? ' ('.$model->model_number.')' : '' }} - + @foreach($fieldset->customizables() as $url => $name) + {{ $name }} + {{-- get_class($customizable) }}: {{ $customizable->name
--}} @endforeach @@ -88,10 +110,13 @@ @can('delete', $fieldset) {{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }} - @if($fieldset->models->count() > 0) - + @if(count($fieldset->customizables()) > 0 /* TODO - hate 'customizables' */) + @else - + @endif {{ Form::close() }} @endcan @@ -118,7 +143,9 @@

{{ trans('admin/custom_fields/general.custom_fields') }}

diff --git a/tests/Unit/HasCustomFieldsTraitTest.php b/tests/Unit/HasCustomFieldsTraitTest.php new file mode 100644 index 0000000000..35a8ad0708 --- /dev/null +++ b/tests/Unit/HasCustomFieldsTraitTest.php @@ -0,0 +1,79 @@ +withComplicatedCustomFields()->create(); + + $this->assertEquals($asset->model->fieldset->fields->count(), 3,'Custom Fieldset should have exactly 3 custom fields'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_mac_address_explicit_2'),'Assets table should have MAC address column'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_plain_text_3'),'Assets table should have MAC address column'); + $this->assertTrue(Schema::hasColumn('assets','_snipeit_date_4'),'Assets table should have MAC address column'); + } + public function testRequired() + { + $asset = Asset::factory()->withComplicatedCustomFields()->create(); + $this->assertFalse($asset->save(),'save() should fail due to required text field'); + } + + public function testFormat() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = 'something'; + $asset->_snipeit_mac_address_explicit_2 = 'fartsssssss'; + $this->assertFalse($asset->save(), 'should fail due to bad MAC address'); + } + + public function testDate() + { +// \Log::error("uh, what the heck is going on here?!"); + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = 'some text'; + $asset->_snipeit_date_4 = '1/2/2023'; +// $asset->save(); +// dd($asset); + $this->assertFalse($asset->save(),'Should fail due to incorrectly formatted date.'); + } + + public function testSaveMinimal() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = "some text"; + $this->assertTrue($asset->save(),"Asset should've saved okay, the one required field was filled out"); + } + + public function testSaveMaximal() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $asset->_snipeit_plain_text_3 = "some text"; + $asset->_snipeit_date_4 = "2023-01-02"; + $asset->_snipeit_mac_address_explicit_2 = "ff:ff:ff:ff:ff:ff"; + $this->assertTrue($asset->save(),"Asset should've saved okay, the one required field was filled out, and so were the others"); + } + + public function testJsonPost() + { + $asset = Asset::factory()->withComplicatedCustomFields()->make(); + $response = $this->postJson('/api/v1/hardware', [ + + ]); + $response->assertStatus(200); + } + +}