217 lines
8.7 KiB
PHP
217 lines
8.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use Authentication\PasswordHasher\DefaultPasswordHasher;
|
|
use Faker\Factory;
|
|
use Faker\Generator;
|
|
use Migrations\BaseSeed;
|
|
|
|
/**
|
|
* Seeds test data: 2 accounts, 100 customers and 1-5 cars per customer.
|
|
*
|
|
* Run: bin/cake seeds run DatabaseSeed
|
|
* Existing data in users, customers and cars is removed first.
|
|
*/
|
|
class DatabaseSeed extends BaseSeed
|
|
{
|
|
private const CARS = [
|
|
'Toyota' => ['Corolla', 'Yaris', 'RAV4', 'Auris', 'C-HR', 'Camry'],
|
|
'Volkswagen' => ['Golf', 'Passat', 'Polo', 'Tiguan', 'T-Roc', 'Arteon'],
|
|
'Skoda' => ['Octavia', 'Fabia', 'Superb', 'Kodiaq', 'Karoq', 'Scala'],
|
|
'Ford' => ['Focus', 'Fiesta', 'Mondeo', 'Kuga', 'Puma'],
|
|
'Opel' => ['Astra', 'Corsa', 'Insignia', 'Mokka', 'Grandland'],
|
|
'BMW' => ['Seria 3', 'Seria 5', 'X1', 'X3', 'X5'],
|
|
'Audi' => ['A3', 'A4', 'A6', 'Q3', 'Q5'],
|
|
'Mercedes-Benz' => ['Klasa A', 'Klasa C', 'Klasa E', 'GLC', 'GLA'],
|
|
'Renault' => ['Clio', 'Megane', 'Captur', 'Kadjar'],
|
|
'Kia' => ['Ceed', 'Sportage', 'Rio', 'Niro'],
|
|
'Hyundai' => ['i20', 'i30', 'Tucson', 'Kona'],
|
|
'Tesla' => ['Model 3', 'Model Y', 'Model S'],
|
|
'Fiat' => ['Panda', '500', 'Tipo'],
|
|
'Dacia' => ['Duster', 'Sandero', 'Logan'],
|
|
];
|
|
|
|
private const COLORS = ['czarny', 'biały', 'srebrny', 'szary', 'niebieski', 'czerwony', 'zielony', 'grafitowy', 'beżowy'];
|
|
|
|
private const PLATE_PREFIXES = ['WA', 'WE', 'WI', 'KR', 'KK', 'PO', 'PZ', 'GD', 'GA', 'WR', 'DW', 'LU', 'SK', 'SC', 'EL', 'BI', 'OP', 'RZ', 'TK', 'NO', 'ZS', 'CB', 'FZ'];
|
|
|
|
private const CUSTOMER_NOTES = [
|
|
'Preferuje kontakt telefoniczny po godzinie 16.',
|
|
'Stały klient, przysługuje rabat 10% na usługi serwisowe.',
|
|
'Prosi o przypomnienia SMS przed terminem przeglądu.',
|
|
'Faktury wysyłać wyłącznie na adres e-mail.',
|
|
'Klient flotowy - rozliczenie zbiorcze na koniec miesiąca.',
|
|
'Zainteresowany wymianą auta na nowsze w przyszłym roku.',
|
|
'Kontakt tylko w dni robocze.',
|
|
];
|
|
|
|
private const CAR_NOTES = [
|
|
'Wymiana oleju przy 15 000 km.',
|
|
'Do sprawdzenia klocki hamulcowe przy następnej wizycie.',
|
|
'Opony zimowe przechowywane w serwisie.',
|
|
'Auto po szkodzie parkingowej - naprawiony tylny zderzak.',
|
|
'Gwarancja producenta do końca roku.',
|
|
'Klient zgłaszał stuki w zawieszeniu.',
|
|
'Zalecana wymiana rozrządu.',
|
|
];
|
|
|
|
private const VIN_CHARS = 'ABCDEFGHJKLMNPRSTUVWXYZ0123456789';
|
|
|
|
/**
|
|
* The seed truncates its tables first, so it can safely be re-run.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isIdempotent(): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function run(): void
|
|
{
|
|
$faker = Factory::create('pl_PL');
|
|
$faker->seed(20261019);
|
|
$now = date('Y-m-d H:i:s');
|
|
$hasher = new DefaultPasswordHasher();
|
|
|
|
$this->execute('SET FOREIGN_KEY_CHECKS=0');
|
|
foreach (['cars', 'customers', 'users'] as $table) {
|
|
$this->execute("TRUNCATE TABLE `{$table}`");
|
|
}
|
|
$this->execute('SET FOREIGN_KEY_CHECKS=1');
|
|
|
|
$this->table('users')->insert([
|
|
[
|
|
'email' => 'admin@example.com',
|
|
'password' => $hasher->hash('Admin123!'),
|
|
'first_name' => 'Adam',
|
|
'last_name' => 'Administrator',
|
|
'role' => 'admin',
|
|
'active' => true,
|
|
'created' => $now,
|
|
'modified' => $now,
|
|
],
|
|
[
|
|
'email' => 'user@example.com',
|
|
'password' => $hasher->hash('User1234!'),
|
|
'first_name' => 'Jan',
|
|
'last_name' => 'Użytkownik',
|
|
'role' => 'user',
|
|
'active' => true,
|
|
'created' => $now,
|
|
'modified' => $now,
|
|
],
|
|
])->saveData();
|
|
|
|
$customers = [];
|
|
$emails = [];
|
|
for ($i = 1; $i <= 100; $i++) {
|
|
$isCompany = $faker->boolean(25);
|
|
$firstName = $faker->firstName();
|
|
$lastName = $faker->lastName();
|
|
do {
|
|
$email = $this->slug($firstName) . '.' . $this->slug($lastName) . $faker->numberBetween(1, 999)
|
|
. '@' . $faker->freeEmailDomain();
|
|
} while (isset($emails[$email]));
|
|
$emails[$email] = true;
|
|
$created = $faker->dateTimeBetween('-3 years', '-1 day')->format('Y-m-d H:i:s');
|
|
|
|
$customers[] = [
|
|
'type' => $isCompany ? 'company' : 'individual',
|
|
'first_name' => $firstName,
|
|
'last_name' => $lastName,
|
|
'email' => $email,
|
|
'phone' => '+48 ' . $faker->numerify('### ### ###'),
|
|
'company_name' => $isCompany ? $faker->company() : null,
|
|
'tax_id' => $isCompany ? $faker->numerify('##########') : null,
|
|
'street' => $faker->boolean(90) ? $faker->streetAddress() : null,
|
|
'city' => $faker->city(),
|
|
'postal_code' => $faker->numerify('##-###'),
|
|
'country' => 'PL',
|
|
'birth_date' => $isCompany ? null : $faker->dateTimeBetween('-80 years', '-18 years')->format('Y-m-d'),
|
|
'notes' => $faker->boolean(20) ? $faker->randomElement(self::CUSTOMER_NOTES) : null,
|
|
'created_by' => $faker->randomElement([1, 2]),
|
|
'created' => $created,
|
|
'modified' => $created,
|
|
];
|
|
}
|
|
$this->table('customers')->insert($customers)->saveData();
|
|
|
|
$cars = [];
|
|
$vins = [];
|
|
$plates = [];
|
|
for ($customerId = 1; $customerId <= 100; $customerId++) {
|
|
$count = $faker->numberBetween(1, 5);
|
|
for ($j = 0; $j < $count; $j++) {
|
|
$make = $faker->randomElement(array_keys(self::CARS));
|
|
$isTesla = $make === 'Tesla';
|
|
$fuel = $isTesla ? 'electric' : $faker->randomElement(['petrol', 'petrol', 'diesel', 'diesel', 'lpg', 'hybrid']);
|
|
do {
|
|
$vin = $this->randomVin($faker);
|
|
} while (isset($vins[$vin]));
|
|
$vins[$vin] = true;
|
|
do {
|
|
$plate = $faker->randomElement(self::PLATE_PREFIXES)
|
|
. $faker->numberBetween(10000, 99999);
|
|
$plate = substr($plate, 0, 2) . $faker->bothify('?') . substr($plate, 3);
|
|
$plate = strtoupper($plate);
|
|
} while (isset($plates[$plate]));
|
|
$plates[$plate] = true;
|
|
$year = $faker->numberBetween(2005, 2026);
|
|
$created = $faker->dateTimeBetween('-3 years', '-1 day')->format('Y-m-d H:i:s');
|
|
|
|
$cars[] = [
|
|
'customer_id' => $customerId,
|
|
'make' => $make,
|
|
'model' => $faker->randomElement(self::CARS[$make]),
|
|
'production_year' => $year,
|
|
'vin' => $vin,
|
|
'registration_number' => $plate,
|
|
'fuel_type' => $fuel,
|
|
'color' => $faker->randomElement(self::COLORS),
|
|
'engine_capacity' => $fuel === 'electric' ? null : $faker->randomElement([999, 1197, 1395, 1498, 1598, 1968, 1995, 2494, 2993]),
|
|
'power_hp' => $faker->numberBetween(70, $isTesla ? 500 : 300),
|
|
'transmission' => $isTesla ? 'automatic' : $faker->randomElement(['manual', 'manual', 'automatic']),
|
|
'mileage' => max(0, (2026 - $year) * $faker->numberBetween(5000, 25000) + $faker->numberBetween(0, 5000)),
|
|
'notes' => $faker->boolean(15) ? $faker->randomElement(self::CAR_NOTES) : null,
|
|
'created' => $created,
|
|
'modified' => $created,
|
|
];
|
|
}
|
|
}
|
|
$this->table('cars')->insert($cars)->saveData();
|
|
|
|
$this->getIo()?->out(sprintf('Seeded 2 users, %d customers and %d cars.', count($customers), count($cars)));
|
|
}
|
|
|
|
/**
|
|
* @param \Faker\Generator $faker Faker
|
|
* @return string
|
|
*/
|
|
private function randomVin(Generator $faker): string
|
|
{
|
|
$vin = '';
|
|
for ($i = 0; $i < 17; $i++) {
|
|
$vin .= self::VIN_CHARS[$faker->numberBetween(0, strlen(self::VIN_CHARS) - 1)];
|
|
}
|
|
|
|
return $vin;
|
|
}
|
|
|
|
/**
|
|
* ASCII slug for e-mail addresses (strips Polish diacritics).
|
|
*
|
|
* @param string $text Text
|
|
* @return string
|
|
*/
|
|
private function slug(string $text): string
|
|
{
|
|
$map = ['ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z'];
|
|
|
|
return preg_replace('/[^a-z]/', '', strtr(mb_strtolower($text), $map)) ?? '';
|
|
}
|
|
}
|