updates to UI and add archiving option
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\ArchiveSetting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<ArchiveSetting>
|
||||
*/
|
||||
class ArchiveSettingFactory extends Factory
|
||||
{
|
||||
protected $model = ArchiveSetting::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'action_id' => null,
|
||||
'decision_id' => null,
|
||||
'segment_id' => null,
|
||||
'entities' => [
|
||||
[
|
||||
'table' => 'documents',
|
||||
'conditions' => [
|
||||
'older_than_days' => $this->faker->numberBetween(30, 365),
|
||||
],
|
||||
'columns' => ['id', 'deleted_at'],
|
||||
],
|
||||
],
|
||||
'name' => $this->faker->sentence(3),
|
||||
'description' => $this->faker->optional()->paragraph(),
|
||||
'enabled' => true,
|
||||
'strategy' => 'immediate',
|
||||
'soft' => true,
|
||||
'options' => [
|
||||
'batch_size' => $this->faker->numberBetween(50, 500),
|
||||
],
|
||||
'created_by' => User::query()->inRandomOrder()->value('id'),
|
||||
'updated_by' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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::create('archive_settings', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
|
||||
// Contextual foreign keys (nullable allows broader global rules)
|
||||
$table->foreignId('action_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('decision_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('segment_id')->nullable()->constrained()->nullOnDelete();
|
||||
|
||||
// JSON describing entities (tables/models) impacted
|
||||
// Example shape: [{"table":"documents","conditions":{"older_than_days":180},"columns":["id","deleted_at"]}]
|
||||
$table->json('entities');
|
||||
|
||||
// Optional descriptive metadata
|
||||
$table->string('name')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('enabled')->default(true);
|
||||
|
||||
// Execution strategy: immediate | scheduled | queued
|
||||
$table->string('strategy')->default('immediate');
|
||||
|
||||
// Whether to perform a soft archive (flag / soft delete) instead of permanent removal
|
||||
$table->boolean('soft')->default(true);
|
||||
|
||||
// Additional arbitrary options (thresholds, flags, custom logic parameters)
|
||||
$table->json('options')->nullable();
|
||||
|
||||
// Auditing foreign keys for who created / last updated the rule
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Useful indexes
|
||||
$table->index(['action_id', 'decision_id', 'segment_id']);
|
||||
$table->index('enabled');
|
||||
$table->index('strategy');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('archive_settings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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::create('archive_entities', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('focus')->unique(); // e.g. contracts, client_cases
|
||||
$table->json('related'); // JSON array of related table names
|
||||
$table->string('name')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('enabled')->default(true);
|
||||
$table->timestamps();
|
||||
$table->index('enabled');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('archive_entities');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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
|
||||
{
|
||||
// Add active column to bookings if missing
|
||||
if (Schema::hasTable('bookings') && ! Schema::hasColumn('bookings', 'active')) {
|
||||
Schema::table('bookings', function (Blueprint $table): void {
|
||||
$table->unsignedTinyInteger('active')->default(1)->after('description');
|
||||
$table->index('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Add active column to activities if missing
|
||||
if (Schema::hasTable('activities') && ! Schema::hasColumn('activities', 'active')) {
|
||||
Schema::table('activities', function (Blueprint $table): void {
|
||||
$table->unsignedTinyInteger('active')->default(1)->after('note');
|
||||
$table->index('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Add active column to documents if missing
|
||||
if (Schema::hasTable('documents') && ! Schema::hasColumn('documents', 'active')) {
|
||||
Schema::table('documents', function (Blueprint $table): void {
|
||||
$table->unsignedTinyInteger('active')->default(1)->after('is_public');
|
||||
$table->index('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasTable('bookings') && Schema::hasColumn('bookings', 'active')) {
|
||||
Schema::table('bookings', function (Blueprint $table): void {
|
||||
$table->dropIndex(['active']);
|
||||
$table->dropColumn('active');
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('activities') && Schema::hasColumn('activities', 'active')) {
|
||||
Schema::table('activities', function (Blueprint $table): void {
|
||||
$table->dropIndex(['active']);
|
||||
$table->dropColumn('active');
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('documents') && Schema::hasColumn('documents', 'active')) {
|
||||
Schema::table('documents', function (Blueprint $table): void {
|
||||
$table->dropIndex(['active']);
|
||||
$table->dropColumn('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasTable('payments') && ! Schema::hasColumn('payments', 'active')) {
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
$table->unsignedTinyInteger('active')->default(1)->after('type_id');
|
||||
$table->index('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasTable('payments') && Schema::hasColumn('payments', 'active')) {
|
||||
Schema::table('payments', function (Blueprint $table): void {
|
||||
$table->dropIndex(['active']);
|
||||
$table->dropColumn('active');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('archive_runs', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('archive_setting_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('status', 30)->default('running'); // running|success|error
|
||||
$table->json('counts')->nullable(); // per-table affected counts
|
||||
$table->json('context')->nullable(); // manual context scope
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('finished_at')->nullable();
|
||||
$table->unsignedInteger('duration_ms')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['archive_setting_id', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('archive_runs');
|
||||
}
|
||||
};
|
||||
+28
@@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasTable('archive_settings') && ! Schema::hasColumn('archive_settings', 'reactivate')) {
|
||||
Schema::table('archive_settings', function (Blueprint $table): void {
|
||||
$table->boolean('reactivate')->default(false)->after('soft');
|
||||
$table->index('reactivate');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasTable('archive_settings') && Schema::hasColumn('archive_settings', 'reactivate')) {
|
||||
Schema::table('archive_settings', function (Blueprint $table): void {
|
||||
$table->dropIndex(['reactivate']);
|
||||
$table->dropColumn('reactivate');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('import_templates', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('import_templates', 'reactivate')) {
|
||||
$table->boolean('reactivate')->default(false)->after('is_active');
|
||||
}
|
||||
});
|
||||
Schema::table('imports', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('imports', 'reactivate')) {
|
||||
$table->boolean('reactivate')->default(false)->after('status');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('import_templates', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('import_templates', 'reactivate')) {
|
||||
$table->dropColumn('reactivate');
|
||||
}
|
||||
});
|
||||
Schema::table('imports', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('imports', 'reactivate')) {
|
||||
$table->dropColumn('reactivate');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
// Drop existing unique index if present (contract_id, reference, deleted_at)
|
||||
try {
|
||||
$table->dropUnique('accounts_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore if it does not exist
|
||||
}
|
||||
// Recreate including active flag so archived/inactive accounts can reuse reference
|
||||
$table->unique(['contract_id', 'reference', 'active', 'deleted_at'], 'accounts_reference_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
try {
|
||||
$table->dropUnique('accounts_reference_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
}
|
||||
// Restore previous definition without active
|
||||
$table->unique(['contract_id', 'reference', 'deleted_at'], 'accounts_reference_unique');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ArchiveEntity;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ArchiveEntitySeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed archive focus entities and their selectable related tables.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$entities = [
|
||||
[
|
||||
'focus' => 'contracts',
|
||||
'name' => 'Contracts',
|
||||
'description' => 'Contracts and their financial / activity related records.',
|
||||
'related' => [
|
||||
// Direct related tables
|
||||
'accounts',
|
||||
'activities',
|
||||
'documents', // polymorphic (contract documents only when used as focus)
|
||||
// Chained relations (dot notation) – resolve via contract -> account -> payments/bookings
|
||||
'account.payments',
|
||||
'account.bookings',
|
||||
],
|
||||
],
|
||||
[
|
||||
'focus' => 'client_cases',
|
||||
'name' => 'Client Cases',
|
||||
'description' => 'Client cases and subordinate contractual / financial records.',
|
||||
'related' => [
|
||||
'contracts', // direct contracts under case
|
||||
'contracts.account', // via contracts (hasOne account)
|
||||
'activities', // case level activities (and possibly contract-linked)
|
||||
'documents', // case level documents
|
||||
// Chained relations:
|
||||
'contracts.account.payments', // contracts -> account -> payments
|
||||
'contracts.account.bookings', // contracts -> account -> bookings
|
||||
'contracts.documents', // contracts -> documents (polymorphic)
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($entities as $data) {
|
||||
ArchiveEntity::query()->updateOrCreate(
|
||||
['focus' => $data['focus']],
|
||||
[
|
||||
'name' => $data['name'],
|
||||
'description' => $data['description'],
|
||||
'related' => $data['related'],
|
||||
'enabled' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ArchiveSetting;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ArchiveSettingSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
if (ArchiveSetting::query()->count() === 0) {
|
||||
ArchiveSetting::factory()->count(2)->create();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public function run(): void
|
||||
$this->call([
|
||||
AccountTypeSeeder::class,
|
||||
PaymentSettingSeeder::class,
|
||||
ArchiveEntitySeeder::class,
|
||||
PersonSeeder::class,
|
||||
SegmentSeeder::class,
|
||||
ActionSeeder::class,
|
||||
|
||||
Reference in New Issue
Block a user