diff --git a/.env.docker b/.env.docker index 79c1ea3a..b7f9519a 100644 --- a/.env.docker +++ b/.env.docker @@ -51,4 +51,6 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MYKAD_READER_PORT=8085 -MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED \ No newline at end of file +MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED + +INTERNAL_NOTIFICATION_SECRET=erahn-local-notification-secret \ No newline at end of file diff --git a/.env.example b/.env.example index e7821ee4..7248b2d3 100644 --- a/.env.example +++ b/.env.example @@ -48,3 +48,6 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MYKAD_READER_PORT=17321 MYKAD_READER_ENABLED_CAWANGAN='' # IF NO CAWANGAN STATED, ALL CAWANGAN IS ENABLED + +# Shared with api_arrahn so the API can write staff inbox rows +INTERNAL_NOTIFICATION_SECRET= diff --git a/app/Http/Controllers/GadaianController.php b/app/Http/Controllers/GadaianController.php index b2074c28..49f10f80 100644 --- a/app/Http/Controllers/GadaianController.php +++ b/app/Http/Controllers/GadaianController.php @@ -11,6 +11,7 @@ use App\Events\SerahanPenilai; use Illuminate\Http\Request; use App\Services\CustomerService; +use App\Services\NotificationService; use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Auth; @@ -50,6 +51,37 @@ class GadaianController extends Controller return $this->isFeatureEnabled($kodcaw, 'pembiayaan_online'); } + /** + * Inbox row for PC at this branch. Failures must not block gadai. + */ + protected function notifyPcPembiayaanOnlinePending($auth_user, Request $request, $pinjamanAmt) + { + try { + app(NotificationService::class)->notifyRoleAtCawangan('pc', $auth_user['cawangan'], [ + 'type' => NotificationService::TYPE_PEMBIAYAAN_PENDING_PC, + 'title' => 'Pembiayaan online menunggu semakan', + 'body' => sprintf( + '%s — %s (RM %s)', + $request->nama_pelanggan, + $request->norujukan_gadai, + number_format((float) $pinjamanAmt, 2) + ), + 'url' => route('pembiayaan.online.pc'), + 'data' => [ + 'norujukan' => $request->norujukan_gadai, + 'nama_pelanggan' => $request->nama_pelanggan, + 'pinjaman' => $pinjamanAmt, + 'created_by' => $auth_user['name'], + ], + ]); + } catch (\Throwable $e) { + Log::warning('Pembiayaan online PC notification failed: '.$e->getMessage(), [ + 'norujukan' => $request->norujukan_gadai, + 'cawangan' => $auth_user['cawangan'], + ]); + } + } + protected function parseMoney($value) { if ($value === null || $value === '') { @@ -278,6 +310,8 @@ class GadaianController extends Controller : 'Gagal daftar pembiayaan online. Sila cuba lagi.'; return redirect()->back()->with('error', $msg); } + + $this->notifyPcPembiayaanOnlinePending($auth_user, $request, $pinjamanAmt); } else { $this->AliranTunaiController->alirantunaiGadai($request, $auth_user['name']); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 1a600d98..d439533a 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -5,9 +5,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Services\CustomerService; -use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Hash; use Carbon\Carbon; class HomeController extends Controller @@ -41,17 +39,6 @@ class HomeController extends Controller - - if(session()->has('iklan') && session()->get('iklan') == 0) - { - session()->put('iklan', 1); - }else{ - if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) { - session()->put('iklan', 0); - } - } - - if($auth_user['roles'] == 'teller'){ if($auth_user['role_harian'] == 'teller'){ $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); @@ -117,16 +104,6 @@ class HomeController extends Controller ]); - if(session()->has('iklan') && session()->get('iklan') == 0) - { - session()->put('iklan', 1); - }else{ - if (Hash::check(config('app.reset_password'), $auth_user['password']) || Hash::check($auth_user['no_kp'], $auth_user['password'])) { - session()->put('iklan', 0); - } - } - - if($auth_user['roles'] == 'teller'){ if($auth_user['role_harian'] == 'teller'){ $cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/'. $auth_user['cawangan'])->json(); diff --git a/app/Http/Controllers/InternalNotificationController.php b/app/Http/Controllers/InternalNotificationController.php new file mode 100644 index 00000000..2c657a19 --- /dev/null +++ b/app/Http/Controllers/InternalNotificationController.php @@ -0,0 +1,32 @@ +header('X-Internal-Token', ''); + + if ($expected === '' || !hash_equals($expected, $provided)) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $audience = $request->input('audience', []); + $role = is_array($audience) && !empty($audience['role']) ? $audience['role'] : null; + + if (!$role) { + return response()->json(['message' => 'audience.role diperlukan'], 422); + } + + $created = $notifications->dispatch($request->all()); + + return response()->json([ + 'created' => $created->count(), + ], 201); + } +} diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php new file mode 100644 index 00000000..7ff0341f --- /dev/null +++ b/app/Http/Controllers/NotificationController.php @@ -0,0 +1,43 @@ +middleware('auth'); + } + + public function index(NotificationService $notifications) + { + return response()->json($notifications->inboxPayload(Auth::user())); + } + + public function markAsRead(NotificationService $notifications, $id) + { + $notification = $notifications->markAsRead(Auth::user(), $id); + + if (!$notification) { + return response()->json(['message' => 'Notifikasi tidak dijumpai.'], 404); + } + + return response()->json([ + 'notification' => $notification->toInboxArray(), + 'unread_count' => $notifications->unreadCount(Auth::user()), + ]); + } + + public function markAllAsRead(NotificationService $notifications) + { + $updated = $notifications->markAllAsRead(Auth::user()); + + return response()->json([ + 'updated' => $updated, + 'unread_count' => 0, + ]); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 0c13b854..4093f35a 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - // + 'internal/notifications', ]; } diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php new file mode 100644 index 00000000..1ca32a52 --- /dev/null +++ b/app/Services/NotificationService.php @@ -0,0 +1,335 @@ +where('roles', $role) + ->where('cawangan', $kodcaw) + ->get(); + + $created = collect(); + foreach ($users as $user) { + $created->push($this->notifyUser($user, $kodcaw, $role, $payload)); + } + + return $created; + } + + /** + * Create one inbox row per staff matching role (any branch). For HQ like operasi. + * + * @param string $role + * @param array $payload + * @return \Illuminate\Support\Collection + */ + public function notifyRole($role, array $payload) + { + $kodcaw = isset($payload['cawangan']) ? $payload['cawangan'] : null; + $users = User::query()->where('roles', $role)->get(); + + $created = collect(); + foreach ($users as $user) { + $created->push($this->notifyUser($user, $kodcaw, $role, $payload)); + } + + return $created; + } + + /** + * Entry point for api_arrahn (and local callers). Builds title/url from type when omitted. + * + * @param array $input + * @return \Illuminate\Support\Collection + */ + public function dispatch(array $input) + { + $type = isset($input['type']) ? $input['type'] : ''; + $kodcaw = isset($input['cawangan']) ? $input['cawangan'] : null; + $audience = isset($input['audience']) && is_array($input['audience']) ? $input['audience'] : []; + $role = isset($audience['role']) ? $audience['role'] : null; + $data = isset($input['data']) && is_array($input['data']) ? $input['data'] : []; + + $payload = $this->payloadForType($type, $kodcaw, $data); + + if (!empty($input['title'])) { + $payload['title'] = $input['title']; + } + if (array_key_exists('body', $input) && $input['body'] !== null && $input['body'] !== '') { + $payload['body'] = $input['body']; + } + if (!empty($input['url'])) { + $payload['url'] = $input['url']; + } + + if ($role === 'operasi') { + return $this->notifyRole('operasi', $payload); + } + + if ($role && $kodcaw) { + return $this->notifyRoleAtCawangan($role, $kodcaw, $payload); + } + + return collect(); + } + + /** + * @param string $type + * @param string|null $kodcaw + * @param array $data + * @return array + */ + protected function payloadForType($type, $kodcaw, array $data) + { + if ($type === self::TYPE_MODAL_PENDING_OPERASI) { + $jenis = isset($data['jenis']) ? strtoupper((string) $data['jenis']) : 'HAD'; + $label = $jenis === 'TOPUP' ? 'tambah modal' : 'had modal'; + $nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw; + $amaun = number_format(isset($data['amaun']) ? (float) $data['amaun'] : 0, 2); + $oleh = isset($data['dimohon_oleh']) ? trim((string) $data['dimohon_oleh']) : ''; + + return [ + 'type' => $type, + 'cawangan' => $kodcaw, + 'title' => 'Permohonan modal online', + 'body' => sprintf( + '%s mohon %s RM %s%s', + $nama, + $label, + $amaun, + $oleh !== '' ? ' — '.$oleh : '' + ), + 'url' => '/pembiayaan-online/operasi/topup', + 'data' => $data, + ]; + } + + if ($type === self::TYPE_PEMBIAYAAN_BATCH_OPERASI) { + $nama = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw; + $batchNo = !empty($data['batch_no']) ? $data['batch_no'] : 'batch'; + $batchId = isset($data['batch_id']) ? $data['batch_id'] : null; + $submitted = isset($data['submitted_count']) ? (int) $data['submitted_count'] : 0; + $jumlah = number_format(isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0, 2); + $oleh = isset($data['pc_submitted_by']) ? trim((string) $data['pc_submitted_by']) : ''; + $appended = !empty($data['appended']); + + if ($appended) { + $body = sprintf( + '%s tambah %d rekod ke %s (RM %s)%s', + $nama, + $submitted, + $batchNo, + $jumlah, + $oleh !== '' ? ' — '.$oleh : '' + ); + } else { + $body = sprintf( + '%s hantar %s — %d rekod (RM %s)%s', + $nama, + $batchNo, + $submitted, + $jumlah, + $oleh !== '' ? ' — '.$oleh : '' + ); + } + + return [ + 'type' => $type, + 'cawangan' => $kodcaw, + 'title' => 'Batch pembiayaan online', + 'body' => $body, + 'url' => ($kodcaw && $batchId) + ? '/pembiayaan-online/operasi/'.$kodcaw.'/batches/'.$batchId + : '/pembiayaan-online/operasi', + 'data' => $data, + ]; + } + + if ($type === self::TYPE_PEMBIAYAAN_REJECTED_PC) { + $norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'rekod'; + $reason = isset($data['reject_reason']) ? trim((string) $data['reject_reason']) : ''; + $amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2); + + return [ + 'type' => $type, + 'cawangan' => $kodcaw, + 'title' => 'Pembiayaan online ditolak', + 'body' => sprintf( + '%s (RM %s) ditolak%s', + $norujukan, + $amaun, + $reason !== '' ? ' — '.$reason : '' + ), + 'url' => '/pembiayaan-online/pc', + 'data' => $data, + ]; + } + + if ($type === self::TYPE_PEMBIAYAAN_PAID_PC) { + $paidCount = isset($data['paid_count']) ? (int) $data['paid_count'] : 1; + $batchNo = !empty($data['batch_no']) ? $data['batch_no'] : ''; + $norujukan = !empty($data['norujukan']) ? $data['norujukan'] : ''; + $nama = isset($data['nama_pelanggan']) ? trim((string) $data['nama_pelanggan']) : ''; + $amaun = number_format( + isset($data['pinjaman']) + ? (float) $data['pinjaman'] + : (isset($data['jumlah_keseluruhan']) ? (float) $data['jumlah_keseluruhan'] : 0), + 2 + ); + + if ($paidCount > 1 || ($batchNo !== '' && $norujukan === '')) { + $body = sprintf( + '%d rekod telah dibayar%s (RM %s)', + $paidCount > 0 ? $paidCount : 0, + $batchNo !== '' ? ' dalam '.$batchNo : '', + $amaun + ); + } else { + $body = sprintf( + '%s telah dibayar (RM %s)%s', + $norujukan !== '' ? $norujukan : 'Rekod', + $amaun, + $nama !== '' ? ' — '.$nama : '' + ); + } + + return [ + 'type' => $type, + 'cawangan' => $kodcaw, + 'title' => 'Pembiayaan online telah dibayar', + 'body' => $body, + 'url' => '/pembiayaan-online/pc/paid', + 'data' => $data, + ]; + } + + return [ + 'type' => $type, + 'cawangan' => $kodcaw, + 'title' => isset($data['title']) ? $data['title'] : $type, + 'body' => isset($data['body']) ? $data['body'] : null, + 'url' => isset($data['url']) ? $data['url'] : null, + 'data' => $data, + ]; + } + + /** + * Create one inbox row for a specific user. + * + * @param \App\User $user + * @param string|null $kodcaw + * @param string|null $role + * @param array $payload + * @return \App\StaffNotification + */ + public function notifyUser(User $user, $kodcaw, $role, array $payload) + { + return StaffNotification::create([ + 'user_id' => $user->id, + 'cawangan' => $kodcaw, + 'role' => $role ?: $user->roles, + 'type' => $payload['type'], + 'title' => $payload['title'], + 'body' => isset($payload['body']) ? $payload['body'] : null, + 'url' => isset($payload['url']) ? $payload['url'] : null, + 'data' => isset($payload['data']) ? $payload['data'] : null, + ]); + } + + /** + * @param \App\User $user + * @param int $limit + * @return \Illuminate\Support\Collection + */ + public function inboxFor(User $user, $limit = 20) + { + return StaffNotification::query() + ->where('user_id', $user->id) + ->orderByDesc('id') + ->limit($limit) + ->get(); + } + + /** + * @param \App\User $user + * @return int + */ + public function unreadCount(User $user) + { + return StaffNotification::query() + ->where('user_id', $user->id) + ->unread() + ->count(); + } + + /** + * @param \App\User $user + * @param int $id + * @return \App\StaffNotification|null + */ + public function markAsRead(User $user, $id) + { + $notification = StaffNotification::query() + ->where('user_id', $user->id) + ->where('id', $id) + ->first(); + + if (!$notification) { + return null; + } + + if ($notification->read_at === null) { + $notification->read_at = now(); + $notification->save(); + } + + return $notification; + } + + /** + * @param \App\User $user + * @return int + */ + public function markAllAsRead(User $user) + { + return StaffNotification::query() + ->where('user_id', $user->id) + ->unread() + ->update(['read_at' => now()]); + } + + /** + * @param \App\User $user + * @param int $limit + * @return array + */ + public function inboxPayload(User $user, $limit = 20) + { + return [ + 'unread_count' => $this->unreadCount($user), + 'notifications' => $this->inboxFor($user, $limit)->map(function (StaffNotification $notification) { + return $notification->toInboxArray(); + })->values(), + ]; + } +} diff --git a/app/StaffNotification.php b/app/StaffNotification.php new file mode 100644 index 00000000..10de8aa5 --- /dev/null +++ b/app/StaffNotification.php @@ -0,0 +1,56 @@ + 'array', + 'read_at' => 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function isRead() + { + return $this->read_at !== null; + } + + public function scopeUnread($query) + { + return $query->whereNull('read_at'); + } + + public function toInboxArray() + { + return [ + 'id' => $this->id, + 'type' => $this->type, + 'title' => $this->title, + 'body' => $this->body, + 'url' => $this->url, + 'read_at' => $this->read_at ? $this->read_at->toIso8601String() : null, + 'created_at' => $this->created_at ? $this->created_at->toIso8601String() : null, + 'created_at_human' => $this->created_at ? $this->created_at->diffForHumans() : null, + ]; + } +} diff --git a/app/User.php b/app/User.php index 70034b80..d4ded83b 100644 --- a/app/User.php +++ b/app/User.php @@ -46,6 +46,11 @@ class User extends Authenticatable return $this->hasOne(Cawangan::class, 'kodcaw', 'cawangan'); } + public function staffNotifications() + { + return $this->hasMany(StaffNotification::class); + } + public function canImpersonate(): bool { return in_array($this->roles ?? '', ['admin'], true); diff --git a/config/notifications.php b/config/notifications.php new file mode 100644 index 00000000..7a0f43ac --- /dev/null +++ b/config/notifications.php @@ -0,0 +1,5 @@ + env('INTERNAL_NOTIFICATION_SECRET', ''), +]; diff --git a/database/migrations/2026_09_21_000000_create_staff_notifications_table.php b/database/migrations/2026_09_21_000000_create_staff_notifications_table.php new file mode 100644 index 00000000..12b81656 --- /dev/null +++ b/database/migrations/2026_09_21_000000_create_staff_notifications_table.php @@ -0,0 +1,43 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('cawangan', 20)->nullable(); + $table->string('role', 30)->nullable(); + $table->string('type', 80); + $table->string('title'); + $table->text('body')->nullable(); + $table->string('url', 500)->nullable(); + $table->text('data')->nullable(); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'read_at']); + $table->index(['cawangan', 'type']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('staff_notifications'); + } +} diff --git a/resources/views/homepage/khazanah/home.blade.php b/resources/views/homepage/khazanah/home.blade.php index 8fb53118..43745fbe 100644 --- a/resources/views/homepage/khazanah/home.blade.php +++ b/resources/views/homepage/khazanah/home.blade.php @@ -219,78 +219,70 @@
-
-
- Dashboard -
- -
- Kelulusan -
- {{-- --}} - - -
-
+ -
+
+ Lelong +
+ + -
+ +
+ Kelulusan +
+ -
+ + + +
+ Bajet +
+ @if(!empty(($cawangan_info['features'] ?? [])['pembiayaan_online'])) -
+ + @endif - -
- {{--
- Capaian -
--}} -
- Lelong -
- - -
- Bajet -
-
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 6747a4b1..39d8b1f8 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -214,14 +214,16 @@ v-pre style="color: {{ isset($cawangan_info['details_caw']) && $cawangan_info['details_caw'] == 'Putatan 2' ? 'black' : '#1abc9c' }};"> + - - @if(session()->has('iklan') && session()->get('iklan') == 0) - - - - @endif - - -
@yield('content')
diff --git a/resources/views/layouts/partials/notification_inbox.blade.php b/resources/views/layouts/partials/notification_inbox.blade.php new file mode 100644 index 00000000..a65ba5a9 --- /dev/null +++ b/resources/views/layouts/partials/notification_inbox.blade.php @@ -0,0 +1,244 @@ + + +
+
+ Notifikasi + +
+
+
Memuatkan...
+
+
+ + diff --git a/resources/views/pembiayaan_online/operasi/batch.blade.php b/resources/views/pembiayaan_online/operasi/batch.blade.php index d9510777..32decfc6 100644 --- a/resources/views/pembiayaan_online/operasi/batch.blade.php +++ b/resources/views/pembiayaan_online/operasi/batch.blade.php @@ -62,7 +62,7 @@ @endif @if($approvedCount > 0) @endif
@@ -133,7 +133,7 @@ class="btn btn-dark btn-sm btn-paid" data-id="{{ $line['id'] }}" data-norujukan="{{ $line['norujukan'] ?? '' }}"> - PAID + Bukti + Dibayar + Bukti
@@ -247,7 +247,7 @@ @csrf diff --git a/resources/views/pembiayaan_online/pc/paid.blade.php b/resources/views/pembiayaan_online/pc/paid.blade.php index 3a90133e..8194ce70 100644 --- a/resources/views/pembiayaan_online/pc/paid.blade.php +++ b/resources/views/pembiayaan_online/pc/paid.blade.php @@ -69,7 +69,7 @@
Senarai Pembiayaan Online — PAID
@@ -168,7 +168,7 @@ function syncBlastButton() { var n = $('.row-check:checked').length; $('#btnMarkBlast').prop('disabled', n === 0); - $('#btnMarkBlast').text(n ? ('Sahkan Tally / Selesai (' + n + ')') : 'Sahkan Tally / Selesai'); + $('#btnMarkBlast').text(n ? ('Sahkan semua sebagai dibayar (' + n + ')') : 'Sahkan semua sebagai dibayar'); } $('#checkAll').on('change', function () { @@ -180,7 +180,7 @@ $('#btnMarkBlast').on('click', function () { if ($('.row-check:checked').length === 0) return; - if (!confirm('Sahkan tally untuk rekod dipilih? Rekod akan ditanda selesai dan WhatsApp notis akan dihantar.')) return; + if (!confirm('Sahkan semua sebagai dibayar untuk rekod dipilih? Rekod akan ditanda sebagai dibayar dan WhatsApp notis akan dihantar.')) return; $('#formMarkBlast').submit(); }); diff --git a/routes/web.php b/routes/web.php index 2176d6f5..25116d6d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,6 +15,8 @@ use Illuminate\Support\Facades\Storage; | */ +Route::post('/internal/notifications', ['as' => 'internal.notifications.store', 'uses' => 'InternalNotificationController@store']); + Route::get('/health', function () { return response()->json(['status' => 'ok']); }); @@ -190,6 +192,10 @@ Route::get('/penebusankhazanah/{norujukan}',['as'=>'penebusan.khazanah','uses' = Route::get('/lelongankhazanah/{norujukan}',['as'=>'lelongan.khazanah','uses' => 'KhazanahController@lelongan'])->middleware('auth','khazanah'); Route::get('/permohonan',['as'=>'permohonan.index','uses' => 'KhazanahController@PaparanPermohonan'])->middleware('auth','khazanah'); +Route::get('/notifications',['as'=>'notifications.index','uses'=>'NotificationController@index'])->middleware('auth'); +Route::post('/notifications/read-all',['as'=>'notifications.read.all','uses'=>'NotificationController@markAllAsRead'])->middleware('auth'); +Route::post('/notifications/{id}/read',['as'=>'notifications.read','uses'=>'NotificationController@markAsRead'])->middleware('auth'); + // Pembiayaan Online — PC Route::get('/pembiayaan-online/pc',['as'=>'pembiayaan.online.pc','uses'=>'PembiayaanOnlineController@index'])->middleware('auth','khazanah'); Route::put('/pembiayaan-online/pc/lines/{id}',['as'=>'pembiayaan.online.pc.update','uses'=>'PembiayaanOnlineController@updateLine'])->middleware('auth','khazanah');