bd605f398e
Build Docker Image / build-backend (push) Successful in 36s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: #6
1559 lines
51 KiB
PHP
1559 lines
51 KiB
PHP
<?php
|
|
|
|
namespace App\Services\PembiayaanOnline;
|
|
|
|
use App\ARFiles;
|
|
use App\Bank;
|
|
use App\BatchPembiayaanOnline;
|
|
use App\Cawangan;
|
|
use App\Customer;
|
|
use App\Gadai;
|
|
use App\PembiayaanOnline;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class PembiayaanOnlineService
|
|
{
|
|
const MIN_PINJAMAN = 5000.00;
|
|
const MAX_PINJAMAN = 50000.00;
|
|
const PC_QUEUE_STATUSES = ['PENDING_PC', 'REJECTED_PC'];
|
|
|
|
protected $modalOnlineService;
|
|
|
|
public function __construct(ModalOnlineService $modalOnlineService = null)
|
|
{
|
|
$this->modalOnlineService = $modalOnlineService ?: new ModalOnlineService();
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed,mysql?:string}
|
|
*/
|
|
public function resolveMysql($kodcaw)
|
|
{
|
|
return $this->modalOnlineService->resolveMysql($kodcaw);
|
|
}
|
|
|
|
protected function resolveNamaBank($kodbank)
|
|
{
|
|
if ($kodbank === null || $kodbank === '') {
|
|
return null;
|
|
}
|
|
|
|
$bank = Bank::on('mysql7')->where('id', $kodbank)->first();
|
|
if ($bank) {
|
|
return $bank->bankdesc ?? $bank->nama ?? $bank->name ?? (string) $kodbank;
|
|
}
|
|
|
|
$bank = Bank::on('mysql7')->where('bankcode', $kodbank)->first();
|
|
if ($bank) {
|
|
return $bank->bankdesc ?? $bank->nama ?? (string) $kodbank;
|
|
}
|
|
|
|
return (string) $kodbank;
|
|
}
|
|
|
|
/**
|
|
* Create online payout line: reserve float + PENDING_PC + gadai.cara_terima.
|
|
*
|
|
* @param array $data
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function store($kodcaw, array $data)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$norujukan = trim((string) ($data['norujukan'] ?? ''));
|
|
$nokp = trim((string) ($data['nokp'] ?? ''));
|
|
$pinjaman = round((float) ($data['pinjaman'] ?? 0), 2);
|
|
$kodbank = $data['kodbank'] ?? null;
|
|
$noakaun = trim((string) ($data['noakaun'] ?? ''));
|
|
$createdBy = $data['created_by'] ?? null;
|
|
$updateCustomerBank = array_key_exists('update_customer_bank', $data)
|
|
? (bool) $data['update_customer_bank']
|
|
: true;
|
|
|
|
if ($norujukan === '' || $nokp === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'norujukan dan nokp diperlukan'],
|
|
];
|
|
}
|
|
|
|
if ($pinjaman < self::MIN_PINJAMAN || $pinjaman > self::MAX_PINJAMAN) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Pembiayaan online hanya untuk RM5,000 hingga RM50,000',
|
|
'pinjaman' => $pinjaman,
|
|
],
|
|
];
|
|
}
|
|
|
|
if ($kodbank === null || $kodbank === '' || $noakaun === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'kodbank dan noakaun diperlukan'],
|
|
];
|
|
}
|
|
|
|
$customer = Customer::on($mysql)
|
|
->where('kpbaru', $nokp)
|
|
->orWhere('kplama', $nokp)
|
|
->first();
|
|
|
|
$namaPelanggan = $data['nama_pelanggan'] ?? ($customer->nama ?? null);
|
|
$nopelanggan = $data['nopelanggan'] ?? ($customer->nopelanggan ?? null);
|
|
$namaAkaun = $data['nama_akaun'] ?? $namaPelanggan;
|
|
$namaBank = $data['nama_bank'] ?? $this->resolveNamaBank($kodbank);
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use (
|
|
$mysql,
|
|
$kodcaw,
|
|
$norujukan,
|
|
$nokp,
|
|
$pinjaman,
|
|
$kodbank,
|
|
$noakaun,
|
|
$namaBank,
|
|
$namaAkaun,
|
|
$namaPelanggan,
|
|
$nopelanggan,
|
|
$createdBy,
|
|
$customer,
|
|
$updateCustomerBank
|
|
) {
|
|
$existing = PembiayaanOnline::on($mysql)
|
|
->where('norujukan', $norujukan)
|
|
->where('kodcaw', $kodcaw)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if ($existing) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 409,
|
|
'payload' => [
|
|
'message' => 'Pembiayaan online untuk SAG ini sudah wujud',
|
|
'pembiayaan_online' => $existing,
|
|
],
|
|
];
|
|
}
|
|
|
|
$gadai = Gadai::on($mysql)
|
|
->where('norujukan', '=', $norujukan)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if (!$gadai) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => [
|
|
'message' => 'Rekod gadai tidak dijumpai',
|
|
'kodcaw' => $kodcaw,
|
|
'norujukan' => $norujukan,
|
|
'hint' => 'Pastikan SAG sudah wujud dalam DB cawangan ini (daftar gadai / Terima dulu).',
|
|
],
|
|
];
|
|
}
|
|
|
|
$reserve = $this->modalOnlineService->reserveAmountOnConnection($mysql, $kodcaw, $pinjaman);
|
|
if (!$reserve['ok']) {
|
|
return $reserve;
|
|
}
|
|
|
|
$line = PembiayaanOnline::on($mysql)->create([
|
|
'norujukan' => $norujukan,
|
|
'nokp' => $nokp,
|
|
'nopelanggan' => $nopelanggan,
|
|
'nama_pelanggan' => $namaPelanggan,
|
|
'kodcaw' => $kodcaw,
|
|
'pinjaman' => $pinjaman,
|
|
'kodbank' => $kodbank,
|
|
'nama_bank' => $namaBank,
|
|
'noakaun' => $noakaun,
|
|
'nama_akaun' => $namaAkaun,
|
|
'status' => 'PENDING_PC',
|
|
'created_by' => $createdBy,
|
|
]);
|
|
|
|
Gadai::on($mysql)
|
|
->where('norujukan', '=', $norujukan)
|
|
->update(['cara_terima' => 'ONLINE']);
|
|
|
|
if ($updateCustomerBank && $customer) {
|
|
Customer::on($mysql)
|
|
->where(function ($q) use ($nokp) {
|
|
$q->where('kpbaru', $nokp)->orWhere('kplama', $nokp);
|
|
})
|
|
->update([
|
|
'kodbank' => $kodbank,
|
|
'noakaun' => $noakaun,
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 201,
|
|
'payload' => [
|
|
'pembiayaan_online' => $line,
|
|
'modal_online' => $reserve['payload'],
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('PembiayaanOnline store failed: ' . $e->getMessage(), [
|
|
'kodcaw' => $kodcaw,
|
|
'norujukan' => $norujukan,
|
|
]);
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal cipta pembiayaan online'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function show($kodcaw, $norujukan)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
|
|
$line = PembiayaanOnline::on($resolved['mysql'])
|
|
->where('kodcaw', $kodcaw)
|
|
->where('norujukan', $norujukan)
|
|
->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}
|
|
*/
|
|
public function queue($kodcaw, $status = null)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$query = PembiayaanOnline::on($mysql)->where('kodcaw', $kodcaw);
|
|
|
|
if ($status) {
|
|
if (!in_array($status, self::PC_QUEUE_STATUSES, true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Status queue tidak sah',
|
|
'allowed' => self::PC_QUEUE_STATUSES,
|
|
],
|
|
];
|
|
}
|
|
$query->where('status', $status);
|
|
} else {
|
|
$query->whereIn('status', self::PC_QUEUE_STATUSES);
|
|
}
|
|
|
|
$lines = $query->orderBy('id', 'asc')->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'kodcaw' => $kodcaw,
|
|
'slot_cadangan' => $this->suggestSlot(),
|
|
'bilangan' => $lines->count(),
|
|
'jumlah' => round((float) $lines->sum('pinjaman'), 2),
|
|
'data' => $lines,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array $input raw request fields
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function updateLine($kodcaw, $id, array $input)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$line = PembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->where('id', $id)
|
|
->first();
|
|
|
|
if (!$line) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Pembiayaan online tidak dijumpai'],
|
|
];
|
|
}
|
|
|
|
if (!in_array($line->status, self::PC_QUEUE_STATUSES, true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Hanya rekod PENDING_PC / REJECTED_PC boleh dikemaskini',
|
|
'status' => $line->status,
|
|
],
|
|
];
|
|
}
|
|
|
|
$fields = [];
|
|
$has = function ($key) use ($input) {
|
|
return array_key_exists($key, $input);
|
|
};
|
|
|
|
if ($has('kodbank')) {
|
|
$fields['kodbank'] = $input['kodbank'];
|
|
if (!$has('nama_bank')) {
|
|
$fields['nama_bank'] = $this->resolveNamaBank($fields['kodbank']);
|
|
}
|
|
}
|
|
if ($has('nama_bank')) {
|
|
$fields['nama_bank'] = $input['nama_bank'];
|
|
}
|
|
if ($has('noakaun')) {
|
|
$fields['noakaun'] = trim((string) $input['noakaun']);
|
|
}
|
|
if ($has('nama_akaun')) {
|
|
$fields['nama_akaun'] = $input['nama_akaun'];
|
|
}
|
|
if ($has('nota')) {
|
|
$fields['nota'] = $input['nota'];
|
|
}
|
|
if ($has('bukti_akaun_file_id')) {
|
|
$fields['bukti_akaun_file_id'] = $input['bukti_akaun_file_id'];
|
|
}
|
|
if ($has('borang_file_id')) {
|
|
$fields['borang_file_id'] = $input['borang_file_id'];
|
|
}
|
|
|
|
if (empty($fields)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'Tiada medan untuk dikemaskini'],
|
|
];
|
|
}
|
|
|
|
if (isset($fields['kodbank']) && ($fields['kodbank'] === null || $fields['kodbank'] === '')) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'kodbank tidak sah'],
|
|
];
|
|
}
|
|
if (array_key_exists('noakaun', $fields) && $fields['noakaun'] === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'noakaun tidak sah'],
|
|
];
|
|
}
|
|
|
|
$updateCustomerBank = array_key_exists('update_customer_bank', $input)
|
|
? (bool) $input['update_customer_bank']
|
|
: true;
|
|
|
|
try {
|
|
DB::connection($mysql)->transaction(function () use ($mysql, $line, $fields, $updateCustomerBank) {
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update($fields);
|
|
|
|
if (
|
|
$updateCustomerBank
|
|
&& (isset($fields['kodbank']) || isset($fields['noakaun']))
|
|
) {
|
|
$kodbank = $fields['kodbank'] ?? $line->kodbank;
|
|
$noakaun = $fields['noakaun'] ?? $line->noakaun;
|
|
Customer::on($mysql)
|
|
->where(function ($q) use ($line) {
|
|
$q->where('kpbaru', $line->nokp)->orWhere('kplama', $line->nokp);
|
|
})
|
|
->update([
|
|
'kodbank' => $kodbank,
|
|
'noakaun' => $noakaun,
|
|
]);
|
|
}
|
|
});
|
|
|
|
$fresh = PembiayaanOnline::on($mysql)->where('id', $line->id)->first();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => $fresh,
|
|
];
|
|
} catch (\Throwable $e) {
|
|
Log::error('PembiayaanOnline updateLine failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal kemaskini pembiayaan online'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function listBatches($kodcaw, $tarikh = null, $status = null)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
|
|
$query = BatchPembiayaanOnline::on($resolved['mysql'])->where('kodcaw', $kodcaw);
|
|
|
|
if ($tarikh) {
|
|
$query->where('tarikh', $tarikh);
|
|
}
|
|
if ($status) {
|
|
$query->where('status', $status);
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => $query->orderBy('id', 'desc')->get(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function showBatch($kodcaw, $batchId)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$batch = BatchPembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->where('id', $batchId)
|
|
->first();
|
|
|
|
if (!$batch) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Batch tidak dijumpai'],
|
|
];
|
|
}
|
|
|
|
$lines = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->orderBy('id', 'asc')
|
|
->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'batch' => $batch,
|
|
'lines' => $lines,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function submitBatch($kodcaw, $ids, $pcBy, $slot = null)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
if (is_string($ids)) {
|
|
$ids = array_filter(array_map('trim', explode(',', $ids)));
|
|
}
|
|
if (!is_array($ids) || count($ids) === 0) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ids diperlukan (senarai pembiayaan_online.id)'],
|
|
];
|
|
}
|
|
|
|
$ids = array_values(array_unique(array_map('intval', $ids)));
|
|
$pcBy = trim((string) $pcBy);
|
|
if ($pcBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'pc_submitted_by diperlukan'],
|
|
];
|
|
}
|
|
|
|
$slotInfo = $this->suggestSlot();
|
|
$slot = strtoupper((string) ($slot ?: $slotInfo['slot']));
|
|
if (!in_array($slot, ['BATCH1', 'BATCH2'], true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'slot mesti BATCH1 atau BATCH2'],
|
|
];
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
$tarikh = $now->toDateString();
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use (
|
|
$mysql,
|
|
$kodcaw,
|
|
$ids,
|
|
$pcBy,
|
|
$slot,
|
|
$tarikh,
|
|
$now,
|
|
$slotInfo
|
|
) {
|
|
$lines = PembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->whereIn('id', $ids)
|
|
->lockForUpdate()
|
|
->get();
|
|
|
|
if ($lines->count() !== count($ids)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Sebahagian id tidak dijumpai untuk cawangan ini',
|
|
'requested' => $ids,
|
|
'found' => $lines->pluck('id')->all(),
|
|
],
|
|
];
|
|
}
|
|
|
|
foreach ($lines as $line) {
|
|
if (!in_array($line->status, self::PC_QUEUE_STATUSES, true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Rekod bukan dalam queue PC',
|
|
'id' => $line->id,
|
|
'norujukan' => $line->norujukan,
|
|
'status' => $line->status,
|
|
],
|
|
];
|
|
}
|
|
if (empty($line->kodbank) || empty($line->noakaun)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Maklumat bank tidak lengkap',
|
|
'id' => $line->id,
|
|
'norujukan' => $line->norujukan,
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
$batchNo = $this->nextBatchNo($mysql, $kodcaw, $tarikh, $slot);
|
|
$jumlah = round((float) $lines->sum('pinjaman'), 2);
|
|
|
|
$batch = BatchPembiayaanOnline::on($mysql)->create([
|
|
'batch_no' => $batchNo,
|
|
'kodcaw' => $kodcaw,
|
|
'tarikh' => $tarikh,
|
|
'slot' => $slot,
|
|
'bilangan' => $lines->count(),
|
|
'jumlah_keseluruhan' => $jumlah,
|
|
'status' => 'SUBMITTED',
|
|
'pc_submitted_by' => $pcBy,
|
|
'submitted_at' => $now,
|
|
]);
|
|
|
|
foreach ($lines as $line) {
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
|
|
'batch_id' => $batch->id,
|
|
'status' => 'IN_BATCH',
|
|
'pc_submitted_by' => $pcBy,
|
|
'reject_reason' => null,
|
|
'rejected_by' => null,
|
|
'rejected_at' => null,
|
|
]);
|
|
}
|
|
|
|
$freshLines = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->orderBy('id', 'asc')
|
|
->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 201,
|
|
'payload' => [
|
|
'batch' => $batch,
|
|
'lines' => $freshLines,
|
|
'slot_info' => $slotInfo,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('PembiayaanOnline submitBatch failed: ' . $e->getMessage(), [
|
|
'kodcaw' => $kodcaw,
|
|
]);
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal hantar batch pembiayaan online'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiListBatches($statusFilter = 'SUBMITTED', $tarikh = null)
|
|
{
|
|
$allowed = ['SUBMITTED', 'PARTIAL', 'OPS_DONE', 'PAID', 'all'];
|
|
if (!in_array($statusFilter, $allowed, true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'status tidak sah',
|
|
'allowed' => $allowed,
|
|
],
|
|
];
|
|
}
|
|
|
|
$cawanganAll = Cawangan::on('mysql7')->get();
|
|
$batches = [];
|
|
|
|
foreach ($cawanganAll as $cawangan) {
|
|
try {
|
|
$query = BatchPembiayaanOnline::on($cawangan['mysql'])
|
|
->where('kodcaw', $cawangan['kodcaw']);
|
|
|
|
if ($statusFilter !== 'all') {
|
|
$query->where('status', $statusFilter);
|
|
} else {
|
|
$query->whereIn('status', ['SUBMITTED', 'PARTIAL', 'OPS_DONE', 'PAID']);
|
|
}
|
|
|
|
if ($tarikh) {
|
|
$query->where('tarikh', $tarikh);
|
|
}
|
|
|
|
foreach ($query->orderBy('id', 'desc')->get() as $batch) {
|
|
$row = $batch->toArray();
|
|
$row['cawangan_nama'] = $cawangan->nama_cawangan;
|
|
$batches[] = $row;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::warning('operasiListBatches skip ' . $cawangan['kodcaw'] . ': ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
usort($batches, function ($a, $b) {
|
|
return ($b['id'] ?? 0) <=> ($a['id'] ?? 0);
|
|
});
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'bilangan' => count($batches),
|
|
'data' => $batches,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiApproveLine($kodcaw, $id, $opsBy)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$opsBy = trim((string) $opsBy);
|
|
if ($opsBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ops_by diperlukan'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $id, $opsBy) {
|
|
$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 (!in_array($line->status, ['IN_BATCH', 'OPS_REVIEW'], true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Hanya rekod IN_BATCH / OPS_REVIEW boleh diluluskan',
|
|
'status' => $line->status,
|
|
],
|
|
];
|
|
}
|
|
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
|
|
'status' => 'APPROVED',
|
|
'ops_by' => $opsBy,
|
|
'reject_reason' => null,
|
|
'rejected_by' => null,
|
|
'rejected_at' => null,
|
|
]);
|
|
|
|
$batch = null;
|
|
if ($line->batch_id) {
|
|
$batch = $this->refreshBatchStatus($mysql, $line->batch_id);
|
|
}
|
|
|
|
$fresh = PembiayaanOnline::on($mysql)->where('id', $line->id)->first();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'pembiayaan_online' => $fresh,
|
|
'batch' => $batch,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('operasiApproveLine failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal lulus pembiayaan online'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiRejectLine($kodcaw, $id, $opsBy, $reason)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$opsBy = trim((string) $opsBy);
|
|
$reason = trim((string) $reason);
|
|
|
|
if ($opsBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ops_by diperlukan'],
|
|
];
|
|
}
|
|
if ($reason === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'reject_reason diperlukan'],
|
|
];
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $id, $opsBy, $reason, $now) {
|
|
$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 (!in_array($line->status, ['IN_BATCH', 'OPS_REVIEW', 'APPROVED'], true)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Status semasa tidak boleh ditolak',
|
|
'status' => $line->status,
|
|
],
|
|
];
|
|
}
|
|
|
|
if ($line->status === 'PAID') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'Rekod sudah PAID, tidak boleh ditolak'],
|
|
];
|
|
}
|
|
|
|
$batchId = $line->batch_id;
|
|
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
|
|
'status' => 'REJECTED_PC',
|
|
'reject_reason' => $reason,
|
|
'rejected_by' => $opsBy,
|
|
'rejected_at' => $now,
|
|
'ops_by' => $opsBy,
|
|
'batch_id' => null,
|
|
]);
|
|
|
|
$batch = null;
|
|
if ($batchId) {
|
|
$batch = $this->refreshBatchStatus($mysql, $batchId);
|
|
}
|
|
|
|
$fresh = PembiayaanOnline::on($mysql)->where('id', $line->id)->first();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'pembiayaan_online' => $fresh,
|
|
'batch' => $batch,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('operasiRejectLine failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal tolak pembiayaan online'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiApproveBatch($kodcaw, $batchId, $opsBy)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$opsBy = trim((string) $opsBy);
|
|
if ($opsBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ops_by diperlukan'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $batchId, $opsBy) {
|
|
$batch = BatchPembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->where('id', $batchId)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if (!$batch) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Batch tidak dijumpai'],
|
|
];
|
|
}
|
|
|
|
$updated = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->whereIn('status', ['IN_BATCH', 'OPS_REVIEW'])
|
|
->update([
|
|
'status' => 'APPROVED',
|
|
'ops_by' => $opsBy,
|
|
'reject_reason' => null,
|
|
'rejected_by' => null,
|
|
'rejected_at' => null,
|
|
]);
|
|
|
|
$batch = $this->refreshBatchStatus($mysql, $batch->id);
|
|
$lines = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->orderBy('id', 'asc')
|
|
->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'diluluskan' => $updated,
|
|
'batch' => $batch,
|
|
'lines' => $lines,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('operasiApproveBatch failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal lulus batch'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Operasi mark one APPROVED line as PAID + upload bukti bayaran.
|
|
*
|
|
* @param array $data ops_by, no_rujukan_bayaran, tarikh_bayaran?,
|
|
* bukti_file_id? OR bukti_data + bukti_filename + bukti_type (base64 like CIT)
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiMarkPaid($kodcaw, $id, array $data)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$opsBy = trim((string) ($data['ops_by'] ?? ''));
|
|
$noRujukanBayaran = trim((string) ($data['no_rujukan_bayaran'] ?? ''));
|
|
$tarikhBayaran = $data['tarikh_bayaran'] ?? Carbon::now()->toDateString();
|
|
|
|
if ($opsBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ops_by diperlukan'],
|
|
];
|
|
}
|
|
if ($noRujukanBayaran === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'no_rujukan_bayaran diperlukan'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
Carbon::parse($tarikhBayaran);
|
|
} catch (\Throwable $e) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'tarikh_bayaran tidak sah'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use (
|
|
$mysql,
|
|
$kodcaw,
|
|
$id,
|
|
$opsBy,
|
|
$noRujukanBayaran,
|
|
$tarikhBayaran,
|
|
$data
|
|
) {
|
|
$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 === 'PAID') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 409,
|
|
'payload' => [
|
|
'message' => 'Rekod sudah PAID',
|
|
'pembiayaan_online' => $line,
|
|
],
|
|
];
|
|
}
|
|
|
|
if ($line->status !== 'APPROVED') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Hanya rekod APPROVED boleh ditanda PAID',
|
|
'status' => $line->status,
|
|
],
|
|
];
|
|
}
|
|
|
|
$buktiFileId = $data['bukti_file_id'] ?? null;
|
|
if (empty($buktiFileId)) {
|
|
$saved = $this->storeBuktiFile($data);
|
|
if (!$saved['ok']) {
|
|
return $saved;
|
|
}
|
|
$buktiFileId = $saved['payload']['id'];
|
|
} else {
|
|
$exists = ARFiles::on('mysql7')->where('id', $buktiFileId)->first();
|
|
if (!$exists) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'bukti_file_id tidak dijumpai'],
|
|
];
|
|
}
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
|
|
'status' => 'PAID',
|
|
'bukti_file_id' => $buktiFileId,
|
|
'no_rujukan_bayaran' => $noRujukanBayaran,
|
|
'tarikh_bayaran' => $tarikhBayaran,
|
|
'paid_at' => $now,
|
|
'ops_by' => $opsBy,
|
|
]);
|
|
|
|
$batch = null;
|
|
if ($line->batch_id) {
|
|
$batch = $this->refreshBatchStatus($mysql, $line->batch_id);
|
|
}
|
|
|
|
$fresh = PembiayaanOnline::on($mysql)->where('id', $line->id)->first();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'pembiayaan_online' => $fresh,
|
|
'batch' => $batch,
|
|
'bukti_file_id' => $buktiFileId,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('operasiMarkPaid failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal tanda PAID / muat naik bukti'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark all APPROVED lines in a batch as PAID (shared bukti + rujukan).
|
|
*
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function operasiMarkPaidBatch($kodcaw, $batchId, array $data)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
$opsBy = trim((string) ($data['ops_by'] ?? ''));
|
|
$noRujukanBayaran = trim((string) ($data['no_rujukan_bayaran'] ?? ''));
|
|
$tarikhBayaran = $data['tarikh_bayaran'] ?? Carbon::now()->toDateString();
|
|
|
|
if ($opsBy === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ops_by diperlukan'],
|
|
];
|
|
}
|
|
if ($noRujukanBayaran === '') {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'no_rujukan_bayaran diperlukan'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
Carbon::parse($tarikhBayaran);
|
|
} catch (\Throwable $e) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'tarikh_bayaran tidak sah'],
|
|
];
|
|
}
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use (
|
|
$mysql,
|
|
$kodcaw,
|
|
$batchId,
|
|
$opsBy,
|
|
$noRujukanBayaran,
|
|
$tarikhBayaran,
|
|
$data
|
|
) {
|
|
$batch = BatchPembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->where('id', $batchId)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if (!$batch) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Batch tidak dijumpai'],
|
|
];
|
|
}
|
|
|
|
$lines = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->where('status', 'APPROVED')
|
|
->lockForUpdate()
|
|
->get();
|
|
|
|
if ($lines->isEmpty()) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'Tiada rekod APPROVED dalam batch ini'],
|
|
];
|
|
}
|
|
|
|
$buktiFileId = $data['bukti_file_id'] ?? null;
|
|
if (empty($buktiFileId)) {
|
|
$saved = $this->storeBuktiFile($data);
|
|
if (!$saved['ok']) {
|
|
return $saved;
|
|
}
|
|
$buktiFileId = $saved['payload']['id'];
|
|
} else {
|
|
$exists = ARFiles::on('mysql7')->where('id', $buktiFileId)->first();
|
|
if (!$exists) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'bukti_file_id tidak dijumpai'],
|
|
];
|
|
}
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
$ids = $lines->pluck('id')->all();
|
|
|
|
PembiayaanOnline::on($mysql)->whereIn('id', $ids)->update([
|
|
'status' => 'PAID',
|
|
'bukti_file_id' => $buktiFileId,
|
|
'no_rujukan_bayaran' => $noRujukanBayaran,
|
|
'tarikh_bayaran' => $tarikhBayaran,
|
|
'paid_at' => $now,
|
|
'ops_by' => $opsBy,
|
|
]);
|
|
|
|
$batch = $this->refreshBatchStatus($mysql, $batch->id);
|
|
$freshLines = PembiayaanOnline::on($mysql)
|
|
->where('batch_id', $batch->id)
|
|
->orderBy('id', 'asc')
|
|
->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'dibayar' => count($ids),
|
|
'bukti_file_id' => $buktiFileId,
|
|
'batch' => $batch,
|
|
'lines' => $freshLines,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('operasiMarkPaidBatch failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal tanda PAID batch'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get bukti metadata/content for a paid (or any) line.
|
|
*
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function getBukti($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_file_id)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Bukti bayaran belum dimuat naik'],
|
|
];
|
|
}
|
|
|
|
$file = ARFiles::on('mysql7')->where('id', $line->bukti_file_id)->first();
|
|
if (!$file) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Fail bukti tidak dijumpai'],
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'pembiayaan_online_id' => $line->id,
|
|
'norujukan' => $line->norujukan,
|
|
'no_rujukan_bayaran' => $line->no_rujukan_bayaran,
|
|
'tarikh_bayaran' => $line->tarikh_bayaran,
|
|
'bukti' => [
|
|
'id' => $file->id,
|
|
'filename' => $file->filename,
|
|
'type' => $file->type,
|
|
'tarikhmasa' => $file->tarikhmasa,
|
|
'data' => $file->data,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* PC list of PAID lines awaiting blast / tally.
|
|
*
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function paidQueue($kodcaw, $blastSent = null)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
|
|
$query = PembiayaanOnline::on($resolved['mysql'])
|
|
->where('kodcaw', $kodcaw)
|
|
->where('status', 'PAID');
|
|
|
|
if ($blastSent !== null && $blastSent !== '') {
|
|
$query->where('blast_sent', (int) $blastSent);
|
|
}
|
|
|
|
$lines = $query->orderBy('paid_at', 'desc')->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'kodcaw' => $kodcaw,
|
|
'bilangan' => $lines->count(),
|
|
'jumlah' => round((float) $lines->sum('pinjaman'), 2),
|
|
'data' => $lines,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* PC marks PAID line(s) as tallied / blast done (blast_sent=1).
|
|
* Actual WhatsApp/SMS dispatch can be wired later.
|
|
*
|
|
* @param array|int|string $ids
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
public function markBlastSent($kodcaw, $ids, $blastBy = null)
|
|
{
|
|
$resolved = $this->resolveMysql($kodcaw);
|
|
if (!$resolved['ok']) {
|
|
return $resolved;
|
|
}
|
|
$mysql = $resolved['mysql'];
|
|
|
|
if (!is_array($ids)) {
|
|
$ids = array_filter(explode(',', (string) $ids));
|
|
}
|
|
$ids = array_values(array_unique(array_map('intval', $ids)));
|
|
if (count($ids) === 0) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => ['message' => 'ids diperlukan'],
|
|
];
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
$updated = 0;
|
|
|
|
try {
|
|
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $ids, $blastBy, $now, &$updated) {
|
|
$lines = PembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->whereIn('id', $ids)
|
|
->where('status', 'PAID')
|
|
->lockForUpdate()
|
|
->get();
|
|
|
|
if ($lines->isEmpty()) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 404,
|
|
'payload' => ['message' => 'Tiada rekod PAID dijumpai untuk ids diberi'],
|
|
];
|
|
}
|
|
|
|
foreach ($lines as $line) {
|
|
$nota = $line->nota;
|
|
if ($blastBy) {
|
|
$tag = '[TALLY/BLAST ' . $now->toDateTimeString() . ' oleh ' . $blastBy . ']';
|
|
$nota = trim((string) $nota);
|
|
$nota = $nota === '' ? $tag : ($nota . "\n" . $tag);
|
|
}
|
|
|
|
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
|
|
'blast_sent' => 1,
|
|
'nota' => $nota,
|
|
]);
|
|
$updated++;
|
|
}
|
|
|
|
$fresh = PembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->whereIn('id', $lines->pluck('id')->all())
|
|
->orderBy('id', 'asc')
|
|
->get();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'dikemaskini' => $updated,
|
|
'data' => $fresh,
|
|
],
|
|
];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
Log::error('markBlastSent failed: ' . $e->getMessage());
|
|
return [
|
|
'ok' => false,
|
|
'status' => 500,
|
|
'payload' => ['message' => 'Gagal tanda blast/tally'],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store bukti into arrahn_files (mysql7), same pattern as CIT.
|
|
*
|
|
* @return array{ok:bool,status:int,payload:mixed}
|
|
*/
|
|
protected function storeBuktiFile(array $data)
|
|
{
|
|
$buktiData = $data['bukti_data'] ?? $data['data'] ?? null;
|
|
$filename = $data['bukti_filename'] ?? $data['filename'] ?? null;
|
|
$type = $data['bukti_type'] ?? $data['type'] ?? null;
|
|
|
|
if (empty($buktiData) || empty($filename) || empty($type)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => 422,
|
|
'payload' => [
|
|
'message' => 'Bukti diperlukan: bukti_data, bukti_filename, bukti_type (atau bukti_file_id)',
|
|
],
|
|
];
|
|
}
|
|
|
|
$file = new ARFiles();
|
|
$file->data = $buktiData;
|
|
$file->filename = $filename;
|
|
$file->type = $type;
|
|
$file->tarikhmasa = Carbon::now()->toDateTimeString();
|
|
$file->save();
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 200,
|
|
'payload' => [
|
|
'id' => $file->id,
|
|
'filename' => $file->filename,
|
|
'type' => $file->type,
|
|
],
|
|
];
|
|
}
|
|
|
|
protected function refreshBatchStatus($mysql, $batchId)
|
|
{
|
|
$batch = BatchPembiayaanOnline::on($mysql)->where('id', $batchId)->first();
|
|
if (!$batch) {
|
|
return null;
|
|
}
|
|
|
|
$lines = PembiayaanOnline::on($mysql)->where('batch_id', $batchId)->get();
|
|
|
|
$bilangan = $lines->count();
|
|
$jumlah = round((float) $lines->sum('pinjaman'), 2);
|
|
$statuses = $lines->pluck('status')->unique()->values()->all();
|
|
|
|
if ($bilangan === 0) {
|
|
$status = 'OPS_DONE';
|
|
} elseif (count(array_diff($statuses, ['PAID'])) === 0) {
|
|
$status = 'PAID';
|
|
} elseif (count(array_diff($statuses, ['APPROVED', 'PAID'])) === 0) {
|
|
$status = 'OPS_DONE';
|
|
} elseif (in_array('IN_BATCH', $statuses, true) || in_array('OPS_REVIEW', $statuses, true)) {
|
|
$hasDecided = count(array_intersect($statuses, ['APPROVED', 'PAID'])) > 0;
|
|
$status = $hasDecided ? 'PARTIAL' : 'SUBMITTED';
|
|
} else {
|
|
$status = 'PARTIAL';
|
|
}
|
|
|
|
BatchPembiayaanOnline::on($mysql)->where('id', $batchId)->update([
|
|
'bilangan' => $bilangan,
|
|
'jumlah_keseluruhan' => $jumlah,
|
|
'status' => $status,
|
|
]);
|
|
|
|
return BatchPembiayaanOnline::on($mysql)->where('id', $batchId)->first();
|
|
}
|
|
|
|
public function suggestSlot()
|
|
{
|
|
$now = Carbon::now();
|
|
$mins = ((int) $now->format('G')) * 60 + (int) $now->format('i');
|
|
|
|
if ($mins < 11 * 60) {
|
|
return [
|
|
'slot' => 'BATCH1',
|
|
'cutoff' => '11:00',
|
|
'proses' => '12:30',
|
|
'lewat' => false,
|
|
'nota' => 'Dalam waktu Batch 1',
|
|
];
|
|
}
|
|
|
|
if ($mins < 15 * 60) {
|
|
return [
|
|
'slot' => 'BATCH2',
|
|
'cutoff' => '15:00',
|
|
'proses' => '16:30',
|
|
'lewat' => false,
|
|
'nota' => 'Dalam waktu Batch 2',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'slot' => 'BATCH2',
|
|
'cutoff' => '15:00',
|
|
'proses' => '16:30 (hari berikutnya jika sudah lewat)',
|
|
'lewat' => true,
|
|
'nota' => 'Selepas cutoff Batch 2 — masih boleh hantar; Operasi mungkin proses esok',
|
|
];
|
|
}
|
|
|
|
protected function nextBatchNo($mysql, $kodcaw, $tarikh, $slot)
|
|
{
|
|
$slotTag = $slot === 'BATCH1' ? 'B1' : 'B2';
|
|
$ymd = Carbon::parse($tarikh)->format('Ymd');
|
|
$prefix = strtoupper($kodcaw) . '-PO-' . $ymd . '-' . $slotTag . '-';
|
|
|
|
$last = BatchPembiayaanOnline::on($mysql)
|
|
->where('kodcaw', $kodcaw)
|
|
->where('tarikh', $tarikh)
|
|
->where('slot', $slot)
|
|
->orderBy('id', 'desc')
|
|
->first();
|
|
|
|
$seq = 1;
|
|
if ($last && preg_match('/-(\d+)$/', $last->batch_no, $m)) {
|
|
$seq = ((int) $m[1]) + 1;
|
|
}
|
|
|
|
return $prefix . str_pad((string) $seq, 3, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|