diff --git a/app/Components/Help/Models/HelpContent.php b/app/Components/Help/Models/HelpContent.php
new file mode 100644
index 0000000..cc882be
--- /dev/null
+++ b/app/Components/Help/Models/HelpContent.php
@@ -0,0 +1,136 @@
+ 'boolean',
+ 'version' => 'integer',
+ ];
+
+ /**
+ * Define which fields should be considered unique for conflict detection
+ * This is used by the SoftDeleteResolution trait to detect conflicts
+ */
+ protected function uniqueFields(): array
+ {
+ return ['context_key', 'version'];
+ }
+
+ /**
+ * Boot the model
+ */
+ protected static function boot()
+ {
+ parent::boot();
+
+ // Before saving, check for conflicting soft-deleted records
+ static::saving(function ($model) {
+ try {
+ $duplicate = $model->findSoftDeletedDuplicate();
+
+ if ($duplicate) {
+ // If we find a soft-deleted duplicate, restore it with the new attributes
+ $model->restoreSoftDeletedRecord($duplicate, $model->getAttributes());
+ return false; // Prevent original save
+ }
+ } catch (\Exception $e) {
+ \Log::error('Error checking for soft-deleted help content', [
+ 'error' => $e->getMessage(),
+ 'model' => get_class($model),
+ 'attributes' => $model->getAttributes()
+ ]);
+ }
+
+ return true;
+ });
+ }
+
+ /**
+ * Get the latest active version of help content for a given context key
+ *
+ * @param string $contextKey
+ * @return HelpContent|null
+ */
+ public static function getLatestForContext(string $contextKey): ?HelpContent
+ {
+ return static::where('context_key', $contextKey)
+ ->where('is_active', true)
+ ->orderBy('version', 'desc')
+ ->first();
+ }
+
+ /**
+ * Create a new version of help content
+ *
+ * @param string $contextKey
+ * @param string $title
+ * @param string $content
+ * @return HelpContent
+ */
+ public static function createNewVersion(string $contextKey, string $title, string $content): HelpContent
+ {
+ // Find the latest version
+ $latestVersion = static::where('context_key', $contextKey)
+ ->orderBy('version', 'desc')
+ ->value('version') ?? 0;
+
+ // Create new version
+ return static::create([
+ 'context_key' => $contextKey,
+ 'title' => $title,
+ 'content' => $content,
+ 'version' => $latestVersion + 1,
+ 'is_active' => true
+ ]);
+ }
+
+ /**
+ * Set this help content as the active version for its context
+ *
+ * @return bool
+ */
+ public function setAsActive(): bool
+ {
+ // Deactivate all other versions for this context
+ static::where('context_key', $this->context_key)
+ ->where('id', '!=', $this->id)
+ ->update(['is_active' => false]);
+
+ // Set this version as active
+ $this->is_active = true;
+ return $this->save();
+ }
+}
diff --git a/app/Components/Help/Providers/HelpServiceProvider.php b/app/Components/Help/Providers/HelpServiceProvider.php
new file mode 100644
index 0000000..aba20c7
--- /dev/null
+++ b/app/Components/Help/Providers/HelpServiceProvider.php
@@ -0,0 +1,58 @@
+group(function () {
+ $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
+ });
+
+ // Register views
+ $this->loadViewsFrom(__DIR__.'/../resources/views', 'help');
+
+ // Register Blade components
+ Blade::component('help-icon', HelpIcon::class);
+ Blade::component('help-modal', HelpModal::class);
+
+ // Register migrations
+ $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
+
+ // Publish assets
+ $this->publishes([
+ __DIR__.'/../resources/js' => public_path('js/components/help'),
+ __DIR__.'/../resources/css' => public_path('css/components/help'),
+ ], 'help-assets');
+
+ // Publish views for customization
+ $this->publishes([
+ __DIR__.'/../resources/views' => resource_path('views/vendor/help'),
+ ], 'help-views');
+
+ // Register the main help script to be included in the layout
+ view()->composer('*', function ($view) {
+ $view->with('helpScriptPath', asset('js/components/help/help-system.js'));
+ });
+ }
+}
diff --git a/app/Components/SoftDelete/Http/Controllers/SoftDeleteController.php b/app/Components/SoftDelete/Http/Controllers/SoftDeleteController.php
new file mode 100644
index 0000000..c38f9f7
--- /dev/null
+++ b/app/Components/SoftDelete/Http/Controllers/SoftDeleteController.php
@@ -0,0 +1,16 @@
+loadRoutesFrom(__DIR__ . '/../routes/web.php');
+
+ // Publish assets
+ if ($this->app->runningInConsole()) {
+ $this->publishes([
+ __DIR__ . '/../resources/js' => public_path('js/soft-delete'),
+ ], 'soft-delete-assets');
+ }
+
+ // Register blade components
+ $this->loadViewsFrom(__DIR__ . '/../resources/views', 'soft-delete');
+
+ // Register the Blade component
+ \Illuminate\Support\Facades\Blade::component('soft-delete::components.conflict-modal', 'soft-delete-conflict-modal');
+ }
+}
diff --git a/app/Components/SoftDelete/resources/js/soft-delete-conflict-handler.js b/app/Components/SoftDelete/resources/js/soft-delete-conflict-handler.js
new file mode 100644
index 0000000..c4a9446
--- /dev/null
+++ b/app/Components/SoftDelete/resources/js/soft-delete-conflict-handler.js
@@ -0,0 +1,127 @@
+/**
+ * Soft Delete Conflict Handler
+ *
+ * This module handles the detection and resolution of soft delete conflicts.
+ * Save this file to public/js/soft-delete-conflict-handler.js
+ */
+const SoftDeleteConflictHandler = {
+ /**
+ * Initialize the handler for a specific form.
+ *
+ * @param {string} formSelector - The CSS selector for the form
+ * @param {string} modelClass - The fully qualified class name of the model
+ * @param {object} options - Additional options
+ */
+ init: function(formSelector, modelClass, options = {}) {
+ const form = document.querySelector(formSelector);
+
+ if (!form) {
+ console.error(`Form not found: ${formSelector}`);
+ return;
+ }
+
+ // Store the model class on the form for later use
+ form.dataset.modelClass = modelClass;
+
+ // Set success and cancel redirect URLs if provided
+ if (options.successRedirect) {
+ form.dataset.successRedirect = options.successRedirect;
+ }
+
+ if (options.cancelRedirect) {
+ form.dataset.cancelRedirect = options.cancelRedirect;
+ }
+
+ // Intercept form submission
+ form.addEventListener('submit', (event) => {
+ // Don't prevent submission if we've already checked for conflicts
+ if (form.dataset.conflictChecked === 'true') {
+ return true;
+ }
+
+ // Prevent the default form submission
+ event.preventDefault();
+
+ // Check for conflicts
+ this.checkForConflicts(form, modelClass, (hasConflict, conflictData) => {
+ if (hasConflict) {
+ // Show the conflict resolution modal
+ this.showConflictModal(conflictData, form);
+ } else {
+ // No conflict, continue with form submission
+ form.dataset.conflictChecked = 'true';
+ form.submit();
+ }
+ });
+ });
+ },
+
+ /**
+ * Check for soft delete conflicts before form submission.
+ *
+ * @param {HTMLFormElement} form - The form element
+ * @param {string} modelClass - The fully qualified class name of the model
+ * @param {Function} callback - Callback function to handle the result
+ */
+ checkForConflicts: function(form, modelClass, callback) {
+ // Create a FormData object from the form
+ const formData = new FormData(form);
+
+ // Add the model class
+ formData.append('model_class', modelClass);
+
+ // Send the request to check for conflicts
+ fetch('/admin/soft-delete/check-conflict', {
+ method: 'POST',
+ body: formData,
+ headers: {
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
+ 'Accept': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success && data.conflict) {
+ // Conflict found
+ callback(true, data.duplicate);
+ } else {
+ // No conflict
+ callback(false, null);
+ }
+ })
+ .catch(error => {
+ console.error('Error checking for conflicts:', error);
+ // On error, allow the form to submit to let server-side validation handle it
+ callback(false, null);
+ });
+ },
+
+ /**
+ * Show the conflict resolution modal.
+ *
+ * @param {object} conflictData - Data about the conflicting record
+ * @param {HTMLFormElement} form - The form element
+ */
+ showConflictModal: function(conflictData, form) {
+ // Get form data as an object
+ const formDataObj = {};
+ const formData = new FormData(form);
+
+ for (const [key, value] of formData.entries()) {
+ formDataObj[key] = value;
+ }
+
+ // Dispatch a custom event to open the modal
+ window.dispatchEvent(new CustomEvent('open-soft-delete-modal', {
+ detail: {
+ conflict: conflictData,
+ formData: formDataObj,
+ formElement: form,
+ modelClass: form.dataset.modelClass
+ }
+ }));
+ }
+};
+
+// Expose to window for global access
+window.SoftDeleteConflictHandler = SoftDeleteConflictHandler;
diff --git a/app/Components/SoftDelete/resources/views/components/conflict-modal.blade.php b/app/Components/SoftDelete/resources/views/components/conflict-modal.blade.php
new file mode 100644
index 0000000..382a064
--- /dev/null
+++ b/app/Components/SoftDelete/resources/views/components/conflict-modal.blade.php
@@ -0,0 +1,213 @@
+{{--
+ Save this file to resources/views/components/soft-delete-conflict-modal.blade.php
+ or to the appropriate component location in your structure
+--}}
+
+@props(['title' => 'Record Already Exists'])
+
+
+
+
+
+
+
+
+
+
+ {{ $title }}
+
+
+
+ A record with the same unique identifier has been deleted previously. This might indicate a duplicate entry.
+
+
+
Deleted record details:
+
+
+ Deleted at:
+
+
+
+ You have the following options:
+
+
+ - Restore the deleted record with your new values
+ - Modify your current form input to avoid the conflict
+ - Cancel the operation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/Components/SoftDelete/routes/web.php b/app/Components/SoftDelete/routes/web.php
new file mode 100644
index 0000000..1420e85
--- /dev/null
+++ b/app/Components/SoftDelete/routes/web.php
@@ -0,0 +1,22 @@
+name('admin.soft-delete.')->middleware(['web', 'auth'])->group(function () {
+ Route::post('check-conflict', [SoftDeleteController::class, 'checkSoftDeleteConflict'])
+ ->name('check-conflict');
+
+ Route::post('resolve-conflict/{id}', [SoftDeleteController::class, 'resolveSoftDeleteConflict'])
+ ->name('resolve-conflict');
+});
diff --git a/app/Traits/SoftDeleteConflictController.php b/app/Traits/SoftDeleteConflictController.php
new file mode 100644
index 0000000..e1a4320
--- /dev/null
+++ b/app/Traits/SoftDeleteConflictController.php
@@ -0,0 +1,171 @@
+json([
+ 'success' => false,
+ 'message' => 'Invalid model class provided',
+ ], 400);
+ }
+
+ try {
+ // Create a new instance of the model
+ /** @var Model $model */
+ $model = new $modelClass();
+
+ // Ensure the model uses SoftDeleteResolution trait
+ if (!method_exists($model, 'findSoftDeletedDuplicate')) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Model does not support soft delete conflict resolution',
+ ], 400);
+ }
+
+ // Only include fields that are present in request
+ $attributes = $request->only($model->getFillable());
+
+ // If editing an existing record, pass the ID to exclude it from the check
+ if ($request->has('id')) {
+ $attributes['id'] = $request->input('id');
+ }
+
+ // Check for soft-deleted duplicate
+ $duplicate = $model->findSoftDeletedDuplicate($attributes);
+
+ if ($duplicate) {
+ // Return details about the conflicting record
+ return response()->json([
+ 'success' => true,
+ 'conflict' => true,
+ 'message' => 'A deleted record with the same unique identifier exists.',
+ 'duplicate' => [
+ 'id' => $duplicate->getKey(),
+ 'attributes' => $duplicate->toArray(),
+ 'deleted_at' => $duplicate->deleted_at->format('Y-m-d H:i:s'),
+ ],
+ ]);
+ }
+
+ // No conflict found
+ return response()->json([
+ 'success' => true,
+ 'conflict' => false,
+ 'message' => 'No conflict found',
+ ]);
+ } catch (\Exception $e) {
+ Log::error('Error checking for soft delete conflict', [
+ 'error' => $e->getMessage(),
+ 'model' => $modelClass,
+ 'data' => $request->all(),
+ ]);
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'An error occurred while checking for conflicts',
+ 'error' => $e->getMessage(),
+ ], 500);
+ }
+ }
+
+ /**
+ * Resolve a soft delete conflict by restoring the deleted record.
+ *
+ * @param Request $request
+ * @param string $modelClass The fully qualified class name of the model
+ * @param mixed $id The ID of the soft-deleted record to restore
+ * @return JsonResponse
+ */
+ public function resolveSoftDeleteConflict(Request $request, string $modelClass, $id): JsonResponse
+ {
+ if (!class_exists($modelClass)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid model class provided',
+ ], 400);
+ }
+
+ try {
+ // Attempt to find the soft-deleted record
+ /** @var Model $model */
+ $modelInstance = new $modelClass();
+
+ // Ensure the model uses SoftDeleteResolution trait
+ if (!method_exists($modelInstance, 'restoreSoftDeletedRecord')) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Model does not support soft delete conflict resolution',
+ ], 400);
+ }
+
+ $deletedRecord = $modelClass::withTrashed()->find($id);
+
+ if (!$deletedRecord) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Deleted record not found',
+ ], 404);
+ }
+
+ if (!$deletedRecord->trashed()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Record is not deleted',
+ ], 400);
+ }
+
+ // Only include fields that are present in request and fillable
+ $attributes = $request->only($modelInstance->getFillable());
+
+ // Restore the record with updated attributes
+ $restored = $modelInstance->restoreSoftDeletedRecord($deletedRecord, $attributes);
+
+ if (!$restored) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Failed to restore record',
+ ], 500);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Record restored successfully',
+ 'record' => $restored->fresh()->toArray(),
+ ]);
+ } catch (\Exception $e) {
+ Log::error('Error restoring soft-deleted record', [
+ 'error' => $e->getMessage(),
+ 'model' => $modelClass,
+ 'id' => $id,
+ 'data' => $request->all(),
+ ]);
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'An error occurred while restoring the record',
+ 'error' => $e->getMessage(),
+ ], 500);
+ }
+ }
+}
diff --git a/app/Traits/SoftDeleteResolution.php b/app/Traits/SoftDeleteResolution.php
new file mode 100644
index 0000000..2be1b62
--- /dev/null
+++ b/app/Traits/SoftDeleteResolution.php
@@ -0,0 +1,244 @@
+ 'John', 'email' => 'john@example.com']);
+ *
+ * // Check for soft-deleted duplicate
+ * $duplicate = $newUser->findSoftDeletedDuplicate();
+ *
+ * if ($duplicate) {
+ * // Restore with updated attributes
+ * $restored = $newUser->restoreSoftDeletedRecord($duplicate, ['name' => 'John']);
+ * return "Restored previously deleted user with updated details.";
+ * }
+ *
+ * // Otherwise, proceed with normal save
+ * $newUser->save();
+ * ```
+ */
+trait SoftDeleteResolution
+{
+ /**
+ * Check if the current model has the SoftDeletes trait
+ *
+ * @return bool
+ */
+ protected function usesSoftDeletes(): bool
+ {
+ return in_array(SoftDeletes::class, class_uses_recursive($this));
+ }
+
+ /**
+ * Get fields that should be considered unique for this model
+ * This method should be implemented in the model using this trait
+ *
+ * @return array An array of field names that should be unique
+ */
+ protected function uniqueFields(): array
+ {
+ // Default implementation returns empty array
+ // Models should override this method to specify their unique fields
+ return [];
+ }
+
+ /**
+ * Check if there's a soft-deleted record that would conflict with this one
+ *
+ * @param array $attributes Optional custom attributes to check instead of the model's current attributes
+ * @return Model|null The conflicting soft-deleted model, or null if none exists
+ */
+ public function findSoftDeletedDuplicate(array $attributes = []): ?Model
+ {
+ // Ensure the model uses soft deletes
+ if (!$this->usesSoftDeletes()) {
+ Log::warning('SoftDeleteResolution trait used on model without SoftDeletes', [
+ 'model' => get_class($this)
+ ]);
+ return null;
+ }
+
+ // Get unique fields for this model
+ $uniqueFields = $this->uniqueFields();
+
+ if (empty($uniqueFields)) {
+ Log::warning('No unique fields defined for model using SoftDeleteResolution', [
+ 'model' => get_class($this)
+ ]);
+ return null;
+ }
+
+ try {
+ // Use provided attributes or current model attributes
+ $checkAttributes = empty($attributes) ? $this->getAttributes() : $attributes;
+
+ // Build query to find soft-deleted duplicates
+ $query = static::onlyTrashed();
+
+ foreach ($uniqueFields as $field) {
+ // Only include fields that exist in checkAttributes and have a value
+ if (array_key_exists($field, $checkAttributes) && !is_null($checkAttributes[$field])) {
+ $query->where($field, $checkAttributes[$field]);
+ }
+ }
+
+ // Return the first matching soft-deleted record
+ return $query->first();
+ } catch (\Exception $e) {
+ Log::error('Error checking for soft-deleted duplicates', [
+ 'model' => get_class($this),
+ 'error' => $e->getMessage(),
+ 'attributes' => $attributes ?: $this->getAttributes()
+ ]);
+
+ return null;
+ }
+ }
+
+ /**
+ * Restore a soft-deleted record and optionally update its attributes
+ *
+ * @param Model $model The soft-deleted model to restore
+ * @param array $attributes New attributes to apply during restoration
+ * @return Model|null The restored model, or null if restoration failed
+ */
+ public function restoreSoftDeletedRecord(Model $model, array $attributes = []): ?Model
+ {
+ // Ensure the model is actually soft-deleted
+ if (!method_exists($model, 'restore') || is_null($model->deleted_at)) {
+ Log::warning('Attempted to restore a model that is not soft-deleted', [
+ 'model' => get_class($model),
+ 'id' => $model->getKey()
+ ]);
+ return null;
+ }
+
+ try {
+ // Begin transaction to ensure atomicity
+ DB::beginTransaction();
+
+ // Apply any new attributes
+ if (!empty($attributes)) {
+ $model->fill($attributes);
+ }
+
+ // Restore the model (removes deleted_at timestamp)
+ $model->restore();
+
+ // Save any updated attributes
+ if (!empty($attributes)) {
+ $model->save();
+ }
+
+ // Commit transaction
+ DB::commit();
+
+ Log::info('Successfully restored soft-deleted record', [
+ 'model' => get_class($model),
+ 'id' => $model->getKey(),
+ 'attributes_updated' => !empty($attributes)
+ ]);
+
+ return $model;
+ } catch (\Exception $e) {
+ // Rollback transaction on error
+ DB::rollBack();
+
+ Log::error('Failed to restore soft-deleted record', [
+ 'model' => get_class($model),
+ 'id' => $model->getKey(),
+ 'error' => $e->getMessage(),
+ 'attributes' => $attributes
+ ]);
+
+ return null;
+ }
+ }
+
+ /**
+ * Detect if a QueryException is due to a unique constraint violation on a soft-deleted record
+ *
+ * @param QueryException $exception The exception to check
+ * @return bool True if the exception is caused by a conflict with a soft-deleted record
+ */
+ public function isSoftDeletedUniqueConstraintViolation(QueryException $exception): bool
+ {
+ // Check if the model uses soft deletes
+ if (!$this->usesSoftDeletes()) {
+ return false;
+ }
+
+ // Check if the exception is due to a unique constraint violation
+ // PostgreSQL error code 23505 is for unique_violation
+ $isPgsqlUniqueViolation = $exception->getCode() === '23505';
+
+ // SQLite and MySQL use different error codes
+ $isMysqlUniqueViolation = strpos($exception->getMessage(), 'Duplicate entry') !== false;
+ $isSqliteUniqueViolation = strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false;
+
+ // If it's not a unique constraint violation, return false
+ if (!($isPgsqlUniqueViolation || $isMysqlUniqueViolation || $isSqliteUniqueViolation)) {
+ return false;
+ }
+
+ // At this point, we have a unique constraint violation
+ // Now we need to check if the conflicting record is soft-deleted
+
+ $uniqueFields = $this->uniqueFields();
+ if (empty($uniqueFields)) {
+ return false;
+ }
+
+ // Check if there's a soft-deleted record with the same unique values
+ return $this->findSoftDeletedDuplicate() !== null;
+ }
+
+ /**
+ * Attempt to resolve a unique constraint violation by restoring a soft-deleted record
+ *
+ * @param array $attributes Attributes for the new or updated record
+ * @return Model|null The restored model, or null if no resolution was possible
+ */
+ public function resolveSoftDeletedConflict(array $attributes = []): ?Model
+ {
+ $duplicate = $this->findSoftDeletedDuplicate($attributes);
+
+ if ($duplicate) {
+ return $this->restoreSoftDeletedRecord($duplicate, $attributes);
+ }
+
+ return null;
+ }
+}
diff --git a/database/migrations/config/2025_03_10_004509_create.config.hep_content_001.php b/database/migrations/config/2025_03_10_004509_create.config.hep_content_001.php
new file mode 100644
index 0000000..9e8c6de
--- /dev/null
+++ b/database/migrations/config/2025_03_10_004509_create.config.hep_content_001.php
@@ -0,0 +1,110 @@
+msg = new ConsoleOutput();
+ }
+
+ public function upInstructions(): void
+ {
+ $this->writeMsg("Creating table: {$this->schema}.tbl_{$this->basename}");
+ $tblDef = "CREATE TABLE IF NOT EXISTS {$this->schema}.tbl_{$this->basename}
+ (
+ id BIGSERIAL,
+ context_key VARCHAR(255) NOT NULL,
+ title VARCHAR(255) NOT NULL,
+ content TEXT NOT NULL,
+ version INTEGER DEFAULT 1,
+ is_active BOOLEAN DEFAULT true,
+ created_at timestamp(0) without time zone,
+ updated_at timestamp(0) without time zone,
+ deleted_at timestamp(0) with time zone,
+ CONSTRAINT pk_{$this->basename}_id PRIMARY KEY (id)
+ )
+ ";
+ $idxs = ([
+ "CREATE UNIQUE INDEX unq_{$this->basename}_context_key_version ON {$this->schema}.tbl_{$this->basename} (context_key, version)",
+ "CREATE INDEX idx_{$this->basename}_context_key ON {$this->schema}.tbl_{$this->basename} (context_key)",
+ "CREATE INDEX idx_{$this->basename}_created_at ON {$this->schema}.tbl_{$this->basename} (created_at)",
+ "CREATE INDEX idx_{$this->basename}_updated_at ON {$this->schema}.tbl_{$this->basename} (updated_at)",
+ "CREATE INDEX idx_{$this->basename}_deleted_at ON {$this->schema}.tbl_{$this->basename} (deleted_at)"
+ ]);
+
+ DB::connection($this->connection)->statement($tblDef);
+
+ foreach($idxs AS $idx){
+ DB::connection($this->connection)->statement($idx);
+ }
+
+ $this->writeMsg("Creating view: {$this->schema}.{$this->basename}");
+ $viewDef = "SELECT * FROM {$this->schema}.tbl_{$this->basename} WHERE deleted_at IS NULL";
+ $viewQry = "CREATE OR REPLACE VIEW {$this->schema}.{$this->basename} AS {$viewDef}";
+ DB::connection($this->connection)->statement($viewQry);
+
+ $this->writeMsg("Creating view: {$this->schema}.vdel_{$this->basename}");
+ $viewDef = "SELECT * FROM {$this->schema}.tbl_{$this->basename} WHERE deleted_at IS NOT NULL";
+ $viewQry = "CREATE OR REPLACE VIEW {$this->schema}.vdel_{$this->basename} AS {$viewDef}";
+ DB::connection($this->connection)->statement($viewQry);
+ }
+
+ public function downInstructions(): void
+ {
+ $this->writeMsg("Dropping view: {$this->schema}.vdel_{$this->basename}");
+ $dropViewQry = "DROP VIEW IF EXISTS {$this->schema}.vdel_{$this->basename}";
+ DB::connection($this->connection)->statement($dropViewQry);
+
+ $this->writeMsg("Dropping view: {$this->schema}.{$this->basename}");
+ $dropViewQry = "DROP VIEW IF EXISTS {$this->schema}.{$this->basename}";
+ DB::connection($this->connection)->statement($dropViewQry);
+
+ $this->writeMsg("Dropping table: {$this->schema}.tbl_{$this->basename}");
+ $dropTblQry = "DROP TABLE IF EXISTS {$this->schema}.tbl_{$this->basename}";
+ DB::connection($this->connection)->statement($dropTblQry);
+ }
+
+ /**
+ * Run the migrations.
+ */
+ public function up(): void
+ {
+ $upTimer = new Timer();
+ $this->upInstructions();
+ $this->writeMsg("This task took {$upTimer->getElapsedTime()} minutes");
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ $downTimer = new Timer();
+ $this->downInstructions();
+ $this->writeMsg("This task took {$downTimer->getElapsedTime()} minutes");
+ }
+
+ // Output messages to the console when the
+ public function writeMsg($msg): void
+ {
+ if(!$this->msgKtr){
+ $this->msg->writeln('');
+ }
+ $this->msgKtr++;
+ $this->msg->writeln("$this->msgKtr) $msg");
+ }
+};