creat controller model,migration,factory,sedder.request resourse,web for 4item . such as event call,callhistory,category

This commit is contained in:
2025-04-29 16:50:55 +03:30
parent 01eaed4bb2
commit 46c7c11260
23 changed files with 892 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers;
use App\Enums\CallEvents;
use App\Http\Requests\Call\ListRequest;
use App\Http\Requests\Call\StoreRequest;
use App\Http\Requests\Call\UpdateRequest;
use App\Http\Resources\Call\CallResource;
use app\Http\Traits\ApiResponse;
use App\Models\Call;
use App\Models\CallHistory;
use App\Models\Category;
use App\Models\Event;
use App\Models\User;
use App\Services\Notification\Exceptions\NotificationServerNotRespondingException;
use App\Services\Notification\Exceptions\OperatorNotConnectedException;
use App\Services\Notification\Exceptions\OperatorOfflineException;
use App\Services\Notification\NotificationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class CallController extends Controller
{
use ApiResponse;
public function index()
{
//
}
public function store(StoreRequest $request,NotificationService $notificationService): JsonResponse
{
try
{
$call = DB::transaction(function () use ($request)
{
$operator = User::query()->where('telephone_id', $request->telephone_id)->first();
$call = Call::query()->create([
'telephone_id' => $request->telephone_id,
'caller_phone_number' => $request->caller_phone_number,
'operator_id' => $operator->id,
'operator_username' => $operator->username,
'operator_full_name' => $operator->full_name
]);
CallHistory::recordHistory($call, CallEvents::CALL_CREATED);
return $call;
});
$data = [
'telephone_id' => $call->telephone_id,
'phone_number' => $call->caller_phone_number,
'call_id' => $call->id,
];
$notificationService->deliverDataToNotificationServer($data);
return $this->successResponse();
}
catch (NotificationServerNotRespondingException $exception)
{
CallHistory::recordHistory($call, CallEvents::NOTIFICATION_SERVER_NOT_RESPONDING);
throw $exception;
}
catch (OperatorOfflineException $exception)
{
CallHistory::recordHistory($call, CallEvents::OPERATOR_IS_OFFLINE);
throw $exception;
}
catch (OperatorNotConnectedException $exception)
{
CallHistory::recordHistory($call, CallEvents::OPERATOR_COULD_NOT_CONNECT);
throw $exception;
}
}
public function show(string $id)
{
//
}
public function update(UpdateRequest $request, Call $call): JsonResponse
{
DB::transaction(function () use ($request, $call) {
$operator = User::query()->where('telephone_id', $call->telephone_id)->first();
$category = Category::query()->where([
['category_id', $request->category_id],
['subcategory_id', $request->subcategory_id]
])->first();
$call->update([
'province_id' => $operator->province_id,
'province_fa' => $operator->province_fa,
'operator_id' => $operator->id,
'operator_username' => $operator->username,
'operator_full_name' => $operator->full_name,
'category_id' => $request->category_id,
'category_name' => $category->category_name,
'subcategory_id' => $request->subcategory_id,
'subcategory_name' => $category->subcategory_name,
'description' => $request->description,
]);
$event_id = CallEvents::OPERATOR_STORED_CALL_DATA;
$event_name = Event::query()->find($event_id)->first()->name;
CallHistory::create([
'call_id' => $call->id,
'telephone_id' => $call->telephone_id,
'caller_phone_number' => $call->caller_phone_number,
'event_id' => $event_id,
'event_name' => $event_name,
]);
});
return $this->successResponse();
}
public function list(ListRequest $request): JsonResponse
{
$phone_number = $request->phone_number;
$size = $request->size ?? config('global_variables.MAX_RECORDS_COUNT');
$calls_data = Call::query()
->when($phone_number, function ($query) use ($phone_number) {
return $query->where('caller_phone_number', $phone_number);
})->orderBy('created_at', 'DESC')
->limit($size)
->get();
return $this->successResponse(CallResource::collection($calls_data));
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use app\Http\Traits\ApiResponse;
use App\Models\Category;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CategoryController extends Controller
{
use ApiResponse;
public function list(): JsonResponse
{
$data = Category::all('category_id', 'category_name', 'subcategory_id', 'subcategory_name');
return $this->successResponse($data);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Call;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class ListRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'phone_number' => 'regex:/^09\d{9}$/',
'size' => 'integer'
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Call;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class StoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'telephone_id' => 'required|string|exists:users,telephone_id',
'caller_phone_number' => 'required|numeric|regex:/^09\d{9}$/',
];
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Http\Requests\Call;
use App\Models\Category;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
/**
* @property mixed $call
* @property mixed $category_id
* @property mixed $subcategory_id
* @OA\Schema(
* * schema="UpdateCallDataRequest",
* * title="Update Call Data",
* * description="OperatorActivity",
* * @OA\Property( property="category_id", type="integer", format="int64", title="category_id", description="category id of caller request"),
* * @OA\Property( property="subcategory_id", type="integer", format="int64", title="subcategory_id", description="subcategory id of caller request"),
* * @OA\Property( property="description", type="string", title="description", description="operator description"),
* * ),
*/
class UpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
$operator_telephone_id = auth()->user()->telephone_id;
if ($operator_telephone_id == $this->call->telephone_id) {
return true;
}
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'category_id' => 'required|integer',
'subcategory_id' => 'required|integer',
'description' => 'string',
];
}
public function after(): array
{
return [
function (Validator $validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add(
'category',
__('messages.combination_of_category_and_subcategory_does_not_exist')
);
}
}
];
}
private function somethingElseIsInvalid(): bool
{
$category_id = $this->category_id;
$subcategory_id = $this->subcategory_id;
if ($category_id && $subcategory_id) {
return !Category::query()->where([
['category_id', $category_id],
['subcategory_id', $subcategory_id]
])->exists();
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources\Call;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CallResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'operator_id' => $this->operator_id,
'operator_full_name' => $this->operator_full_name,
'operator_username' => $this->operator_username,
'telephone_id' => $this->telephone_id,
'caller_phone_number' => $this->caller_phone_number,
'description' => $this->description,
'category_id' => $this->category_id,
'category_name' => $this->category_name,
'subcategory_id' => $this->subcategory_id,
'subcategory_name' => $this->subcategory_name,
'created_at' => $this->created_at,
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Call extends Model
{
/** @use HasFactory<\Database\Factories\CallFactory> */
use HasFactory;
protected $guarded = [
'call_id',
'created_at',
'updated_at'
];
// public function histories(): HasMany
// {
// return $this->hasMany(CallHistory::class);
// }
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CallHistory extends Model
{
/** @use HasFactory<\Database\Factories\CallHistoryFactory> */
use HasFactory;
protected $guarded = ['id'];
public static function recordHistory(Call $call, $event_id): void
{
$event = Event::query()->where('id', $event_id)->first();
self::create([
'call_id' => $call->id,
'telephone_id' => $call->telephone_id,
'caller_phone_number' => $call->caller_phone_number,
'event_id' => $event->id,
'event_name' => $event->name
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
/** @use HasFactory<\Database\Factories\CategoryFactory> */
use HasFactory;
protected $guarded = ['id'];
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
/** @use HasFactory<\Database\Factories\EventFactory> */
use HasFactory;
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use App\Models\Category;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<\App\Models\Call>
*/
class CallFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'telephone_id' => $this->faker->numerify('tel-####'),
'caller_phone_number' => '09' . $this->faker->unique()->randomNumber(9, true),
'operator_id' => User::factory(),
'operator_username' => function (array $attributes) {
return User::find($attributes['operator_id'])->username;
},
'operator_full_name' => function (array $attributes) {
return User::find($attributes['operator_id'])->full_name;
},
'description' => $this->faker->text,
'category_id' => Category::factory(),
'category_name' => function (array $attributes) {
return Category::find($attributes['category_id'])->category_name;
},
'subcategory_id' => function (array $attributes) {
return Category::find($attributes['category_id'])->subcategory_id;
},
'subcategory_name' => function (array $attributes) {
return Category::find($attributes['category_id'])->subcategory_name;
},
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Database\Factories;
use App\Models\Call;
use App\Models\Event;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\CallHistory>
*/
class CallHistoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'call_id' => Call::factory(),
'telephone_id' => $this->faker->numerify('tel-####'),
'caller_phone_number' => '09' . $this->faker->randomNumber(9, true),
'event_id' => Event::factory(),
'event_name' => function (array $attributes) {
return Event::query()->find($attributes['event_id'])->name;
}
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category>
*/
class CategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'category_id' => $this->faker->randomNumber(3, true),
'category_name' => $this->faker->words(3, true),
'subcategory_id' => $this->faker->randomNumber(4, true),
'subcategory_name' => $this->faker->words(3, true)
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
*/
class EventFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->sentence,
];
}
}

View File

@@ -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('calls', function (Blueprint $table) {
$table->id();
$table->string('telephone_id');
$table->string('caller_phone_number');
$table->foreignId('province_id')
->nullable()
->constrained();
$table->string('province_fa')->nullable();
$table->foreignId('operator_id')
->nullable()
->constrained('users');
$table->string('operator_username')->nullable();
$table->string('operator_full_name')->nullable();
$table->unsignedSmallInteger('category_id')->nullable();
$table->string('category_name')->nullable();
$table->unsignedSmallInteger('subcategory_id')->nullable();
$table->string('subcategory_name')->nullable();
$table->text('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('calls');
}
};

View File

@@ -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('categories', function (Blueprint $table) {
$table->id();
$table->unsignedSmallInteger('category_id');
$table->string('category_name');
$table->unsignedSmallInteger('subcategory_id')->nullable();
$table->string('subcategory_name')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};

View File

@@ -0,0 +1,37 @@
<?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('call_histories', function (Blueprint $table) {
$table->id();
$table->foreignId('call_id')
->constrained('calls');
$table->string('telephone_id');
$table->string('caller_phone_number');
$table->foreignId('event_id')
->constrained();
$table->string('event_name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('call_histories');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('events', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('events');
}
};

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class CallHistorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class CallSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class CategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$data = [
[
'category_id' => 1,
'category_name' => 'اطلاعات راه',
'subcategory_id' => 1,
'subcategory_name' => 'اطلاعات باز و بسته',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 1,
'category_name' => 'اطلاعات راه',
'subcategory_id' => 2,
'subcategory_name' => 'پرس و جو دلیل ترافیک',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 1,
'category_name' => 'اطلاعات راه',
'subcategory_id' => 3,
'subcategory_name' => 'آب و هوا',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 1,
'category_name' => 'اطلاعات راه',
'subcategory_id' => 4,
'subcategory_name' => 'یک طرفه دو طرفه',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 1,
'category_name' => 'اطلاعات راه',
'subcategory_id' => 5,
'subcategory_name' => 'مسیر مسافت',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 2,
'category_name' => 'شکایت',
'subcategory_id' => 1,
'subcategory_name' => 'شکایت از راه',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 2,
'category_name' => 'شکایت',
'subcategory_id' => 2,
'subcategory_name' => 'شکایت از اتوبوس',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 2,
'category_name' => 'شکایت',
'subcategory_id' => 3,
'subcategory_name' => 'شکایت از حمل و نقل عمومی',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 2,
'category_name' => 'شکایت',
'subcategory_id' => 4,
'subcategory_name' => 'نیاز به برف روبی',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 3,
'category_name' => 'سایر',
'subcategory_id' => 1,
'subcategory_name' => 'اشتباه',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 3,
'category_name' => 'سایر',
'subcategory_id' => 2,
'subcategory_name' => 'مزاحم',
'created_at' => now(),
'updated_at'=> now()
],
[
'category_id' => 3,
'category_name' => 'سایر',
'subcategory_id' => 3,
'subcategory_name' => 'اختلال در ارتباط',
'created_at' => now(),
'updated_at'=> now()
],
];
DB::table('categories')->insert($data);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class EventSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('events')->insert([
[
'id' => 1,
'name' => 'تماس ساخته شد',
'created_at' => now(),
'updated_at' => now()
],
[
'id' => 2,
'name' => 'اپراتور آفلاین بود',
'created_at' => now(),
'updated_at' => now()
],
[
'id' => 3,
'name' => 'مشکل در ارتباط با اپراتور (نقص فنی)',
'created_at' => now(),
'updated_at' => now()
],
[
'id' => 4,
'name' => 'اپراتور اطلاعات تماس را ثبت کرد',
'created_at' => now(),
'updated_at' => now()
],
[
'id' => 5,
'name' => 'سرور پیام رسان قطع میباشد',
'created_at' => now(),
'updated_at' => now()
],
]);
}
}

View File

@@ -2,6 +2,7 @@
use App\Http\Controllers\Admin\UserManagementController;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\CallController;
use App\Http\Controllers\ProfileController;
use App\Http\Middleware\SuperAdminCheck;
use Illuminate\Support\Facades\Route;
@@ -35,3 +36,16 @@ Route::prefix('users')
Route::delete('/{user}', 'destroy')->middleware([SuperAdminCheck::class])->name('destroy');
});
Route::prefix('calls')
->name('calls.')
->controller(CallController::class)
->group(function () {
Route::post('/', 'store')->name('store');
Route::get('/list', 'list')->name('list')->middleware(['auth', 'permission:manage_calls']);
Route::post('/{call}', 'update')->name('update')->middleware(['auth', 'permission:manage_calls']);
});
Route::middleware('auth')->get('/categories', [CategoryController::class, 'list'])->name('categories.list');