Compare commits

1 Commits

Author SHA1 Message Date
ismailmasseran 90c6b993da Explore/ismail (#10)
Build Docker Image / build-backend (push) Successful in 22s
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Co-authored-by: afrina <afrina@koppkb.com>
Reviewed-on: #10
2026-09-06 12:41:25 +08:00
13 changed files with 266 additions and 77 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
export IMAGE_TAG
docker compose --env-file .env.staging -f docker-compose.yml pull app
docker compose --env-file .env.staging -f docker-compose.yml up -d --no-build --remove-orphans
docker compose --env-file .env.staging -f docker-compose.yml up -d --no-build --force-recreate --remove-orphans app web
echo "✓ Deployed IMAGE_TAG=${IMAGE_TAG}"
+8 -1
View File
@@ -14,4 +14,11 @@ FLUSH PRIVILEGES;
```
Gadai=Pembiayaan
Tebus=penyelesaian penuh/ansuran
Tebus=penyelesaian penuh/ansuran
## Load db dump without using docker
```bash
docker exec -i api_arrahn_staging_mysql mysql -uroot -proot erahn_test < erahn_test.sql
```
## Disable the cawangan in arrahn_master_db
+2 -1
View File
@@ -62,7 +62,8 @@ class Customer extends Model
'telefon2',
'nota_pekerjaan',
'tagpelanggan',
'tag_anggota'
'tag_anggota',
'gambar_mykad'
];
/**
+114 -3
View File
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Audit;
use App\Marhun;
use App\BankMaster;
use App\ARFiles;
use App\Customer;
use App\Gadai;
use App\Lelong;
@@ -64,7 +65,7 @@ class CustomerController extends Controller
->orWhere('kpbaru','=',$ic)
->first();
return response()->json($customer);
return response()->json($this->withMykadPhoto($customer));
}
public function showOneCustomerByICAdmin(Request $request){
@@ -74,7 +75,7 @@ class CustomerController extends Controller
->orWhere('kpbaru','=',$request->ic)
->first();
return response()->json($customer);
return response()->json($this->withMykadPhoto($customer));
}
public function showOneCustomerByICMobile($ic){
@@ -368,6 +369,8 @@ class CustomerController extends Controller
'tujuangadai'=>$request->tujuangadai
]);
$this->attachMykadPhoto($customer, $request);
$audit = new Audit();
$audit->users = $request->teller;
$audit->actions = "Daftar Pelanggan Baru";
@@ -377,7 +380,7 @@ class CustomerController extends Controller
$audit->updated_at = $current;
$audit->save();
return response()->json($customer, 201);
return response()->json($this->withMykadPhoto($customer), 201);
}
public function customerUpdate($nokp,Request $request){
@@ -1124,6 +1127,114 @@ public function AdminsearchCustomerBySAG(Request $request)
return response()->json(['message' => 'Customer PDPA updated successfully.'], 200);
}
/**
* Save MyKad photo into arrahn_files and link it on customer.gambar_mykad.
*/
protected function attachMykadPhoto($customer, Request $request)
{
if (!$customer) {
return;
}
$raw = $this->photoBase64FromRequest($request);
if ($raw === '') {
return;
}
try {
$fileId = $this->storeMykadPhoto($raw, $request->input('photo_mime', $request->json('photo_mime')), $customer);
if (!$fileId) {
return;
}
Customer::on('mysql7')
->where('kpbaru', $customer->kpbaru)
->where('kplama', $customer->kplama)
->update(['gambar_mykad' => $fileId]);
$customer->gambar_mykad = $fileId;
} catch (\Throwable $e) {
Log::error('Failed to attach MyKad photo: ' . $e->getMessage());
}
}
protected function photoBase64FromRequest(Request $request)
{
$raw = $request->input('photo_base64');
if ($raw === null || $raw === '') {
$raw = $request->json('photo_base64');
}
return is_string($raw) ? $raw : '';
}
protected function storeMykadPhoto($raw, $mime, $customer)
{
$commaPos = strpos($raw, ',');
if ($commaPos !== false) {
$raw = substr($raw, $commaPos + 1);
}
$raw = preg_replace('/\s+/', '', $raw);
if ($raw === '') {
return null;
}
$decoded = base64_decode($raw, true);
if ($decoded === false) {
$decoded = base64_decode($raw);
}
if ($decoded === false || $decoded === '') {
Log::warning('MyKad photo skipped: invalid base64');
return null;
}
if (strlen($raw) > 400000) {
Log::warning('MyKad photo skipped: payload too large');
return null;
}
$mime = strtolower((string) ($mime ?: 'image/jpeg'));
if (!in_array($mime, ['image/jpeg', 'image/jpg', 'image/png'], true)) {
$mime = 'image/jpeg';
}
$nric = preg_replace('/[^0-9A-Za-z]/', '', (string) ($customer->kpbaru ?: ''));
$ext = $mime === 'image/png' ? 'png' : 'jpg';
$file = new ARFiles();
$file->data = $raw;
$file->filename = ($nric !== '' ? $nric : 'mykad') . '.' . $ext;
$file->type = $mime;
$file->tarikhmasa = Carbon::now()->toDateTimeString();
$file->save();
return $file->id;
}
protected function withMykadPhoto($customer)
{
if (!$customer) {
return $customer;
}
$payload = $customer->toArray();
$payload['photo_base64'] = null;
$payload['photo_mime'] = null;
if (empty($customer->gambar_mykad)) {
return $payload;
}
$file = ARFiles::on('mysql7')->where('id', $customer->gambar_mykad)->first();
if ($file && $file->data) {
$payload['photo_base64'] = $file->data;
$payload['photo_mime'] = $file->type ?: 'image/jpeg';
}
return $payload;
}
// public function createNotaArcc($kodcaw, Request $request)
// {
// $current = Carbon::now();
+4 -5
View File
@@ -147,17 +147,16 @@ class KomuditiController extends Controller
$unit_current = $komuditi_current['unit_komuditi'];
$update_unit = $unit_current - $request->unit_komuditi;
// $unit_current = round((float) $komuditi_current['unit_komuditi'], 2);
// $unit_komuditi = round((float) $request->unit_komuditi, 2);
// $update_unit = round($unit_current - $unit_komuditi, 2);
$unit_current = round((float) $komuditi_current['unit_komuditi'], 2);
$unit_komuditi = round((float) $request->unit_komuditi, 2);
$update_unit = round($unit_current - $unit_komuditi, 2);
$update_komuditi = KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([
'unit_komuditi' => $update_unit
]);
$komuditi_transaksi = KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => $request->unit_komuditi,
// 'unit_komuditi' => $unit_komuditi,
'unit_komuditi' => $unit_komuditi,
'nosijil'=>$komuditi_current['no_sijil'],
'norujukan' => $request->norujukan,
'kodcaw' => $request->kodcaw ,
+1 -1
View File
@@ -605,7 +605,7 @@ class LelongController extends Controller
if($cawangan['tagarrahnbid'] == 1){
if(env('APP_ENV') != 'production'){
$link = 'http://niklah.arrahn.com.my/api/portalApi';
$link = 'https://adminbid-kopkb.erahn.com.my/api/portalApi';
}else{
$link = 'https://adminbid-kopkb.erahn.com.my/api/portalApi';
}
@@ -593,20 +593,14 @@ class PembiayaanOnlineService
}
}
$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,
]);
list($batch, $appended) = $this->findOrCreateSlotBatch(
$mysql,
$kodcaw,
$tarikh,
$slot,
$pcBy,
$now
);
foreach ($lines as $line) {
PembiayaanOnline::on($mysql)->where('id', $line->id)->update([
@@ -619,6 +613,14 @@ class PembiayaanOnlineService
]);
}
if ($appended) {
BatchPembiayaanOnline::on($mysql)->where('id', $batch->id)->update([
'submitted_at' => $now,
]);
}
$batch = $this->refreshBatchStatus($mysql, $batch->id);
$freshLines = PembiayaanOnline::on($mysql)
->where('batch_id', $batch->id)
->orderBy('id', 'asc')
@@ -626,11 +628,12 @@ class PembiayaanOnlineService
return [
'ok' => true,
'status' => 201,
'status' => $appended ? 200 : 201,
'payload' => [
'batch' => $batch,
'lines' => $freshLines,
'slot_info' => $slotInfo,
'appended' => $appended,
],
];
});
@@ -1535,24 +1538,45 @@ class PembiayaanOnlineService
];
}
protected function nextBatchNo($mysql, $kodcaw, $tarikh, $slot)
/**
* One batch per branch / day / slot (B1-001 or B2-001). Later submits append.
*
* @return array{0:\App\BatchPembiayaanOnline,1:bool} [batch, appended]
*/
protected function findOrCreateSlotBatch($mysql, $kodcaw, $tarikh, $slot, $pcBy, $now)
{
$slotTag = $slot === 'BATCH1' ? 'B1' : 'B2';
$ymd = Carbon::parse($tarikh)->format('Ymd');
$prefix = strtoupper($kodcaw) . '-PO-' . $ymd . '-' . $slotTag . '-';
$last = BatchPembiayaanOnline::on($mysql)
$batch = BatchPembiayaanOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->where('slot', $slot)
->orderBy('id', 'desc')
->orderBy('id', 'asc')
->lockForUpdate()
->first();
$seq = 1;
if ($last && preg_match('/-(\d+)$/', $last->batch_no, $m)) {
$seq = ((int) $m[1]) + 1;
if ($batch) {
return [$batch, true];
}
return $prefix . str_pad((string) $seq, 3, '0', STR_PAD_LEFT);
$batch = BatchPembiayaanOnline::on($mysql)->create([
'batch_no' => $this->slotBatchNo($kodcaw, $tarikh, $slot),
'kodcaw' => $kodcaw,
'tarikh' => $tarikh,
'slot' => $slot,
'bilangan' => 0,
'jumlah_keseluruhan' => 0,
'status' => 'SUBMITTED',
'pc_submitted_by' => $pcBy,
'submitted_at' => $now,
]);
return [$batch, false];
}
protected function slotBatchNo($kodcaw, $tarikh, $slot)
{
$slotTag = $slot === 'BATCH1' ? 'B1' : 'B2';
$ymd = Carbon::parse($tarikh)->format('Ymd');
return strtoupper($kodcaw) . '-PO-' . $ymd . '-' . $slotTag . '-001';
}
}
+1 -1
View File
@@ -398,7 +398,7 @@ class OnePercentService
$t_gadai = Carbon::parse($gadai->t_gadai);
$t_bayaran = Carbon::parse($trans->tarikh_bayaran);
if ($t_gadai->month === $t_bayaran->month && $t_gadai->year === $t_bayaran->year) {
if (($t_gadai->month == $t_bayaran->month) && ($t_gadai->year == $t_bayaran->year)) {
return false;
}else{
return true;
+36 -34
View File
@@ -58,31 +58,34 @@ class GadaiV2
}
}
private function getKadarUpahAnggota($nokp,$total_nilai_marhun = 0,$groupid = 2) : Float{
//Log::info('DEBUG NOKP:' . $nokp);
if($nokp){
$customer = Customer::on('mysql7')
->select('tag_anggota')
->where(function($q) use ($nokp) {
$q->where('kpbaru', $nokp)
->orWhere('kplama', $nokp);
})
->first();
$customer = $customer ? $customer->toArray() : [];
if(isset($customer) && $customer['tag_anggota'] == 1){
return 0.16;
}else{
return KadarUpah::where('min', '<=', $total_nilai_marhun)
->where('max', '>=', $total_nilai_marhun)
->where('group_id', $groupid)
->value('kadarujrah');
}
}
return 0.0;
private function getKadarUpahAnggota($nokp, $total_nilai_marhun = 0, $groupid = 2): ?float
{
if (!$nokp) {
return null; // tiada nokp = tak boleh tentukan kadar
}
$customer = Customer::on('mysql7')
->select('tag_anggota')
->where(function ($q) use ($nokp) {
$q->where('kpbaru', $nokp)
->orWhere('kplama', $nokp);
})
->first();
$customer = $customer ? $customer->toArray() : [];
if (isset($customer['tag_anggota']) && $customer['tag_anggota'] == 1) {
return 0.00; // ahli — kadar 0.00 memang sah
}
$kadar = KadarUpah::where('min', '<=', $total_nilai_marhun)
->where('max', '>=', $total_nilai_marhun)
->where('group_id', $groupid)
->value('kadarujrah');
return is_null($kadar) ? null : (float) $kadar; // tak jumpa row = null (block)
}
public function createMarhun($norujukan,HttpRequest $request){
$current = Carbon::now();
$current = new Carbon();
@@ -108,19 +111,18 @@ class GadaiV2
$total_nilai_marhun = Marhun::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->sum('n_marhun');
$total_berat = Marhun::on($cawangan['mysql'])->where('norujukan','=',$norujukan)->sum('berat');
$kadar_raw = $this->getKadarUpahAnggota($request->nokp, $total_nilai_marhun, $cawangan['group_id']);
$kadar_raw = $this->getKadarUpahAnggota($request->nokp, $total_nilai_marhun, $cawangan['group_id']);
if (empty($kadar_raw) || $kadar_raw == 0) {
Log::error('[BLOCKED] Kadar upah kosong / 0', [
'nokp' => $request->nokp,
'total_nilai_marhun' => $total_nilai_marhun,
'group_id' => $cawangan['group_id'],
]);
throw new \Exception("Kadar upah kosong / 0, transaksi dibatalkan!");
}
if (is_null($kadar_raw)) {
Log::error('[BLOCKED] Kadar upah tidak dijumpai', [
'nokp' => $request->nokp,
'total_nilai_marhun' => $total_nilai_marhun,
'group_id' => $cawangan['group_id'],
]);
throw new \Exception("Kadar upah tidak dijumpai, transaksi dibatalkan!");
}
// $kadar_raw = 0.00 (ahli) akan lepas ke sini dan diteruskan
// query kadar upah lama
// $kadar_raw = KadarUpah::where('min','<=',$total_nilai_marhun)->where('max','>=',$total_nilai_marhun)->first();
@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddGambarMykadToCustomerTable extends Migration
{
/**
* Master DB (mysql7) — stores arrahn_files.id for the MyKad photo.
*
* @return void
*/
public function up()
{
if (
Schema::connection('mysql7')->hasTable('customer')
&& !Schema::connection('mysql7')->hasColumn('customer', 'gambar_mykad')
) {
Schema::connection('mysql7')->table('customer', function (Blueprint $table) {
if (Schema::connection('mysql7')->hasColumn('customer', 'tag_anggota')) {
$table->unsignedBigInteger('gambar_mykad')->nullable()->after('tag_anggota');
} else {
$table->unsignedBigInteger('gambar_mykad')->nullable();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (
Schema::connection('mysql7')->hasTable('customer')
&& Schema::connection('mysql7')->hasColumn('customer', 'gambar_mykad')
) {
Schema::connection('mysql7')->table('customer', function (Blueprint $table) {
$table->dropColumn('gambar_mykad');
});
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
#
# CI (image already in registry):
# APP_IMAGE=git.koppkb.com/KoPKB/api_arrahn IMAGE_TAG=<sha> \
# docker compose --env-file .env.staging -f docker-compose.staging.yml up -d --no-build
# docker compose --env-file .env.staging -f docker-compose.staging.yml up -d --no-build --force-recreate app web
#
# Host nginx reverse-proxies the public domain to 127.0.0.1:${HTTP_PORT}.
# See docker/nginx/host-proxy.example.conf
+1
View File
@@ -9,6 +9,7 @@ server {
client_max_body_size 50M;
location / {
rewrite ^/(.*)/$ /$1 last;
try_files $uri @php;
}
+1 -3
View File
@@ -371,11 +371,9 @@ $router->group(['prefix'=>'api'], function () use ($router){
$router->get('GetCurrentTunai/{kodcaw}',['uses' =>'TunaiController@getCurrentTunai']);
$router->get('getPastTunai/{kodcaw}',['uses' =>'TunaiController@getTunaiPast']);
$router->get('GetLatestTunai/{kodcaw}',['uses' =>'TunaiController@getLatestTunai']);
$router->get('getAddTunai/{kodcaw}/kodlaluan',['uses' =>'TunaiController@getAddTunaibyKodlaluan']);
$router->get('getAddTunai/{kodcaw}/kodlaluan/',['uses' =>'TunaiController@getAddTunaibyKodlaluan']);
$router->get('getAddTunai/{kodcaw}',['uses' =>'TunaiController@getAddTunai']);
$router->get('getAddTunai/{kodcaw}/',['uses' =>'TunaiController@getAddTunai']);
$router->get('getAddTunaiDate/{kodcaw}/',['uses' =>'TunaiController@getAddTunaiDate']);
$router->get('getAddTunai/{kodcaw}/kodlaluan/',['uses' =>'TunaiController@getAddTunaibyKodlaluan']);
$router->get('getAddTunaiLatest/{kodcaw}',['uses' =>'TunaiController@getAddTunaibyKodlaluanLatest']);
$router->put('gadai/hapus/{kodcaw}/{norujukan}', ['uses' => 'GadaiController@GadaiHapus']);
$router->put('tebus/hapus/{kodcaw}/{norujukan}', ['uses' => 'GadaiController@TebusHapus']);