Mager updated

This commit is contained in:
Simon Pocrnjič
2025-09-27 17:45:55 +02:00
parent d17e34941b
commit 7227c888d4
74 changed files with 6339 additions and 342 deletions
+9 -1
View File
@@ -17,7 +17,15 @@ class PersonFactory extends Factory
public function definition(): array
{
return [
//
'first_name' => $this->faker->firstName(),
'last_name' => $this->faker->lastName(),
'full_name' => fn(array $attrs) => trim(($attrs['first_name'] ?? '').' '.($attrs['last_name'] ?? '')),
'gender' => $this->faker->randomElement(['m','w']),
'birthday' => $this->faker->optional()->date(),
'tax_number' => $this->faker->optional()->bothify('########'),
'social_security_number' => $this->faker->optional()->bothify('#########'),
'description' => $this->faker->optional()->sentence(),
// group_id/type_id are required; keep null here and let tests/seeds assign or rely on defaults in code paths that use factories
];
}
}
@@ -0,0 +1,30 @@
<?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('accounts', function (Blueprint $table) {
$table->decimal("initial_amount", 20, 4)->default(0);
$table->decimal("balance_amount", 20, 4)->default(0);
$table->date("promise_date")->nullable();
$table->index('balance_amount');
$table->index('promise_date');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};
@@ -0,0 +1,46 @@
<?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('object_types', function (Blueprint $table) {
$table->id();
$table->string('name',50);
$table->string('description',125)->nullable();
$table->softDeletes();
$table->timestamps();
});
Schema::create('objects', function (Blueprint $table) {
$table->id();
$table->string('reference', 125)->nullable();
$table->string('name', 255);
$table->string('description', 255)->nullable();
// If you keep the column name as 'type_id', specify the table explicitly
$table->foreignId('type_id')->constrained('object_types')->nullOnDelete();
// Indexes for faster lookups
$table->softDeletes();
$table->timestamps();
$table->index('reference');
$table->index('type_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('objects');
Schema::dropIfExists('object_types');
}
};
@@ -0,0 +1,50 @@
<?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('bank_accounts', function (Blueprint $table) {
$table->id();
// Ownership (Person-specific). Change to your actual Person table name.
$table->foreignId('person_id')->constrained('person')->cascadeOnDelete();
// Account details
$table->string('bank_name', 100);
$table->string('iban', 34)->nullable();
$table->string('bic_swift', 11)->nullable();
$table->string('account_number', 34)->nullable();
$table->string('routing_number', 20)->nullable();
$table->char('currency', 3)->default('EUR');
$table->char('country_code', 2)->nullable();
$table->string('holder_name', 125)->nullable();
// Status and lifecycle
$table->boolean('is_active')->default(true);
// Misc
$table->text('notes')->nullable();
$table->json('meta')->nullable();
// Indexes
$table->index('person_id');
$table->index('iban');
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bank_accounts');
}
};
@@ -0,0 +1,47 @@
<?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('emails', function (Blueprint $table) {
$table->id();
$table->foreignId('person_id')->constrained('person')->cascadeOnDelete();
// The email address
$table->string('value', 255);
// Optional label like "work", "home", etc.
$table->string('label', 50)->nullable();
// Mark a preferred email for the person (enforce at most one in app logic)
$table->boolean('is_primary')->default(false);
// Whether this email is considered currently active/usable
$table->boolean('is_active')->default(true);
// Whether validation checks passed (syntax/deliverability)
$table->boolean('valid')->default(true);
// When the email was verified (e.g., via confirmation link)
$table->timestamp('verified_at')->nullable();
// JSON columns for notification preferences and arbitrary metadata
$table->json('preferences')->nullable();
$table->json('meta')->nullable();
// Soft delete support
$table->softDeletes();
// Avoid duplicate emails per person among non-deleted records
$table->unique(['person_id', 'value', 'deleted_at'], 'emails_person_value_unique');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('emails');
}
};
@@ -0,0 +1,64 @@
<?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('imports', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
// Who initiated the import
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
// Optional template applied to this import (FK added in a later migration to avoid ordering issues)
$table->foreignId('import_template_id')->nullable();
// Optional client this import is for (many imports per client)
$table->foreignId('client_id')->nullable()->constrained('clients')->nullOnDelete();
// File/source metadata
$table->string('source_type', 12); // csv|xml|xls|xlsx|json
$table->string('file_name', 255);
$table->string('original_name', 255)->nullable();
$table->string('disk', 50)->default('local');
$table->string('path', 2048);
$table->unsignedBigInteger('size')->nullable(); // bytes
$table->string('sheet_name', 64)->nullable(); // for Excel
// Progress/status
$table->string('status', 20)->default('uploaded'); // uploaded|parsing|parsed|validating|importing|completed|failed
$table->unsignedInteger('total_rows')->default(0);
$table->unsignedInteger('valid_rows')->default(0);
$table->unsignedInteger('invalid_rows')->default(0);
$table->unsignedInteger('imported_rows')->default(0);
$table->timestamp('started_at')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamp('failed_at')->nullable();
// Diagnostics and flexibility
$table->json('error_summary')->nullable();
$table->json('meta')->nullable();
// Helpful indexes
$table->index('user_id');
$table->index('import_template_id');
$table->index('status');
$table->index('client_id');
$table->index('source_type');
$table->index(['disk', 'path']);
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('imports');
}
};
@@ -0,0 +1,47 @@
<?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('import_rows', function (Blueprint $table) {
$table->id();
$table->foreignId('import_id')->constrained('imports')->cascadeOnDelete();
$table->unsignedInteger('row_number');
$table->string('sheet_name', 64)->nullable();
// Type of record represented in this row (person, account, etc.)
$table->string('record_type', 50)->nullable();
// Data and results
$table->json('raw_data')->nullable();
$table->json('mapped_data')->nullable();
$table->string('status', 20)->default('pending'); // pending|valid|invalid|imported|skipped
$table->json('errors')->nullable();
$table->json('warnings')->nullable();
// Link to created entity (optional, polymorphic)
$table->nullableMorphs('entity'); // entity_type + entity_id
// Dedup/trace
$table->string('fingerprint', 64)->nullable()->index();
// Helpful indexes
$table->index(['import_id', 'status']);
$table->index(['import_id', 'row_number']);
$table->index('record_type');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('import_rows');
}
};
@@ -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
{
public function up(): void
{
Schema::create('import_mappings', function (Blueprint $table) {
$table->id();
$table->foreignId('import_id')->constrained('imports')->cascadeOnDelete();
// Column/header from the source file and the target field in the system
$table->string('source_column', 255);
$table->string('target_field', 255)->nullable();
$table->string('transform', 50)->nullable(); // e.g., trim|upper|date:dd.MM.yyyy
$table->json('options')->nullable(); // any extra config for mapping/transforms
// Indexes
$table->index(['import_id', 'source_column']);
$table->index(['import_id', 'target_field']);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('import_mappings');
}
};
@@ -0,0 +1,38 @@
<?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('import_events', function (Blueprint $table) {
$table->id();
$table->foreignId('import_id')->constrained('imports')->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
// Event details
$table->string('event', 50); // created|parsing_started|parsed|validating|importing|completed|failed|retry|...
$table->string('level', 10)->default('info'); // info|warning|error
$table->text('message')->nullable();
$table->json('context')->nullable();
// An optional pointer to a specific row related to the event
$table->foreignId('import_row_id')->nullable()->constrained('import_rows')->nullOnDelete();
// Indexes
$table->index(['import_id', 'event']);
$table->index(['import_id', 'level']);
$table->index('user_id');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('import_events');
}
};
@@ -0,0 +1,43 @@
<?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('import_templates', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->string('name', 100);
$table->string('description', 255)->nullable();
// What kind of source this template is for (csv|xml|xls|xlsx|json)
$table->string('source_type', 12)->default('csv');
// Defaults for records handled by this template (e.g., person, account)
$table->string('default_record_type', 50)->nullable();
// Optional sample header row for UI assistance
$table->json('sample_headers')->nullable();
// Ownership and lifecycle
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
$table->boolean('is_active')->default(true);
$table->json('meta')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['source_type', 'is_active']);
});
}
public function down(): void
{
Schema::dropIfExists('import_templates');
}
};
@@ -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
{
public function up(): void
{
Schema::create('import_template_mappings', function (Blueprint $table) {
$table->id();
$table->foreignId('import_template_id')->constrained('import_templates')->cascadeOnDelete();
$table->string('source_column', 255);
$table->string('target_field', 255)->nullable();
$table->string('transform', 50)->nullable();
$table->json('options')->nullable();
$table->unsignedInteger('position')->nullable(); // order in the header
$table->timestamps();
$table->index(['import_template_id', 'source_column']);
$table->index(['import_template_id', 'position']);
});
}
public function down(): void
{
Schema::dropIfExists('import_template_mappings');
}
};
@@ -0,0 +1,27 @@
<?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) {
$table->foreignId('client_id')->nullable()->constrained('clients')->nullOnDelete()->after('user_id');
$table->index('client_id');
});
}
public function down(): void
{
Schema::table('import_templates', function (Blueprint $table) {
if (Schema::hasColumn('import_templates', 'client_id')) {
$table->dropForeign(['client_id']);
$table->dropIndex(['client_id']);
$table->dropColumn('client_id');
}
});
}
};
@@ -0,0 +1,78 @@
<?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
{
// People: unique by (tax_number, social_security_number, deleted_at)
Schema::table('person', function (Blueprint $table) {
if (!self::hasIndex('person', 'person_identity_unique')) {
$table->unique(['tax_number', 'social_security_number', 'deleted_at'], 'person_identity_unique');
}
});
// Phones: unique by (person_id, nu, country_code, deleted_at)
Schema::table('person_phones', function (Blueprint $table) {
if (!self::hasIndex('person_phones', 'person_phones_unique')) {
$table->unique(['person_id', 'nu', 'country_code', 'deleted_at'], 'person_phones_unique');
}
});
// Addresses: unique by (person_id, address, country, deleted_at)
Schema::table('person_addresses', function (Blueprint $table) {
if (!self::hasIndex('person_addresses', 'person_addresses_unique')) {
$table->unique(['person_id', 'address', 'country', 'deleted_at'], 'person_addresses_unique');
}
});
// Contracts: unique by (client_case_id, reference, deleted_at)
Schema::table('contracts', function (Blueprint $table) {
if (!self::hasIndex('contracts', 'contracts_reference_unique')) {
$table->unique(['client_case_id', 'reference', 'deleted_at'], 'contracts_reference_unique');
}
});
// Accounts: unique by (contract_id, reference, deleted_at)
Schema::table('accounts', function (Blueprint $table) {
if (!self::hasIndex('accounts', 'accounts_reference_unique')) {
$table->unique(['contract_id', 'reference', 'deleted_at'], 'accounts_reference_unique');
}
});
}
public function down(): void
{
Schema::table('person', function (Blueprint $table) {
$table->dropUnique('person_identity_unique');
});
Schema::table('person_phones', function (Blueprint $table) {
$table->dropUnique('person_phones_unique');
});
Schema::table('person_addresses', function (Blueprint $table) {
$table->dropUnique('person_addresses_unique');
});
Schema::table('contracts', function (Blueprint $table) {
$table->dropUnique('contracts_reference_unique');
});
Schema::table('accounts', function (Blueprint $table) {
$table->dropUnique('accounts_reference_unique');
});
}
private static function hasIndex(string $table, string $index): bool
{
// Attempt to detect index presence; if not supported, return false to try creating
try {
$connection = Schema::getConnection();
$schemaManager = $connection->getDoctrineSchemaManager();
$doctrineTable = $schemaManager->listTableDetails($table);
return $doctrineTable->hasIndex($index);
} catch (\Throwable $e) {
return false;
}
}
};
@@ -0,0 +1,26 @@
<?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_template_mappings', function (Blueprint $table) {
$table->string('apply_mode', 10)->default('both')->after('transform'); // insert|update|both
$table->index(['import_template_id', 'apply_mode']);
});
}
public function down(): void
{
Schema::table('import_template_mappings', function (Blueprint $table) {
if (Schema::hasColumn('import_template_mappings', 'apply_mode')) {
$table->dropIndex(['import_template_id', 'apply_mode']);
$table->dropColumn('apply_mode');
}
});
}
};
@@ -0,0 +1,26 @@
<?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_mappings', function (Blueprint $table) {
$table->string('apply_mode', 10)->default('both')->after('transform');
$table->index(['import_id', 'apply_mode']);
});
}
public function down(): void
{
Schema::table('import_mappings', function (Blueprint $table) {
if (Schema::hasColumn('import_mappings', 'apply_mode')) {
$table->dropIndex(['import_id', 'apply_mode']);
$table->dropColumn('apply_mode');
}
});
}
};
@@ -0,0 +1,30 @@
<?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) {
if (!Schema::hasColumn('accounts', 'balance_amount')) {
$table->decimal('balance_amount', 18, 4)->nullable()->after('description');
$table->index('balance_amount');
}
});
}
public function down(): void
{
Schema::table('accounts', function (Blueprint $table) {
if (Schema::hasColumn('accounts', 'balance_amount')) {
$table->dropIndex(['balance_amount']);
$table->dropColumn('balance_amount');
}
});
}
};
@@ -0,0 +1,27 @@
<?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('imports', function (Blueprint $table) {
if (!Schema::hasColumn('imports', 'import_template_id')) {
$table->foreignId('import_template_id')->nullable();
}
// Add foreign key if not exists (Postgres will error if duplicate, so wrap in try/catch in runtime, but Schema builder doesn't support conditional FKs)
$table->foreign('import_template_id', 'imports_import_template_id_foreign')
->references('id')->on('import_templates')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('imports', function (Blueprint $table) {
$table->dropForeign('imports_import_template_id_foreign');
});
}
};
@@ -0,0 +1,102 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
return new class extends Migration
{
public function up(): void
{
// Change column type to text/longer string temporarily (nullable) to allow backfill without truncation
$driver = DB::connection()->getDriverName();
if ($driver === 'pgsql') {
DB::statement('ALTER TABLE person ALTER COLUMN nu DROP NOT NULL');
DB::statement('ALTER TABLE person ALTER COLUMN nu TYPE TEXT USING nu::text');
} elseif ($driver === 'mysql') {
DB::statement('ALTER TABLE person MODIFY nu VARCHAR(32) NULL');
} else {
// Fallback: try schema change (may require doctrine/dbal)
Schema::table('person', function (Blueprint $table) {
$table->string('nu', 32)->nullable()->change();
});
}
// Backfill unique 6-char alphanumeric 'nu' values
$rows = DB::table('person')->select('id', 'nu')->orderBy('id')->get();
$used = [];
foreach ($rows as $row) {
if (is_string($row->nu) && preg_match('/^[A-Za-z0-9]{6}$/', $row->nu)) {
if (!isset($used[$row->nu])) {
$used[$row->nu] = true;
continue;
}
// duplicate will be regenerated below
}
// mark to regenerate
$used[$row->nu] = false;
}
$updates = [];
foreach ($rows as $row) {
$needsNew = true;
if (is_string($row->nu) && preg_match('/^[A-Za-z0-9]{6}$/', $row->nu) && ($used[$row->nu] === true)) {
// valid and unique
$needsNew = false;
}
if ($needsNew) {
do {
$nu = Str::random(6); // [A-Za-z0-9]
} while (isset($used[$nu]));
$used[$nu] = true;
$updates[] = ['id' => $row->id, 'nu' => $nu];
}
}
// Apply updates in chunks
foreach (array_chunk($updates, 500) as $chunk) {
foreach ($chunk as $u) {
DB::table('person')->where('id', $u['id'])->update(['nu' => $u['nu']]);
}
}
// Add unique index and then narrow type to VARCHAR(6) and make not nullable
Schema::table('person', function (Blueprint $table) {
$table->unique('nu');
});
if ($driver === 'pgsql') {
DB::statement('ALTER TABLE person ALTER COLUMN nu TYPE VARCHAR(6) USING nu::varchar(6)');
DB::statement('ALTER TABLE person ALTER COLUMN nu SET NOT NULL');
} elseif ($driver === 'mysql') {
DB::statement('ALTER TABLE person MODIFY nu VARCHAR(6) NOT NULL');
} else {
Schema::table('person', function (Blueprint $table) {
$table->string('nu', 6)->nullable(false)->change();
});
}
}
public function down(): void
{
// Drop unique and revert to integer (best-effort)
Schema::table('person', function (Blueprint $table) {
$table->dropUnique(['nu']);
});
$driver = DB::connection()->getDriverName();
// Coerce values back to numeric to avoid issues on some DBs
DB::table('person')->update(['nu' => '0']);
if ($driver === 'pgsql') {
DB::statement('ALTER TABLE person ALTER COLUMN nu TYPE BIGINT USING nu::bigint');
DB::statement('ALTER TABLE person ALTER COLUMN nu SET DEFAULT 0');
DB::statement('ALTER TABLE person ALTER COLUMN nu SET NOT NULL');
} elseif ($driver === 'mysql') {
DB::statement('ALTER TABLE person MODIFY nu BIGINT UNSIGNED NOT NULL DEFAULT 0');
} else {
Schema::table('person', function (Blueprint $table) {
$table->unsignedBigInteger('nu')->default(0)->change();
});
}
}
};
@@ -0,0 +1,24 @@
<?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('documents', function (Blueprint $table) {
$table->string('preview_path', 2048)->nullable()->after('path');
$table->string('preview_mime', 100)->nullable()->after('preview_path');
$table->timestamp('preview_generated_at')->nullable()->after('preview_mime');
});
}
public function down(): void
{
Schema::table('documents', function (Blueprint $table) {
$table->dropColumn(['preview_path', 'preview_mime', 'preview_generated_at']);
});
}
};
@@ -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
{
Schema::table('import_mappings', function (Blueprint $table) {
if (!Schema::hasColumn('import_mappings', 'position')) {
$table->unsignedInteger('position')->nullable()->after('options');
}
$table->index(['import_id', 'position']);
});
}
public function down(): void
{
Schema::table('import_mappings', function (Blueprint $table) {
if (Schema::hasColumn('import_mappings', 'position')) {
$table->dropIndex(['import_id', 'position']);
$table->dropColumn('position');
}
});
}
};
@@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('import_mappings', function (Blueprint $table) {
if (!Schema::hasColumn('import_mappings', 'entity')) {
$table->string('entity', 64)->nullable()->after('import_id');
}
$table->index(['import_id', 'entity']);
});
// Backfill entity from target_field's first segment where possible
DB::table('import_mappings')->orderBy('id')->chunkById(1000, function ($rows) {
foreach ($rows as $row) {
if (!empty($row->entity)) continue;
$entity = null;
if (!empty($row->target_field)) {
$parts = explode('.', $row->target_field);
$record = $parts[0] ?? null;
if ($record) {
// Map record segment to UI entity key
$map = [
'person' => 'person',
'address' => 'person_addresses',
'phone' => 'person_phones',
'email' => 'emails',
'account' => 'accounts',
'contract' => 'contracts',
];
$entity = $map[$record] ?? $record;
}
}
if ($entity) {
DB::table('import_mappings')->where('id', $row->id)->update(['entity' => $entity]);
}
}
});
}
public function down(): void
{
Schema::table('import_mappings', function (Blueprint $table) {
if (Schema::hasColumn('import_mappings', 'entity')) {
// drop composite index if exists
try { $table->dropIndex(['import_id', 'entity']); } catch (\Throwable $e) { /* ignore */ }
$table->dropColumn('entity');
}
});
}
};
@@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('import_template_mappings', function (Blueprint $table) {
if (!Schema::hasColumn('import_template_mappings', 'entity')) {
$table->string('entity', 64)->nullable()->after('import_template_id');
}
$table->index(['import_template_id', 'entity']);
});
// Backfill entity from target_field first segment
DB::table('import_template_mappings')->orderBy('id')->chunkById(1000, function ($rows) {
foreach ($rows as $row) {
if (!empty($row->entity)) continue;
$entity = null;
if (!empty($row->target_field)) {
$parts = explode('.', $row->target_field);
$record = $parts[0] ?? null;
if ($record) {
$map = [
'person' => 'person',
'address' => 'person_addresses',
'phone' => 'person_phones',
'email' => 'emails',
'account' => 'accounts',
'contract' => 'contracts',
];
$entity = $map[$record] ?? $record;
}
}
if ($entity) {
DB::table('import_template_mappings')->where('id', $row->id)->update(['entity' => $entity]);
}
}
});
}
public function down(): void
{
Schema::table('import_template_mappings', function (Blueprint $table) {
if (Schema::hasColumn('import_template_mappings', 'entity')) {
try { $table->dropIndex(['import_template_id', 'entity']); } catch (\Throwable $e) { /* ignore */ }
$table->dropColumn('entity');
}
});
}
};
+2 -1
View File
@@ -27,7 +27,8 @@ public function run(): void
PersonSeeder::class,
SegmentSeeder::class,
ActionSeeder::class,
EventSeeder::class
EventSeeder::class,
ImportTemplateSeeder::class,
]);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace Database\Seeders;
use App\Models\ImportTemplate;
use App\Models\ImportTemplateMapping;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class ImportTemplateSeeder extends Seeder
{
public function run(): void
{
$template = ImportTemplate::query()->firstOrCreate([
'name' => 'Person basic CSV',
], [
'uuid' => (string) Str::uuid(),
'description' => 'Basic person import: name, email, phone, address',
'source_type' => 'csv',
'default_record_type' => 'person',
'sample_headers' => ['first_name','last_name','email','phone','address','city','postal_code','country'],
'is_active' => true,
'meta' => [
'delimiter' => ',',
'enclosure' => '"',
'escape' => '\\',
],
]);
$mappings = [
['source_column' => 'first_name', 'target_field' => 'person.first_name', 'position' => 1],
['source_column' => 'last_name', 'target_field' => 'person.last_name', 'position' => 2],
['source_column' => 'email', 'target_field' => 'person.email', 'position' => 3],
['source_column' => 'phone', 'target_field' => 'person.phone', 'position' => 4],
['source_column' => 'address', 'target_field' => 'person.address.street', 'position' => 5],
['source_column' => 'city', 'target_field' => 'person.address.city', 'position' => 6],
['source_column' => 'postal_code', 'target_field' => 'person.address.postal_code', 'position' => 7],
['source_column' => 'country', 'target_field' => 'person.address.country', 'position' => 8],
];
foreach ($mappings as $map) {
ImportTemplateMapping::firstOrCreate([
'import_template_id' => $template->id,
'source_column' => $map['source_column'],
], [
'target_field' => $map['target_field'],
'position' => $map['position'],
]);
}
}
}