From c1370a26cabf60a0f7c028a766646439e66e471d Mon Sep 17 00:00:00 2001 From: ahmadafriadi Date: Tue, 3 Nov 2026 09:37:59 +0700 Subject: [PATCH] Update Harga Internasional --- .env.example | 6 + .../InternationalMarketController.php | 210 +++++++++++++ app/Services/AlphaVantageMarket.php | 68 ++++ config/services.php | 6 + public/assets/img/providers/alpha-vantage.ico | Bin 0 -> 15086 bytes public/assets/img/providers/imf.ico | Bin 0 -> 3270 bytes public/assets/img/providers/yahoo-finance.ico | Bin 0 -> 2238 bytes resources/views/dashboard/pasar.blade.php | 294 ++++++++++++++++++ resources/views/layout/footer.blade.php | 2 +- routes/web.php | 1 + 10 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/Web/Dashboard/InternationalMarketController.php create mode 100644 app/Services/AlphaVantageMarket.php create mode 100644 public/assets/img/providers/alpha-vantage.ico create mode 100644 public/assets/img/providers/imf.ico create mode 100644 public/assets/img/providers/yahoo-finance.ico diff --git a/.env.example b/.env.example index 3dfc344..2f91c1a 100644 --- a/.env.example +++ b/.env.example @@ -63,3 +63,9 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +# Optional CA bundle path for Yahoo Finance when PHP has no system CA configured. +YAHOO_FINANCE_CA_BUNDLE= + + +ALPHA_VANTAGE_API_KEY= diff --git a/app/Http/Controllers/Web/Dashboard/InternationalMarketController.php b/app/Http/Controllers/Web/Dashboard/InternationalMarketController.php new file mode 100644 index 0000000..5d1bf3b --- /dev/null +++ b/app/Http/Controllers/Web/Dashboard/InternationalMarketController.php @@ -0,0 +1,210 @@ +remember('market.yahoo.quotes.v3', 300, function () use ($cache) { + $commodities = [ + ['name' => 'Kopi Arabica', 'symbol' => 'KC=F'], + ['name' => 'Kakao (Cocoa)', 'symbol' => 'CC=F'], + ]; + + try { + $responses = Http::pool(function (Pool $pool) use ($commodities) { + return array_map(fn ($commodity) => $pool->as($commodity['symbol']) + ->acceptJson()->withHeaders(['User-Agent' => 'Mozilla/5.0']) + ->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true]) + ->connectTimeout(3)->timeout(8) + ->get('https://query1.finance.yahoo.com/v8/finance/chart/'.rawurlencode($commodity['symbol']), [ + 'interval' => '1d', 'range' => '5d', + ]), [...$commodities, ['symbol' => 'IDR=X']]); + }); + } catch (Throwable $exception) { + report($exception); + $responses = []; + } + + $exchangeRate = $cache->get('market.yahoo.usd_idr'); + if (is_array($exchangeRate)) { + $exchangeRate['stale'] = true; + } + $rateResponse = $responses['IDR=X'] ?? null; + if ($rateResponse instanceof \Illuminate\Http\Client\Response && $rateResponse->successful()) { + $meta = $rateResponse->json('chart.result.0.meta'); + if (is_array($meta) && ($meta['symbol'] ?? null) === 'IDR=X' + && ($meta['currency'] ?? null) === 'IDR' + && is_numeric($meta['regularMarketPrice'] ?? null) && $meta['regularMarketPrice'] > 0 + && is_numeric($meta['regularMarketTime'] ?? null)) { + $exchangeRate = [ + 'value' => (float) $meta['regularMarketPrice'], + 'quoted_at' => gmdate('c', (int) $meta['regularMarketTime']), + 'source' => 'Yahoo Finance', + 'source_url' => 'https://finance.yahoo.com/quote/IDR%3DX/', + 'stale' => false, + ]; + $cache->put('market.yahoo.usd_idr', $exchangeRate, now()->addDays(7)); + } + } + + $quotes = array_map(function ($commodity) use ($responses, $cache) { + $symbol = $commodity['symbol']; + $key = 'market.yahoo.last.'.rawurlencode($symbol); + $quote = $commodity + [ + 'source' => 'Yahoo Finance', + 'source_url' => 'https://finance.yahoo.com/quote/'.rawurlencode($symbol).'/', + 'price' => null, 'currency' => null, 'unit' => null, + 'quoted_at' => null, 'stale' => false, + ]; + + try { + $response = $responses[$symbol] ?? null; + if (!$response instanceof \Illuminate\Http\Client\Response || !$response->successful()) { + throw new \RuntimeException('Quote unavailable'); + } + $meta = $response->json('chart.result.0.meta'); + if ($response->json('chart.error') || !is_array($meta) + || ($meta['symbol'] ?? null) !== $symbol + || !is_numeric($meta['regularMarketPrice'] ?? null) + || (float) $meta['regularMarketPrice'] <= 0 + || !is_numeric($meta['regularMarketTime'] ?? null) + || !is_string($meta['currency'] ?? null)) { + throw new \RuntimeException('Invalid quote'); + } + + $quote['price'] = (float) $meta['regularMarketPrice']; + $quote['currency'] = $meta['currency']; + // Yahoo quotes Arabica in US cents/lb; cocoa and robusta in USD/tonne. + // Apply the unit only when the API currency matches that convention. + $quote['unit'] = match (true) { + $symbol === 'KC=F' && $meta['currency'] === 'USX' => 'sen AS / lb', + $symbol === 'CC=F' && $meta['currency'] === 'USD' => 'USD / ton metrik', + default => $meta['currency'].' (satuan mengikuti sumber)', + }; + $quote['quoted_at'] = gmdate('c', (int) $meta['regularMarketTime']); + $cache->put($key, $quote, now()->addDays(7)); + + return $quote; + } catch (Throwable $exception) { + $previous = $cache->get($key); + if (is_array($previous)) { + $previous['stale'] = true; + return $previous; + } + return $quote; + } + }, $commodities); + + return ['quotes' => $quotes, 'exchange_rate' => $exchangeRate]; + }); + + $quotes = $marketData['quotes']; + $exchangeRate = $marketData['exchange_rate']; + foreach ($quotes as &$quote) { + $quote['id'] = 'yahoo:'.$quote['symbol']; + $quote['description'] = 'Estimasi konversi harga kontrak berjangka internasional.'; + $quote['period'] = $quote['quoted_at']; + $quote['frequency'] = 'market'; + } + unset($quote); + $quotes = array_merge($quotes, $this->imfQuotes($cache), app(AlphaVantageMarket::class)->quotes($cache)); + $quotes = array_values(array_filter($quotes, fn ($quote) => is_numeric($quote['price']) && is_finite((float) $quote['price']) && $quote['price'] > 0)); + foreach ($quotes as &$quote) { + // All active providers quote either US cents/lb or USD/metric tonne. + $usdPerKg = match ($quote['unit']) { + 'sen AS / lb' => $quote['price'] / 100 / 0.45359237, + 'USD / ton metrik' => $quote['price'] / 1000, + default => null, + }; + $quote['price_idr_per_kg'] = $exchangeRate && $usdPerKg !== null + ? round($usdPerKg * $exchangeRate['value'], 2) : null; + unset($quote['usd_per_kg']); // Old Alpha cache entries may contain this field. + } + unset($quote); + + return response()->json([ + 'quotes' => $quotes, + 'exchange_rate' => $exchangeRate, + ])->header('Cache-Control', 'no-store'); + } + + private function imfQuotes(Repository $cache): array + { + // IMF observations are monthly averages; fetch at most once a day. + return $cache->remember('market.imf.monthly.v1', 86400, function () use ($cache) { + try { + $response = Http::withHeaders(['Accept' => 'text/csv', 'User-Agent' => 'Mozilla/5.0']) + ->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true]) + ->connectTimeout(3)->timeout(10) + ->get('https://api.imf.org/external/sdmx/2.1/data/IMF.RES,PCPS/G001.PCOFFOTM+PCOFFROB+PCOCO.USD.M', [ + 'startPeriod' => now()->subYears(2)->format('Y-m'), 'format' => 'csvfile', + ])->throw(); + $stream = fopen('php://memory', 'r+'); + try { + fwrite($stream, $response->body()); + rewind($stream); + $headers = fgetcsv($stream, 0, ',', '"', ''); + if (!$headers || array_diff(['COUNTRY', 'INDICATOR', 'DATA_TRANSFORMATION', 'FREQUENCY', 'TIME_PERIOD', 'OBS_VALUE', 'SCALE'], $headers)) { + throw new \RuntimeException('Unexpected IMF CSV format'); + } + $latest = []; + while (($values = fgetcsv($stream, 0, ',', '"', '')) !== false) { + if (count($values) !== count($headers)) continue; + $row = array_combine($headers, $values); + if ($row['COUNTRY'] !== 'G001' || $row['DATA_TRANSFORMATION'] !== 'USD' || $row['FREQUENCY'] !== 'M' + || $row['SCALE'] !== '0' || !is_numeric($row['OBS_VALUE']) || $row['OBS_VALUE'] <= 0 + || !preg_match('/^\d{4}-M(0[1-9]|1[0-2])$/', $row['TIME_PERIOD'])) continue; + $indicator = $row['INDICATOR']; + if (!isset($latest[$indicator]) || $row['TIME_PERIOD'] > $latest[$indicator]['TIME_PERIOD']) { + $latest[$indicator] = $row; + } + } + } finally { + fclose($stream); + } + $definitions = [ + 'PCOFFOTM' => ['Kopi Arabica', 'USX', 'sen AS / lb', 'Biji kopi Arabica (Other Milds). Estimasi konversi harga acuan rata-rata bulanan IMF.'], + 'PCOFFROB' => ['Kopi Robusta', 'USX', 'sen AS / lb', 'Biji kopi Robusta. Estimasi konversi harga acuan rata-rata bulanan IMF.'], + 'PCOCO' => ['Kakao (Cocoa)', 'USD', 'USD / ton metrik', 'Biji kakao. Estimasi konversi harga acuan rata-rata bulanan IMF.'], + ]; + $quotes = []; + foreach ($definitions as $indicator => [$name, $currency, $unit, $description]) { + if (!isset($latest[$indicator])) continue; + $row = $latest[$indicator]; + $quotes[] = [ + 'id' => 'imf:'.$indicator, 'symbol' => $indicator, 'name' => $name, + 'price' => (float) $row['OBS_VALUE'], 'currency' => $currency, 'unit' => $unit, + 'source' => 'IMF · PCPS', 'source_url' => 'https://data.imf.org/Datasets/PCPS', + 'description' => $description, 'frequency' => 'monthly', + 'period' => str_replace('-M', '-', $row['TIME_PERIOD']), 'stale' => false, + ]; + } + if (!$quotes) throw new \RuntimeException('No valid IMF observations'); + $cache->put('market.imf.last.v1', $quotes, now()->addDays(30)); + return $quotes; + } catch (Throwable $exception) { + report($exception); + return array_map(function ($quote) { + $quote['stale'] = true; + return $quote; + }, $cache->get('market.imf.last.v1', [])); + } + }); + } + +} diff --git a/app/Services/AlphaVantageMarket.php b/app/Services/AlphaVantageMarket.php new file mode 100644 index 0000000..66298a1 --- /dev/null +++ b/app/Services/AlphaVantageMarket.php @@ -0,0 +1,68 @@ +get($cacheKey))) { + return $quotes; + } + + try { + $response = Http::acceptJson() + ->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true]) + ->connectTimeout(3)->timeout(6) + ->get('https://www.alphavantage.co/query', [ + 'function' => 'COFFEE', 'interval' => 'monthly', 'apikey' => $key, + ])->throw(); + $data = $response->json(); + if (!is_array($data) || ($data['interval'] ?? null) !== 'monthly' + || strtolower($data['unit'] ?? '') !== 'cents per pound' || !is_array($data['data'] ?? null)) { + throw new \RuntimeException('Invalid Alpha Vantage response'); + } + $latest = null; + foreach ($data['data'] as $row) { + if (!is_array($row) || !is_numeric($row['value'] ?? null) || !is_finite((float) $row['value']) || $row['value'] <= 0 + || !is_string($row['date'] ?? null) || !preg_match('/^\d{4}-(0[1-9]|1[0-2])-01$/', $row['date'])) { + continue; + } + if (!$latest || $row['date'] > $latest['date']) $latest = $row; + } + if (!$latest) throw new \RuntimeException('No valid Alpha Vantage price'); + $quote = [ + 'id' => 'alpha:COFFEE', 'name' => 'Kopi Arabica', + 'source' => 'Alpha Vantage (data IMF via FRED)', + 'source_url' => 'https://www.alphavantage.co/documentation/#coffee', + 'price' => (float) $latest['value'], 'currency' => 'USX', 'unit' => 'sen AS / lb', + 'period' => substr($latest['date'], 0, 7), 'frequency' => 'monthly', 'stale' => false, + 'description' => 'Biji kopi Arabica (Other Milds). Estimasi konversi harga acuan rata-rata bulanan IMF melalui Alpha Vantage.', + 'update_info' => 'Data Alpha Vantage diperiksa setiap hari untuk menjaga kuota API. Harga merupakan rata-rata bulanan dari IMF via FRED.', + ]; + $cache->put($lastKey, $quote, now()->addDays(30)); + $cache->put($cacheKey, [$quote], 86400); + return [$quote]; + } catch (Throwable) { + // Request exceptions can contain the API key in their URL; do not log them. + $quotes = []; + if (is_array($last = $cache->get($lastKey))) { + $last['stale'] = true; + $quotes[] = $last; + } + $cache->put($cacheKey, $quotes, 3600); + return $quotes; + } + } +} diff --git a/config/services.php b/config/services.php index 6a90eb8..7370b69 100644 --- a/config/services.php +++ b/config/services.php @@ -14,6 +14,12 @@ return [ | */ + 'alpha_vantage' => ['api_key' => env('ALPHA_VANTAGE_API_KEY')], + + 'yahoo_finance' => [ + 'ca_bundle' => env('YAHOO_FINANCE_CA_BUNDLE'), + ], + 'postmark' => [ 'key' => env('POSTMARK_API_KEY'), ], diff --git a/public/assets/img/providers/alpha-vantage.ico b/public/assets/img/providers/alpha-vantage.ico new file mode 100644 index 0000000000000000000000000000000000000000..0af8e0f34bafead329ee665bf483d8197da33fbb GIT binary patch literal 15086 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 vcsS=v=&6~OFQtz#iIi|V0|4$meJ z>-hHv|HQwpUBd@|zlv+`U&TNE`Um{&&sXsNdslGv%Dec>Z!Z(Qgg?D=5r6#6FYw;m zzs8k|KgaLhd;`CK^EJHt`m1>7XD{LMg_m*p)$_RY%JaDN<8yfXrRQ+*g|m3;{2Bc6 z-1qU;^WVm=&V3heoF)3~X}tdQH}Tr{p2SbTa}pQ6bqW_wpTtj2oxsaap1><#eHbr3 z{uo|7c?>Un@i@+(IE?2WeE>f?eh5E2c0Znd=sx`5$TH3zJ%DEpE#u6g8JsydkEcJC z(`h`lw1B6UCyBiW-Ek45p7d&BtB zXg|I@)`c&`x^QA=D;|saaeSZ)j}C3ZBZGb%>ubfa9zPzAxbet<0}pkz;Pc%wKG*HS z;dVcccH2o@NqjQ4)kb0qjYzy#!k3q(@5vs#R#)?P`Wa=!))|s&+mEyj761#w9iNUfRP&P7H5H}DDERYS5 z;DK2Nm?oUh5T(?W9R?{P7=LlyB%8n+R?Lq6%=Exss>zi$|6dbAL0DW8KI?1W6J!Vb$ zc*vxqv|7D}GJkwzb;v zAuMH;+s1ghyo%eBO)Ft|y{zDhuBP35jmgr)n9c0XZcsu`v{}|yiTw7}siB@Wxy0;t z2}1i~wynrvhFhw*mDm`l?C_&XWXDzpHU+)KjdpvW!On4Q$(4|%hSPOS_#}F*3}b)D zZssJqH*@tDAE-d{ zsvDJzZ%co^75cJ`gEhRRNcCfrl!sPrO*iGstDN`f`y;T@r8~(Hz9yP-#unFbD+Bqz z;K*b=ruPYF(|2hLs^MN08RpB-?U{wSCHJ&}ZU#R5TXyIu=Dp zhDw1QP!}q4%biu_j_TY-7t-l)nZv|!@Q)YzpyO#$ez9+RYG10CW<*nK8AGw;zU7W? zzZMcx!okSj+z}8sb~Kwv?3f63jz-4GgT@&(fdH3lN3E&UkS~q+wAtQK!Ww$LcmG_Fk6kZD~DC$P3dH9I;Z~GMnayoIQ%ihzB*anfc-R?E zsZ$)H_ma0`L3EC02^*C}G8GHRE;+CxnN;E)r!O9#QW9!6EjFpWH?VLR;2oMt>`kOn zskx+**heHaH?=20yyT8rfwfoP#PW^Z(_Q8W>-2O!sI9+R8ctr%~nbV+V3$>_v&sD zOjb}*x{Wo-rC=pVEa7=`iPcuE1MjHHc%||iDc7vVQK#jWWJ%H(i+Dlgig^uP@AvN4 zZwY+_nG1P-IO?9LvamJfMp#7aR>r2f2Jf=R+lEXe*U#wJPWSBMG zR_d)=P)3KLWH}}qh4`G&7~d}1EB+6%u!eOS?TqddUeq>JuY31fsdFApLras!-9CCJ I`dB;gUv$Nh#Q*>R literal 0 HcmV?d00001 diff --git a/public/assets/img/providers/yahoo-finance.ico b/public/assets/img/providers/yahoo-finance.ico new file mode 100644 index 0000000000000000000000000000000000000000..2dd7936312a3d1bafc571390cdabda2fafcd8089 GIT binary patch literal 2238 zcmeH|Sx8iI6o)^z)YQ^6TP(G*#ayz@GF!C8O07)IThK%MW{?y_Mn&6$4^dQR6uo2# zl2imi(ME-d6cmh(d#Ir)E|b|fZsXXwZ?`dhsYlP{{LcCRzQcJq4>MpRpT9q%PUJd) zH!w?5ETSY;f3VQm>_BI`6P=x;{VsG4yD4(qLy_}did^@=0(=ctOfKvm>LzD8$I9e0`P_$$;T zT%{)QIyK3e)Nak9cH2$9ZNEibYBu$U?@@mwhx)V}8csZ*Aw7@AGmmII`*S648XP~FvfZ1Tg z+}45lrwMbri9TZ&eI1gfZY({$SbEJ^%zaq;BrO*D`}+qw`>;QJf#D1M#|tb_6lL)c z`^E}Sb{}FOQU-#D;6B3R2mc*qvT$W8IEJl@_WR48Wh!3-bEKq4O&BAx*>yyWgp6rp zx{%eN-e@LEMYnipvF%#10>D?wdb>%YN~o5re!@twi @endpush @@ -358,12 +414,250 @@ + +
+
+
Harga Komoditas Internasional
+
+ + + +
+
+

Setiap kartu menampilkan satu komoditas dari satu sumber. Periode dan jenis harga mengikuti penyedia; tidak mengikuti filter wilayah.

+

Memuat harga internasional...

+
+ +
@endsection @push('scripts') - , by Yayasan Bhumi Loka Lestari + , by PT Republik Solusi diff --git a/routes/web.php b/routes/web.php index ba27b28..44569e1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -43,6 +43,7 @@ Route::middleware('auth') Route::get('/dashboard/produktivitas/map-information', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'mapInformation'])->name('dashboard.produktivitas.map-information'); Route::get('/dashboard/produktivitas', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'produktivitas'])->name('dashboard.produktivitas'); + Route::get('/dashboard/pasar/harga-internasional', App\Http\Controllers\Web\Dashboard\InternationalMarketController::class)->name('dashboard.pasar.international'); Route::get('/dashboard/pasar', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'pasar'])->name('dashboard.pasar'); // Dashboard