DONE: add line verification for pc/ppc to check customer detail, attach bank proof and sag preview on pc and operasi, printable list online payment using datatable component, set minimum_pinjaman to 1

This commit is contained in:
ISMAIL MASSERAN
2026-09-17 14:21:16 +08:00
parent 0b9ec1c0ee
commit 510dc7dd6a
5 changed files with 386 additions and 5 deletions
@@ -35,6 +35,14 @@ class PembiayaanOnlineController extends Controller
return $this->respond($this->service->show($kodcaw, $norujukan)); return $this->respond($this->service->show($kodcaw, $norujukan));
} }
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}
*/
public function showLine($kodcaw, $id)
{
return $this->respond($this->service->showLine($kodcaw, $id));
}
/** /**
* GET /api/pembiayaan-online/{kodcaw}/queue * GET /api/pembiayaan-online/{kodcaw}/queue
*/ */
@@ -53,6 +61,7 @@ class PembiayaanOnlineController extends Controller
foreach ([ foreach ([
'kodbank', 'noakaun', 'nama_bank', 'nama_akaun', 'nota', 'kodbank', 'noakaun', 'nama_bank', 'nama_akaun', 'nota',
'bukti_akaun_file_id', 'borang_file_id', 'update_customer_bank', 'bukti_akaun_file_id', 'borang_file_id', 'update_customer_bank',
'bukti_akaun_data', 'bukti_akaun_filename', 'bukti_akaun_type',
] as $key) { ] as $key) {
if ($request->has($key)) { if ($request->has($key)) {
$input[$key] = $request->input($key); $input[$key] = $request->input($key);
@@ -62,6 +71,26 @@ class PembiayaanOnlineController extends Controller
return $this->respond($this->service->updateLine($kodcaw, $id, $input)); return $this->respond($this->service->updateLine($kodcaw, $id, $input));
} }
/**
* POST /api/pembiayaan-online/{kodcaw}/lines/{id}/verify
*/
public function verifyLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->verifyLine(
$kodcaw,
$id,
$request->input('pc_verified_by', '')
));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/lines/{id}/unverify
*/
public function unverifyLine($kodcaw, $id)
{
return $this->respond($this->service->unverifyLine($kodcaw, $id));
}
/** /**
* GET /api/pembiayaan-online/{kodcaw}/batches * GET /api/pembiayaan-online/{kodcaw}/batches
*/ */
@@ -177,6 +206,14 @@ class PembiayaanOnlineController extends Controller
return $this->respond($this->service->getBukti($kodcaw, $id)); return $this->respond($this->service->getBukti($kodcaw, $id));
} }
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}/bukti-akaun
*/
public function getBuktiAkaun($kodcaw, $id)
{
return $this->respond($this->service->getBuktiAkaun($kodcaw, $id));
}
/** /**
* GET /api/pembiayaan-online/{kodcaw}/paid?blast_sent=0 * GET /api/pembiayaan-online/{kodcaw}/paid?blast_sent=0
*/ */
+2
View File
@@ -32,6 +32,8 @@ class PembiayaanOnline extends Model
'paid_at', 'paid_at',
'created_by', 'created_by',
'pc_submitted_by', 'pc_submitted_by',
'pc_verified_by',
'pc_verified_at',
'ops_by', 'ops_by',
'nota', 'nota',
'blast_sent', 'blast_sent',
@@ -16,9 +16,11 @@ use Illuminate\Support\Facades\Log;
class PembiayaanOnlineService class PembiayaanOnlineService
{ {
const MIN_PINJAMAN = 5000.00; const MIN_PINJAMAN = 1.00;
const MAX_PINJAMAN = 50000.00; const MAX_PINJAMAN = 50000.00;
const PC_QUEUE_STATUSES = ['PENDING_PC', 'REJECTED_PC']; const PC_QUEUE_STATUSES = ['PENDING_PC', 'REJECTED_PC', 'VERIFIED_PC'];
const PC_VERIFY_FROM_STATUSES = ['PENDING_PC', 'REJECTED_PC'];
const PC_SUBMITTABLE_STATUSES = ['VERIFIED_PC'];
protected $modalOnlineService; protected $modalOnlineService;
@@ -258,6 +260,36 @@ class PembiayaanOnlineService
]; ];
} }
/**
* @return array{ok:bool,status:int,payload:mixed}
*/
public function showLine($kodcaw, $id)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$line = PembiayaanOnline::on($resolved['mysql'])
->where('kodcaw', $kodcaw)
->where('id', $id)
->first();
if (!$line) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Pembiayaan online tidak dijumpai'],
];
}
return [
'ok' => true,
'status' => 200,
'payload' => $line,
];
}
/** /**
* @return array{ok:bool,status:int,payload:mixed} * @return array{ok:bool,status:int,payload:mixed}
*/ */
@@ -332,7 +364,7 @@ class PembiayaanOnlineService
'ok' => false, 'ok' => false,
'status' => 422, 'status' => 422,
'payload' => [ 'payload' => [
'message' => 'Hanya rekod PENDING_PC / REJECTED_PC boleh dikemaskini', 'message' => 'Hanya rekod PENDING_PC / REJECTED_PC / VERIFIED_PC boleh dikemaskini',
'status' => $line->status, 'status' => $line->status,
], ],
]; ];
@@ -368,6 +400,18 @@ class PembiayaanOnlineService
$fields['borang_file_id'] = $input['borang_file_id']; $fields['borang_file_id'] = $input['borang_file_id'];
} }
if (!empty($input['bukti_akaun_data'])) {
$saved = $this->storeBuktiFile([
'data' => $input['bukti_akaun_data'],
'filename' => $input['bukti_akaun_filename'] ?? $input['filename'] ?? null,
'type' => $input['bukti_akaun_type'] ?? $input['type'] ?? null,
]);
if (!$saved['ok']) {
return $saved;
}
$fields['bukti_akaun_file_id'] = $saved['payload']['id'];
}
if (empty($fields)) { if (empty($fields)) {
return [ return [
'ok' => false, 'ok' => false,
@@ -395,6 +439,12 @@ class PembiayaanOnlineService
? (bool) $input['update_customer_bank'] ? (bool) $input['update_customer_bank']
: true; : true;
if ($line->status === 'VERIFIED_PC') {
$fields['status'] = !empty($line->reject_reason) ? 'REJECTED_PC' : 'PENDING_PC';
$fields['pc_verified_by'] = null;
$fields['pc_verified_at'] = null;
}
try { try {
DB::connection($mysql)->transaction(function () use ($mysql, $line, $fields, $updateCustomerBank) { DB::connection($mysql)->transaction(function () use ($mysql, $line, $fields, $updateCustomerBank) {
PembiayaanOnline::on($mysql)->where('id', $line->id)->update($fields); PembiayaanOnline::on($mysql)->where('id', $line->id)->update($fields);
@@ -433,6 +483,159 @@ class PembiayaanOnlineService
} }
} }
/**
* PC confirms one queue line before it can be submitted to Operasi.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function verifyLine($kodcaw, $id, $pcBy)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$pcBy = trim((string) $pcBy);
if ($pcBy === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'pc_verified_by diperlukan'],
];
}
try {
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $id, $pcBy) {
$line = PembiayaanOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('id', $id)
->lockForUpdate()
->first();
if (!$line) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Pembiayaan online tidak dijumpai'],
];
}
if ($line->status === 'VERIFIED_PC') {
return [
'ok' => true,
'status' => 200,
'payload' => $line,
];
}
if (!in_array($line->status, self::PC_VERIFY_FROM_STATUSES, true)) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya rekod PENDING_PC / REJECTED_PC boleh disemak',
'status' => $line->status,
],
];
}
if (empty($line->kodbank) || empty($line->noakaun)) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Maklumat bank tidak lengkap. Kemaskini bank sebelum semak.',
'id' => $line->id,
'norujukan' => $line->norujukan,
],
];
}
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
'status' => 'VERIFIED_PC',
'pc_verified_by' => $pcBy,
'pc_verified_at' => Carbon::now(),
]);
return [
'ok' => true,
'status' => 200,
'payload' => PembiayaanOnline::on($mysql)->where('id', $line->id)->first(),
];
});
} catch (\Throwable $e) {
Log::error('PembiayaanOnline verifyLine failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal semak pembiayaan online'],
];
}
}
/**
* Undo PC verification so the line must be checked again.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function unverifyLine($kodcaw, $id)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
try {
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $id) {
$line = PembiayaanOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('id', $id)
->lockForUpdate()
->first();
if (!$line) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Pembiayaan online tidak dijumpai'],
];
}
if ($line->status !== 'VERIFIED_PC') {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya rekod VERIFIED_PC boleh dibatalkan semakan',
'status' => $line->status,
],
];
}
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
'status' => !empty($line->reject_reason) ? 'REJECTED_PC' : 'PENDING_PC',
'pc_verified_by' => null,
'pc_verified_at' => null,
]);
return [
'ok' => true,
'status' => 200,
'payload' => PembiayaanOnline::on($mysql)->where('id', $line->id)->first(),
];
});
} catch (\Throwable $e) {
Log::error('PembiayaanOnline unverifyLine failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal batal semakan pembiayaan online'],
];
}
}
/** /**
* @return array{ok:bool,status:int,payload:mixed} * @return array{ok:bool,status:int,payload:mixed}
*/ */
@@ -573,12 +776,12 @@ class PembiayaanOnlineService
} }
foreach ($lines as $line) { foreach ($lines as $line) {
if (!in_array($line->status, self::PC_QUEUE_STATUSES, true)) { if (!in_array($line->status, self::PC_SUBMITTABLE_STATUSES, true)) {
return [ return [
'ok' => false, 'ok' => false,
'status' => 422, 'status' => 422,
'payload' => [ 'payload' => [
'message' => 'Rekod bukan dalam queue PC', 'message' => 'Rekod belum disemak PC. Sahkan satu-satu sebelum hantar ke Operasi.',
'id' => $line->id, 'id' => $line->id,
'norujukan' => $line->norujukan, 'norujukan' => $line->norujukan,
'status' => $line->status, 'status' => $line->status,
@@ -869,6 +1072,8 @@ class PembiayaanOnlineService
'rejected_at' => $now, 'rejected_at' => $now,
'ops_by' => $opsBy, 'ops_by' => $opsBy,
'batch_id' => null, 'batch_id' => null,
'pc_verified_by' => null,
'pc_verified_at' => null,
]); ]);
$batch = null; $batch = null;
@@ -1314,6 +1519,65 @@ class PembiayaanOnlineService
]; ];
} }
/**
* Get attached bank-account slip (bukti_akaun_file_id) for a line.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function getBuktiAkaun($kodcaw, $id)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$line = PembiayaanOnline::on($resolved['mysql'])
->where('kodcaw', $kodcaw)
->where('id', $id)
->first();
if (!$line) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Pembiayaan online tidak dijumpai'],
];
}
if (empty($line->bukti_akaun_file_id)) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Slip bank belum dimuat naik'],
];
}
$file = ARFiles::on('mysql7')->where('id', $line->bukti_akaun_file_id)->first();
if (!$file) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Fail slip bank tidak dijumpai'],
];
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'pembiayaan_online_id' => $line->id,
'norujukan' => $line->norujukan,
'file' => [
'id' => $file->id,
'filename' => $file->filename,
'type' => $file->type,
'tarikhmasa' => $file->tarikhmasa,
'data' => $file->data,
],
],
];
}
/** /**
* PC list of PAID lines awaiting blast / tally. * PC list of PAID lines awaiting blast / tally.
* *
@@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class AddPcVerifiedToPembiayaanOnlineTable extends Migration
{
public function getActiveDB()
{
return array_filter(
array_keys(config('database.connections')),
function ($connection) {
try {
if ($connection === 'mysql7') {
return false;
}
DB::connection($connection)->getPdo();
return true;
} catch (\Exception $e) {
return false;
}
}
);
}
public function up()
{
foreach ($this->getActiveDB() as $dbase) {
if (!Schema::connection($dbase)->hasTable('pembiayaan_online')) {
continue;
}
Schema::connection($dbase)->table('pembiayaan_online', function (Blueprint $table) use ($dbase) {
if (!Schema::connection($dbase)->hasColumn('pembiayaan_online', 'pc_verified_by')) {
$table->string('pc_verified_by', 100)
->nullable()
->after('pc_submitted_by');
}
if (!Schema::connection($dbase)->hasColumn('pembiayaan_online', 'pc_verified_at')) {
$table->timestamp('pc_verified_at')
->nullable()
->after('pc_verified_by');
}
});
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
if (!Schema::connection($dbase)->hasTable('pembiayaan_online')) {
continue;
}
Schema::connection($dbase)->table('pembiayaan_online', function (Blueprint $table) use ($dbase) {
$cols = [];
if (Schema::connection($dbase)->hasColumn('pembiayaan_online', 'pc_verified_at')) {
$cols[] = 'pc_verified_at';
}
if (Schema::connection($dbase)->hasColumn('pembiayaan_online', 'pc_verified_by')) {
$cols[] = 'pc_verified_by';
}
if (!empty($cols)) {
$table->dropColumn($cols);
}
});
}
}
}
+4
View File
@@ -713,7 +713,11 @@ $router->group(['prefix'=>'api'], function () use ($router){
$router->get('pembiayaan-online/{kodcaw}/paid', ['uses' => 'PembiayaanOnlineController@paidQueue']); $router->get('pembiayaan-online/{kodcaw}/paid', ['uses' => 'PembiayaanOnlineController@paidQueue']);
$router->post('pembiayaan-online/{kodcaw}/paid/blast', ['uses' => 'PembiayaanOnlineController@markBlastSent']); $router->post('pembiayaan-online/{kodcaw}/paid/blast', ['uses' => 'PembiayaanOnlineController@markBlastSent']);
$router->put('pembiayaan-online/{kodcaw}/lines/{id}', ['uses' => 'PembiayaanOnlineController@updateLine']); $router->put('pembiayaan-online/{kodcaw}/lines/{id}', ['uses' => 'PembiayaanOnlineController@updateLine']);
$router->post('pembiayaan-online/{kodcaw}/lines/{id}/verify', ['uses' => 'PembiayaanOnlineController@verifyLine']);
$router->post('pembiayaan-online/{kodcaw}/lines/{id}/unverify', ['uses' => 'PembiayaanOnlineController@unverifyLine']);
$router->get('pembiayaan-online/{kodcaw}/lines/{id}/bukti', ['uses' => 'PembiayaanOnlineController@getBukti']); $router->get('pembiayaan-online/{kodcaw}/lines/{id}/bukti', ['uses' => 'PembiayaanOnlineController@getBukti']);
$router->get('pembiayaan-online/{kodcaw}/lines/{id}/bukti-akaun', ['uses' => 'PembiayaanOnlineController@getBuktiAkaun']);
$router->get('pembiayaan-online/{kodcaw}/lines/{id}', ['uses' => 'PembiayaanOnlineController@showLine']);
$router->get('pembiayaan-online/{kodcaw}/batches', ['uses' => 'PembiayaanOnlineController@listBatches']); $router->get('pembiayaan-online/{kodcaw}/batches', ['uses' => 'PembiayaanOnlineController@listBatches']);
$router->get('pembiayaan-online/{kodcaw}/batches/{batchId}', ['uses' => 'PembiayaanOnlineController@showBatch']); $router->get('pembiayaan-online/{kodcaw}/batches/{batchId}', ['uses' => 'PembiayaanOnlineController@showBatch']);
$router->post('pembiayaan-online/{kodcaw}/batches/submit', ['uses' => 'PembiayaanOnlineController@submitBatch']); $router->post('pembiayaan-online/{kodcaw}/batches/submit', ['uses' => 'PembiayaanOnlineController@submitBatch']);