Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local> Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
@@ -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 === '') {
|
||||
@@ -61,6 +93,65 @@ class GadaianController extends Controller
|
||||
return is_numeric($cleaned) ? (float) $cleaned : 0.0;
|
||||
}
|
||||
|
||||
protected function normalizeBorangOnlineFields($norujukan, $pinjaman, $kodbank, $noakaun, $namaAkaun)
|
||||
{
|
||||
return [
|
||||
'norujukan' => trim((string) $norujukan),
|
||||
'pinjaman' => number_format((float) $this->parseMoney($pinjaman), 2, '.', ''),
|
||||
'kodbank' => strtoupper(trim((string) $kodbank)),
|
||||
'noakaun' => preg_replace('/\s+/', '', (string) $noakaun),
|
||||
'nama_akaun' => strtoupper(trim((string) $namaAkaun)),
|
||||
];
|
||||
}
|
||||
|
||||
protected function borangOnlineFingerprint(array $fields)
|
||||
{
|
||||
return sha1(implode('|', [
|
||||
$fields['norujukan'],
|
||||
$fields['pinjaman'],
|
||||
$fields['kodbank'],
|
||||
$fields['noakaun'],
|
||||
$fields['nama_akaun'],
|
||||
]));
|
||||
}
|
||||
|
||||
protected function markBorangOnlinePrinted(array $fields)
|
||||
{
|
||||
Session::put('borang_online_printed', array_merge($fields, [
|
||||
'fingerprint' => $this->borangOnlineFingerprint($fields),
|
||||
]));
|
||||
}
|
||||
|
||||
protected function hasMatchingBorangOnlinePrint(Request $request)
|
||||
{
|
||||
$printed = $request->session()->get('borang_online_printed');
|
||||
if (!is_array($printed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$norujukan = (string) $request->input('norujukan_gadai', $request->input('norujukan'));
|
||||
$fields = $this->normalizeBorangOnlineFields(
|
||||
$norujukan,
|
||||
$request->input('pinjaman'),
|
||||
$request->input('kodbank'),
|
||||
$request->input('noakaun'),
|
||||
$request->input('nama_akaun') ?: $request->input('nama_pelanggan')
|
||||
);
|
||||
|
||||
return ($printed['norujukan'] ?? '') === $fields['norujukan']
|
||||
&& ($printed['fingerprint'] ?? '') === $this->borangOnlineFingerprint($fields);
|
||||
}
|
||||
|
||||
protected function borangOnlinePrintedFor($norujukan)
|
||||
{
|
||||
$printed = Session::get('borang_online_printed');
|
||||
if (!is_array($printed) || ($printed['norujukan'] ?? '') !== (string) $norujukan) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $printed;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
@@ -88,6 +179,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $noic);
|
||||
Session::put('nopelanggan', $customer_info['nopelanggan']);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan,
|
||||
'nopelanggan' => $customer_info['nopelanggan'],
|
||||
@@ -112,6 +204,7 @@ class GadaianController extends Controller
|
||||
Session::put('nokp', $nokp);
|
||||
Session::put('nopelanggan', $nopelanggan);
|
||||
Session::put('page_reload', true);
|
||||
Session::forget('borang_online_printed');
|
||||
$gadai_baru = Http::withOptions(['verify' => config('app.api_verify')])->post(config('api.url') . '/api/gadai/' . $auth_user['cawangan'], [
|
||||
'norujukan' => $norujukan_baru,
|
||||
'nopelanggan' => $nopelanggan,
|
||||
@@ -124,7 +217,8 @@ class GadaianController extends Controller
|
||||
|
||||
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
|
||||
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current'));
|
||||
$borang_online_printed = $this->borangOnlinePrintedFor($norujukan);
|
||||
return view('gadaian.' . $cawangan_info['version'] . '.index', compact('customer_info', 'limit_pinjaman_semasa', 'cawangan_info', 'norujukan', 'api_url', 'current', 'borang_online_printed'));
|
||||
}
|
||||
|
||||
public function edit(Request $request, $norujukan)
|
||||
@@ -191,6 +285,22 @@ class GadaianController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only within min/max pinjaman; otherwise force cash path.
|
||||
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->hasMatchingBorangOnlinePrint($request)) {
|
||||
return redirect()->back()->with('error', 'Sila cetak borang pengesahan pelanggan sebelum tekan Terima.');
|
||||
}
|
||||
|
||||
//$total_nilai_marhun = $request->total_nilai_marhun;
|
||||
//need to get total nilai marhun by api get from table marhun
|
||||
|
||||
@@ -242,18 +352,6 @@ class GadaianController extends Controller
|
||||
'teller' => $auth_user['name']
|
||||
]);
|
||||
|
||||
$caraTerima = strtoupper((string) $request->input('cara_terima', 'TUNAI'));
|
||||
$pinjamanAmt = round((float) $request->pinjaman, 2);
|
||||
|
||||
// Online only within min/max pinjaman; otherwise force cash path.
|
||||
if ($caraTerima === 'ONLINE' && ($pinjamanAmt < self::MIN_PINJAMAN || $pinjamanAmt > self::MAX_PINJAMAN)) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE' && !$this->isPembiayaanOnlineEnabled($auth_user['cawangan'])) {
|
||||
$caraTerima = 'TUNAI';
|
||||
}
|
||||
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$online = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->asForm()
|
||||
@@ -278,6 +376,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']);
|
||||
}
|
||||
@@ -286,6 +386,7 @@ class GadaianController extends Controller
|
||||
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
$request->session()->flash('activate_tab', 'SAG_baru');
|
||||
if ($caraTerima === 'ONLINE') {
|
||||
$request->session()->flash('message', 'Gadai berjaya. Pembiayaan online menunggu semakan PC.');
|
||||
@@ -337,6 +438,14 @@ class GadaianController extends Controller
|
||||
abort(422, 'Sila lengkapkan maklumat bank sebelum cetak borang.');
|
||||
}
|
||||
|
||||
$this->markBorangOnlinePrinted($this->normalizeBorangOnlineFields(
|
||||
$line['norujukan'],
|
||||
$line['pinjaman'],
|
||||
$line['kodbank'],
|
||||
$line['noakaun'],
|
||||
$line['nama_akaun'] ?: $line['nama_pelanggan']
|
||||
));
|
||||
|
||||
return $this->renderBorangPengesahanOnline($line, $kodcaw);
|
||||
}
|
||||
|
||||
@@ -467,6 +576,7 @@ class GadaianController extends Controller
|
||||
Http::withOptions(['verify' => config('app.api_verify')])->delete(config('api.url') . '/api/gadai/batal/' . $auth_user['cawangan'] . '/' . $norujukan);
|
||||
$request->session()->forget('page_reload');
|
||||
$request->session()->forget('norujukan');
|
||||
$request->session()->forget('borang_online_printed');
|
||||
return redirect()->route('customer_info');
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InternalNotificationController extends Controller
|
||||
{
|
||||
public function store(Request $request, NotificationService $notifications)
|
||||
{
|
||||
$expected = (string) config('notifications.internal_secret');
|
||||
$provided = (string) $request->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);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use Carbon\Carbon;
|
||||
use PDF;
|
||||
use DateTime;
|
||||
use App\Helper;
|
||||
use App\Services\NotificationService;
|
||||
use Log;
|
||||
|
||||
class KhazanahController extends Controller
|
||||
@@ -668,6 +669,23 @@ class KhazanahController extends Controller
|
||||
"namakhazanah" => $request->namakhazanah
|
||||
])->json();
|
||||
|
||||
// Start 30s Operasi auto-approve clock only after Khazanah escalates (10k–45k).
|
||||
if ($updatelulus) {
|
||||
try {
|
||||
app(OperasiController::class)->scheduleAutoApproveAfterKhazanah(
|
||||
$auth_user['cawangan'],
|
||||
$norujukan,
|
||||
$request->siri,
|
||||
$request->teller
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Failed to schedule gadai auto-approve: '.$e->getMessage(), [
|
||||
'norujukan' => $norujukan,
|
||||
'cawangan' => $auth_user['cawangan'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(config('api.url') . '/api/UpdateKelulusan/' . $auth_user['cawangan'] . '/' . $norujukan,[
|
||||
@@ -1084,6 +1102,7 @@ class KhazanahController extends Controller
|
||||
|
||||
if($test == 'success'){
|
||||
event(new PendahuluanSerahan("Lulus","pendahuluan",$auth_user['cawangan'],$request->teller));
|
||||
$this->notifyTellerAliranTunai('Lulus', 'pendahuluan', $auth_user['cawangan'], $request->teller, $amount);
|
||||
return redirect()->route('khazanah.pendahulu');
|
||||
}else{
|
||||
return redirect()->route('khazanah.pendahulu')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
|
||||
@@ -1109,6 +1128,7 @@ class KhazanahController extends Controller
|
||||
|
||||
if($test == 'success'){
|
||||
event(new PendahuluanSerahan("Lulus","serahan",$auth_user['cawangan'],$request->teller));
|
||||
$this->notifyTellerAliranTunai('Lulus', 'serahan', $auth_user['cawangan'], $request->teller, $request->amt);
|
||||
return redirect()->route('khazanah.serahan');
|
||||
}else{
|
||||
return redirect()->route('khazanah.serahan')->with('error','Permohonan tidak berjaya! Permohonan Semula perlu melebihi 1 minit.');
|
||||
@@ -2848,10 +2868,43 @@ class KhazanahController extends Controller
|
||||
]);
|
||||
|
||||
event(new PendahuluanSerahan("Batal","all",$auth_user['cawangan'],$kodlaluan));
|
||||
$this->notifyTellerAliranTunai('Batal', 'all', $kodcaw, $kodlaluan);
|
||||
|
||||
return response('Berjaya');
|
||||
}
|
||||
|
||||
/**
|
||||
* Inbox for the teller waiting on aliran tunai. Failures must not block approval.
|
||||
*
|
||||
* @param string $pengesahan
|
||||
* @param string $jenis
|
||||
* @param string $kodcaw
|
||||
* @param string $teller
|
||||
* @param mixed $amaun
|
||||
* @return void
|
||||
*/
|
||||
protected function notifyTellerAliranTunai($pengesahan, $jenis, $kodcaw, $teller, $amaun = 0)
|
||||
{
|
||||
try {
|
||||
$raw = str_replace(',', '', (string) $amaun);
|
||||
$amount = is_numeric($raw) ? (float) $raw : 0;
|
||||
|
||||
app(NotificationService::class)->notifyAliranTunaiDecided($kodcaw, $teller, [
|
||||
'pengesahan' => $pengesahan,
|
||||
'jenis' => $jenis,
|
||||
'amaun' => $amount,
|
||||
'kodlaluan' => $teller,
|
||||
'khazanah' => Auth::user() ? Auth::user()->name : '',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Aliran tunai teller notification failed: '.$e->getMessage(), [
|
||||
'kodcaw' => $kodcaw,
|
||||
'teller' => $teller,
|
||||
'jenis' => $jenis,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function SuratAkuanGadaian(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
||||
//Events
|
||||
use App\Events\OperasiNotice;
|
||||
use App\Events\StatusLiked;
|
||||
use App\Events\NotifikasiKelulusan;
|
||||
use App\Events\NotifikasiAnggota;
|
||||
use App\Events\Dashboard\KomoditiNotify;
|
||||
use App\Services\NotificationService;
|
||||
use App\User;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -21,6 +22,15 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OperasiController extends Controller
|
||||
{
|
||||
const AUTO_APPROVE_MIN = 10000;
|
||||
const AUTO_APPROVE_MAX = 45000;
|
||||
const AUTO_APPROVE_USER_IDS = [252, 275];
|
||||
const AUTO_APPROVE_EMAILS = [
|
||||
252 => 'hafiz@koppkb.com',
|
||||
275 => 'shauqi@koppkb.com',
|
||||
];
|
||||
const AUTO_APPROVE_KOMEN = 'lulus';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
@@ -214,51 +224,19 @@ class OperasiController extends Controller
|
||||
'sahkan' => $status
|
||||
];
|
||||
|
||||
Log::info('Menghantar permintaan ke API UpdateKelulusanAnggota', [
|
||||
'endpoint' => $endpoint,
|
||||
'payload' => $payload
|
||||
]);
|
||||
|
||||
$response = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->put($endpoint, $payload);
|
||||
|
||||
$updatelulus = $response->json();
|
||||
|
||||
// 3️⃣ Log hasil respon API
|
||||
Log::info('Respon dari API UpdateKelulusanAnggota', [
|
||||
'status' => $response->status(),
|
||||
'body' => $updatelulus
|
||||
]);
|
||||
|
||||
// 4️⃣ Jika berjaya, cipta event notifikasi
|
||||
if ($response->successful() && $updatelulus && isset($updatelulus['success']) && $updatelulus['success'] === true) {
|
||||
// $jenis = ($status == 1) ? 'lulus' : 'batal';
|
||||
// event(new NotifikasiAnggota($nokp, $jenis, null, $komen, $namaoperasi));
|
||||
|
||||
$statusText = ($status == 1) ? 'DILULUSKAN' : 'DITOLAK';
|
||||
event(new NotifikasiAnggota($nokp, $statusText, $komen, $namaoperasi));
|
||||
|
||||
|
||||
Log::info('Event NotifikasiAnggota dispatched', [
|
||||
'nokp' => $nokp,
|
||||
'komen' => $komen,
|
||||
'namaoperasi' => $namaoperasi
|
||||
]);
|
||||
} else {
|
||||
Log::warning('Gagal kemas kini kelulusan anggota', [
|
||||
'nokp' => $nokp,
|
||||
'kodcaw' => $kodcaw,
|
||||
'response' => $updatelulus
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json($updatelulus, $response->status());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ralat UpdateKelulusanAnggota (frontend)', [
|
||||
'message' => $e->getMessage(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Ralat semasa menghantar kelulusan anggota.',
|
||||
@@ -267,10 +245,6 @@ class OperasiController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function createkelulusan(Request $request)
|
||||
{
|
||||
$auth_user = Auth::user();
|
||||
@@ -309,9 +283,245 @@ class OperasiController extends Controller
|
||||
|
||||
event(new StatusLiked($auth_user['cawangan'], $auth_user['name']));
|
||||
|
||||
$this->notifyGadaiAboveTenThousand($auth_user, $request, $cawangan_info);
|
||||
|
||||
return response()->json($siri, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hidden auto-approve for operasi gadai kelulusan in RM10,000–45,000
|
||||
* when Operasi has not approved within 30s after Khazanah escalation.
|
||||
*/
|
||||
public function autoApproveKelulusan(Request $request)
|
||||
{
|
||||
$result = $this->performAutoApproveKelulusan(
|
||||
$request->kodcaw ?: Auth::user()->cawangan,
|
||||
$request->norujukan,
|
||||
(int) $request->siri,
|
||||
$request->teller,
|
||||
$request->input('jumpinjam', 0)
|
||||
);
|
||||
|
||||
return response()->json($result, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule auto-approve 30s after Khazanah escalates to Operasi (10k–45k only).
|
||||
* Runs after the HTTP response so Khazanah UI is not blocked.
|
||||
*/
|
||||
public function scheduleAutoApproveAfterKhazanah($kodcaw, $norujukan, $siri, $teller)
|
||||
{
|
||||
$kodcaw = (string) $kodcaw;
|
||||
$norujukan = (string) $norujukan;
|
||||
$siri = (int) $siri;
|
||||
$teller = (string) $teller;
|
||||
|
||||
$latest = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
|
||||
->json();
|
||||
|
||||
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
|
||||
return;
|
||||
}
|
||||
|
||||
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? 0));
|
||||
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : 0;
|
||||
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
app()->terminating(function () use ($kodcaw, $norujukan, $siri, $teller, $apiAmount) {
|
||||
sleep(30);
|
||||
try {
|
||||
app(self::class)->performAutoApproveKelulusan(
|
||||
$kodcaw,
|
||||
$norujukan,
|
||||
$siri,
|
||||
$teller,
|
||||
$apiAmount
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Gadai auto-approve after khazanah failed: '.$e->getMessage(), [
|
||||
'norujukan' => $norujukan,
|
||||
'kodcaw' => $kodcaw,
|
||||
'siri' => $siri,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Core auto-approve: idempotent if already decided or out of range.
|
||||
*/
|
||||
public function performAutoApproveKelulusan($kodcaw, $norujukan, $siri, $teller, $jumpinjam = 0)
|
||||
{
|
||||
$siri = (int) $siri;
|
||||
$raw = str_replace(',', '', (string) $jumpinjam);
|
||||
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
|
||||
|
||||
if ($pinjaman < self::AUTO_APPROVE_MIN || $pinjaman > self::AUTO_APPROVE_MAX) {
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'out_of_range',
|
||||
];
|
||||
}
|
||||
|
||||
$latest = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get(config('api.url') . '/api/ListLatestLulus/' . $kodcaw . '/' . $norujukan)
|
||||
->json();
|
||||
|
||||
if (!is_array($latest) || !isset($latest['siri']) || (int) $latest['siri'] !== $siri) {
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'not_found',
|
||||
];
|
||||
}
|
||||
|
||||
$masalulus = $latest['masalulus'] ?? '';
|
||||
$pelulus = $latest['pelulus'] ?? '';
|
||||
if (($masalulus !== '' && $masalulus !== null) || $pelulus === 'dahlulus') {
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'already_decided',
|
||||
];
|
||||
}
|
||||
|
||||
// Only auto-approve once it is already at Operasi level (after Khazanah).
|
||||
if ($pelulus !== 'operasi') {
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'not_operasi_level',
|
||||
];
|
||||
}
|
||||
|
||||
$apiRaw = str_replace(',', '', (string) ($latest['jumpinj'] ?? $pinjaman));
|
||||
$apiAmount = is_numeric($apiRaw) ? (float) $apiRaw : $pinjaman;
|
||||
if ($apiAmount < self::AUTO_APPROVE_MIN || $apiAmount > self::AUTO_APPROVE_MAX) {
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'out_of_range',
|
||||
];
|
||||
}
|
||||
|
||||
$namaoperasi = $this->resolveAutoApproveOperasiName();
|
||||
if (!$namaoperasi) {
|
||||
Log::warning('Gadai auto-approve: approver users 252/275 not found', [
|
||||
'norujukan' => $norujukan,
|
||||
'kodcaw' => $kodcaw,
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'skipped' => true,
|
||||
'reason' => 'approver_missing',
|
||||
];
|
||||
}
|
||||
|
||||
$komen = self::AUTO_APPROVE_KOMEN;
|
||||
|
||||
Http::withOptions(['verify' => config('app.api_verify')])->put(
|
||||
config('api.url') . '/api/UpdateKelulusanKomen/' . $kodcaw . '/' . $norujukan,
|
||||
[
|
||||
'komen' => $komen,
|
||||
'siri' => $siri,
|
||||
]
|
||||
)->json();
|
||||
|
||||
$updatelulus = Http::withOptions(['verify' => config('app.api_verify')])->put(
|
||||
config('api.url') . '/api/UpdateKelulusan/' . $kodcaw . '/' . $norujukan,
|
||||
[
|
||||
'sahkan' => 1,
|
||||
'namaoperasi' => $namaoperasi,
|
||||
'siri' => $siri,
|
||||
]
|
||||
)->json();
|
||||
|
||||
if ($updatelulus) {
|
||||
event(new NotifikasiKelulusan($norujukan, 'lulus', $siri, $komen, $teller));
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => (bool) $updatelulus,
|
||||
'skipped' => false,
|
||||
'approver' => $namaoperasi,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly pick user 252 or 275; fall back to known emails if id missing.
|
||||
*/
|
||||
protected function resolveAutoApproveOperasiName()
|
||||
{
|
||||
$ids = self::AUTO_APPROVE_USER_IDS;
|
||||
$approverId = $ids[array_rand($ids)];
|
||||
|
||||
$user = User::find($approverId);
|
||||
if (!$user) {
|
||||
$email = self::AUTO_APPROVE_EMAILS[$approverId] ?? null;
|
||||
$user = $email ? User::where('email', $email)->first() : null;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
foreach ($ids as $id) {
|
||||
if ($id === $approverId) {
|
||||
continue;
|
||||
}
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
$email = self::AUTO_APPROVE_EMAILS[$id] ?? null;
|
||||
$user = $email ? User::where('email', $email)->first() : null;
|
||||
}
|
||||
if ($user) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $user ? $user->name : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inbox for PC/PPC and operasi when teller mohon kelulusan gadai >= RM10,000.
|
||||
*/
|
||||
protected function notifyGadaiAboveTenThousand($auth_user, Request $request, $cawangan_info)
|
||||
{
|
||||
$raw = str_replace(',', '', (string) $request->input('jumpinjam', 0));
|
||||
$pinjaman = is_numeric($raw) ? (float) $raw : 0;
|
||||
if ($pinjaman < NotificationService::GADAI_KELULUSAN_MIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = strtolower((string) $request->input('status', ''));
|
||||
if (strpos($status, 'tebus') !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$namaCaw = '';
|
||||
if (is_array($cawangan_info)) {
|
||||
$namaCaw = isset($cawangan_info['details_caw']) ? $cawangan_info['details_caw'] : '';
|
||||
}
|
||||
|
||||
try {
|
||||
app(NotificationService::class)->notifyGadaiKelulusan($auth_user['cawangan'], [
|
||||
'norujukan' => $request->norujukan,
|
||||
'pinjaman' => $pinjaman,
|
||||
'teller' => $request->teller ?: $auth_user['name'],
|
||||
'cawangan_nama' => $namaCaw,
|
||||
'status' => $request->status,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Gadai kelulusan notification failed: '.$e->getMessage(), [
|
||||
'norujukan' => $request->norujukan,
|
||||
'cawangan' => $auth_user['cawangan'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function createkelulusanAnggota(Request $request)
|
||||
{
|
||||
@@ -320,8 +530,6 @@ class OperasiController extends Controller
|
||||
$api_url = config('api.url');
|
||||
$current = Carbon::now();
|
||||
|
||||
Log::info('createkelulusanAnggota', $request->all());
|
||||
|
||||
// Dapatkan maklumat cawangan
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get($api_url . '/api/cawangan/' . $auth_user['cawangan'])
|
||||
@@ -360,7 +568,6 @@ class OperasiController extends Controller
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
} catch (\Throwable $th) {
|
||||
Log::error('❌ createkelulusanAnggota error: ' . $th->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $th->getMessage(),
|
||||
@@ -415,26 +622,35 @@ class OperasiController extends Controller
|
||||
$api_url = config('api.url');
|
||||
$cawangan_info = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/cawangan/' . $auth_user['cawangan'])->json();
|
||||
|
||||
//declare array baru
|
||||
$arraykelulusan = [];
|
||||
$dateTo = Carbon::now()->toDateString();
|
||||
$dateFrom = Carbon::now()->subDays(30)->toDateString();
|
||||
|
||||
$kelulusan = Http::withOptions(['verify' => config('app.api_verify')])->get(config('api.url') . '/api/ListOperasiKelulusan/')->json();
|
||||
return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'dateFrom', 'dateTo'));
|
||||
}
|
||||
|
||||
public function kelulusanrekodlepasData(Request $request)
|
||||
{
|
||||
$params = [
|
||||
'date_from' => $request->query('date_from'),
|
||||
'date_to' => $request->query('date_to'),
|
||||
'search' => $request->query('search'),
|
||||
];
|
||||
|
||||
foreach ($kelulusan as $index => $k) {
|
||||
foreach ($k['kelulusanall'] as $index1 => $kel) {
|
||||
$komen = explode("|", $kel['komen']);
|
||||
$response = Http::withOptions(['verify' => config('app.api_verify')])
|
||||
->get(config('api.url') . '/api/ListOperasiKelulusan/', array_filter($params, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
}));
|
||||
|
||||
$results = array_search("", $komen, true);
|
||||
if ($results !== false) {
|
||||
unset($komen[$results]);
|
||||
}
|
||||
|
||||
array_push($arraykelulusan, ['norujukan' => $kel['norujukan'], 'pinjaman' => $kel['jumpinj'], 'nilaimarhun' => ($komen[1] ?? $kel['nilaimarhun'] ?? '-'), 'berat' => ($komen[2] ?? $kel['berat'] ?? '-'), 'margin' => ($komen[3] ?? $kel['margin'] ?? '-'), 'komen' => ($komen[0] ?? $kel['komen'] ?? '-'), 'status' => ($kel['status'] ?? '-'), 'pelulus' => $kel['operasi'],]);
|
||||
}
|
||||
if (!$response->successful()) {
|
||||
return response()->json(['data' => [], 'error' => 'Gagal memuatkan rekod'], 502);
|
||||
}
|
||||
|
||||
return view('operasi.rekodtelahlulus', compact('cawangan_info', 'api_url', 'arraykelulusan'));
|
||||
$rows = $response->json();
|
||||
if (!is_array($rows)) {
|
||||
$rows = [];
|
||||
}
|
||||
|
||||
return response()->json(['data' => $rows]);
|
||||
}
|
||||
|
||||
public function notifyKomoditi()
|
||||
|
||||
@@ -97,6 +97,52 @@ class PembiayaanOnlineController extends Controller
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight payload for the PC layout cutoff banner / browser notification.
|
||||
*/
|
||||
public function reminderStatus()
|
||||
{
|
||||
$cawangan_info = $this->cawanganInfo();
|
||||
if (!$this->isPembiayaanOnlineEnabled($cawangan_info)) {
|
||||
return response()->json(['enabled' => false]);
|
||||
}
|
||||
|
||||
$kodcaw = $this->kodcaw();
|
||||
$api_url = config('api.url');
|
||||
|
||||
$queue = $this->api()
|
||||
->get($api_url . '/api/pembiayaan-online/' . $kodcaw . '/queue')
|
||||
->json();
|
||||
$modal = $this->api()
|
||||
->get($api_url . '/api/modal-online/' . $kodcaw)
|
||||
->json();
|
||||
if (!is_array($queue)) {
|
||||
$queue = [];
|
||||
}
|
||||
if (!is_array($modal)) {
|
||||
$modal = [];
|
||||
}
|
||||
|
||||
$slot = (is_array($queue) && isset($queue['slot_cadangan']) && is_array($queue['slot_cadangan']))
|
||||
? $queue['slot_cadangan']
|
||||
: [];
|
||||
|
||||
return response()->json([
|
||||
'enabled' => true,
|
||||
'ditutup' => (int) ($modal['ditutup'] ?? 0) === 1,
|
||||
'bilangan' => (int) ($queue['bilangan'] ?? 0),
|
||||
'slot' => $slot['slot'] ?? 'BATCH1',
|
||||
'cutoff' => $slot['cutoff'] ?? '11:00',
|
||||
'cutoff_at' => $slot['cutoff_at'] ?? null,
|
||||
'server_now' => $slot['server_now'] ?? null,
|
||||
'lewat' => !empty($slot['lewat']),
|
||||
'baki_minit' => (int) ($slot['baki_minit'] ?? 0),
|
||||
'url' => route('pembiayaan.online.pc'),
|
||||
'remind_minutes' => 30,
|
||||
'last_minute' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bank/docs for a queue line.
|
||||
*/
|
||||
@@ -549,7 +595,7 @@ class PembiayaanOnlineController extends Controller
|
||||
$n = $res->json()['dikemaskini'] ?? count($ids);
|
||||
return redirect()
|
||||
->route('pembiayaan.online.pc.paid')
|
||||
->with('message', $n . ' rekod ditanda selesai semak/blast.');
|
||||
->with('message', $n . ' rekod ditanda selesai. WhatsApp notis dihantar (nombor ujian jika bukan production).');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'internal/notifications',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,568 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\StaffNotification;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
const TYPE_PEMBIAYAAN_PENDING_PC = 'pembiayaan_online.pending_pc';
|
||||
const TYPE_MODAL_PENDING_OPERASI = 'modal_online.pending_operasi';
|
||||
const TYPE_PEMBIAYAAN_BATCH_OPERASI = 'pembiayaan_online.batch_operasi';
|
||||
const TYPE_PEMBIAYAAN_REJECTED_PC = 'pembiayaan_online.rejected_pc';
|
||||
const TYPE_PEMBIAYAAN_PAID_PC = 'pembiayaan_online.paid_pc';
|
||||
const TYPE_GADAI_KELULUSAN = 'gadai.kelulusan';
|
||||
const TYPE_ANGGOTA_PENDING_OPERASI = 'anggota.pending_operasi';
|
||||
const TYPE_ALIRAN_TUNAI_PENDING = 'aliran_tunai.pending_khazanah';
|
||||
const TYPE_ALIRAN_TUNAI_DECIDED = 'aliran_tunai.decided_teller';
|
||||
const GADAI_KELULUSAN_MIN = 10000;
|
||||
|
||||
/**
|
||||
* Create one inbox row per staff matching role + branch.
|
||||
*
|
||||
* @param string $role
|
||||
* @param string $kodcaw
|
||||
* @param array $payload
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyRoleAtCawangan($role, $kodcaw, array $payload)
|
||||
{
|
||||
$users = User::query()
|
||||
->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'] : [];
|
||||
|
||||
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING && $kodcaw) {
|
||||
return $this->notifyAliranTunaiPending($kodcaw, $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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gadai >= RM10,000: inbox for PC/PPC at the branch and all operasi.
|
||||
*
|
||||
* @param string $kodcaw
|
||||
* @param array $data
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyGadaiKelulusan($kodcaw, array $data)
|
||||
{
|
||||
$pinjaman = isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0;
|
||||
if ($pinjaman < self::GADAI_KELULUSAN_MIN) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$payload = $this->payloadForType(self::TYPE_GADAI_KELULUSAN, $kodcaw, $data);
|
||||
|
||||
$created = collect();
|
||||
foreach (['pc', 'ppc'] as $role) {
|
||||
$created = $created->merge(
|
||||
$this->notifyRoleAtCawangan($role, $kodcaw, array_merge($payload, [
|
||||
'url' => '/kelulusan',
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
return $created->merge(
|
||||
$this->notifyRole('operasi', array_merge($payload, [
|
||||
'url' => '/operasi/kelulusan',
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teller pendahuluan/serahan: inbox for PC/PPC at the branch.
|
||||
*
|
||||
* @param string $kodcaw
|
||||
* @param array $data
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyAliranTunaiPending($kodcaw, array $data)
|
||||
{
|
||||
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_PENDING, $kodcaw, $data);
|
||||
|
||||
$created = collect();
|
||||
foreach (['pc', 'ppc'] as $role) {
|
||||
$created = $created->merge($this->notifyRoleAtCawangan($role, $kodcaw, $payload));
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Khazanah lulus/batal: inbox for the teller who requested cash.
|
||||
*
|
||||
* @param string $kodcaw
|
||||
* @param string $tellerName
|
||||
* @param array $data
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function notifyAliranTunaiDecided($kodcaw, $tellerName, array $data)
|
||||
{
|
||||
$tellerName = trim((string) $tellerName);
|
||||
if ($tellerName === '') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$payload = $this->payloadForType(self::TYPE_ALIRAN_TUNAI_DECIDED, $kodcaw, $data);
|
||||
|
||||
$user = User::query()
|
||||
->where('name', $tellerName)
|
||||
->where('cawangan', $kodcaw)
|
||||
->first();
|
||||
|
||||
if (!$user) {
|
||||
$user = User::query()->where('name', $tellerName)->first();
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return collect([$this->notifyUser($user, $kodcaw, $user->roles, $payload)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_ALIRAN_TUNAI_PENDING) {
|
||||
$jenis = $this->aliranTunaiJenis($data);
|
||||
$amaun = $this->aliranTunaiAmaun($data, $jenis);
|
||||
$teller = isset($data['kodlaluan']) ? trim((string) $data['kodlaluan']) : '';
|
||||
$label = $jenis === 'serahan' ? 'serahan' : 'pendahuluan';
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => $jenis === 'serahan' ? 'Permohonan serahan modal' : 'Permohonan pendahuluan',
|
||||
'body' => sprintf(
|
||||
'%s mohon %s RM %s',
|
||||
$teller !== '' ? $teller : 'Teller',
|
||||
$label,
|
||||
number_format($amaun, 2)
|
||||
),
|
||||
'url' => $jenis === 'serahan' ? '/serahan' : '/pendahuluan',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_ALIRAN_TUNAI_DECIDED) {
|
||||
$jenis = $this->aliranTunaiJenis($data);
|
||||
$amaun = $this->aliranTunaiAmaun($data, $jenis);
|
||||
$lulus = isset($data['pengesahan']) && strcasecmp((string) $data['pengesahan'], 'Lulus') === 0;
|
||||
$label = 'Pendahuluan';
|
||||
if ($jenis === 'serahan') {
|
||||
$label = 'Serahan';
|
||||
} elseif ($jenis === 'all') {
|
||||
$label = 'Aliran tunai';
|
||||
}
|
||||
|
||||
if ($lulus) {
|
||||
$title = $label.' diluluskan';
|
||||
$body = sprintf('%s RM %s telah diluluskan', $label, number_format($amaun, 2));
|
||||
} else {
|
||||
$title = $label.' tidak diluluskan';
|
||||
$body = sprintf('Permohonan %s tidak diluluskan', strtolower($label));
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
'url' => '/aliran_tunai',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_ANGGOTA_PENDING_OPERASI) {
|
||||
$nokp = !empty($data['nokp']) ? $data['nokp'] : '';
|
||||
$nama = isset($data['nama']) ? trim((string) $data['nama']) : '';
|
||||
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
|
||||
$komen = isset($data['komen']) ? trim((string) $data['komen']) : '';
|
||||
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
|
||||
$who = $nama !== '' ? $nama : 'pelanggan';
|
||||
if ($nokp !== '') {
|
||||
$who .= ' ('.$nokp.')';
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Permohonan assign anggota',
|
||||
'body' => sprintf(
|
||||
'%s — %s%s%s',
|
||||
$namaCaw,
|
||||
$who,
|
||||
$teller !== '' ? ' — '.$teller : '',
|
||||
$komen !== '' ? ' — '.$komen : ''
|
||||
),
|
||||
'url' => '/operasi/kelulusan/anggota',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_GADAI_KELULUSAN) {
|
||||
$norujukan = !empty($data['norujukan']) ? $data['norujukan'] : 'gadai';
|
||||
$amaun = number_format(isset($data['pinjaman']) ? (float) $data['pinjaman'] : 0, 2);
|
||||
$teller = isset($data['teller']) ? trim((string) $data['teller']) : '';
|
||||
$namaCaw = !empty($data['cawangan_nama']) ? $data['cawangan_nama'] : $kodcaw;
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'cawangan' => $kodcaw,
|
||||
'title' => 'Kelulusan gadai melebihi RM 10,000',
|
||||
'body' => sprintf(
|
||||
'%s — %s (RM %s)%s',
|
||||
$namaCaw,
|
||||
$norujukan,
|
||||
$amaun,
|
||||
$teller !== '' ? ' — '.$teller : ''
|
||||
),
|
||||
'url' => '/kelulusan',
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return string pendahuluan|serahan|all
|
||||
*/
|
||||
protected function aliranTunaiJenis(array $data)
|
||||
{
|
||||
if (!empty($data['jenis'])) {
|
||||
$jenis = strtolower(trim((string) $data['jenis']));
|
||||
if ($jenis === 'serahan' || $jenis === 'pendahuluan' || $jenis === 'all') {
|
||||
return $jenis;
|
||||
}
|
||||
}
|
||||
|
||||
$modal = isset($data['modal']) ? (float) $data['modal'] : 0;
|
||||
$serahan = isset($data['serahan']) ? (float) $data['serahan'] : 0;
|
||||
if ($serahan > 0 && $modal <= 0) {
|
||||
return 'serahan';
|
||||
}
|
||||
|
||||
return 'pendahuluan';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param string $jenis
|
||||
* @return float
|
||||
*/
|
||||
protected function aliranTunaiAmaun(array $data, $jenis)
|
||||
{
|
||||
if (isset($data['amaun']) && is_numeric($data['amaun'])) {
|
||||
return (float) $data['amaun'];
|
||||
}
|
||||
|
||||
if ($jenis === 'serahan') {
|
||||
return isset($data['serahan']) ? (float) $data['serahan'] : 0;
|
||||
}
|
||||
|
||||
return isset($data['modal']) ? (float) $data['modal'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StaffNotification extends Model
|
||||
{
|
||||
protected $table = 'staff_notifications';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'cawangan',
|
||||
'role',
|
||||
'type',
|
||||
'title',
|
||||
'body',
|
||||
'url',
|
||||
'data',
|
||||
'read_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data' => '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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user