initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
*.sqlite*
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends Factory<User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('first_name', 100);
|
||||
$table->string('last_name', 100);
|
||||
$table->string('email')->unique();
|
||||
$table->string('phone', 20)->unique()->nullable();
|
||||
$table->date('birthdate')->nullable();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->string('locale', 10)->default('fr');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamp('anonymized_at')->nullable();
|
||||
$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();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@@ -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->bigInteger('expiration')->index();
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->bigInteger('expiration')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -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->unsignedSmallInteger('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->string('connection', 100);
|
||||
$table->string('queue', 100);
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent()->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,328 @@
|
||||
<?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('organizations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('name', 200);
|
||||
$table->string('code', 50)->unique()->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('establishments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('name', 200);
|
||||
$table->text('address');
|
||||
$table->string('city', 100)->nullable();
|
||||
$table->string('zip_code', 10)->nullable();
|
||||
$table->decimal('latitude', 10, 8)->nullable();
|
||||
$table->decimal('longitude', 11, 8)->nullable();
|
||||
$table->string('timezone', 50)->default('Europe/Paris');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('supervisors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained();
|
||||
$table->foreignId('establishment_id')->nullable()->constrained();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('first_name', 100);
|
||||
$table->string('last_name', 100);
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->enum('role', ['platform_admin', 'owner', 'manager', 'viewer'])->default('manager');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamp('last_login_at')->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('gdpr_consents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->enum('type', ['data_processing', 'marketing', 'analytics']);
|
||||
$table->boolean('accepted');
|
||||
$table->string('policy_version', 50);
|
||||
$table->string('policy_text_hash');
|
||||
$table->enum('source', ['mobile', 'web', 'backoffice']);
|
||||
$table->string('consent_language', 10)->default('fr');
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->timestamp('accepted_at');
|
||||
$table->timestamp('revoked_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('user_devices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->enum('platform', ['android', 'ios', 'web']);
|
||||
$table->string('push_token')->unique();
|
||||
$table->string('app_version', 50)->nullable();
|
||||
$table->string('device_name', 100)->nullable();
|
||||
$table->timestamp('last_seen_at')->nullable();
|
||||
$table->timestamp('revoked_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('notification_preferences', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->unique()->constrained();
|
||||
$table->boolean('transaction_enabled')->default(true);
|
||||
$table->boolean('reminder_enabled')->default(true);
|
||||
$table->boolean('marketing_enabled')->default(false);
|
||||
$table->boolean('system_enabled')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('machines', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('establishment_id')->constrained();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('name', 100);
|
||||
$table->enum('type', ['washer_small', 'washer_large', 'dryer_small', 'dryer_large']);
|
||||
$table->string('qr_code')->unique();
|
||||
$table->enum('status', ['available', 'reserved', 'running', 'maintenance', 'offline', 'error'])->default('available');
|
||||
$table->foreignId('current_user_id')->nullable()->constrained('users');
|
||||
$table->timestamp('cycle_started_at')->nullable();
|
||||
$table->timestamp('cycle_ends_at')->nullable();
|
||||
$table->timestamp('last_heartbeat_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('machine_integrations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->string('provider', 100);
|
||||
$table->string('external_machine_id', 100);
|
||||
$table->string('external_site_id', 100)->nullable();
|
||||
$table->enum('mode', ['pull', 'push', 'hybrid', 'simulated'])->default('simulated');
|
||||
$table->json('config')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
$table->unique(['provider', 'external_machine_id']);
|
||||
});
|
||||
|
||||
Schema::create('machine_commands', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->string('provider', 100);
|
||||
$table->enum('command_type', ['start_cycle', 'stop_cycle', 'refresh_status']);
|
||||
$table->json('payload')->nullable();
|
||||
$table->enum('status', ['pending', 'sent', 'acknowledged', 'failed', 'timeout', 'cancelled'])->default('pending');
|
||||
$table->string('external_reference')->nullable();
|
||||
$table->string('correlation_id')->nullable();
|
||||
$table->foreignId('requested_by_user_id')->nullable()->constrained('users');
|
||||
$table->foreignId('requested_by_supervisor_id')->nullable()->constrained('supervisors');
|
||||
$table->timestamp('sent_at')->nullable();
|
||||
$table->timestamp('responded_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('machine_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->string('provider', 100);
|
||||
$table->string('external_event_id')->nullable();
|
||||
$table->enum('event_type', [
|
||||
'heartbeat', 'machine_online', 'machine_offline',
|
||||
'cycle_started', 'cycle_completed', 'cycle_failed',
|
||||
'error_reported', 'status_changed',
|
||||
]);
|
||||
$table->json('payload')->nullable();
|
||||
$table->timestamp('occurred_at');
|
||||
$table->timestamp('received_at');
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->enum('processing_status', ['pending', 'processed', 'failed', 'ignored'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('machine_status_history', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->string('previous_status', 50)->nullable();
|
||||
$table->string('new_status', 50);
|
||||
$table->string('source', 100);
|
||||
$table->string('reason')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('pricing_rules', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('establishment_id')->constrained();
|
||||
$table->foreignId('machine_id')->nullable()->constrained();
|
||||
$table->enum('machine_type', ['washer_small', 'washer_large', 'dryer_small', 'dryer_large'])->nullable();
|
||||
$table->enum('day_type', ['weekday', 'weekend', 'holiday', 'all'])->default('all');
|
||||
$table->time('slot_start');
|
||||
$table->time('slot_end');
|
||||
$table->decimal('price', 8, 2);
|
||||
$table->string('label', 100)->nullable();
|
||||
$table->boolean('requires_app')->default(false);
|
||||
$table->smallInteger('priority')->default(100);
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('promotions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('establishment_id')->constrained();
|
||||
$table->enum('machine_type', ['washer_small', 'washer_large', 'dryer_small', 'dryer_large', 'all'])->default('all');
|
||||
$table->enum('discount_type', ['percent', 'fixed']);
|
||||
$table->decimal('discount_value', 8, 2);
|
||||
$table->timestamp('starts_at');
|
||||
$table->timestamp('ends_at');
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('wallets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->unique()->constrained();
|
||||
$table->char('currency', 3)->default('EUR');
|
||||
$table->decimal('current_balance', 10, 2)->default(0);
|
||||
$table->enum('status', ['active', 'blocked', 'closed'])->default('active');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('payment_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->string('provider', 50);
|
||||
$table->string('provider_payment_id')->nullable();
|
||||
$table->decimal('amount', 10, 2);
|
||||
$table->char('currency', 3)->default('EUR');
|
||||
$table->enum('status', ['initiated', 'pending', 'succeeded', 'failed', 'cancelled', 'refunded'])->default('initiated');
|
||||
$table->string('idempotency_key', 100)->unique();
|
||||
$table->string('return_url')->nullable();
|
||||
$table->json('raw_payload')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('wallet_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('wallet_id')->constrained();
|
||||
$table->enum('type', ['credit', 'debit', 'hold', 'release', 'refund', 'adjustment']);
|
||||
$table->decimal('amount', 10, 2);
|
||||
$table->decimal('balance_before', 10, 2);
|
||||
$table->decimal('balance_after', 10, 2);
|
||||
$table->string('source_type', 100);
|
||||
$table->unsignedBigInteger('source_id')->nullable();
|
||||
$table->string('idempotency_key', 100)->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('bookings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->timestamp('slot_start');
|
||||
$table->timestamp('slot_end');
|
||||
$table->decimal('booking_fee', 8, 2)->default(0);
|
||||
$table->decimal('reserved_amount', 8, 2)->default(0);
|
||||
$table->decimal('penalty_amount', 8, 2)->default(0);
|
||||
$table->enum('status', ['pending', 'confirmed', 'cancelled', 'expired', 'active', 'completed', 'no_show'])->default('pending');
|
||||
$table->timestamp('cancelled_at')->nullable();
|
||||
$table->timestamp('penalty_applied_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('washes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->foreignId('machine_id')->constrained();
|
||||
$table->foreignId('booking_id')->nullable()->constrained();
|
||||
$table->foreignId('machine_command_id')->nullable()->constrained();
|
||||
$table->enum('trigger_method', ['qr_code', 'booking', 'supervisor', 'system']);
|
||||
$table->enum('status', ['pending_start', 'running', 'completed', 'failed', 'cancelled'])->default('pending_start');
|
||||
$table->string('program', 100)->nullable();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('ended_at')->nullable();
|
||||
$table->unsignedInteger('duration_minutes')->nullable();
|
||||
$table->decimal('cost', 8, 2);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('push_notifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained();
|
||||
$table->string('type', 100);
|
||||
$table->string('title');
|
||||
$table->text('body');
|
||||
$table->json('data')->nullable();
|
||||
$table->enum('status', ['pending', 'sent', 'failed'])->default('pending');
|
||||
$table->timestamp('sent_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('audit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('actor_type', 50);
|
||||
$table->unsignedBigInteger('actor_id');
|
||||
$table->foreignId('organization_id')->nullable()->constrained();
|
||||
$table->foreignId('establishment_id')->nullable()->constrained();
|
||||
$table->string('action', 100);
|
||||
$table->string('target_type', 100);
|
||||
$table->unsignedBigInteger('target_id')->nullable();
|
||||
$table->json('before_data')->nullable();
|
||||
$table->json('after_data')->nullable();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('daily_establishment_stats', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('establishment_id')->constrained();
|
||||
$table->date('stat_date');
|
||||
$table->unsignedInteger('total_washes')->default(0);
|
||||
$table->decimal('total_revenue', 10, 2)->default(0);
|
||||
$table->unsignedInteger('bookings_count')->default(0);
|
||||
$table->unsignedInteger('no_show_count')->default(0);
|
||||
$table->decimal('top_up_amount', 10, 2)->default(0);
|
||||
$table->decimal('occupancy_rate', 5, 2)->default(0);
|
||||
$table->timestamps();
|
||||
$table->unique(['establishment_id', 'stat_date']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('daily_establishment_stats');
|
||||
Schema::dropIfExists('audit_logs');
|
||||
Schema::dropIfExists('push_notifications');
|
||||
Schema::dropIfExists('washes');
|
||||
Schema::dropIfExists('bookings');
|
||||
Schema::dropIfExists('wallet_transactions');
|
||||
Schema::dropIfExists('payment_transactions');
|
||||
Schema::dropIfExists('wallets');
|
||||
Schema::dropIfExists('promotions');
|
||||
Schema::dropIfExists('pricing_rules');
|
||||
Schema::dropIfExists('machine_status_history');
|
||||
Schema::dropIfExists('machine_events');
|
||||
Schema::dropIfExists('machine_commands');
|
||||
Schema::dropIfExists('machine_integrations');
|
||||
Schema::dropIfExists('machines');
|
||||
Schema::dropIfExists('notification_preferences');
|
||||
Schema::dropIfExists('user_devices');
|
||||
Schema::dropIfExists('gdpr_consents');
|
||||
Schema::dropIfExists('supervisors');
|
||||
Schema::dropIfExists('establishments');
|
||||
Schema::dropIfExists('organizations');
|
||||
}
|
||||
};
|
||||
@@ -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('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->text('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
DemoSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Establishment;
|
||||
use App\Models\Machine;
|
||||
use App\Models\MachineIntegration;
|
||||
use App\Models\Organization;
|
||||
use App\Models\PricingRule;
|
||||
use App\Models\Promotion;
|
||||
use App\Models\Supervisor;
|
||||
use App\Models\User;
|
||||
use App\Models\Wallet;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Jeu de données de démonstration pour Laverie Connectée.
|
||||
*
|
||||
* Crée organisations, établissements, superviseurs, utilisateurs,
|
||||
* machines simulées, tarifs et promotions actives.
|
||||
*/
|
||||
class DemoSeeder extends Seeder
|
||||
{
|
||||
/** Mot de passe commun pour tous les comptes de démo. */
|
||||
private const DEMO_PASSWORD = 'password';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$simulatedProvider = config('laverie.simulation.provider_name', 'simulated');
|
||||
|
||||
// --- Organisations ---
|
||||
$orgCentre = Organization::create([
|
||||
'name' => 'Laverie du Centre',
|
||||
'code' => 'LDC',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$orgExpress = Organization::create([
|
||||
'name' => 'Laverie Express',
|
||||
'code' => 'LEX',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// --- Établissements (2 pour org1, 1 pour org2) ---
|
||||
$estCentreVille = Establishment::create([
|
||||
'organization_id' => $orgCentre->id,
|
||||
'name' => 'Laverie Centre-Ville',
|
||||
'address' => '12 rue de la République',
|
||||
'city' => 'Lyon',
|
||||
'zip_code' => '69002',
|
||||
'latitude' => 45.7578137,
|
||||
'longitude' => 4.8320114,
|
||||
'timezone' => 'Europe/Paris',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$estPartDieu = Establishment::create([
|
||||
'organization_id' => $orgCentre->id,
|
||||
'name' => 'Laverie Part-Dieu',
|
||||
'address' => '45 avenue du Général Leclerc',
|
||||
'city' => 'Lyon',
|
||||
'zip_code' => '69003',
|
||||
'latitude' => 45.7601061,
|
||||
'longitude' => 4.8566930,
|
||||
'timezone' => 'Europe/Paris',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$estExpressNord = Establishment::create([
|
||||
'organization_id' => $orgExpress->id,
|
||||
'name' => 'Laverie Express Nord',
|
||||
'address' => '8 boulevard de la Croix-Rousse',
|
||||
'city' => 'Lyon',
|
||||
'zip_code' => '69004',
|
||||
'latitude' => 45.7740000,
|
||||
'longitude' => 4.8325000,
|
||||
'timezone' => 'Europe/Paris',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// --- Superviseurs ---
|
||||
$this->createSupervisor(
|
||||
organizationId: $orgCentre->id,
|
||||
establishmentId: null,
|
||||
firstName: 'Admin',
|
||||
lastName: 'Plateforme',
|
||||
email: 'platform_admin@laverie.local',
|
||||
role: 'platform_admin',
|
||||
);
|
||||
|
||||
$this->createSupervisor(
|
||||
organizationId: $orgCentre->id,
|
||||
establishmentId: null,
|
||||
firstName: 'Pierre',
|
||||
lastName: 'Moreau',
|
||||
email: 'owner1@laverie.local',
|
||||
role: 'owner',
|
||||
);
|
||||
|
||||
$this->createSupervisor(
|
||||
organizationId: $orgCentre->id,
|
||||
establishmentId: $estCentreVille->id,
|
||||
firstName: 'Sophie',
|
||||
lastName: 'Bernard',
|
||||
email: 'manager1@laverie.local',
|
||||
role: 'manager',
|
||||
);
|
||||
|
||||
$this->createSupervisor(
|
||||
organizationId: $orgExpress->id,
|
||||
establishmentId: null,
|
||||
firstName: 'Marc',
|
||||
lastName: 'Lefèvre',
|
||||
email: 'owner2@laverie.local',
|
||||
role: 'owner',
|
||||
);
|
||||
|
||||
// --- Utilisateurs finaux avec portefeuille (20 € chacun) ---
|
||||
$demoUsers = [
|
||||
['first_name' => 'Marie', 'last_name' => 'Dupont', 'email' => 'marie.dupont@demo.local'],
|
||||
['first_name' => 'Jean', 'last_name' => 'Martin', 'email' => 'jean.martin@demo.local'],
|
||||
['first_name' => 'Lucie', 'last_name' => 'Petit', 'email' => 'lucie.petit@demo.local'],
|
||||
['first_name' => 'Thomas', 'last_name' => 'Roux', 'email' => 'thomas.roux@demo.local'],
|
||||
['first_name' => 'Emma', 'last_name' => 'Girard', 'email' => 'emma.girard@demo.local'],
|
||||
];
|
||||
|
||||
$users = collect($demoUsers)->map(function (array $data) {
|
||||
$user = User::create([
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'email' => $data['email'],
|
||||
'password' => self::DEMO_PASSWORD,
|
||||
'locale' => 'fr',
|
||||
'is_active' => true,
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
Wallet::create([
|
||||
'user_id' => $user->id,
|
||||
'currency' => 'EUR',
|
||||
'current_balance' => 20.00,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
return $user;
|
||||
});
|
||||
|
||||
// --- Machines (10 au total, statuts variés) ---
|
||||
$machineDefinitions = [
|
||||
// Laverie Centre-Ville (4 machines)
|
||||
['establishment' => $estCentreVille, 'name' => 'Lave-linge 1', 'type' => 'washer_small', 'status' => 'available'],
|
||||
['establishment' => $estCentreVille, 'name' => 'Lave-linge 2', 'type' => 'washer_large', 'status' => 'running', 'user' => $users[0]],
|
||||
['establishment' => $estCentreVille, 'name' => 'Sèche-linge 1', 'type' => 'dryer_small', 'status' => 'reserved'],
|
||||
['establishment' => $estCentreVille, 'name' => 'Sèche-linge 2', 'type' => 'dryer_large', 'status' => 'maintenance'],
|
||||
// Laverie Part-Dieu (3 machines)
|
||||
['establishment' => $estPartDieu, 'name' => 'Lave-linge A', 'type' => 'washer_small', 'status' => 'available'],
|
||||
['establishment' => $estPartDieu, 'name' => 'Lave-linge B', 'type' => 'washer_large', 'status' => 'offline'],
|
||||
['establishment' => $estPartDieu, 'name' => 'Sèche-linge A', 'type' => 'dryer_small', 'status' => 'error'],
|
||||
// Laverie Express Nord (3 machines)
|
||||
['establishment' => $estExpressNord, 'name' => 'Express Lave 1', 'type' => 'washer_small', 'status' => 'available'],
|
||||
['establishment' => $estExpressNord, 'name' => 'Express Lave 2', 'type' => 'washer_large', 'status' => 'running', 'user' => $users[1]],
|
||||
['establishment' => $estExpressNord, 'name' => 'Express Sèche 1', 'type' => 'dryer_large', 'status' => 'available'],
|
||||
];
|
||||
|
||||
$machineIndex = 1;
|
||||
|
||||
foreach ($machineDefinitions as $definition) {
|
||||
$machine = Machine::create([
|
||||
'establishment_id' => $definition['establishment']->id,
|
||||
'name' => $definition['name'],
|
||||
'type' => $definition['type'],
|
||||
'qr_code' => 'LAVERIE-DEMO-'.str_pad((string) $machineIndex, 3, '0', STR_PAD_LEFT),
|
||||
'status' => $definition['status'],
|
||||
'current_user_id' => isset($definition['user']) ? $definition['user']->id : null,
|
||||
'cycle_started_at' => $definition['status'] === 'running' ? now()->subMinutes(15) : null,
|
||||
'cycle_ends_at' => $definition['status'] === 'running' ? now()->addMinutes(27) : null,
|
||||
'last_heartbeat_at' => $definition['status'] === 'offline' ? now()->subHours(2) : now()->subMinutes(2),
|
||||
]);
|
||||
|
||||
MachineIntegration::create([
|
||||
'machine_id' => $machine->id,
|
||||
'provider' => $simulatedProvider,
|
||||
'external_machine_id' => 'SIM-'.$machine->uuid,
|
||||
'external_site_id' => 'SITE-'.$definition['establishment']->uuid,
|
||||
'mode' => 'simulated',
|
||||
'config' => [
|
||||
'cycle_duration_seconds' => config('laverie.simulation.cycle_duration_seconds', 30),
|
||||
],
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$machineIndex++;
|
||||
}
|
||||
|
||||
// --- Règles tarifaires (4 € lavage, 3 € séchage) par établissement ---
|
||||
foreach ([$estCentreVille, $estPartDieu, $estExpressNord] as $establishment) {
|
||||
PricingRule::create([
|
||||
'establishment_id' => $establishment->id,
|
||||
'machine_type' => 'washer_small',
|
||||
'day_type' => 'all',
|
||||
'slot_start' => '00:00:00',
|
||||
'slot_end' => '23:59:59',
|
||||
'price' => 4.00,
|
||||
'label' => 'Lavage standard',
|
||||
'requires_app' => false,
|
||||
'priority' => 100,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
PricingRule::create([
|
||||
'establishment_id' => $establishment->id,
|
||||
'machine_type' => 'washer_large',
|
||||
'day_type' => 'all',
|
||||
'slot_start' => '00:00:00',
|
||||
'slot_end' => '23:59:59',
|
||||
'price' => 4.00,
|
||||
'label' => 'Lavage grand tambour',
|
||||
'requires_app' => false,
|
||||
'priority' => 100,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
PricingRule::create([
|
||||
'establishment_id' => $establishment->id,
|
||||
'machine_type' => 'dryer_small',
|
||||
'day_type' => 'all',
|
||||
'slot_start' => '00:00:00',
|
||||
'slot_end' => '23:59:59',
|
||||
'price' => 3.00,
|
||||
'label' => 'Séchage standard',
|
||||
'requires_app' => false,
|
||||
'priority' => 100,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
PricingRule::create([
|
||||
'establishment_id' => $establishment->id,
|
||||
'machine_type' => 'dryer_large',
|
||||
'day_type' => 'all',
|
||||
'slot_start' => '00:00:00',
|
||||
'slot_end' => '23:59:59',
|
||||
'price' => 3.00,
|
||||
'label' => 'Sèche-linge grand tambour',
|
||||
'requires_app' => false,
|
||||
'priority' => 100,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// --- Promotions actives (2) ---
|
||||
Promotion::create([
|
||||
'establishment_id' => $estCentreVille->id,
|
||||
'machine_type' => 'washer_small',
|
||||
'discount_type' => 'percent',
|
||||
'discount_value' => 10.00,
|
||||
'starts_at' => now()->subDays(7),
|
||||
'ends_at' => now()->addDays(30),
|
||||
'description' => 'Offre de bienvenue : -10 % sur les lavages petit tambour',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
Promotion::create([
|
||||
'establishment_id' => $estExpressNord->id,
|
||||
'machine_type' => 'all',
|
||||
'discount_type' => 'fixed',
|
||||
'discount_value' => 0.50,
|
||||
'starts_at' => now()->subDays(3),
|
||||
'ends_at' => now()->addDays(14),
|
||||
'description' => 'Happy hour Express : 0,50 € de réduction sur tous les cycles',
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un superviseur avec mot de passe de démonstration.
|
||||
*/
|
||||
private function createSupervisor(
|
||||
int $organizationId,
|
||||
?int $establishmentId,
|
||||
string $firstName,
|
||||
string $lastName,
|
||||
string $email,
|
||||
string $role,
|
||||
): Supervisor {
|
||||
return Supervisor::create([
|
||||
'organization_id' => $organizationId,
|
||||
'establishment_id' => $establishmentId,
|
||||
'first_name' => $firstName,
|
||||
'last_name' => $lastName,
|
||||
'email' => $email,
|
||||
'password' => self::DEMO_PASSWORD,
|
||||
'role' => $role,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user