changes 0230092025

This commit is contained in:
Simon Pocrnjič
2025-09-30 00:06:47 +02:00
parent 1fddf959f0
commit a2bb75fdcc
31 changed files with 2729 additions and 628 deletions
@@ -0,0 +1,29 @@
<?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_entities', function (Blueprint $table) {
$table->id();
$table->string('key')->unique(); // UI key (plural except person)
$table->string('canonical_root'); // canonical root for processor (singular)
$table->string('label');
$table->json('fields')->nullable(); // array of field names for UI
$table->json('field_aliases')->nullable(); // map alias -> canonical field
$table->json('aliases')->nullable(); // array of root aliases (e.g., ["contracts","contract"])
$table->json('rules')->nullable(); // array of suggestion rules: { pattern, field, priority? }
$table->json('ui')->nullable(); // optional UI hints (default_field, order, etc.)
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('import_entities');
}
};
@@ -0,0 +1,39 @@
<?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('client_cases', function (Blueprint $table) {
if (! Schema::hasColumn('client_cases', 'client_ref')) {
$table->string('client_ref', 191)->nullable()->after('person_id');
}
// Add indexes
$table->index('client_ref');
// Composite unique per client for client_ref
$table->unique(['client_id', 'client_ref']);
});
}
public function down(): void
{
Schema::table('client_cases', function (Blueprint $table) {
if (Schema::hasColumn('client_cases', 'client_ref')) {
// Drop constraints first
try {
$table->dropUnique('client_cases_client_id_client_ref_unique');
} catch (\Throwable $e) {
}
try {
$table->dropIndex('client_cases_client_ref_index');
} catch (\Throwable $e) {
}
$table->dropColumn('client_ref');
}
});
}
};
@@ -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('accounts', function (Blueprint $table) {
if (! Schema::hasColumn('accounts', 'initial_amount')) {
$table->decimal('initial_amount', 18, 4)->nullable()->after('description');
$table->index('initial_amount');
}
});
}
public function down(): void
{
Schema::table('accounts', function (Blueprint $table) {
if (Schema::hasColumn('accounts', 'initial_amount')) {
$table->dropIndex(['initial_amount']);
$table->dropColumn('initial_amount');
}
});
}
};
@@ -0,0 +1,69 @@
<?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('imports')) {
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 can be added separately if needed)
$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
{
// Only drop if this migration created it (to be safe if others depend on it)
if (Schema::hasTable('imports')) {
Schema::drop('imports');
}
}
};
@@ -0,0 +1,51 @@
<?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('import_rows')) {
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
{
if (Schema::hasTable('import_rows')) {
Schema::drop('import_rows');
}
}
};
@@ -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
{
if (! Schema::hasTable('import_mappings')) {
Schema::create('import_mappings', function (Blueprint $table) {
$table->id();
$table->foreignId('import_id')->constrained('imports')->cascadeOnDelete();
$table->string('source_column', 255);
$table->string('target_field', 255)->nullable();
$table->string('transform', 50)->nullable();
$table->json('options')->nullable();
$table->index(['import_id', 'source_column']);
$table->index(['import_id', 'target_field']);
$table->timestamps();
});
}
}
public function down(): void
{
if (Schema::hasTable('import_mappings')) {
Schema::drop('import_mappings');
}
}
};
@@ -0,0 +1,39 @@
<?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('import_events')) {
Schema::create('import_events', function (Blueprint $table) {
$table->id();
$table->foreignId('import_id')->constrained('imports')->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
$table->string('event', 50);
$table->string('level', 10)->default('info');
$table->text('message')->nullable();
$table->json('context')->nullable();
$table->foreignId('import_row_id')->nullable()->constrained('import_rows')->nullOnDelete();
$table->index(['import_id', 'event']);
$table->index(['import_id', 'level']);
$table->index('user_id');
$table->timestamps();
});
}
}
public function down(): void
{
if (Schema::hasTable('import_events')) {
Schema::drop('import_events');
}
}
};
@@ -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('contracts', function (Blueprint $table) {
// Increase description length from 255 to 500, keep nullable
$table->string('description', 500)->nullable()->change();
});
}
public function down(): void
{
Schema::table('contracts', function (Blueprint $table) {
// Revert to original length 255, keep nullable
$table->string('description', 255)->nullable()->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('contracts', function (Blueprint $table) {
// Switch description to TEXT while keeping it nullable
$table->text('description')->nullable()->change();
});
}
public function down(): void
{
Schema::table('contracts', function (Blueprint $table) {
// Revert back to VARCHAR(500) nullable
$table->string('description', 500)->nullable()->change();
});
}
};
+2
View File
@@ -14,6 +14,8 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// \App\Models\User::factory(10)->create();
$this->call(ImportEntitySeeder::class);
// User::factory(10)->create();
// Ensure a default test user exists (idempotent)
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Database\Seeders;
use App\Models\ImportEntity;
use Illuminate\Database\Seeder;
class ImportEntitySeeder extends Seeder
{
public function run(): void
{
$defs = [
[
'key' => 'person',
'canonical_root' => 'person',
'label' => 'Person',
'fields' => ['first_name', 'last_name', 'full_name', 'gender', 'birthday', 'tax_number', 'social_security_number', 'description'],
'field_aliases' => [
'dob' => 'birthday',
'date_of_birth' => 'birthday',
'name' => 'full_name',
],
'aliases' => ['person'],
'rules' => [
['pattern' => '/^(ime|first\s*name|firstname)\b|\bime\b/i', 'field' => 'first_name'],
['pattern' => '/^(priimek|last\s*name|lastname)\b|\bpriimek\b/i', 'field' => 'last_name'],
['pattern' => '/^(naziv|ime\s+in\s+priimek|full\s*name|name)\b|\bnaziv\b/i', 'field' => 'full_name'],
['pattern' => '/^(davcna|davčna|tax|tax\s*number|tin)\b/i', 'field' => 'tax_number'],
['pattern' => '/^(emso|emšo|ssn|social|social\s*security)\b/i', 'field' => 'social_security_number'],
['pattern' => '/^(spol|gender)\b/i', 'field' => 'gender'],
['pattern' => '/^(rojstvo|datum\s*rojstva|dob|birth|birthday|date\s*of\s*birth)\b/i', 'field' => 'birthday'],
['pattern' => '/^(komentar|opis|opomba|comment|description|note)\b/i', 'field' => 'description'],
],
'ui' => ['order' => 1],
],
[
'key' => 'person_addresses',
'canonical_root' => 'address',
'label' => 'Person Addresses',
'fields' => ['address', 'country', 'type_id', 'description'],
'aliases' => ['address', 'person_addresses'],
'rules' => [
['pattern' => '/^(naslov|ulica|address)\b/i', 'field' => 'address'],
['pattern' => '/^(drzava|država|country)\b/i', 'field' => 'country'],
],
'ui' => ['order' => 2],
],
[
'key' => 'person_phones',
'canonical_root' => 'phone',
'label' => 'Person Phones',
'fields' => ['nu', 'country_code', 'type_id', 'description'],
'field_aliases' => ['number' => 'nu'],
'aliases' => ['phone', 'person_phones'],
'rules' => [
['pattern' => '/^(telefon|tel\.?|gsm|mobile|phone|kontakt)\b/i', 'field' => 'nu'],
],
'ui' => ['order' => 3],
],
[
'key' => 'emails',
'canonical_root' => 'email',
'label' => 'Emails',
'fields' => ['value', 'is_primary', 'label'],
'field_aliases' => ['email' => 'value'],
'aliases' => ['email', 'emails'],
'rules' => [
['pattern' => '/^(email|e-?mail|mail)\b/i', 'field' => 'value'],
],
'ui' => ['order' => 4],
],
[
'key' => 'contracts',
'canonical_root' => 'contract',
'label' => 'Contracts',
'fields' => ['reference', 'start_date', 'end_date', 'description', 'type_id', 'client_case_id'],
'aliases' => ['contract', 'contracts', 'contracs'],
'rules' => [
['pattern' => '/^(sklic|reference|ref)\b/i', 'field' => 'reference'],
['pattern' => '/^(od|from|start|zacetek|začetek)\b/i', 'field' => 'start_date'],
['pattern' => '/^(do|to|end|konec)\b/i', 'field' => 'end_date'],
['pattern' => '/^(komentar|opis|opomba|comment|description|note)\b/i', 'field' => 'description'],
],
'ui' => ['order' => 5],
],
[
'key' => 'accounts',
'canonical_root' => 'account',
'label' => 'Accounts',
'fields' => ['reference', 'initial_amount', 'balance_amount', 'contract_id', 'contract_reference', 'type_id', 'active', 'description'],
'aliases' => ['account', 'accounts'],
'rules' => [
['pattern' => '/^(dolg|znesek|amount|saldo|balance|debt)\b/i', 'field' => 'balance_amount'],
['pattern' => '/^(sklic|reference|ref)\b/i', 'field' => 'reference'],
],
'ui' => ['order' => 6],
],
[
'key' => 'client_cases',
'canonical_root' => 'client_case',
'label' => 'Client Cases',
'fields' => ['client_ref'],
'aliases' => ['client_case', 'client_cases', 'case', 'primeri', 'primer'],
'rules' => [
['pattern' => '/^(client\s*ref|client_ref|case\s*ref|case_ref|primer|primeri|zadeva)\b/i', 'field' => 'client_ref'],
],
'ui' => ['order' => 7],
],
];
foreach ($defs as $d) {
ImportEntity::updateOrCreate(['key' => $d['key']], $d);
}
}
}