diff --git a/.config/psysh/psysh_history b/.config/psysh/psysh_history index fbad47c..6fb5d73 100644 --- a/.config/psysh/psysh_history +++ b/.config/psysh/psysh_history @@ -22,3 +22,20 @@ BigdataResume::generateResumeData(253,2025,"simbg"); exit BigdataResume::generateResumeData(253,2025,"simbg"); exit +$importDatasource = \App\Models\ImportDatasource::create([ + 'message' => 'Testing BigdataResume Generation', + 'status' => 'success', + 'start_time' => now(), + 'finish_time' => now() +]); +$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg'); +$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg'); +exit +$importDatasource = \App\Models\ImportDatasource::create([ + 'message' => 'Testing BigdataResume Generation', + 'status' => 'success', + 'start_time' => now(), + 'finish_time' => now() +]); +$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg'); +exit diff --git a/app/Console/Commands/SyncPbgTaskPayments.php b/app/Console/Commands/SyncPbgTaskPayments.php new file mode 100644 index 0000000..d0f83dc --- /dev/null +++ b/app/Console/Commands/SyncPbgTaskPayments.php @@ -0,0 +1,88 @@ +info('🚀 Starting PBG Task Payments sync...'); + $this->newLine(); + + try { + $service = new ServiceGoogleSheet(); + + // Show progress bar + $this->info('📊 Fetching data from Google Sheets...'); + $result = $service->sync_pbg_task_payments(); + + // Display results + $this->newLine(); + $this->info('✅ Sync completed successfully!'); + $this->newLine(); + + $this->table( + ['Metric', 'Count'], + [ + ['Total rows processed', $result['total_rows']], + ['Successful syncs', $result['successful_syncs']], + ['Failed syncs', $result['failed_syncs']], + ['Tasks not found', $result['not_found_tasks']], + ] + ); + + // Show success rate + $success_rate = $result['total_rows'] > 0 + ? round(($result['successful_syncs'] / $result['total_rows']) * 100, 2) + : 0; + + $this->info("📈 Success rate: {$success_rate}%"); + + // Show errors if any + if (!empty($result['errors'])) { + $this->newLine(); + $this->warn('⚠️ Errors encountered:'); + foreach (array_slice($result['errors'], 0, 5) as $error) { + $this->line(" Row {$error['row']} ({$error['registration_number']}): {$error['error']}"); + } + + if (count($result['errors']) > 5) { + $remaining = count($result['errors']) - 5; + $this->line(" ... and {$remaining} more errors (check logs for details)"); + } + } + + $this->newLine(); + $this->info('📝 Check Laravel logs for detailed information.'); + + return Command::SUCCESS; + + } catch (\Exception $e) { + $this->newLine(); + $this->error('❌ Sync failed!'); + $this->error('Error: ' . $e->getMessage()); + + return Command::FAILURE; + } + } +} diff --git a/app/Http/Controllers/Api/BigDataResumeController.php b/app/Http/Controllers/Api/BigDataResumeController.php index 3952c34..42c30ab 100644 --- a/app/Http/Controllers/Api/BigDataResumeController.php +++ b/app/Http/Controllers/Api/BigDataResumeController.php @@ -9,6 +9,7 @@ use App\Http\Resources\BigdataResumeResource; use App\Models\BigdataResume; use App\Models\DataSetting; use App\Models\SpatialPlanning; +use App\Models\PbgTaskPayment; use Barryvdh\DomPDF\Facade\Pdf; use Carbon\Carbon; use Illuminate\Http\Request; @@ -59,6 +60,11 @@ class BigDataResumeController extends Controller $tata_ruang = $spatialData['sum']; $tata_ruang_count = $spatialData['count']; + // Get real-time PBG Task Payments data + $pbgPaymentsData = $this->getPbgTaskPaymentsData(); + $pbg_task_payments_sum = $pbgPaymentsData['sum']; + $pbg_task_payments_count = $pbgPaymentsData['count']; + $kekurangan_potensi = $target_pad - $big_data_resume->potention_sum; // percentage kekurangan potensi @@ -107,6 +113,10 @@ class BigDataResumeController extends Controller $proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum >= 0 ? round(($proses_dinas_teknis_sum / $big_data_resume->verified_sum) * 100, 2) : 0; + // percentage pbg_task_payments (payments / verified) + $pbg_task_payments_percentage = $realisasi_terbit_pbg_sum > 0 && $pbg_task_payments_sum >= 0 + ? round(($pbg_task_payments_sum / $realisasi_terbit_pbg_sum) * 100, 2) : 0; + $business_rab_count = $big_data_resume->business_rab_count; $business_krk_count = $big_data_resume->business_krk_count; $non_business_rab_count = $big_data_resume->non_business_rab_count; @@ -171,7 +181,12 @@ class BigDataResumeController extends Controller 'business_krk_count' => $business_krk_count, 'non_business_rab_count' => $non_business_rab_count, 'non_business_krk_count' => $non_business_krk_count, - 'business_dlh_count' => $business_dlh_count + 'business_dlh_count' => $business_dlh_count, + 'pbg_task_payments' => [ + 'sum' => (float) $pbg_task_payments_sum, + 'count' => $pbg_task_payments_count, + 'percentage' => $pbg_task_payments_percentage + ] ]; return response()->json($result); }catch(\Exception $e){ @@ -398,6 +413,11 @@ class BigDataResumeController extends Controller 'sum' => 0, 'count' => 0, 'percentage' => 0 + ], + 'pbg_task_payments' => [ + 'sum' => 0, + 'count' => 0, + 'percentage' => 0 ] ]; @@ -457,4 +477,33 @@ class BigDataResumeController extends Controller ]; } } + + /** + * Get PBG Task Payments data from database + */ + private function getPbgTaskPaymentsData(): array + { + try { + // Get sum and count from PbgTaskPayment model + $totalSum = PbgTaskPayment::whereYear('payment_date', date('Y'))->sum('pad_amount') ?? 0; + $totalCount = PbgTaskPayment::whereYear('payment_date', date('Y'))->count() ?? 0; + + Log::info("Real-time PBG Task Payments Data", [ + 'total_records' => $totalCount, + 'total_sum' => $totalSum, + 'source' => 'pbg_task_payments table' + ]); + + return [ + 'sum' => (float) $totalSum, + 'count' => (int) $totalCount, + ]; + } catch (\Exception $e) { + Log::error("Error getting PBG task payments data", ['error' => $e->getMessage()]); + return [ + 'sum' => 0.0, + 'count' => 0, + ]; + } + } } diff --git a/app/Models/BigdataResume.php b/app/Models/BigdataResume.php index 01c468e..e9def1c 100644 --- a/app/Models/BigdataResume.php +++ b/app/Models/BigdataResume.php @@ -314,20 +314,19 @@ class BigdataResume extends Model ]) ]); - // Get sum values using proper aggregation to handle multiple retributions + // Calculate totals using count-based formula + // Business: $business_count * 200 * 44300 + // Non-Business: $non_business_count * 72 * 16000 + $business_total = $business_count * 200 * 44300; + $non_business_total = $non_business_count * 72 * 16000; + $non_verified_total = $business_total + $non_business_total; + + // Get other sum values using proper aggregation to handle multiple retributions $stats = PbgTask::leftJoin('pbg_task_retributions as ptr', 'pbg_task.uuid', '=', 'ptr.pbg_task_uid') ->where('pbg_task.is_valid', true) ->whereYear('pbg_task.task_created_at', $year) ->selectRaw(" SUM(CASE WHEN pbg_task.status in (".implode(',', PbgTaskStatus::getVerified()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS verified_total, - SUM(CASE WHEN pbg_task.status in (".implode(',', PbgTaskStatus::getNonVerified()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS non_verified_total, - SUM(CASE WHEN (LOWER(TRIM(pbg_task.function_type)) LIKE '%fungsi usaha%' - OR LOWER(TRIM(pbg_task.function_type)) LIKE '%sebagai tempat usaha%') - AND pbg_task.status in (".implode(',', PbgTaskStatus::getNonVerified()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS business_total, - SUM(CASE WHEN (LOWER(TRIM(pbg_task.function_type)) NOT LIKE '%fungsi usaha%' - AND LOWER(TRIM(pbg_task.function_type)) NOT LIKE '%sebagai tempat usaha%' - AND pbg_task.status in (".implode(',', PbgTaskStatus::getNonVerified()).")) - OR (pbg_task.function_type IS NULL AND pbg_task.status in (".implode(',', PbgTaskStatus::getNonVerified()).")) THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS non_business_total, SUM(CASE WHEN pbg_task.status in (".implode(',', PbgTaskStatus::getWaitingClickDpmptsp()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS waiting_click_dpmptsp_total, SUM(CASE WHEN pbg_task.status in (".implode(',', PbgTaskStatus::getIssuanceRealizationPbg()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS issuance_realization_pbg_total, SUM(CASE WHEN pbg_task.status in (".implode(',', PbgTaskStatus::getProcessInTechnicalOffice()).") THEN COALESCE(ptr.nilai_retribusi_bangunan, 0) ELSE 0 END) AS process_in_technical_office_total, @@ -338,7 +337,11 @@ class BigdataResume extends Model ->first(); \Log::info('Stats calculation result', [ - 'non_verified_total' => $stats->non_verified_total ?? 'NULL', + 'business_count' => $business_count, + 'non_business_count' => $non_business_count, + 'business_total' => $business_total, + 'non_business_total' => $non_business_total, + 'non_verified_total' => $non_verified_total, 'non_verified_tasks_count' => $stats->non_verified_tasks_count ?? 'NULL', 'non_verified_with_retribution_count' => $stats->non_verified_with_retribution_count ?? 'NULL' ]); @@ -352,13 +355,13 @@ class BigdataResume extends Model 'potention_count' => $potention_count, 'potention_sum' => ($stats->potention_total ?? 0), 'non_verified_count' => $non_verified_count, - 'non_verified_sum' => $stats->non_verified_total ?? 0.00, + 'non_verified_sum' => $non_verified_total, 'verified_count' => $verified_count, 'verified_sum' => $stats->verified_total ?? 0.00, 'business_count' => $business_count, - 'business_sum' => $stats->business_total ?? 0.00, + 'business_sum' => $business_total, 'non_business_count' => $non_business_count, - 'non_business_sum' => $stats->non_business_total ?? 0.00, + 'non_business_sum' => $non_business_total, 'year' => $year, 'waiting_click_dpmptsp_count' => $waiting_click_dpmptsp_count, 'waiting_click_dpmptsp_sum' => $stats->waiting_click_dpmptsp_total ?? 0.00, diff --git a/app/Models/PbgTaskPayment.php b/app/Models/PbgTaskPayment.php new file mode 100644 index 0000000..8a4685d --- /dev/null +++ b/app/Models/PbgTaskPayment.php @@ -0,0 +1,72 @@ + 'date', + 'pad_amount' => 'decimal:2' + ]; + + /** + * Get the PBG task that owns this payment + */ + public function pbgTask(): BelongsTo + { + return $this->belongsTo(PbgTask::class, 'pbg_task_id', 'id'); + } + + /** + * Clean and convert registration number for matching + */ + public static function cleanRegistrationNumber(string $registrationNumber): string + { + return trim($registrationNumber); + } + + /** + * Convert pad amount string to decimal + */ + public static function convertPadAmount(?string $padAmount): float + { + if (empty($padAmount)) { + return 0.0; + } + + // Remove dots (thousands separator) and convert to float + $cleaned = str_replace('.', '', $padAmount); + $cleaned = str_replace(',', '.', $cleaned); // Handle comma as decimal separator if present + + return is_numeric($cleaned) ? (float) $cleaned : 0.0; + } + + /** + * Convert date string to proper format + */ + public static function convertPaymentDate(?string $dateString): ?string + { + if (empty($dateString)) { + return null; + } + + try { + return Carbon::parse($dateString)->format('Y-m-d'); + } catch (\Exception $e) { + return null; + } + } +} diff --git a/app/Services/ServiceGoogleSheet.php b/app/Services/ServiceGoogleSheet.php index b10d197..1f18bb5 100644 --- a/app/Services/ServiceGoogleSheet.php +++ b/app/Services/ServiceGoogleSheet.php @@ -427,6 +427,203 @@ class ServiceGoogleSheet } } + public function get_realisasi_pad_data(){ + try { + // Get data from "REALISASI PAD" sheet + $sheet_data = $this->get_data_by_sheet_name("REALISASI PAD"); + + if (empty($sheet_data)) { + Log::warning("No data found in REALISASI PAD sheet"); + return []; + } + + // Column indices: C=2, AK=36, AL=37, AW=48 (0-based) + $columns = [ + 'C' => 2, + 'AK' => 36, + 'AL' => 37, + 'AW' => 48 + ]; + + $result = [ + 'headers' => [], + 'data' => [] + ]; + + foreach ($sheet_data as $row_index => $row) { + if (!is_array($row)) continue; + + if ($row_index === 0) { + // First row contains headers + foreach ($columns as $column_name => $column_index) { + $result['headers'][$column_name] = isset($row[$column_index]) ? trim($row[$column_index]) : ''; + } + } else { + // Data rows + $row_data = [ + 'row' => $row_index + 1 // 1-based row number + ]; + + $has_data = false; + foreach ($columns as $column_name => $column_index) { + $value = isset($row[$column_index]) ? trim($row[$column_index]) : ''; + $row_data[$column_name] = $value; + if ($value !== '') { + $has_data = true; + } + } + + // Only add row if it has at least one non-empty value + if ($has_data) { + $result['data'][] = $row_data; + } + } + } + + Log::info("REALISASI PAD Multiple Columns Data", [ + 'sheet_name' => 'REALISASI PAD', + 'columns' => array_keys($columns), + 'headers' => $result['headers'], + 'total_data_rows' => count($result['data']), + 'sample_data' => array_slice($result['data'], 0, 5), // Show first 5 rows as sample + 'column_indices' => $columns + ]); + + return $result; + + } catch (\Exception $e) { + Log::error("Error getting REALISASI PAD data", ['error' => $e->getMessage()]); + throw $e; + } + } + + public function sync_pbg_task_payments(){ + try { + // Get payment data from REALISASI PAD sheet + $payment_data = $this->get_realisasi_pad_data(); + + if (empty($payment_data['data'])) { + Log::warning("No payment data found to sync"); + return ['success' => false, 'message' => 'No payment data found']; + } + + $successful_syncs = 0; + $failed_syncs = 0; + $not_found_tasks = 0; + $not_found_tasks_registration_number = []; + $failed_sync_registration_numbers = []; + $errors = []; + + foreach ($payment_data['data'] as $row) { + try { + // Clean registration number from column C + $registration_number = \App\Models\PbgTaskPayment::cleanRegistrationNumber($row['C']); + + if (empty($registration_number)) { + $failed_syncs++; + $failed_sync_registration_numbers[] = [ + 'row' => $row['row'], + 'registration_number' => $row['C'] ?? 'EMPTY', + 'reason' => 'Empty registration number' + ]; + continue; + } + + // Find PBG task by registration number + $pbg_task = \App\Models\PbgTask::where('registration_number', $registration_number)->first(); + + if (!$pbg_task) { + $not_found_tasks_registration_number[] = [ + 'row' => $row['row'], + 'registration_number' => $registration_number + ]; + $not_found_tasks++; + Log::warning("PBG Task not found for registration number", [ + 'registration_number' => $registration_number, + 'row' => $row['row'] + ]); + continue; + } + + // Convert data types + $payment_date = \App\Models\PbgTaskPayment::convertPaymentDate($row['AK']); + $pad_amount = \App\Models\PbgTaskPayment::convertPadAmount($row['AW']); + $sts_form_number = !empty($row['AL']) ? trim($row['AL']) : null; + + // Create or update payment record + \App\Models\PbgTaskPayment::updateOrCreate( + [ + 'pbg_task_id' => $pbg_task->id, + 'registration_number' => $registration_number + ], + [ + 'pbg_task_uid' => $pbg_task->uuid, + 'sts_form_number' => $sts_form_number, + 'payment_date' => $payment_date, + 'pad_amount' => $pad_amount + ] + ); + + $successful_syncs++; + + } catch (\Exception $e) { + $failed_syncs++; + $registration_number = $row['C'] ?? 'N/A'; + $failed_sync_registration_numbers[] = [ + 'row' => $row['row'], + 'registration_number' => $registration_number, + 'reason' => $e->getMessage() + ]; + $errors[] = [ + 'row' => $row['row'], + 'registration_number' => $registration_number, + 'error' => $e->getMessage() + ]; + + Log::error("Error syncing payment data for row", [ + 'row' => $row['row'], + 'registration_number' => $registration_number, + 'error' => $e->getMessage() + ]); + } + } + + $result = [ + 'success' => true, + 'total_rows' => count($payment_data['data']), + 'successful_syncs' => $successful_syncs, + 'failed_syncs' => $failed_syncs, + 'not_found_tasks' => $not_found_tasks, + 'not_found_tasks_registration_number' => $not_found_tasks_registration_number, + 'failed_sync_registration_numbers' => $failed_sync_registration_numbers, + 'errors' => $errors + ]; + + Log::info("PBG Task Payments sync completed", $result); + + // Log detailed arrays for failed syncs and not found registration numbers + if (!empty($failed_sync_registration_numbers)) { + Log::warning("Failed Sync Registration Numbers", [ + 'count' => count($failed_sync_registration_numbers), + 'details' => $failed_sync_registration_numbers + ]); + } + + if (!empty($not_found_tasks_registration_number)) { + Log::warning("Not Found Registration Numbers", [ + 'count' => count($not_found_tasks_registration_number), + 'details' => $not_found_tasks_registration_number + ]); + } + + return $result; + + } catch (\Exception $e) { + Log::error("Error syncing PBG task payments", ['error' => $e->getMessage()]); + throw $e; + } + } + private function get_data_by_sheet($no_sheet = 8){ $spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID); $sheets = $spreadsheet->getSheets(); @@ -437,6 +634,45 @@ class ServiceGoogleSheet return!empty($values)? $values : []; } + private function get_data_by_sheet_name($sheet_name){ + try { + $spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID); + $sheets = $spreadsheet->getSheets(); + + // Find sheet by name + $targetSheet = null; + foreach ($sheets as $sheet) { + if ($sheet->getProperties()->getTitle() === $sheet_name) { + $targetSheet = $sheet; + break; + } + } + + if (!$targetSheet) { + Log::warning("Sheet not found", ['sheet_name' => $sheet_name]); + return []; + } + + $range = "{$sheet_name}"; + $response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range); + $values = $response->getValues(); + + Log::info("Sheet data retrieved", [ + 'sheet_name' => $sheet_name, + 'total_rows' => count($values ?? []) + ]); + + return !empty($values) ? $values : []; + + } catch (\Exception $e) { + Log::error("Error getting data by sheet name", [ + 'sheet_name' => $sheet_name, + 'error' => $e->getMessage() + ]); + return []; + } + } + /** * Get specific values from a row that contains a specific text/section identifier * @param string $section_identifier Text to search for in the row diff --git a/database/migrations/2025_08_19_204825_create_pbg_task_payments_table.php b/database/migrations/2025_08_19_204825_create_pbg_task_payments_table.php new file mode 100644 index 0000000..d88eb76 --- /dev/null +++ b/database/migrations/2025_08_19_204825_create_pbg_task_payments_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('pbg_task_id')->constrained('pbg_task', 'id')->onDelete('cascade'); + $table->string('pbg_task_uid'); + $table->string('registration_number'); + $table->string('sts_form_number')->nullable(); + $table->date('payment_date')->nullable(); + $table->decimal('pad_amount', 12, 2)->default(0); + $table->timestamps(); + + // Add index for better performance + $table->index('pbg_task_id'); + $table->index('pbg_task_uid'); + $table->index('registration_number'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pbg_task_payments'); + } +}; diff --git a/resources/js/dashboards/bigdata.js b/resources/js/dashboards/bigdata.js index 8e45b35..69e4386 100644 --- a/resources/js/dashboards/bigdata.js +++ b/resources/js/dashboards/bigdata.js @@ -535,16 +535,49 @@ class BigData { }); } initChartNonBusinessDLH() { + document.querySelectorAll("#business-dlh-count").forEach((element) => { + const count = this.safeGet( + this.resumeBigData, + "business_dlh_count", + 0 + ); + element.innerText = `${count}`; + }); + } + + initChartPotensiTataRuang() { document - .querySelectorAll("#non-business-dlh-count") + .querySelectorAll(".document-count.chart-payment-pbg-task") .forEach((element) => { const count = this.safeGet( this.resumeBigData, - "business_dlh_count", + "pbg_task_payments.count", 0 ); element.innerText = `${count}`; }); + document + .querySelectorAll(".document-total.chart-payment-pbg-task") + .forEach((element) => { + const sum = this.safeGet( + this.resumeBigData, + "pbg_task_payments.sum", + 0 + ); + element.innerText = `Rp.${addThousandSeparators( + sum.toString() + )}`; + }); + document + .querySelectorAll(".small-percentage.chart-payment-pbg-task") + .forEach((element) => { + const percentage = this.safeGet( + this.resumeBigData, + "pbg_task_payments.percentage", + 0 + ); + element.innerText = `${percentage}%`; + }); } } diff --git a/resources/views/dashboards/bigdata.blade.php b/resources/views/dashboards/bigdata.blade.php index ccfd1e2..62ee6c2 100644 --- a/resources/views/dashboards/bigdata.blade.php +++ b/resources/views/dashboards/bigdata.blade.php @@ -202,6 +202,17 @@ ]) @endcomponent + @component('components.circle',[ + 'document_title' => 'Pembayaran Realisasi PBG', + 'document_color' => '#8cc540', + 'document_type' => 'Berkas', + 'document_id' => 'chart-payment-pbg-task', + 'visible_small_circle' => false, + 'style' => 'top:550px;left:-150px;', + 'document_url' => '#' + ]) + @endcomponent +