complete Complete User Request Submission section RAD-1

This commit is contained in:
2026-01-03 19:14:30 +03:30
commit 7137ec5792
393 changed files with 152602 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
*.sqlite*
@@ -0,0 +1,51 @@
<?php
namespace Database\Factories;
use App\Models\Document;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\DocumentAssignment>
*/
class DocumentAssignmentFactory extends Factory
{
public function definition(): array
{
return [
'document_id' => Document::factory(),
'user_id' => User::factory(),
'role' => fake()->randomElement(['رادیولوژیست', 'پزشک', 'تکنسین', 'سرپرست']),
];
}
public function radiologist(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'رادیولوژیست',
]);
}
public function doctor(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'پزشک',
]);
}
public function technician(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'تکنسین',
]);
}
public function supervisor(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'سرپرست',
]);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace Database\Factories;
use App\Enums\DocumentStatusEnum;
use App\Enums\DocumentPriorityEnum;
use App\Models\Pet;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Document>
*/
class DocumentFactory extends Factory
{
public function definition(): array
{
return [
'code' => fake()->unique()->numberBetween(100000, 999999),
'modality' => fake()->randomElement(['CT', 'MRI', 'X-Ray', 'Ultrasound', 'PET']),
'status' => fake()->randomElement(DocumentStatusEnum::cases()),
'priority' => fake()->randomElement(DocumentPriorityEnum::cases()),
'note' => fake()->optional()->sentence(),
'submitted_at' => fake()->optional()->dateTimeBetween('-30 days', 'now'),
'scan_date' => fake()->optional()->dateTimeBetween('-30 days', 'now'),
'processing_deadline' => fake()->optional()->dateTimeBetween('now', '+30 days'),
'finalized_at' => fake()->optional()->dateTimeBetween('-30 days', 'now'),
'finalized_by' => fake()->optional()->randomElement(User::pluck('id')->toArray()),
'rejection_reason' => fake()->optional()->sentence(),
'pet_id' => Pet::factory(),
'requester_id' => User::factory(),
];
}
public function registered(): static
{
return $this->state(fn (array $attributes) => [
'status' => DocumentStatusEnum::Registered,
'submitted_at' => now(),
]);
}
public function inProgress(): static
{
return $this->state(fn (array $attributes) => [
'status' => DocumentStatusEnum::InProgress,
'submitted_at' => fake()->dateTimeBetween('-7 days', 'now'),
'scan_date' => fake()->dateTimeBetween('-7 days', 'now'),
]);
}
public function completed(): static
{
return $this->state(fn (array $attributes) => [
'status' => DocumentStatusEnum::Completed,
'submitted_at' => fake()->dateTimeBetween('-30 days', '-7 days'),
'scan_date' => fake()->dateTimeBetween('-30 days', '-7 days'),
'finalized_at' => fake()->dateTimeBetween('-7 days', 'now'),
'finalized_by' => User::factory(),
]);
}
public function rejected(): static
{
return $this->state(fn (array $attributes) => [
'status' => DocumentStatusEnum::Rejected,
'submitted_at' => fake()->dateTimeBetween('-30 days', '-7 days'),
'rejection_reason' => fake()->sentence(),
]);
}
public function high(): static
{
return $this->state(fn (array $attributes) => [
'priority' => DocumentPriorityEnum::High,
]);
}
public function medium(): static
{
return $this->state(fn (array $attributes) => [
'priority' => DocumentPriorityEnum::Medium,
'processing_deadline' => fake()->dateTimeBetween('now', '+3 days'),
]);
}
}
@@ -0,0 +1,89 @@
<?php
namespace Database\Factories;
use App\Enums\MediaCollectionEnum;
use App\Models\Document;
use App\Models\Medium;
use App\Models\Report;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\MediaAssignment>
*/
class MediaAssignmentFactory extends Factory
{
public function definition(): array
{
$mediableType = fake()->randomElement([Document::class, Report::class]);
return [
'media_id' => Medium::factory(),
'mediable_id' => $mediableType::factory(),
'mediable_type' => $mediableType,
'collection' => fake()->randomElement(MediaCollectionEnum::cases()),
];
}
public function forDocument(): static
{
return $this->state(fn (array $attributes) => [
'mediable_id' => Document::factory(),
'mediable_type' => Document::class,
]);
}
public function forReport(): static
{
return $this->state(fn (array $attributes) => [
'mediable_id' => Report::factory(),
'mediable_type' => Report::class,
]);
}
public function primary(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::Primary,
]);
}
public function history(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::History,
]);
}
public function attachments(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::Attachments,
]);
}
public function thumbnails(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::Thumbnails,
]);
}
public function documents(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::Documents,
]);
}
public function images(): static
{
return $this->state(fn (array $attributes) => [
'collection' => MediaCollectionEnum::Images,
]);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace Database\Factories;
use App\Enums\MediaDiskEnum;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Medium>
*/
class MediumFactory extends Factory
{
public function definition(): array
{
$filename = fake()->word() . '.' . fake()->fileExtension();
$disk = fake()->randomElement(MediaDiskEnum::cases());
$path = 'uploads/' . fake()->date('Y/m/d') . '/' . $filename;
return [
'filename' => $filename,
'disk' => $disk,
'path' => $path,
'size' => fake()->numberBetween(1024, 10485760),
'url' => fake()->optional()->url(),
'mime_type' => fake()->mimeType(),
];
}
public function public(): static
{
return $this->state(fn (array $attributes) => [
'disk' => MediaDiskEnum::Public,
'url' => null,
]);
}
public function private(): static
{
return $this->state(fn (array $attributes) => [
'disk' => MediaDiskEnum::Private,
'url' => null,
]);
}
public function image(): static
{
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$extension = fake()->randomElement($imageExtensions);
$filename = fake()->word() . '.' . $extension;
return $this->state(fn (array $attributes) => [
'filename' => $filename,
'mime_type' => 'image/' . $extension,
'size' => fake()->numberBetween(102400, 5242880),
]);
}
public function document(): static
{
$docExtensions = ['pdf', 'doc', 'docx', 'txt'];
$extension = fake()->randomElement($docExtensions);
$filename = fake()->word() . '.' . $extension;
return $this->state(fn (array $attributes) => [
'filename' => $filename,
'mime_type' => $extension === 'pdf' ? 'application/pdf' : 'application/msword',
'size' => fake()->numberBetween(51200, 10485760),
]);
}
public function small(): static
{
return $this->state(fn (array $attributes) => [
'size' => fake()->numberBetween(1024, 102400),
]);
}
public function large(): static
{
return $this->state(fn (array $attributes) => [
'size' => fake()->numberBetween(5242880, 52428800),
]);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace Database\Factories;
use App\Enums\PetTypeEnum;
use App\Enums\PetGenderEnum;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Pet>
*/
class PetFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->firstName(),
'type' => fake()->randomElement(PetTypeEnum::cases()),
'gender' => fake()->randomElement(PetGenderEnum::cases()),
'breed' => fake()->word(),
'weight' => fake()->numberBetween(1, 50),
'sterilized' => fake()->boolean(),
'birthday' => fake()->dateTimeBetween('-15 years', '-1 year'),
'owner_id' => User::factory(),
];
}
public function dog(): static
{
return $this->state(fn (array $attributes) => [
'type' => PetTypeEnum::Dog,
'breed' => fake()->randomElement(['ژرمن شپرد', 'گلدن رتریور', 'لابادور', 'بولداگ', 'پودل']),
]);
}
public function cat(): static
{
return $this->state(fn (array $attributes) => [
'type' => PetTypeEnum::Cat,
'breed' => fake()->randomElement(['پرشین', 'سیامی', 'مین کون', 'رگدال', 'بنگال']),
]);
}
public function male(): static
{
return $this->state(fn (array $attributes) => [
'gender' => PetGenderEnum::Male,
]);
}
public function female(): static
{
return $this->state(fn (array $attributes) => [
'gender' => PetGenderEnum::Female,
]);
}
public function sterilized(): static
{
return $this->state(fn (array $attributes) => [
'sterilized' => true,
]);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace Database\Factories;
use App\Enums\ReportStatusEnum;
use App\Models\Document;
use App\Models\Medium;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Report>
*/
class ReportFactory extends Factory
{
public function definition(): array
{
return [
'document_id' => Document::factory(),
'doctor_id' => User::factory(),
'medium_id' => fake()->optional()->randomElement(Medium::pluck('id')->toArray()),
'status' => fake()->randomElement(ReportStatusEnum::cases()),
'body' => fake()->optional()->paragraphs(3, true),
];
}
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::Draft,
'body' => fake()->optional()->paragraph(),
'submitted_at' => now(),
]);
}
public function inProgress(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::InProgress,
'body' => fake()->paragraphs(2, true),
'submitted_at' => fake()->dateTimeBetween('-30 days', 'now'),
]);
}
public function completed(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::Completed,
'body' => fake()->paragraphs(4, true),
'submitted_at' => fake()->dateTimeBetween('-30 days', 'now'),
]);
}
public function reviewed(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::Reviewed,
'body' => fake()->paragraphs(4, true),
'submitted_at' => fake()->dateTimeBetween('-30 days', 'now'),
]);
}
public function approved(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::Approved,
'body' => fake()->paragraphs(4, true),
'submitted_at' => fake()->dateTimeBetween('-30 days', 'now'),
]);
}
public function rejected(): static
{
return $this->state(fn (array $attributes) => [
'status' => ReportStatusEnum::Rejected,
'body' => fake()->paragraphs(2, true),
'submitted_at' => fake()->dateTimeBetween('-30 days', 'now'),
]);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Database\Factories;
use App\Enums\UserEnum;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\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 [
'username' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'phone' => fake()->phoneNumber(),
'level' => UserEnum::Supervisor->value,
'fullname' => fake()->firstName() . ' ' . fake()->lastName(),
'national_code' => fake()->numberBetween(1000000000, 9999999999),
'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,62 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Enums\UserEnum;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('username')->unique();
$table->string('phone');
$table->string('profile_url')->nullable();
$table->string('fullname')->nullable();
$table->string('national_code');
$table->string('level')->default(UserEnum::Supervisor->value);
$table->string('email')->nullable();
$table->string('password');
$table->string('clinic_name')->nullable();
$table->string('clinic_phone')->nullable();
$table->string('specialty')->nullable();
$table->string('province')->nullable();
$table->string('address' , 500)->nullable();
$table->string('city')->nullable();
$table->timestamp('email_verified_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();
});
}
/**
* Reverse the migrations.
*/
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->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* 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->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,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('media', function (Blueprint $table) {
$table->id();
$table->string('filename', 512);
$table->string('disk')->default('public');
$table->text('path');
$table->text('url')->nullable();
$table->bigInteger('size');
$table->string('mime_type', 255);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('media');
}
};
@@ -0,0 +1,143 @@
<?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
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->string('label')->nullable();
$table->bigInteger('parent_id')->default(0);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->string('label')->nullable();
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
};
@@ -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('otp_codes', function (Blueprint $table) {
$table->id();
$table->string('phone');
$table->string('code');
$table->timestamp('expires_at');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('otp_codes');
}
};
@@ -0,0 +1,38 @@
<?php
use App\Enums\PetGenderEnum;
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('pets', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->string('type')->nullable();
$table->string('gender')->default(PetGenderEnum::Unknown->value);
$table->string('breed')->nullable();
$table->unsignedInteger('weight')->nullable();
$table->boolean('sterilized')->nullable();
$table->date('birthday')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreignId('owner_id')->constrained('users')->cascadeOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pets');
}
};
@@ -0,0 +1,44 @@
<?php
use App\Enums\DocumentStatusEnum;
use App\Enums\DocumentPriorityEnum;
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('documents', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('modality')->nullable();
$table->string('status')->default(DocumentStatusEnum::Registered->value);
$table->string('priority')->default(DocumentPriorityEnum::Medium->value);
$table->text('note')->nullable();
$table->timestamp('submitted_at')->nullable();
$table->timestamp('scan_date')->nullable();
$table->timestamp('processing_deadline')->nullable();
$table->timestamp('finalized_at')->nullable();
$table->foreignId('finalized_by')->nullable()->constrained('users')->cascadeOnDelete();
$table->text('rejection_reason')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreignId('pet_id')->constrained('pets')->cascadeOnDelete();
$table->foreignId('requester_id')->constrained('users')->cascadeOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('documents');
}
};
@@ -0,0 +1,36 @@
<?php
use App\Enums\ReportStatusEnum;
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('reports', function (Blueprint $table) {
$table->id();
$table->string('status')->default(ReportStatusEnum::Draft->value);
$table->text('body')->nullable();
$table->timestamp('submitted_at')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreignId('medium_id')->nullable()->constrained('media')->cascadeOnDelete();
$table->foreignId('document_id')->constrained('documents')->cascadeOnDelete();
$table->foreignId('doctor_id')->constrained('users')->cascadeOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('reports');
}
};
@@ -0,0 +1,31 @@
<?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('document_assignments', function (Blueprint $table) {
$table->foreignId('document_id')->constrained('documents')->cascadeOnDelete();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->string('role')->nullable();
$table->timestamps();
$table->unique(['document_id', 'user_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('document_assignments');
}
};
@@ -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('media_assignments', function (Blueprint $table) {
$table->unsignedBigInteger('mediable_id');
$table->string('mediable_type');
$table->string('collection')->nullable();
$table->foreignId('media_id')->constrained('media')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('media_assignments');
}
};
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
PermissionSeeder::class,
// FullDatasetSeeder::class,
]);
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace Database\Seeders;
use App\Enums\UserEnum;
use App\Models\Document;
use App\Models\DocumentAssignment;
use App\Models\MediaAssignment;
use App\Models\Medium;
use App\Models\Pet;
use App\Models\Report;
use App\Models\User;
use Illuminate\Database\Seeder;
class FullDatasetSeeder extends Seeder
{
public function run(): void
{
$users = collect();
$admin = User::where('email', 'admin@gmail.com')->first();
if ($admin) {
$users->push($admin);
}
$supervisors = User::factory()->count(3)->create([
'level' => UserEnum::Supervisor->value,
]);
$users = $users->merge($supervisors);
$vets = User::factory()->count(5)->create([
'level' => UserEnum::Vet->value,
]);
$users = $users->merge($vets);
$doctors = User::factory()->count(8)->create([
'level' => UserEnum::Doctor->value,
]);
$users = $users->merge($doctors);
$owners = User::factory()->count(20)->create();
$users = $users->merge($owners);
$pets = collect();
foreach ($owners as $owner) {
$petCount = rand(1, 3);
for ($i = 0; $i < $petCount; $i++) {
$pet = Pet::factory()->create([
'owner_id' => $owner->id,
]);
$pets->push($pet);
}
}
$documents = collect();
foreach ($pets as $pet) {
$docCount = rand(2, 5);
for ($i = 0; $i < $docCount; $i++) {
$document = Document::factory()->create([
'pet_id' => $pet->id,
'requester_id' => $pet->owner_id,
]);
$documents->push($document);
}
}
$media = collect();
for ($i = 0; $i < 100; $i++) {
$medium = Medium::factory()->create();
$media->push($medium);
}
foreach ($documents as $document) {
$assigneeCount = rand(1, 3);
$assignees = $users->where('level', '!=', UserEnum::Admin->value)
->random($assigneeCount);
foreach ($assignees as $assignee) {
DocumentAssignment::factory()->create([
'document_id' => $document->id,
'user_id' => $assignee->id,
]);
}
}
$reports = collect();
foreach ($documents as $document) {
if (rand(1, 100) <= 70) {
$report = Report::factory()->create([
'document_id' => $document->id,
'doctor_id' => $doctors->random()->id,
]);
$reports->push($report);
}
}
foreach ($documents as $document) {
$mediaCount = rand(2, 5);
$documentMedia = $media->random($mediaCount);
foreach ($documentMedia as $medium) {
MediaAssignment::factory()->forDocument()->create([
'media_id' => $medium->id,
'mediable_id' => $document->id,
'mediable_type' => Document::class,
]);
}
}
foreach ($reports as $report) {
$mediaCount = rand(1, 3);
$reportMedia = $media->random($mediaCount);
foreach ($reportMedia as $medium) {
MediaAssignment::factory()->forReport()->create([
'media_id' => $medium->id,
'mediable_id' => $report->id,
'mediable_type' => Report::class,
]);
}
}
$this->createSpecificScenarios($users, $pets, $documents, $reports, $media);
}
private function createSpecificScenarios($users, $pets, $documents, $reports, $media): void
{
$mediumDocuments = $documents->random(5);
foreach ($mediumDocuments as $document) {
$document->update([
'status' => \App\Enums\DocumentStatusEnum::InProgress,
'priority' => \App\Enums\DocumentPriorityEnum::Medium,
'processing_deadline' => now()->addDays(1),
]);
}
$highDocuments = $documents->random(5);
foreach ($highDocuments as $document) {
$document->update([
'status' => \App\Enums\DocumentStatusEnum::InProgress,
'priority' => \App\Enums\DocumentPriorityEnum::High,
'processing_deadline' => now()->addDays(1),
]);
}
$completedDocuments = $documents->random(10);
foreach ($completedDocuments as $document) {
$document->update([
'status' => \App\Enums\DocumentStatusEnum::Completed,
'finalized_at' => now()->subDays(rand(1, 30)),
'finalized_by' => $users->where('level', UserEnum::Supervisor->value)->random()->id,
]);
if (!$reports->where('document_id', $document->id)->count()) {
Report::factory()->approved()->create([
'document_id' => $document->id,
'doctor_id' => $users->where('level', UserEnum::Doctor->value)->random()->id,
]);
}
}
$rejectedDocuments = $documents->random(3);
foreach ($rejectedDocuments as $document) {
$document->update([
'status' => \App\Enums\DocumentStatusEnum::Rejected,
'rejection_reason' => 'کیفیت تصاویر نامناسب است',
]);
}
$dogs = Pet::factory()->count(10)->dog()->create();
$cats = Pet::factory()->count(8)->cat()->create();
foreach ($dogs as $dog) {
Document::factory()->count(2)->create([
'pet_id' => $dog->id,
'requester_id' => $dog->owner_id,
]);
}
foreach ($cats as $cat) {
Document::factory()->count(2)->create([
'pet_id' => $cat->id,
'requester_id' => $cat->owner_id,
]);
}
}
}
+127
View File
@@ -0,0 +1,127 @@
<?php
namespace Database\Seeders;
use App\Enums\UserEnum;
use Illuminate\Database\Seeder;
use App\Models\Permission;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\Hash;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::updateOrCreate(
['email' => 'admin@gmail.com'],
[
'password' => Hash::make('12345678'),
'username' => 'admin',
'phone' => '09123456789',
'level' => UserEnum::Admin->value,
'fullname' => 'admin',
'national_code' => '1234567890',
]
);
$permissions = [
'users' => [
'label' => 'دسترسی به کاربران',
'children' => [
'index' => 'دسترسی به لیست کاربران',
'create' => 'دسترسی به ایجاد کاربر',
'edit' => 'دسترسی به ویرایش کاربر',
'delete' => 'دسترسی به حذف کاربر',
'own' => 'دسترسی به اطلاعات خود کاربر',
],
],
'permissions' => [
'label' => 'دسترسی به سطح دسترسی‌ها',
'children' => [
'index' => 'دسترسی به لیست سطح دسترسی‌ها',
'create' => 'دسترسی به ایجاد سطح دسترسی',
'edit' => 'دسترسی به ویرایش سطح دسترسی',
'delete' => 'دسترسی به حذف سطح دسترسی',
'own' => 'دسترسی به سطح دسترسی خود',
],
],
'medium' => [
'label' => 'دسترسی به رسانه ها',
'children' => [
'index' => 'دسترسی به لیست رسانه ها',
'create' => 'دسترسی به ایجاد رسانه',
'edit' => 'دسترسی به ویرایش رسانه',
'delete' => 'دسترسی به حذف رسانه',
'own' => 'دسترسی به رسانه های خود',
],
],
'roles' => [
'label' => 'دسترسی به نقش‌ها',
'children' => [
'index' => 'دسترسی به لیست نقش‌ها',
'create' => 'دسترسی به ایجاد نقش',
'edit' => 'دسترسی به ویرایش نقش',
'delete' => 'دسترسی به حذف نقش',
'own' => 'دسترسی به نقش خود',
],
],
'pets' => [
'label' => 'دسترسی به کیس ها',
'children' => [
'index' => 'دسترسی به لیست کیس ها',
'create' => 'دسترسی به ایجاد کیس',
'edit' => 'دسترسی به ویرایش کیس',
'delete' => 'دسترسی به حذف کیس',
'own' => 'دسترسی به کیس خود',
],
],
'documents' => [
'label' => 'دسترسی به پرونده ها',
'children' => [
'index' => 'دسترسی به لیست پرونده ها',
'create' => 'دسترسی به ایجاد پرونده',
'edit' => 'دسترسی به ویرایش پرونده',
'delete' => 'دسترسی به حذف پرونده',
'own' => 'دسترسی به پرونده خود',
],
],
'reports' => [
'label' => 'دسترسی به گزارش ها',
'children' => [
'index' => 'دسترسی به لیست گزارش ها',
'create' => 'دسترسی به ایجاد گزارش',
'edit' => 'دسترسی به ویرایش گزارش',
'delete' => 'دسترسی به حذف گزارش',
'own' => 'دسترسی به گزارش خود',
],
],
];
foreach ($permissions as $key => $data) {
$parent = Permission::create([
'name' => $key,
'label' => $data['label'],
'parent_id' => 0,
]);
foreach ($data['children'] as $action => $label) {
Permission::create([
'name' => "$key.$action",
'label' => $label,
'parent_id' => $parent->id,
]);
}
}
$adminRole = Role::firstOrCreate(['name' => 'admin', 'label' => 'مدیر کل']);
$adminRole->syncPermissions(Permission::all());
$admin = User::where('email', 'admin@gmail.com')->first();
$admin->assignRole($adminRole);
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace Database\Seeders;
use App\Enums\UserEnum;
use App\Models\Document;
use App\Models\DocumentAssignment;
use App\Models\MediaAssignment;
use App\Models\Medium;
use App\Models\Pet;
use App\Models\Report;
use App\Models\User;
use Illuminate\Database\Seeder;
class SampleDataSeeder extends Seeder
{
public function run(): void
{
$admin = User::where('email', 'admin@gmail.com')->first();
if (!$admin) {
$admin = User::factory()->create([
'email' => 'admin@radical.dev',
'username' => 'admin_dev',
'level' => UserEnum::Admin->value,
'fullname' => 'مدیر سیستم',
]);
}
$supervisor = User::factory()->create([
'email' => 'supervisor@radical.dev',
'username' => 'supervisor',
'level' => UserEnum::Supervisor->value,
'fullname' => 'سرپرست بخش',
]);
$doctor = User::factory()->create([
'email' => 'doctor@radical.dev',
'username' => 'doctor',
'level' => UserEnum::Doctor->value,
'fullname' => 'دکتر رادیولوژی',
]);
$owner = User::factory()->create([
'email' => 'owner@radical.dev',
'username' => 'owner',
'level' => UserEnum::Supervisor->value,
'fullname' => 'صاحب حیوان',
]);
$dog = Pet::factory()->dog()->male()->create([
'name' => 'رکس',
'owner_id' => $owner->id,
]);
$cat = Pet::factory()->cat()->female()->create([
'name' => 'میمی',
'owner_id' => $owner->id,
]);
$urgentDocument = Document::factory()->urgent()->create([
'pet_id' => $dog->id,
'requester_id' => $owner->id,
]);
$completedDocument = Document::factory()->completed()->create([
'pet_id' => $cat->id,
'requester_id' => $owner->id,
'finalized_by' => $supervisor->id,
]);
DocumentAssignment::factory()->radiologist()->create([
'document_id' => $urgentDocument->id,
'user_id' => $doctor->id,
]);
DocumentAssignment::factory()->supervisor()->create([
'document_id' => $urgentDocument->id,
'user_id' => $supervisor->id,
]);
$imageFile = Medium::factory()->image()->public()->create([
'filename' => 'xray_dog.jpg',
]);
$documentFile = Medium::factory()->document()->create([
'filename' => 'medical_history.pdf',
]);
MediaAssignment::factory()->forDocument()->primary()->create([
'media_id' => $imageFile->id,
'mediable_id' => $urgentDocument->id,
'mediable_type' => Document::class,
]);
MediaAssignment::factory()->forDocument()->history()->create([
'media_id' => $documentFile->id,
'mediable_id' => $urgentDocument->id,
'mediable_type' => Document::class,
]);
Report::factory()->draft()->create([
'document_id' => $urgentDocument->id,
'doctor_id' => $doctor->id,
]);
$approvedReport = Report::factory()->approved()->create([
'document_id' => $completedDocument->id,
'doctor_id' => $doctor->id,
]);
$reportImage = Medium::factory()->image()->create([
'filename' => 'report_attachment.jpg',
]);
MediaAssignment::factory()->forReport()->attachments()->create([
'media_id' => $reportImage->id,
'mediable_id' => $approvedReport->id,
'mediable_type' => Report::class,
]);
}
}