feat: L'Ami Fiduciaire V1.0.0 — full codebase with Story 0.1 complete
Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12, Vue 3, Inertia.js 2, and Tailwind CSS 4. Story 0.1 (rename folders to declarations in database) is implemented and code-reviewed: migration, rollback, and 6 Pest tests all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
52
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
52
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\UserGroup;
|
||||
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('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->string('group')->default(UserGroup::User);
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
@@ -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
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration')->index();
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
$table->text('two_factor_secret')->after('password')->nullable();
|
||||
$table->text('two_factor_recovery_codes')->after('two_factor_secret')->nullable();
|
||||
$table->timestamp('two_factor_confirmed_at')->after('two_factor_recovery_codes')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'two_factor_secret',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_confirmed_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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::create('workspaces', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('workspaces');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\WorkspaceUserRole;
|
||||
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('workspace_user', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('role')->default(WorkspaceUserRole::Member);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['workspace_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('workspace_user');
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('clients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('company_name');
|
||||
$table->string('legal_form');
|
||||
$table->string('ice')->nullable();
|
||||
$table->string('fiscal_id')->nullable();
|
||||
$table->string('rc')->nullable();
|
||||
$table->string('cnss')->nullable();
|
||||
$table->string('patente')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('clients');
|
||||
}
|
||||
};
|
||||
@@ -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::table('clients', function (Blueprint $table) {
|
||||
$table->string('contact_last_name')->nullable()->after('patente');
|
||||
$table->string('contact_first_name')->nullable()->after('contact_last_name');
|
||||
$table->string('contact_job_title')->nullable()->after('contact_first_name');
|
||||
$table->string('contact_email')->nullable()->after('contact_job_title');
|
||||
$table->string('contact_phone')->nullable()->after('contact_email');
|
||||
$table->foreignId('internal_responsible_id')->nullable()->after('contact_phone')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->string('status')->nullable()->after('internal_responsible_id');
|
||||
$table->text('internal_notes')->nullable()->after('status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->dropForeign(['internal_responsible_id']);
|
||||
$table->dropColumn([
|
||||
'contact_last_name',
|
||||
'contact_first_name',
|
||||
'contact_job_title',
|
||||
'contact_email',
|
||||
'contact_phone',
|
||||
'internal_responsible_id',
|
||||
'status',
|
||||
'internal_notes',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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('folders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('client_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
|
||||
$table->string('title');
|
||||
$table->string('type');
|
||||
$table->unsignedSmallInteger('period_year')->nullable();
|
||||
$table->unsignedTinyInteger('period_month')->nullable();
|
||||
$table->unsignedTinyInteger('period_quarter')->nullable();
|
||||
|
||||
$table->date('due_date')->nullable();
|
||||
$table->string('status');
|
||||
$table->string('priority')->nullable();
|
||||
|
||||
$table->foreignId('assigned_to')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('validated_at')->nullable();
|
||||
$table->timestamp('closed_at')->nullable();
|
||||
|
||||
$table->text('notes_internal')->nullable();
|
||||
$table->text('notes_client')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('folders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('log_name')->nullable();
|
||||
$table->text('description');
|
||||
$table->nullableMorphs('subject', 'subject');
|
||||
$table->nullableMorphs('causer', 'causer');
|
||||
$table->json('properties')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index('log_name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddEventColumnToActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->string('event')->nullable()->after('subject_type');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->dropColumn('event');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->uuid('batch_uuid')->nullable()->after('properties');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->dropColumn('batch_uuid');
|
||||
});
|
||||
}
|
||||
}
|
||||
32
database/migrations/2026_03_01_143910_create_media_table.php
Normal file
32
database/migrations/2026_03_01_143910_create_media_table.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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('media', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->morphs('model');
|
||||
$table->uuid()->nullable()->unique();
|
||||
$table->string('collection_name');
|
||||
$table->string('name');
|
||||
$table->string('file_name');
|
||||
$table->string('mime_type')->nullable();
|
||||
$table->string('disk');
|
||||
$table->string('conversions_disk')->nullable();
|
||||
$table->unsignedBigInteger('size');
|
||||
$table->json('manipulations');
|
||||
$table->json('custom_properties');
|
||||
$table->json('generated_conversions');
|
||||
$table->json('responsive_images');
|
||||
$table->unsignedInteger('order_column')->nullable()->index();
|
||||
|
||||
$table->nullableTimestamps();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('folder_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('type'); // invite, situation, file_request, confirmation, text
|
||||
$table->text('body');
|
||||
$table->string('sent_by_type'); // user, client
|
||||
$table->unsignedBigInteger('sent_by_id');
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['folder_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('messages');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('folder_invitations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('folder_id')->constrained()->cascadeOnDelete();
|
||||
$table->uuid('token')->unique();
|
||||
$table->string('email')->nullable();
|
||||
$table->timestamp('expires_at');
|
||||
$table->timestamp('used_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['token', 'expires_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('folder_invitations');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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('folders', function (Blueprint $table) {
|
||||
$table->timestamp('confirmation_requested_at')->nullable()->after('closed_at');
|
||||
$table->unsignedBigInteger('confirmation_media_id')->nullable()->after('confirmation_requested_at');
|
||||
$table->string('confirmed_by_type')->nullable()->after('confirmation_media_id');
|
||||
$table->unsignedBigInteger('confirmed_by_id')->nullable()->after('confirmed_by_type');
|
||||
$table->text('confirmation_signature')->nullable()->after('confirmed_by_id');
|
||||
$table->timestamp('refused_at')->nullable()->after('confirmation_signature');
|
||||
$table->text('refusal_reason')->nullable()->after('refused_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('folders', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'confirmation_requested_at',
|
||||
'confirmation_media_id',
|
||||
'confirmed_by_type',
|
||||
'confirmed_by_id',
|
||||
'confirmation_signature',
|
||||
'refused_at',
|
||||
'refusal_reason',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('client_contacts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('client_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('full_name');
|
||||
$table->string('job_title')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->boolean('is_principal')->default(false);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['client_id', 'is_principal']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('client_contacts');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$clients = DB::table('clients')
|
||||
->where(function ($q) {
|
||||
$q->whereNotNull('contact_first_name')
|
||||
->orWhereNotNull('contact_last_name')
|
||||
->orWhereNotNull('contact_email')
|
||||
->orWhereNotNull('contact_phone')
|
||||
->orWhereNotNull('contact_job_title');
|
||||
})
|
||||
->get();
|
||||
|
||||
$now = now();
|
||||
|
||||
foreach ($clients as $client) {
|
||||
$fullName = trim(implode(' ', array_filter([
|
||||
$client->contact_first_name,
|
||||
$client->contact_last_name,
|
||||
])));
|
||||
|
||||
DB::table('client_contacts')->insert([
|
||||
'client_id' => $client->id,
|
||||
'full_name' => $fullName !== '' ? $fullName : '(sans nom)',
|
||||
'job_title' => $client->contact_job_title,
|
||||
'email' => $client->contact_email,
|
||||
'phone' => $client->contact_phone,
|
||||
'is_principal' => true,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
Log::info("Migrated {$clients->count()} client contacts to client_contacts table.");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('client_contacts')->truncate();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Log rows with both period_month AND period_quarter set
|
||||
$ambiguousRows = DB::table('folders')
|
||||
->where('type', 'vat')
|
||||
->whereNotNull('period_month')
|
||||
->whereNotNull('period_quarter')
|
||||
->pluck('id');
|
||||
|
||||
if ($ambiguousRows->isNotEmpty()) {
|
||||
Log::warning('VAT folders with both period_month AND period_quarter set (migrated as vat_monthly, quarter nulled): '.$ambiguousRows->implode(', '));
|
||||
}
|
||||
|
||||
// Monthly: has month, no quarter
|
||||
DB::table('folders')
|
||||
->where('type', 'vat')
|
||||
->whereNotNull('period_month')
|
||||
->whereNull('period_quarter')
|
||||
->update(['type' => 'vat_monthly']);
|
||||
|
||||
// Quarterly: has quarter, no month
|
||||
DB::table('folders')
|
||||
->where('type', 'vat')
|
||||
->whereNotNull('period_quarter')
|
||||
->whereNull('period_month')
|
||||
->update(['type' => 'vat_quarterly']);
|
||||
|
||||
// Both set: treat as monthly, null quarter
|
||||
DB::table('folders')
|
||||
->where('type', 'vat')
|
||||
->whereNotNull('period_month')
|
||||
->whereNotNull('period_quarter')
|
||||
->update(['type' => 'vat_monthly', 'period_quarter' => null]);
|
||||
|
||||
// Neither set: fallback to monthly
|
||||
DB::table('folders')
|
||||
->where('type', 'vat')
|
||||
->whereNull('period_month')
|
||||
->whereNull('period_quarter')
|
||||
->update(['type' => 'vat_monthly']);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('folders')
|
||||
->whereIn('type', ['vat_monthly', 'vat_quarterly'])
|
||||
->update(['type' => 'vat']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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('notifications', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->string('type');
|
||||
$table->morphs('notifiable');
|
||||
$table->text('data');
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notifications');
|
||||
}
|
||||
};
|
||||
@@ -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::create('media_downloads', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('media_id')->constrained('media')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->timestamp('downloaded_at');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->unique(['media_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('media_downloads');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
// Depends on: split_vat_folder_type (2026_03_09_000003) which references DB::table('folders').
|
||||
// This migration MUST run after that one — timestamp ordering guarantees this.
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 1. Drop FK constraints (use column array for SQLite compatibility)
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->dropForeign(['folder_id']);
|
||||
});
|
||||
Schema::table('folder_invitations', function (Blueprint $table) {
|
||||
$table->dropForeign(['folder_id']);
|
||||
});
|
||||
|
||||
// 2. Drop old index (must happen before column rename for cross-driver safety)
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->dropIndex(['folder_id', 'created_at']);
|
||||
});
|
||||
|
||||
// 3. Rename columns
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->renameColumn('folder_id', 'declaration_id');
|
||||
});
|
||||
Schema::table('folder_invitations', function (Blueprint $table) {
|
||||
$table->renameColumn('folder_id', 'declaration_id');
|
||||
});
|
||||
|
||||
// 4. Create new index with renamed column
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->index(['declaration_id', 'created_at']);
|
||||
});
|
||||
|
||||
// 5-6. Rename tables
|
||||
Schema::rename('folders', 'declarations');
|
||||
Schema::rename('folder_invitations', 'declaration_invitations');
|
||||
|
||||
// 7. Re-add FK constraints
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete();
|
||||
});
|
||||
Schema::table('declaration_invitations', function (Blueprint $table) {
|
||||
$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 1. Drop FK constraints (use column array for SQLite compatibility)
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->dropForeign(['declaration_id']);
|
||||
});
|
||||
Schema::table('declaration_invitations', function (Blueprint $table) {
|
||||
$table->dropForeign(['declaration_id']);
|
||||
});
|
||||
|
||||
// 2-3. Rename tables back
|
||||
Schema::rename('declaration_invitations', 'folder_invitations');
|
||||
Schema::rename('declarations', 'folders');
|
||||
|
||||
// 4. Drop new index
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->dropIndex(['declaration_id', 'created_at']);
|
||||
});
|
||||
|
||||
// 5. Rename columns back
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->renameColumn('declaration_id', 'folder_id');
|
||||
});
|
||||
Schema::table('folder_invitations', function (Blueprint $table) {
|
||||
$table->renameColumn('declaration_id', 'folder_id');
|
||||
});
|
||||
|
||||
// 6. Recreate old index
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->index(['folder_id', 'created_at']);
|
||||
});
|
||||
|
||||
// 7. Re-add FK constraints
|
||||
Schema::table('messages', function (Blueprint $table) {
|
||||
$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete();
|
||||
});
|
||||
Schema::table('folder_invitations', function (Blueprint $table) {
|
||||
$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user