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 @@