$date, 'time' => $time ]); // Validasi input null atau kosong if (empty($date)) { Log::debug('Tanggal kosong, return empty string'); return ''; } Carbon::setLocale('id'); try { $waktu = Carbon::parse($date); if (!$time) { $result = $waktu->translatedFormat('d F Y'); Log::debug('Format tanggal berhasil', ['result' => $result]); return $result; } $result = $waktu->translatedFormat('d F Y') . ' pukul ' . $waktu->format('H.i') . ' WIB'; Log::debug('Format tanggal dengan waktu berhasil', ['result' => $result]); return $result; } catch (Throwable $e) { Log::warning('Gagal parse tanggal', [ 'date' => $date, 'error' => $e->getMessage() ]); // Return input as-is jika gagal parse return (string) $date; } } /** * Format angka ke dalam format mata uang Rupiah Indonesia * * Mengubah angka menjadi format mata uang Rupiah dengan pemisah ribuan * dan menggunakan koma sebagai pemisah desimal sesuai standar Indonesia. * * @param int|float|string $number Angka yang akan diformat (bisa negatif) * @param int $decimals Jumlah digit desimal (default: 0) * @return string Angka yang sudah diformat dalam format Rupiah * * @example * formatRupiah(1500000) // "Rp 1.500.000" * formatRupiah(1500000.50, 2) // "Rp 1.500.000,50" * formatRupiah(-500000) // "Rp -500.000" * formatRupiah(0) // "Rp 0" * formatRupiah(null) // "Rp 0" */ function formatRupiah($number, $decimals = 0): string { Log::debug('Memulai format Rupiah', [ 'number' => $number, 'decimals' => $decimals ]); // Handle null atau kosong if ($number === null || $number === '') { Log::debug('Number null atau kosong, return Rp 0'); return 'Rp 0'; } // Konversi ke float dan handle error try { $number = (float) $number; } catch (Throwable $e) { Log::warning('Gagal konversi number ke float', [ 'number' => $number, 'error' => $e->getMessage() ]); return 'Rp 0'; } // Validasi decimals $decimals = max(0, (int) $decimals); $result = 'Rp ' . number_format($number, $decimals, ',', '.'); Log::debug('Format Rupiah berhasil', ['result' => $result]); return $result; } function formatAlamat($alamat) { return ($alamat->address ? $alamat->address . ', ' : '') . (isset($alamat->village) ? $alamat->village->name . ', ' : '') . (isset($alamat->city) ? $alamat->city->name . ', ' : '') . (isset($alamat->province) ? $alamat->province->name . ', ' : '') . ($alamat->village->postal_code ?? ''); } // andy add function checkActiveDateRangePenawaran($id) { $penawaran = PenawaranTender::find($id); $start_date = strtotime($penawaran->start_date); $end_date = strtotime($penawaran->end_date); $todays_date = strtotime(now()); //$todays_date = strtotime("+1 day", strtotime(now())); $allow = true; if ($todays_date >= $start_date && $todays_date <= $end_date) { //Penawaran dibuka $allow = true; } else { if ($todays_date < $start_date) { //Penawaran Belum dibuka $allow = true; } else { //Penawaran sudah ditutup $allow = false; } } return $allow; } function checkKelengkapanDetailKJPP($id) { $allow = true; // DB::enableQueryLog(); // detail_penawaran apakah isian biaya_penawaran, attachment, dokumen_persetujuan sudah lengkap? $query = PenawaranDetailTender::select('id')->where('penawaran_id', '=', $id)->where('status', '=', 1)->where( function ($query) { // no_proposal $query->orWhere('no_proposal', '', ""); $query->orWhereNull('no_proposal'); // tgl_proposal $query->orWhere('tgl_proposal', '', ""); $query->orWhereNull('tgl_proposal'); $query->orWhere('biaya_penawaran', '', ""); $query->orWhereNull('biaya_penawaran'); $query->orWhere('attachment', '', ""); $query->orWhereNull('attachment'); $query->orWhere('dokumen_persetujuan', '', ""); $query->orWhereNull('dokumen_persetujuan'); }, )->get(); if (sizeof($query) > 0) { $allow = false; } return $allow; } // convert function convertSlug($slug) { $words = explode('-', $slug); foreach ($words as $index => $word) { $words[$index] = strtoupper($word); } return implode(' ', $words); } // generate last penawaran.code function onLastnumberCodePenawaran() : string { // ambil code terakhir $maxCode = PenawaranTender::max('code'); // chek data penawaran terakhir --> mengurutkan data berdasarkan kolom `created_at` secara DESC // $penawaran = PenawaranTender::latest()->first(); $penawaran = PenawaranTender::where('code', '=', $maxCode)->first(); $code_penawaran_last = ''; // nomor di set 0001 $noUrutAkhirString = sprintf("%04s", 1); if ($penawaran) { $isNum = substr($maxCode, 2); // memastikan string ke 3 s/d 8 adalan numiric $isNP = substr($maxCode, 0, 2); if ((8 == strlen($maxCode)) && ("NP" == $isNP) && (ctype_digit($isNum))) { $code_penawaran_last = substr($maxCode, -4); $year_penawaran_last = Carbon::parse($penawaran->created_at)->year; $year_now = Carbon::now()->year; if ($year_now == $year_penawaran_last) { $noUrutAkhirString = sprintf("%04s", abs($code_penawaran_last + 1)); } // jika ternyata tahun tdk sama (kurang dari tahun sekarang), maka nomor di set 0001 } } return 'NP' . Carbon::now()->format('y') . $noUrutAkhirString; } // generate last penawaran.no_spk function onLastnumberCodePenawaranSPK($jenis_laporan_code) : string { // 20241124_001 ==> spk_no_core // XXX / PJ / JKT / MONTH-ROM / FR|SR / 2024 // 001 / PJ / JKT / XI / FR / 2024 $maxCode = PenawaranTender::max('spk_no_core'); $penawaran = PenawaranTender::where('spk_no_core', '=', $maxCode)->first(); $no_spk_penawaran_last = ''; $year_penawaran_last = ''; $year_now = Carbon::now()->year; // nomor di set 001 $noUrutAkhirString = sprintf("%03s", 1); if ($penawaran) { $no_spk_penawaran_last = substr($maxCode, -3); $year_penawaran_last = substr($maxCode, 0, 4); if ($year_now == $year_penawaran_last) { $noUrutAkhirString = sprintf("%03s", abs($no_spk_penawaran_last + 1)); } // jika ternyata tahun tdk sama (kurang dari tahun sekarang), maka nomor di set 001 } $month = onRomawi(Carbon::now()->month); $lastSPK = $noUrutAkhirString . ' / PJ / JKT / ' . $month . ' / ' . $jenis_laporan_code . ' / ' . $year_now; return $lastSPK; } function onRomawi(int $bln) : string { return convertToRoman($bln); } function penyebut($nilai) { $nilai = abs($nilai); $huruf = [ "", "satu", "dua", "tiga", "empat", "lima", "enam", "tujuh", "delapan", "sembilan", "sepuluh", "sebelas" ]; $temp = ""; if ($nilai < 12) { $temp = " " . $huruf[$nilai]; } else if ($nilai < 20) { $temp = penyebut($nilai - 10) . " belas"; } else if ($nilai < 100) { $temp = penyebut($nilai / 10) . " puluh" . penyebut($nilai % 10); } else if ($nilai < 200) { $temp = " seratus" . penyebut($nilai - 100); } else if ($nilai < 1000) { $temp = penyebut($nilai / 100) . " ratus" . penyebut($nilai % 100); } else if ($nilai < 2000) { $temp = " seribu" . penyebut($nilai - 1000); } else if ($nilai < 1000000) { $temp = penyebut($nilai / 1000) . " ribu" . penyebut($nilai % 1000); } else if ($nilai < 1000000000) { $temp = penyebut($nilai / 1000000) . " juta" . penyebut($nilai % 1000000); } else if ($nilai < 1000000000000) { $temp = penyebut($nilai / 1000000000) . " milyar" . penyebut(fmod($nilai, 1000000000)); } else if ($nilai < 1000000000000000) { $temp = penyebut($nilai / 1000000000000) . " trilyun" . penyebut(fmod($nilai, 1000000000000)); } return $temp; } function terbilang($nilai) { if ($nilai < 0) { $hasil = "minus " . trim(penyebut($nilai)); } else { $hasil = trim(penyebut($nilai)); } return $hasil; } function hitungHariKerja($tanggalMulai, $tanggalSelesai) { $tanggalMulai = Carbon::parse($tanggalMulai)->startOfDay(); $tanggalSelesai = Carbon::parse($tanggalSelesai)->endOfDay(); $hariKerja = 0; $tanggalSekarang = $tanggalMulai->copy(); while ($tanggalSekarang <= $tanggalSelesai) { // Cek apakah hari ini bukan Sabtu atau Minggu dan bukan hari libur if (!$tanggalSekarang->isWeekend() && !in_array($tanggalSekarang->format('Y-m-d'), holidays())) { $hariKerja++; } $tanggalSekarang->addDay(); } return $hariKerja; } function countPermohonanForUser($userId) { $validStatuses = [ 'assign', 'survey-completed', 'proses-laporan', 'paparan', 'proses-paparan', 'revisi-laporan', 'revisi-paparan', 'survey', 'proses-survey', 'request-reschedule', 'reschedule', 'rejected-reschedule', 'approved-reschedule', 'revisi-survey', 'revisi-pembayaran' ]; $result = Penilaian::whereHas('userPenilai', function ($query) use ($userId) { $query->where('user_id', $userId); }) ->whereHas('permohonan', function ($query) use ($validStatuses) { $query->whereIn('status', $validStatuses); }) ->get() ->groupBy(function ($item) { // Pastikan mengakses properti dari model yang valid return $item->userPenilai->first()->user_id . '-' . $item->permohonan->id; }) ->map(function ($group) { return [ 'statuses' => $group->pluck('permohonan.status')->unique()->values()->all(), ]; }); return $result->count(); } function getMaxFileSize($jenis) { $jenisDokumen = JenisDokumen::where('name', $jenis)->first(); if (!$jenisDokumen) { return 2048; } //konversi ke KB (1 MB = 1024 KB) $maxSizeInKB = (int) $jenisDokumen->max_size * 1024; return $maxSizeInKB; } function getUser($userId) { return User::find($userId); } function generateLpjUniqueCode($randomLength = 6) { $year = date('y'); $month = str_pad(date('m'), 2, '0', STR_PAD_LEFT); $day = str_pad(date('d'), 2, '0', STR_PAD_LEFT); // Generate random numbers $randomNumber = str_pad(mt_rand(0, pow(10, $randomLength) - 1), $randomLength, '0', STR_PAD_LEFT); // Concatenate components to create the custom code return $year . $month . $day . $randomNumber; } function checkRegionUserName($userId) { $region = TeamsUsers::where('user_id', $userId)->first(); if ($region) { return $region->team->regions->name; } else { return null; } } function getNomorLaporan($permohonanId, $documentId, $type = 'nomor_laporan') { $laporan = Laporan::where([ 'permohonan_id' => $permohonanId, 'dokumen_jaminan_id' => $documentId, ])->first(); if (!$laporan) { return $type == 'nomor_laporan' ? '-' : null; } return $type == 'nomor_laporan' ? $laporan->nomor_laporan : $laporan->created_at; } function getCustomField($param) { if (is_numeric($param)) { $field = CustomField::find($param); } else { $field = CustomField::where(['name' => $param])->first(); } if ($field) { return $field; } else { return null; } } function getWilayahName($code, $type) { try { $wilayah = null; if (!$code) { return null; } switch ($type) { case 'province': $wilayah = Province::where('code', $code)->first(); return $wilayah ? $wilayah->name : null; case 'city': $wilayah = City::where('code', $code)->first(); return $wilayah ? $wilayah->name : null; case 'district': $wilayah = District::where('code', $code)->first(); return $wilayah ? $wilayah->name : null; case 'village': $wilayah = Village::where('code', $code)->first(); return $wilayah ? $wilayah->name : null; default: return null; } } catch (Exception $e) { return null; } } function formatLabel($key) { static $labelCache = []; if (isset($labelCache[$key])) { return $labelCache[$key]; } $customLabel = CustomField::where('name', $key)->first(); $labelCache[$key] = $customLabel->label ?? ucwords(str_replace('_', ' ', $key)); return $labelCache[$key]; } function calculateSLA($permohonan, $type) { if (!$type) { return null; } $nilai_plafond = in_array($permohonan->nilai_plafond_id, [2, 3]); $nilai_plafond_2 = in_array($permohonan->nilai_plafond_id, [1]); $slaMap = [ 'resume' => $nilai_plafond ? 2 : null, 'paparan' => $nilai_plafond ? 2 : null, 'standard' => $nilai_plafond ? 3 : null, 'sederhana' => $nilai_plafond ? 2 : null, 'paparan' => $nilai_plafond_2 ? 3 : null, 'rap' => 3, 'memo' => $nilai_plafond ? 1 : null ]; if ($type === 'paparan' && isset($permohonan->tujuanPenilaian->name) && $permohonan->tujuanPenilaian->name === 'rap') { return 2; } return $slaMap[$type] ?? null; } /** * Menghitung total nilai berdasarkan key dan jenis legalitas. * * @param array $detailsArray * @param string $key * @param int $jenisLegalitas * * @return int */ function calculateTotalLuas($detailsArray, $key, $jenisLegalitas, $defaultJenisLegalitas, $fallbackJenisLegalitas) { $total = 0; if ($detailsArray) { foreach ($detailsArray as $item) { if (isset($item->jenis_legalitas_jaminan_id) && $item->jenis_legalitas_jaminan_id === $jenisLegalitas) { $details = json_decode($item->details, true); if (is_array($details)) { foreach ($details as $detail) { if (isset($detail[$key])) { $total += (int) $detail[$key]; } } } } } // Jika total masih 0, gunakan jenis jaminan ppjb if ($total === 0) { foreach ($detailsArray as $item) { if (isset($item->jenis_legalitas_jaminan_id) && $item->jenis_legalitas_jaminan_id === $defaultJenisLegalitas) { $details = json_decode($item->details, true); if (is_array($details)) { foreach ($details as $detail) { if (isset($detail[$key]) && $detail[$key] !== null) { $total += (int) $detail[$key]; } } } } } } // jika total masih kosong juga maka gunakan ppb if ($total === 0 && $fallbackJenisLegalitas !== null) { foreach ($detailsArray as $item) { if (isset($item->jenis_legalitas_jaminan_id) && $item->jenis_legalitas_jaminan_id === $fallbackJenisLegalitas) { $details = json_decode($item->details, true); if (is_array($details)) { foreach ($details as $detail) { if (isset($detail[$key]) && $detail[$key] !== null) { $total += (int) $detail[$key]; } } } } } } } return $total > 0 ? $total : 0; } function ubahNomorHp($nomorHp) { $nomorHp = preg_replace('/\D/', '', $nomorHp); if (strpos($nomorHp, '62') === 0) { $nomorBaru = substr($nomorHp, 0, 5) . "xxxxx"; return '+' . $nomorBaru; } else if (strpos($nomorHp, '0') === 0) { $nomorBaru = substr($nomorHp, 0, 5) . "xxxxxx"; return $nomorBaru; } else { return "Nomor HP tidak valid"; } } function parsePembandingMigration($keterangan) { $keterangan = preg_replace('/[-]{5,}/', '',$keterangan); // Hapus ------ $keterangan = preg_replace('/[.]{5,}/', '',$keterangan); // Hapus ..... $keterangan = preg_replace('/\s+/', ' ',$keterangan); $keterangan = preg_replace('/\s*\n\s*/', "\n",$keterangan); // Pecah teks per baris untuk diproses $lines = explode("\n",$keterangan); $cleaned = []; foreach ($lines as $line) { $line = trim($line); if (!empty($line)) { // Format angka dalam format Rp. 123.456.789 $line = preg_replace_callback('/Rp\.\s*([\d.,]+)/', function($matches) { $angka = str_replace(['.', ','], '', $matches[1]); return 'Rp. ' . number_format((int)$angka, 0, ',', '.'); }, $line); // Jika ada tanda pagar (#), pisahkan menjadi baris baru $line = str_replace('#', "\n#", $line); $cleaned[] = $line; } } return implode("\n", $cleaned); } /** * get full path to internal storage file or external storage file * * @param string $path * @return string */ function getFilePath($path) { // define base path external storage (use .env) example: 'F:\path\to\storage' in windows $externalBase = env('EXTERNAL_STORAGE_BASE_PATH', 'F:LPJ/lpj/LPJ Gambar/001/'); $segments = explode('/', $path); if(strtoupper($segments[0]) === 'SURVEYOR'){ $year = $segments[1]; $month = ucfirst(strtolower($segments[2])); $date = $segments[3]; $code = $segments[4]; $file = $segments[5] ?? ''; $extenalFullpath = $externalBase . $year . '/' . $month . '/' . $date . '/' . $code . '/' . $file; if(File::exists($extenalFullpath)){ return $extenalFullpath; } } // if not found in external storage, try to find in internal storage if (Storage::exists($path)) { return Storage::url('app/' . $path); } return $path; } function parseTimestamp(?string $timestamp): ?string { if (!$timestamp) { return null; } // Trim whitespace dan normalize $timestamp = trim($timestamp); // Log untuk debugging Log::info('Mencoba parsing timestamp: "' . $timestamp . '"'); // Parsing dengan DateTime native PHP untuk lebih robust try { // Pattern untuk format d/m/Y H:i:s if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $timestamp, $matches)) { $day = (int) $matches[1]; $month = (int) $matches[2]; $year = (int) $matches[3]; $hour = (int) $matches[4]; $minute = (int) $matches[5]; $second = (int) $matches[6]; // Validasi nilai if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100 && $hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59 && $second >= 0 && $second <= 59) { // Buat DateTime object langsung $dateTime = new \DateTime(); $dateTime->setDate($year, $month, $day); $dateTime->setTime($hour, $minute, $second); $result = $dateTime->format('Y-m-d H:i:s'); Log::info('Berhasil parsing dengan DateTime: ' . $timestamp . ' -> ' . $result); return $result; } } // Pattern untuk format d/m/Y tanpa waktu if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $timestamp, $matches)) { $day = (int) $matches[1]; $month = (int) $matches[2]; $year = (int) $matches[3]; // Validasi nilai if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100) { // Buat DateTime object langsung $dateTime = new \DateTime(); $dateTime->setDate($year, $month, $day); $dateTime->setTime(0, 0, 0); $result = $dateTime->format('Y-m-d H:i:s'); Log::info('Berhasil parsing tanpa waktu dengan DateTime: ' . $timestamp . ' -> ' . $result); return $result; } } } catch (\Exception $e) { Log::error('Gagal parsing dengan DateTime: ' . $timestamp . '. Error: ' . $e->getMessage()); } // Fallback ke format Carbon standar untuk format lainnya $formats = [ 'Y-m-d H:i:s', 'Y-m-d', 'd-m-Y H:i:s', 'd-m-Y', 'j-n-Y H:i:s', 'j-n-Y', ]; foreach ($formats as $format) { try { $carbon = \Carbon\Carbon::createFromFormat($format, $timestamp); if ($carbon && $carbon->format($format) === $timestamp) { // Jika format tidak mengandung waktu, set ke awal hari if (!str_contains($format, 'H:i:s')) { $carbon = $carbon->startOfDay(); } Log::info('Berhasil parsing dengan format ' . $format . ': ' . $timestamp . ' -> ' . $carbon->toDateTimeString()); return $carbon->toDateTimeString(); } } catch (\Exception $e) { // Lanjut ke format berikutnya continue; } } Log::error('Tidak dapat memparsing timestamp dengan format apapun: "' . $timestamp . '"'); return null; }