diff --git a/app/Http/Controllers/RequestAssignment/PbgTaskController.php b/app/Http/Controllers/RequestAssignment/PbgTaskController.php index b19b8f7..a72e44f 100644 --- a/app/Http/Controllers/RequestAssignment/PbgTaskController.php +++ b/app/Http/Controllers/RequestAssignment/PbgTaskController.php @@ -37,7 +37,7 @@ class PbgTaskController extends Controller */ public function show(string $id) { - $data = PbgTask::with(['pbg_task_retributions','pbg_task_index_integrations', 'pbg_task_retributions.pbg_task_prasarana'])->findOrFail($id); + $data = PbgTask::with(['pbg_task_retributions','pbg_task_index_integrations', 'pbg_task_retributions.pbg_task_prasarana', 'taskAssignments'])->findOrFail($id); return view("pbg_task.show", compact("data")); } diff --git a/app/Http/Controllers/Settings/SyncronizeController.php b/app/Http/Controllers/Settings/SyncronizeController.php index cbdba15..f50d5a5 100644 --- a/app/Http/Controllers/Settings/SyncronizeController.php +++ b/app/Http/Controllers/Settings/SyncronizeController.php @@ -33,7 +33,7 @@ class SyncronizeController extends Controller public function syncIndexIntegration(Request $request, $uuid){ $token = $request->get('token'); - $res = $this->service_simbg->syncIndexIntegration($uuid, $token); + $res = $this->service_simbg->syncIndexIntegration($uuid); return $res; } @@ -42,4 +42,9 @@ class SyncronizeController extends Controller $res = $this->service_simbg->syncTaskDetailSubmit($uuid, $token); return $res; } + + public function syncTaskAssignments($uuid){ + $res = $this->service_simbg->syncTaskAssignments($uuid); + return $res; + } } diff --git a/app/Jobs/SyncronizeSIMBG.php b/app/Jobs/SyncronizeSIMBG.php index 6319d48..18fb3ac 100644 --- a/app/Jobs/SyncronizeSIMBG.php +++ b/app/Jobs/SyncronizeSIMBG.php @@ -14,6 +14,7 @@ class SyncronizeSIMBG implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $tries = 1; public function __construct() { diff --git a/app/Models/PbgTask.php b/app/Models/PbgTask.php index b46a802..a8cacfe 100644 --- a/app/Models/PbgTask.php +++ b/app/Models/PbgTask.php @@ -41,4 +41,9 @@ class PbgTask extends Model public function googleSheet(){ return $this->hasOne(PbgTaskGoogleSheet::class, 'no_registrasi', 'registration_number'); } + + public function taskAssignments() + { + return $this->hasMany(TaskAssignment::class, 'pbg_task_uid', 'uuid'); + } } diff --git a/app/Models/TaskAssignment.php b/app/Models/TaskAssignment.php new file mode 100644 index 0000000..6ebf643 --- /dev/null +++ b/app/Models/TaskAssignment.php @@ -0,0 +1,28 @@ + 'boolean', + 'is_verif' => 'boolean', + 'file' => 'array', // JSON field casting + ]; + + public function pbgTask() + { + return $this->belongsTo(PbgTask::class, 'pbg_task_uid', 'uuid'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 68ad581..d8930f0 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -19,6 +19,9 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { + $this->app->singleton(GoogleSheetService::class, function () { + return new GoogleSheetService(); + }); $this->app->singleton(ServiceSIMBG::class, function ($app) { return new ServiceSIMBG($app->make(GoogleSheetService::class)); }); diff --git a/app/Services/ServiceClient.php b/app/Services/ServiceClient.php index f209569..88de0b3 100644 --- a/app/Services/ServiceClient.php +++ b/app/Services/ServiceClient.php @@ -51,10 +51,26 @@ class ServiceClient $resultResponse = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR); return $this->resSuccess($resultResponse); - } catch (Exception $e) { - \Log::error('error from client service'. $e->getMessage()); - return $this->resError($e->getMessage()); - } + } catch (\GuzzleHttp\Exception\ClientException $e) { + // Handle 4xx errors (e.g., 401 Unauthorized) + $responseBody = (string) $e->getResponse()->getBody(); + $errorResponse = json_decode($responseBody, true); + + if (isset($errorResponse['code']) && $errorResponse['code'] === 'token_not_valid') { + return $this->resError('Invalid token, please refresh your token.', $errorResponse, 401); + } + + return $this->resError('Client error from API', $errorResponse, $e->getResponse()->getStatusCode()); + } catch (\GuzzleHttp\Exception\ServerException $e) { + // Handle 5xx errors (e.g., Internal Server Error) + return $this->resError('Server error from API', (string) $e->getResponse()->getBody(), 500); + } catch (\GuzzleHttp\Exception\RequestException $e) { + // Handle network errors (e.g., timeout, connection issues) + return $this->resError('Network error: ' . $e->getMessage(), null, 503); + } catch (Exception $e) { + // Handle unexpected errors + return $this->resError('Unexpected error: ' . $e->getMessage(), null, 500); + } } // Fungsi untuk melakukan permintaan GET diff --git a/app/Services/ServiceSIMBG.php b/app/Services/ServiceSIMBG.php index 5419daa..1b4b25c 100644 --- a/app/Services/ServiceSIMBG.php +++ b/app/Services/ServiceSIMBG.php @@ -9,6 +9,7 @@ use App\Models\ImportDatasource; use App\Models\PbgTaskIndexIntegrations; use App\Models\PbgTaskPrasarana; use App\Models\PbgTaskRetributions; +use App\Models\TaskAssignment; use Exception; use App\Models\PbgTask; use App\Traits\GlobalApiResponse; @@ -66,12 +67,19 @@ class ServiceSIMBG } } - public function syncIndexIntegration($uuids, $token) + public function syncIndexIntegration($uuids) { try{ if(empty($uuids)){ return false; } + + $initResToken = $this->getToken(); + if (empty($initResToken->original['data']['token']['access'])) { + Log::error("API response indicates failure", ['token' => 'Failed to retrieve token']); + return false; + } + $token = $initResToken->original['data']['token']['access']; $integrations = []; foreach($uuids as $uuid){ @@ -120,6 +128,7 @@ class ServiceSIMBG public function syncTaskPBG() { try { + Log::info("Processing google sheet sync"); $importDatasource = ImportDatasource::create([ 'status' => ImportDatasourceStatus::Processing->value, ]); @@ -145,14 +154,14 @@ class ServiceSIMBG // If a section is found and we reach "Grand Total", save the corresponding values if ($found_section && isset($row[0]) && trim($row[0]) === "Grand Total") { if ($found_section === "MENUNGGU_KLIK_DPMPTSP") { - $data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $row[2] ?? null; - $data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $row[3] ?? null; + $data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $this->convertToInteger($row[2]) ?? null; + $data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $this->convertToDecimal($row[3]) ?? null; } elseif ($found_section === "REALISASI_TERBIT_PBG") { - $data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $row[2] ?? null; - $data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $row[4] ?? null; + $data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $this->convertToInteger($row[2]) ?? null; + $data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $this->convertToDecimal($row[4]) ?? null; } elseif ($found_section === "PROSES_DINAS_TEKNIS") { - $data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $row[2] ?? null; - $data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $row[3] ?? null; + $data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $this->convertToInteger($row[2]) ?? null; + $data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $this->convertToDecimal($row[3]) ?? null; } // Reset section tracking after capturing "Grand Total" @@ -160,6 +169,8 @@ class ServiceSIMBG } } + Log::info("data setting result", ['result' => $data_setting_result]); + foreach ($data_setting_result as $key => $value) { DataSetting::updateOrInsert( ["key" => $key], // Find by key @@ -167,7 +178,6 @@ class ServiceSIMBG ); } $mapToUpsert = []; - $count = 0; foreach($sheetData as $data){ $mapToUpsert[] = @@ -289,7 +299,7 @@ class ServiceSIMBG if (empty($initResToken->original['data']['token']['access'])) { $importDatasource->update([ 'status' => ImportDatasourceStatus::Failed->value, - 'message' => 'Failed to retrieve token' + 'response_body' => 'Failed to retrieve token' ]); return $this->resError("Failed to retrieve token"); } @@ -303,20 +313,57 @@ class ServiceSIMBG if ($totalPage == 0) { $importDatasource->update([ 'status' => ImportDatasourceStatus::Failed->value, - 'message' => 'Invalid response: no total_page' + 'response_body' => 'Invalid response: no total_page' ]); return $this->resError("Invalid response from API"); } $savedCount = $failedCount = 0; + Log::info("Fetching tasks", ['total page' => $totalPage]); + for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) { try { $pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC"; Log::info("Fetching tasks", ['currentPage' => $currentPage]); + $headers = [ + 'Authorization' => "Bearer " . $apiToken, // Update headers + ]; + + for ($attempt = 0; $attempt < 2; $attempt++) { // Try twice (original + retry) + + $response = $this->service_client->get($pageUrl, $headers); + + if ($response instanceof \Illuminate\Http\JsonResponse) { + $decodedResponse = json_decode($response->getContent(), true); + + if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') { + Log::warning("Token is invalid, refreshing token..."); + + // Regenerate token + $initResToken = $this->getToken(); + + // Check if new token is valid + if (!empty($initResToken->original['data']['token']['access'])) { + $new_token = $initResToken->original['data']['token']['access']; + + // **Fix: Update headers before retrying** + $headers['Authorization'] = "Bearer " . $new_token; + + Log::info("Token refreshed successfully, retrying API request..."); + continue; // Retry with new token + } else { + Log::error("Failed to refresh token"); + return $this->resError("Failed to refresh token"); + } + } + } + + // Success case, break loop + break; + } - $response = $this->service_client->get($pageUrl, $headers); $tasks = $response->original['data']['data'] ?? []; if (empty($tasks)) { @@ -351,6 +398,7 @@ class ServiceSIMBG ]; $this->syncTaskDetailSubmit($item['uid'], $apiToken); + $this->syncTaskAssignments($item['uid']); $savedCount++; } catch (Exception $e) { $failedCount++; @@ -371,7 +419,7 @@ class ServiceSIMBG ]); $uuids = array_column($tasksCollective, 'uuid'); - $this->syncIndexIntegration($uuids, $apiToken); + $this->syncIndexIntegration($uuids); } } catch (Exception $e) { Log::error("Failed to process page", [ @@ -400,34 +448,61 @@ class ServiceSIMBG if (isset($importDatasource)) { $importDatasource->update([ 'status' => ImportDatasourceStatus::Failed->value, - 'message' => 'Critical failure: ' . $e->getMessage() + 'response_body' => 'Critical failure: ' . $e->getMessage() ]); } return $this->resError("Critical failure occurred: " . $e->getMessage()); } } - - public function syncTaskDetailSubmit($uuid, $token) { try{ $url = "/api/pbg/v1/detail/" . $uuid . "/retribution/submit/"; + $headers = [ 'Authorization' => "Bearer " . $token, ]; - - $res = $this->service_client->get($url, $headers); - - if (empty($res->original['success']) || !$res->original['success']) { - // Log error - Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]); - return false; + + for ($attempt = 0; $attempt < 2; $attempt++) { // Try twice (original + retry) + $res = $this->service_client->get($url, $headers); + + // Check if response is JsonResponse and decode it + if ($res instanceof \Illuminate\Http\JsonResponse) { + $decodedResponse = json_decode($res->getContent(), true); + + // Handle invalid token case + if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') { + Log::warning("Token is invalid, refreshing token..."); + + // Regenerate the token + $initResToken = $this->getToken(); + + // Check if the new token is valid + if (!empty($initResToken->original['data']['token']['access'])) { + $new_token = $initResToken->original['data']['token']['access']; + + // **Fix: Update headers with the new token** + $headers['Authorization'] = "Bearer " . $new_token; + + Log::info("Token refreshed successfully, retrying API request..."); + continue; // Retry the request with the new token + } else { + Log::error("Failed to refresh token"); + return $this->resError("Failed to refresh token"); + } + } + } + + // If request succeeds, break out of retry loop + break; } - - $data = $res->original['data']['data'] ?? []; + + // Ensure response is valid before accessing properties + $responseData = $res->original ?? []; + $data = $responseData['data']['data'] ?? []; if (empty($data)) { - Log::error("No data returned from API", ['url' => $url, 'uuid' => $uuid]); + Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid, 'response' => $responseData]); return false; } @@ -489,6 +564,58 @@ class ServiceSIMBG throw $e; } } + + public function syncTaskAssignments($uuid){ + try{ + $init_token = $this->getToken(); + $token = $init_token->original['data']['token']['access']; + $url = "/api/pbg/v1/list-tim-penilai/". $uuid . "/?page=1&size=10"; + $headers = [ + 'Authorization' => "Bearer " . $token, + ]; + + $response = $this->service_client->get($url, $headers); + $datas = $response->original['data']['data'] ?? []; + if(empty($datas)){ + return false; + } + $task_assignments = []; + + foreach ($datas as $data) { + $task_assignments[] = [ + 'pbg_task_uid' => $uuid, // Assuming this is a foreign key + 'user_id' => $data['user_id'], + 'name' => $data['name'], + 'username' => $data['username'], + 'email' => $data['email'], + 'phone_number' => $data['phone_number'], + 'role' => $data['role'], + 'role_name' => $data['role_name'], + 'is_active' => $data['is_active'], + 'file' => json_encode($data['file']), // Store as JSON if it's an array + 'expertise' => $data['expertise'], + 'experience' => $data['experience'], + 'is_verif' => $data['is_verif'], + 'uid' => $data['uid'], // Unique identifier + 'status' => $data['status'], + 'status_name' => $data['status_name'], + 'note' => $data['note'], + 'created_at' => now(), + 'updated_at' => now(), + ]; + } + TaskAssignment::upsert( + $task_assignments, // Data to insert/update + ['uid'], // Unique key for conflict resolution + ['name', 'username', 'email', 'phone_number', 'role', 'role_name', 'is_active', 'file', 'expertise', 'experience', 'is_verif', 'status', 'status_name', 'note', 'updated_at'] + ); + return true; + }catch(Exception $e){ + Log::error("Failed to sync task assignments", ['error' => $e->getMessage()]); + throw $e; + } + } + protected function convertToDecimal(?string $value): ?float { if (empty($value)) { @@ -522,8 +649,10 @@ class ServiceSIMBG return null; } + $cleaned = str_replace('.','', $value); + // Otherwise, cast to integer - return (int) $value; + return (int) $cleaned; } protected function convertToDate($dateString) diff --git a/composer.json b/composer.json index 8dc9ef8..307ce4d 100755 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ ], "dev": [ "Composer\\Config::disableProcessTimeout", - "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" + "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" ] }, "extra": { diff --git a/database/migrations/2025_03_05_211133_create_task_assignments_table.php b/database/migrations/2025_03_05_211133_create_task_assignments_table.php new file mode 100644 index 0000000..2ce12e9 --- /dev/null +++ b/database/migrations/2025_03_05_211133_create_task_assignments_table.php @@ -0,0 +1,48 @@ +id(); // Auto-increment primary key + + // Foreign key reference to pbg_tasks (uid column) + $table->string('pbg_task_uid'); + $table->foreign('pbg_task_uid')->references('uuid')->on('pbg_task')->onDelete('cascade'); + + $table->unsignedBigInteger('user_id'); // Reference to users table + $table->string('name'); + $table->string('username')->unique(); + $table->string('email')->unique(); + $table->string('phone_number')->nullable(); + $table->unsignedInteger('role'); // Assuming role is numeric + $table->string('role_name'); + $table->boolean('is_active')->default(true); + $table->json('file')->nullable(); // Store as JSON if 'file' is an array + $table->string('expertise')->nullable(); + $table->string('experience')->nullable(); + $table->boolean('is_verif')->default(false); + $table->string('uid')->unique(); + $table->unsignedTinyInteger('status')->default(0); // Assuming status is a small integer + $table->string('status_name')->nullable(); + $table->text('note')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('task_assignments'); + } +}; diff --git a/resources/js/bigdata-resumes/index.js b/resources/js/bigdata-resumes/index.js index b2cb7a4..47d11e9 100644 --- a/resources/js/bigdata-resumes/index.js +++ b/resources/js/bigdata-resumes/index.js @@ -109,6 +109,7 @@ class BigdataResume { }, total: (data) => data.total, }, + width: "auto", }).render(tableContainer); } async handleDelete(deleteButton) { diff --git a/resources/views/bigdata-resumes/index.blade.php b/resources/views/bigdata-resumes/index.blade.php index 9a23c18..c89842a 100644 --- a/resources/views/bigdata-resumes/index.blade.php +++ b/resources/views/bigdata-resumes/index.blade.php @@ -2,6 +2,79 @@ @section('css') @vite(['node_modules/gridjs/dist/theme/mermaid.min.css']) + @endsection @section('content') @@ -11,13 +84,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@endsection diff --git a/resources/views/pbg_task/show.blade.php b/resources/views/pbg_task/show.blade.php index 49a529a..8b18768 100644 --- a/resources/views/pbg_task/show.blade.php +++ b/resources/views/pbg_task/show.blade.php @@ -92,6 +92,11 @@ PBG Task Prasarana +
@@ -246,6 +251,40 @@ @endif
+
+ @if ($data->taskAssignments && $data->taskAssignments->isNotEmpty()) + @foreach ($data->taskAssignments as $task_assignment) +
+
+
Nama
+
{{$task_assignment->name}}
+
+
+
Email
+
{{$task_assignment->email}}
+
+
+
Nomor Telepon
+
{{$task_assignment->phone_number}}
+
+
+
Keahlian
+
{{$task_assignment->expertise}}
+
+
+
Status
+
{{$task_assignment->status_name}}
+
+
+ @endforeach + @else +
+
+ Data Not Available +
+
+ @endif +
diff --git a/routes/api.php b/routes/api.php index fb42143..4f53238 100644 --- a/routes/api.php +++ b/routes/api.php @@ -102,6 +102,7 @@ Route::group(['middleware' => 'auth:sanctum'], function (){ Route::get('/get-user-token', [SyncronizeController::class, 'getUserToken'])->name('api.task.token'); Route::get('/get-index-integration-retribution/{uuid}', [SyncronizeController::class, 'syncIndexIntegration'])->name('api.task.inntegration'); Route::get('/sync-task-submit/{uuid}', [SyncronizeController::class, 'syncTaskDetailSubmit'])->name('api.task.submit'); + Route::get('/sync-task-assignments/{uuid}', [SyncronizeController::class, 'syncTaskAssignments'])->name('api.task.assignments'); // menus api Route::controller(MenusController::class)->group(function (){