Compare commits

29 Commits

Author SHA1 Message Date
ISMAIL MASSERAN 5793cbc664 DONE: fix trailing issue on nginx 2026-09-01 13:49:24 +08:00
ISMAIL MASSERAN adab98701f DONE: request online modal from pc to operasi, operasi can edit amount; WIP: customer detail confirmation form, retest pembiayaan online, notification if required 2026-08-26 15:31:34 +08:00
ISMAIL MASSERAN ac96050ed0 DONE: ignore db-dump folder in gitignore 2026-08-26 15:31:34 +08:00
ISMAIL MASSERAN e989f053c4 DO NOT PR THIS BRANCHgit add .!; DONE: add docker config for development 2026-08-26 15:31:24 +08:00
ismailmasseran 51860cea37 DONE: add cicd for api_arrahn (#2)
Build Docker Image / build-backend (push) Successful in 8s
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #2
2026-08-26 12:03:29 +08:00
afrina f1618c1ffd Rebate anggota (#1)
Co-authored-by: nurafrinaalimi16 <nurafrinaalimi16@gmail.com>
Reviewed-on: #1
2026-08-26 11:40:55 +08:00
nurafrinaalimi16 162f246acc function wtd baru 2026-08-03 12:43:55 +08:00
nurafrinaalimi16 95997841cf Merge remote-tracking branch 'origin/rebate-anggota' into rebate-anggota 2026-06-14 15:58:31 +08:00
nurafrinaalimi16 9ff4393313 include kplama on report marhun 2026-06-14 15:38:38 +08:00
nurafrinaalimi16 5bc5d286b4 fix figure on laporan terkumpul 2026-06-14 15:37:58 +08:00
nurafrinaalimi16 5826f92661 pdpa for koperasi 2026-06-14 15:37:25 +08:00
nurafrinaalimi16 2fa36eabf7 include kplama on report marhun 2026-06-14 15:06:24 +08:00
nurafrinaalimi16 237a5aa44b fix figure on laporan terkumpul 2026-06-14 15:05:02 +08:00
nurafrinaalimi16 e1878e31e4 pdpa for koperasi 2026-06-14 15:02:45 +08:00
nurafrinaalimi16 043c1fa83b include lahad datu on report stok audit 2025-12-23 10:21:02 +08:00
nurafrinaalimi16 b8cf2a172d fix bilangan pelanggan baru on report terkumpul 2025-12-18 10:54:32 +08:00
nurafrinaalimi16 2451969c88 disable logs kadar anggota 2025-12-14 10:37:29 +08:00
nurafrinaalimi16 bc95a60009 put excpetion if kadar upah = 0 2025-11-18 00:30:36 +08:00
nurafrinaalimi16 cb0b80428b fix kadar upah return 0 2025-11-13 23:33:11 +08:00
nurafrinaalimi16 da7883919e add column name on operasi approval 2025-11-11 22:23:37 +08:00
nurafrinaalimi16 f998d6e7dc fix error url lelong 2025-11-11 00:08:26 +08:00
nurafrinaalimi16 0aaf81f1cb fix url lelong 2025-11-11 00:05:33 +08:00
nurafrinaalimi16 42c2c80726 comment command import csv 2025-11-10 23:02:30 +08:00
nurafrinaalimi16 600cd753f0 import csv anggota koperasi 2025-11-10 19:14:29 +08:00
nurafrinaalimi16 6898a78506 route for kadar anggota 2025-11-10 19:13:29 +08:00
nurafrinaalimi16 2ebeae54ea database migration for kadar anggota koperasi 2025-11-10 19:12:26 +08:00
nurafrinaalimi16 dde4d910bb calculation for kadar anggota koperasi 2025-11-10 19:11:13 +08:00
nurafrinaalimi16 4f5edb69fb update all controllers for kadar anggota koperasi 2025-11-10 18:55:17 +08:00
nurafrinaalimi16 1075d7d585 models for kadar anggota koperasi 2025-11-10 18:51:01 +08:00
45 changed files with 4380 additions and 311 deletions
+16
View File
@@ -0,0 +1,16 @@
.git
.gitea
node_modules
vendor
.env
.env.*
!.env.example
storage/logs
storage/framework/cache
storage/framework/sessions
storage/framework/views
storage/debugbar
npm-debug.log
Dockerfile
docker-compose.yml
docker-compose.staging.yml
+66
View File
@@ -0,0 +1,66 @@
APP_NAME=Lumen
APP_ENV=local
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=Asia/Kuala_Lumpur
LOG_CHANNEL=stack
LOG_LEVEL=debug
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=erahn_xtra
DB_USERNAME=root
DB_PASSWORD=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
# DB_CONNECTION_3=mysql
# DB_HOST_3=127.0.0.1
# DB_PORT_3=3306
# DB_DATABASE_3=erahn_baling
# DB_USERNAME_3=root
# DB_PASSWORD_3=root
# DB_CONNECTION_4=mysql
# DB_HOST_4=127.0.0.1
# DB_PORT_4=3306
# DB_DATABASE_4=arrahn_ld
# DB_USERNAME_4=root
# DB_PASSWORD_4=root
# DB_CONNECTION_5=mysql
# DB_HOST_5=127.0.0.1
# DB_PORT_5=3306
# DB_DATABASE_5=erahn_segamat
# DB_USERNAME_5=root
# DB_PASSWORD_5=root
# DB_CONNECTION_6=mysql
# DB_HOST_6=127.0.0.1
# DB_PORT_6=3306
# DB_DATABASE_6=arrahn_kb2
# DB_USERNAME_6=spider
# DB_PASSWORD_6=G0dz!ll42020
# DB_CONNECTION_8=mysql
# DB_HOST_8=127.0.0.1
# DB_PORT_8=3306
# DB_DATABASE_8=arrahn_klanas
# DB_USERNAME_8=spider
# DB_PASSWORD_8=G0dz!ll42020
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
+84
View File
@@ -0,0 +1,84 @@
APP_NAME=Lumen
APP_ENV=staging
APP_KEY=WbPGkYNmXZ6ZQplGlvl0iZ7zyW1b09xa
APP_DEBUG=true
APP_URL=http://127.0.0.1:8090
APP_TIMEZONE=Asia/Kuala_Lumpur
# Unique host ports — change these if they collide with other containers on the server.
# Host nginx should proxy the staging domain to 127.0.0.1:${HTTP_PORT}
HTTP_PORT=8090
MYSQL_PUBLISH_PORT=3308
LOG_CHANNEL=stack
LOG_LEVEL=debug
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=erahn_xtra
DB_USERNAME=root
DB_PASSWORD=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
DB_CONNECTION_2=mysql
DB_HOST_2=mysql
DB_PORT_2=3306
DB_DATABASE_2=erahn_xtra
DB_USERNAME_2=root
DB_PASSWORD_2=root
DB_CONNECTION_3=mysql
DB_HOST_3=mysql
DB_PORT_3=3306
DB_DATABASE_3=erahn_baling
DB_USERNAME_3=root
DB_PASSWORD_3=root
DB_CONNECTION_4=mysql
DB_HOST_4=mysql
DB_PORT_4=3306
DB_DATABASE_4=arrahn_ld
DB_USERNAME_4=root
DB_PASSWORD_4=root
DB_CONNECTION_5=mysql
DB_HOST_5=mysql
DB_PORT_5=3306
DB_DATABASE_5=erahn_segamat
DB_USERNAME_5=root
DB_PASSWORD_5=root
DB_CONNECTION_7=mysql
DB_HOST_7=mysql
DB_PORT_7=3306
DB_DATABASE_7=master_erahn
DB_USERNAME_7=root
DB_PASSWORD_7=root
DB_CONNECTION_6=mysql
DB_HOST_6=mysql
DB_PORT_6=3306
DB_DATABASE_6=erahn_test
DB_USERNAME_6=root
DB_PASSWORD_6=root
# DB_CONNECTION_8=mysql
# DB_HOST_8=mysql
# DB_PORT_8=3306
# DB_DATABASE_8=arrahn_klanas
# DB_USERNAME_8=spider
# DB_PASSWORD_8=G0dz!ll42020
TENNANT_MASTER_DB=master_erahn
TENNANT_KAUNTER_DB=kaunter_erahn
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
+49
View File
@@ -0,0 +1,49 @@
name: Build Docker Image
on:
push:
branches:
- staging
tags:
- "v*"
workflow_dispatch:
jobs:
build-backend:
runs-on: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
- name: Build & push backend image
run: |
set -e
IMAGE="git.koppkb.com/kopkb/api_arrahn"
TAGS="-t ${IMAGE}:${{ gitea.sha }}"
if [ "${{ gitea.ref_type }}" = "tag" ] && echo "${{ gitea.ref_name }}" | grep -q '^v'; then
TAGS="${TAGS} -t ${IMAGE}:${{ gitea.ref_name }}"
fi
if [ "${{ gitea.ref_name }}" = "staging" ]; then
TAGS="${TAGS} -t ${IMAGE}:staging"
fi
echo "Building and pushing backend: ${TAGS}"
docker buildx build \
--target staging \
--platform linux/amd64 \
--cache-from type=registry,ref=${IMAGE}:buildcache,ignore-error=true \
--cache-to type=registry,ref=${IMAGE}:buildcache,mode=max \
-f docker/php/Dockerfile \
${TAGS} \
--push .
+122
View File
@@ -0,0 +1,122 @@
name: Deploy to Staging
on:
workflow_dispatch:
inputs:
image_tag:
description: "Docker image tag to deploy (commit SHA, staging, or v*)"
required: true
default: "staging"
type: string
env:
APP_IMAGE: git.koppkb.com/kopkb/api_arrahn
DEPLOY_DIR: /home/arrahn/Project/api_arrahn
jobs:
deploy:
runs-on: host
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.image_tag }}
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
- name: Verify image exists in registry
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
IMAGE="${APP_IMAGE}:${IMAGE_TAG}"
echo "Checking if image exists: ${IMAGE}"
if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
echo "ERROR: Image ${IMAGE} does not exist in registry!"
echo "Run the Build Docker Image workflow first."
exit 1
fi
echo "✓ ${IMAGE} found"
- name: Pull Docker image
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
docker pull "${APP_IMAGE}:${IMAGE_TAG}"
echo "✓ Image pulled successfully"
- name: Sync compose file and deploy
run: |
set -e
IMAGE_TAG="${{ inputs.image_tag }}"
mkdir -p "${DEPLOY_DIR}/docker/nginx" "${DEPLOY_DIR}/docker/mysql"
cp docker-compose.staging.yml "${DEPLOY_DIR}/docker-compose.yml"
cp docker/nginx/default.conf "${DEPLOY_DIR}/docker/nginx/default.conf"
cp -R docker/mysql/init "${DEPLOY_DIR}/docker/mysql/"
if [ ! -f "${DEPLOY_DIR}/.env.staging" ]; then
if [ -f .env.staging ]; then
cp .env.staging "${DEPLOY_DIR}/.env.staging"
echo "Copied .env.staging from repo (first deploy). Edit ${DEPLOY_DIR}/.env.staging on the server if needed."
else
echo "ERROR: ${DEPLOY_DIR}/.env.staging is missing on the server"
exit 1
fi
fi
cd "${DEPLOY_DIR}"
export APP_IMAGE
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
echo "✓ Deployed IMAGE_TAG=${IMAGE_TAG}"
- name: Verify deployment
run: |
set -e
cd "${DEPLOY_DIR}"
echo "Waiting for services to start..."
sleep 10
for SERVICE in app web mysql; do
STATUS=$(docker compose --env-file .env.staging -f docker-compose.yml ps --status running --format '{{.Name}}' "$SERVICE" 2>/dev/null || true)
if [ -z "$STATUS" ]; then
echo "ERROR: ${SERVICE} is not running"
docker compose --env-file .env.staging -f docker-compose.yml ps
docker compose --env-file .env.staging -f docker-compose.yml logs --tail=50 "$SERVICE" || true
exit 1
fi
echo "✓ ${SERVICE} is running (${STATUS})"
done
HTTP_PORT=$(grep -E '^HTTP_PORT=' .env.staging 2>/dev/null | cut -d= -f2- | tr -d '"' || true)
HTTP_PORT=${HTTP_PORT:-8090}
check_health() {
NAME=$1
URL=$2
for i in 1 2 3 4 5 6; do
if curl -sf "$URL" > /dev/null; then
echo "✓ ${NAME} health check passed (${URL})"
return 0
fi
echo "Waiting for ${NAME}... attempt ${i}/6"
sleep 5
done
echo "ERROR: ${NAME} health check failed (${URL})"
docker compose --env-file .env.staging -f docker-compose.yml logs --tail=50 app web || true
return 1
}
check_health "api" "http://127.0.0.1:${HTTP_PORT}/health"
+2 -1
View File
@@ -5,6 +5,7 @@ Homestead.yaml
.env .env
.phpunit.result.cache .phpunit.result.cache
ray.php ray.php
docker-compose.yml
/docker_src /docker_src
/.vscode /.vscode
**/.DS_Store
db-dump
+16
View File
@@ -1 +1,17 @@
You know what is this when you in this project You know what is this when you in this project
If you does't know what is this you probably in wrong project
```sql
--- for all ip
CREATE USER 'master_erahn'@'%' IDENTIFIED BY 'password';
--- for specific ip
CREATE USER 'kaunter_erahn'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'kaunter_erahn'@'%';
FLUSH PRIVILEGES;
```
Gadai=Pembiayaan
Tebus=penyelesaian penuh/ansuran
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BatchPembiayaanOnline extends Model
{
protected $table = 'batch_pembiayaan_online';
protected $fillable = [
'batch_no',
'kodcaw',
'tarikh',
'slot',
'bilangan',
'jumlah_keseluruhan',
'status',
'pc_submitted_by',
'submitted_at',
];
}
+11
View File
@@ -11,6 +11,17 @@ class Cawangan extends Model
public $timestamps = false; public $timestamps = false;
/**
* Display name from master table (`details_caw`).
* Keep this as the single mapping so callers do not guess nama/namacaw.
*/
public function getNamaCawanganAttribute()
{
$nama = trim((string) ($this->attributes['details_caw'] ?? ''));
return $nama !== '' ? $nama : (string) ($this->attributes['kodcaw'] ?? '');
}
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
+1
View File
@@ -25,6 +25,7 @@ class Gadai extends Model
'nilaimarhun', 'nilaimarhun',
'berat', 'berat',
'pinjaman', 'pinjaman',
'cara_terima',
'telahbayar', 'telahbayar',
'upahtelahbyr', 'upahtelahbyr',
'cajtelahbyr', 'cajtelahbyr',
+229 -239
View File
@@ -30,6 +30,7 @@ use App\Status;
use App\LookupStatus; use App\LookupStatus;
use App\ARFiles; use App\ARFiles;
use App\WTDDraft; use App\WTDDraft;
use App\WTDSimulasi;
use App\SerahBaki; use App\SerahBaki;
use App\WTD; use App\WTD;
use App\StokAudit1; use App\StokAudit1;
@@ -62,7 +63,6 @@ use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Jobs\AccountCheckJob; use App\Jobs\AccountCheckJob;
use App\Penyelarasan;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@@ -2365,7 +2365,6 @@ class AdminPageController extends Controller
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first(); $cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
//$latestbatch = SerahBaki::on($cawangan['mysql'])->where('norujukan','baru')->get()->toArray(); //$latestbatch = SerahBaki::on($cawangan['mysql'])->where('norujukan','baru')->get()->toArray();
//new added //new added
$serahBaki = SerahBaki::on($cawangan['mysql']) $serahBaki = SerahBaki::on($cawangan['mysql'])
->where('norujukan', 'baru') ->where('norujukan', 'baru')
@@ -2438,18 +2437,16 @@ class AdminPageController extends Controller
{ {
//array_push($latestbatch,['recno' => 0,'norujukan' => 'lama','batchno' => 0,'baki' => $lelong_data->sum('baki'),'aktif' => 1]); //array_push($latestbatch,['recno' => 0,'norujukan' => 'lama','batchno' => 0,'baki' => $lelong_data->sum('baki'),'aktif' => 1]);
array_push($finalDataArray,['recno' => 0,'norujukan' => 'lama','batchno' => 0,'baki' => $lelong_data->sum('baki'),'aktif' => 1]); array_push($finalDataArray,['recno' => 0,'norujukan' => 'lama','batchno' => 0,'baki' => $lelong_data->sum('baki'),'aktif' => 1]);
} }
//$data = $this->paginate($latestbatch,8); //$data = $this->paginate($latestbatch,8);
$data = $this->paginate($finalDataArray,8); $data = $this->paginate($finalDataArray,8);
//dd($data);
return response()->json($data,200); return response()->json($data,200);
} }
public function rekodWTDByBatchNo($batchno,Request $request){ public function rekodWTDByBatchNo($batchno,Request $request){
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first(); $cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
@@ -2727,7 +2724,7 @@ class AdminPageController extends Controller
'prkod' => "01", 'prkod' => "01",
'teller' => "DM", 'teller' => "DM",
'bakipjm' => ($lel['bakipjm']) ? $lel['bakipjm'] : 0, 'bakipjm' => ($lel['bakipjm']) ? $lel['bakipjm'] : 0,
'bakiupah' => ($lel['bakiupah']) ? $lel['bakiupah'] : 0, 'bakiupah' => $lel['bakiupah'],
'cajlain' => '0.00', 'cajlain' => '0.00',
'cajlelong' => $lel['cajlelong'], 'cajlelong' => $lel['cajlelong'],
'kodgl' => '', 'kodgl' => '',
@@ -2749,7 +2746,7 @@ class AdminPageController extends Controller
't_jual' => $lel['t_jual'], 't_jual' => $lel['t_jual'],
'hargajual' => $lel['hargajual'], 'hargajual' => $lel['hargajual'],
'pinjaman' => $lel['pinjaman'], 'pinjaman' => $lel['pinjaman'],
'bakiupah' => ($lel['bakiupah']) ? $lel['bakiupah'] : 0, 'bakiupah' => $lel['bakiupah'],
'cajlelong' => $lel['cajlelong'], 'cajlelong' => $lel['cajlelong'],
'gst_cl' => $lel['gst_cl'], 'gst_cl' => $lel['gst_cl'],
'baki' => $lel['baki'], 'baki' => $lel['baki'],
@@ -2779,222 +2776,6 @@ class AdminPageController extends Controller
} }
public function janaSimulasiWTD(Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
//listing sag belum tuntut cwgn
$sag_cwgn = collect(
Lelong::on($cawangan['mysql'])
->selectRaw('norujukan')
->whereYear('t_jual', $request->tahun)
->whereNull('trkhtuntut')
->where('baki', '>', 0)
->pluck('norujukan') // lebih bersih
);
//listing sag blocked
$sag_online = collect(
WTDDraft::on('mysql7')
->selectRaw('norujukan')
->where('kodcaw', '=', $request->kodcaw)
->pluck('norujukan')
);
//dd($sag_online);
//filter sag yg tk wujud dlm wtd_draft
$filtersag = $sag_cwgn->diff($sag_online);
//query baru
$tarikh_belum_tuntut = Lelong::on($cawangan['mysql'])
->selectRaw('
MONTH(t_jual) as bulan,
SUM(pinjaman) as pembiayaan,
ABS(SUM(baki)) as baki,
COUNT(DISTINCT norujukan) as jumlah_norujukan,
kodcaw
')
->whereIn('norujukan', $filtersag)
->groupBy('bulan', 'kodcaw')
->orderBy('bulan')
->get();
$tarikh_belum_tuntut = $tarikh_belum_tuntut->map(function ($item) {
$item->bulan = \Carbon\Carbon::create()->month($item->bulan)->format('F');
return $item;
});
return response()->json($tarikh_belum_tuntut);
}
public function RekodSimulasiWTD(Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
$rekod_simulasi = Lelong::on($cawangan['mysql'])
->join('gadai','lelong.norujukan','=','gadai.norujukan')
->selectRaw('ABS(lelong.baki) as baki, lelong.t_lelong, lelong.pinjaman, lelong.norujukan, gadai.nokp')
->whereMonth('lelong.t_jual', $request->month)
->whereYear('lelong.t_jual', $request->year)
->whereNull('lelong.trkhtuntut')
->where('lelong.baki', '>', 0)
->get();
return response()->json($rekod_simulasi);
}
public function prosesSimulasiWTD(Request $request)
{
$proses = $request->input;
// dd($proses, $cawangan);
$baki = 0;
$jumlahPembiayaan = 0;
$simulasi = WTDSimulasi::max('simulasi')+1;
foreach ($proses as $item) {
WTDSimulasi::create([
'bulan' => $item['bulan'],
'tahun' => $item['tahun'],
'kodcaw' => $item['kodcaw'],
'jumlah_sag' => $item['jumlah_sag'],
'pembiayaan'=> $item['pembiayaan'],
'baki'=> $item['baki'],
'simulasi' => $simulasi,
]);
$baki += $item['baki'];
$jumlahPembiayaan = $item['pembiayaan'];
}
return response()->json(['status' => 'success','simulasi' => $simulasi,'baki' => $baki,'pinjaman' => $jumlahPembiayaan]);
}
public function pengesahanSimulasiWTD(Request $request)
{
// Decode the JSON records from the request
$records = json_decode($request->records, true);
// Retrieve and clean 'kodcaw' from the request
$kodcaw = $request->kodcaw;
$kodcaw_clean = str_replace('"', '', $kodcaw);
// Get the database connection for the branch (cawangan)
$cawangan = Cawangan::where('kodcaw', $kodcaw_clean)->first();
// Check if 'Cawangan' exists
if (!$cawangan) {
return response()->json(['status' => 'error', 'message' => 'Invalid branch code (kodcaw).'], 404);
}
$cawangan_connection = $cawangan->mysql;
// Get the latest batch number and increment it
$latestBatch = SerahBaki::on($cawangan_connection)->max('batchno') + 1;
// Initialize total variables
$totalPembiayaan = 0;
$totalBaki = 0;
try {
// Iterate through the records and save them to the WTDDraft table
foreach ($records as $record) {
// Check if 'bulan' and 'tahun' exist in the record
if (!isset($record['bulan']) || !isset($record['tahun'])) {
return response()->json(['status' => 'error', 'message' => 'Missing bulan or tahun in records.'], 400);
}
$month = DateTime::createFromFormat('F', $record["bulan"])->format('n');
// Query norujukan records from the database
$query_norujukan = Lelong::on($cawangan_connection)
->join('gadai', 'lelong.norujukan', '=', 'gadai.norujukan')
->selectRaw('ABS(lelong.baki) as baki, lelong.t_lelong, lelong.pinjaman, lelong.norujukan, gadai.nokp, lelong.t_jual')
->whereMonth('lelong.t_jual', $month)
->whereYear('lelong.t_jual', $record["tahun"])
->whereNull('lelong.trkhtuntut')
->where('lelong.baki', '>', 0)
->get();
// Save each record to the WTDDraft table
foreach ($query_norujukan as $query) {
$wtddraft = new WTDDraft();
$wtddraft->nokp = $query['nokp'];
$wtddraft->kodcaw = $kodcaw_clean;
$wtddraft->norujukan = $query['norujukan'];
$wtddraft->t_jual = $query['t_jual'];
$wtddraft->t_gadai = $query['t_gadai'] ?? null; // Optional: ensure 't_gadai' exists
$wtddraft->t_lelong = $query['t_lelong'];
$wtddraft->batchno_serah = $latestBatch;
$wtddraft->save();
}
// Accumulate totals
$totalPembiayaan += $record['pembiayaan'] ?? 0;
$totalBaki += $record['baki'] ?? 0;
}
// Create a new record in the SerahBaki table
SerahBaki::on($cawangan_connection)->create([
'norujukan' => 'baru',
'batchno' => $latestBatch,
'hargajual' => 0.00,
'pinjaman' => $totalPembiayaan,
'upah' => 0.00,
'cajlel' => 0.00,
'gst_cl' => 0.00,
'gst_hj' => 0.00,
'baki' => $totalBaki,
'tarikh' => Carbon::now()->toDateString(),
't_jual' => Carbon::now()->toDateString(),
'nopelanggan' => '',
'aktif' => 0,
]);
// Return a success response with the new batch number
return response()->json(['status' => 'success', 'batchno' => $latestBatch], 200);
} catch (\Throwable $e) {
// Log the error and return a failure response
Log::error($e->getMessage());
Log::error($e->getLine());
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
}
}
public function simulasiToDraft(Request $request)
{
$bulan = $request->bulan;
if (!$bulan || count($bulan) === 0) {
return response()->json(['error' => 'Sila pilih sekurang-kurangnya satu bulan.'], 400);
}
$simulasi = WTDSimulasi::whereIn('bulan', $bulan)->get();
if ($simulasi->isEmpty()) {
return response()->json(['error' => 'Tiada data untuk bulan yang dipilih.'], 404);
}
// foreach ($simulasi as $data) {
// WTDDraft::create([
// 'nokp' => $data->nokp ?? null,
// 'kodcaw' => $data->kodcaw ?? null,
// 'norujukan' => $data->norujukan ?? null,
// 't_jual' => $data->t_jual ?? null,
// 't_gadai' => $data->t_gadai ?? null,
// 't_lelong' => $data->t_lelong ?? null,
// 'batchno_serah' => $data->simulasi, // Simpan sebagai rujukan
// ]);
// }
dd($bulan, $simulasi);
return response()->json(['message' => 'Data berjaya dipindahkan ke WTD Draft!']);
}
public function PenjanaanPC(Request $request){ public function PenjanaanPC(Request $request){
$timeloni = Carbon::now(); $timeloni = Carbon::now();
@@ -3446,8 +3227,6 @@ class AdminPageController extends Controller
return response()->json($customers); return response()->json($customers);
} }
public function AkruKeuntunganSebenar(Request $request){ public function AkruKeuntunganSebenar(Request $request){
$cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first(); $cawangan = Cawangan::on('mysql7')->where('kodcaw','=',$request->kodcaw)->first();
@@ -4045,13 +3824,13 @@ class AdminPageController extends Controller
$get_imbangduga = imbangdugabackup::on($cawangan['mysql'])->where('tarikh', $lastDayOfYear)->first(); $get_imbangduga = imbangdugabackup::on($cawangan['mysql'])->where('tarikh', $lastDayOfYear)->first();
$get_imbangduga = optional($get_imbangduga)->toArray() ?? []; $get_imbangduga = optional($get_imbangduga)->toArray() ?? [];
if(empty($get_imbangduga)){ // if(empty($get_imbangduga)){
$requestData = new Request([ // $requestData = new Request([
'tarikh' => $request->tarikh, // 'tarikh' => $request->tarikh,
'kodcaw' => $kodcaw, // 'kodcaw' => $kodcaw,
]); // ]);
$exception_case = $this->AkruKeuntunganSebenar($requestData)->getData(); // $exception_case = $this->AkruKeuntunganSebenar($requestData);
} // }
$backdate_keun_sebenar += isset($get_imbangduga['keun_sebenar']) ? $get_imbangduga['keun_sebenar'] : 0; $backdate_keun_sebenar += isset($get_imbangduga['keun_sebenar']) ? $get_imbangduga['keun_sebenar'] : 0;
$backdate_keun_sebenar += (float) ($exception_case ?? 0); $backdate_keun_sebenar += (float) ($exception_case ?? 0);
@@ -4072,13 +3851,6 @@ class AdminPageController extends Controller
if($kodcaw == '011229') if($kodcaw == '011229')
$total -= 45390.37; $total -= 45390.37;
if($kodcaw == 'KB2')
$total -= 64.8;
$total += Penyelarasan::on('mysql7')
->where('kodcaw', $kodcaw)
->sum(DB::raw('rugilelong + bakilelong + `lain-lain`'));
return response()->json($total,200); return response()->json($total,200);
} }
@@ -4250,4 +4022,222 @@ class AdminPageController extends Controller
'result' => $status === 'completed' ? $result : null, 'result' => $status === 'completed' ? $result : null,
]); ]);
} }
//function wtd baru
public function janaSimulasiWTD(Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
//listing sag belum tuntut cwgn
$sag_cwgn = collect(
Lelong::on($cawangan['mysql'])
->selectRaw('norujukan')
->whereYear('t_jual', $request->tahun)
->whereNull('trkhtuntut')
->where('baki', '>', 0)
->pluck('norujukan') // lebih bersih
);
//listing sag blocked
$sag_online = collect(
WTDDraft::on('mysql7')
->selectRaw('norujukan')
->where('kodcaw', '=', $request->kodcaw)
->pluck('norujukan')
);
//dd($sag_online);
//filter sag yg tk wujud dlm wtd_draft
$filtersag = $sag_cwgn->diff($sag_online);
//query baru
$tarikh_belum_tuntut = Lelong::on($cawangan['mysql'])
->selectRaw('
MONTH(t_jual) as bulan,
SUM(pinjaman) as pembiayaan,
ABS(SUM(baki)) as baki,
COUNT(DISTINCT norujukan) as jumlah_norujukan,
kodcaw
')
->whereIn('norujukan', $filtersag)
->groupBy('bulan', 'kodcaw')
->orderBy('bulan')
->get();
$tarikh_belum_tuntut = $tarikh_belum_tuntut->map(function ($item) {
$item->bulan = \Carbon\Carbon::create()->month($item->bulan)->format('F');
return $item;
});
return response()->json($tarikh_belum_tuntut);
}
public function RekodSimulasiWTD(Request $request)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $request->kodcaw)->first();
$rekod_simulasi = Lelong::on($cawangan['mysql'])
->join('gadai','lelong.norujukan','=','gadai.norujukan')
->selectRaw('ABS(lelong.baki) as baki, lelong.t_lelong, lelong.pinjaman, lelong.norujukan, gadai.nokp')
->whereMonth('lelong.t_jual', $request->month)
->whereYear('lelong.t_jual', $request->year)
->whereNull('lelong.trkhtuntut')
->where('lelong.baki', '>', 0)
->get();
return response()->json($rekod_simulasi);
}
public function prosesSimulasiWTD(Request $request)
{
$proses = $request->input;
// dd($proses, $cawangan);
$baki = 0;
$jumlahPembiayaan = 0;
$simulasi = WTDSimulasi::max('simulasi')+1;
foreach ($proses as $item) {
WTDSimulasi::create([
'bulan' => $item['bulan'],
'tahun' => $item['tahun'],
'kodcaw' => $item['kodcaw'],
'jumlah_sag' => $item['jumlah_sag'],
'pembiayaan'=> $item['pembiayaan'],
'baki'=> $item['baki'],
'simulasi' => $simulasi,
]);
$baki += $item['baki'];
$jumlahPembiayaan = $item['pembiayaan'];
}
return response()->json(['status' => 'success','simulasi' => $simulasi,'baki' => $baki,'pinjaman' => $jumlahPembiayaan]);
}
public function pengesahanSimulasiWTD(Request $request)
{
// Decode the JSON records from the request
$records = json_decode($request->records, true);
// Retrieve and clean 'kodcaw' from the request
$kodcaw = $request->kodcaw;
$kodcaw_clean = str_replace('"', '', $kodcaw);
// Get the database connection for the branch (cawangan)
$cawangan = Cawangan::where('kodcaw', $kodcaw_clean)->first();
// Check if 'Cawangan' exists
if (!$cawangan) {
return response()->json(['status' => 'error', 'message' => 'Invalid branch code (kodcaw).'], 404);
}
$cawangan_connection = $cawangan->mysql;
// Get the latest batch number and increment it
$latestBatch = SerahBaki::on($cawangan_connection)->max('batchno') + 1;
// Initialize total variables
$totalPembiayaan = 0;
$totalBaki = 0;
try {
// Iterate through the records and save them to the WTDDraft table
foreach ($records as $record) {
// Check if 'bulan' and 'tahun' exist in the record
if (!isset($record['bulan']) || !isset($record['tahun'])) {
return response()->json(['status' => 'error', 'message' => 'Missing bulan or tahun in records.'], 400);
}
$month = DateTime::createFromFormat('F', $record["bulan"])->format('n');
// Query norujukan records from the database
$query_norujukan = Lelong::on($cawangan_connection)
->join('gadai', 'lelong.norujukan', '=', 'gadai.norujukan')
->selectRaw('ABS(lelong.baki) as baki, lelong.t_lelong, lelong.pinjaman, lelong.norujukan, gadai.nokp, lelong.t_jual')
->whereMonth('lelong.t_jual', $month)
->whereYear('lelong.t_jual', $record["tahun"])
->whereNull('lelong.trkhtuntut')
->where('lelong.baki', '>', 0)
->get();
// Save each record to the WTDDraft table
foreach ($query_norujukan as $query) {
$wtddraft = new WTDDraft();
$wtddraft->nokp = $query['nokp'];
$wtddraft->kodcaw = $kodcaw_clean;
$wtddraft->norujukan = $query['norujukan'];
$wtddraft->t_jual = $query['t_jual'];
$wtddraft->t_gadai = $query['t_gadai'] ?? null; // Optional: ensure 't_gadai' exists
$wtddraft->t_lelong = $query['t_lelong'];
$wtddraft->batchno_serah = $latestBatch;
$wtddraft->save();
}
// Accumulate totals
$totalPembiayaan += $record['pembiayaan'] ?? 0;
$totalBaki += $record['baki'] ?? 0;
}
// Create a new record in the SerahBaki table
SerahBaki::on($cawangan_connection)->create([
'norujukan' => 'baru',
'batchno' => $latestBatch,
'hargajual' => 0.00,
'pinjaman' => $totalPembiayaan,
'upah' => 0.00,
'cajlel' => 0.00,
'gst_cl' => 0.00,
'gst_hj' => 0.00,
'baki' => $totalBaki,
'tarikh' => Carbon::now()->toDateString(),
't_jual' => Carbon::now()->toDateString(),
'nopelanggan' => '',
'aktif' => 0,
]);
// Return a success response with the new batch number
return response()->json(['status' => 'success', 'batchno' => $latestBatch], 200);
} catch (\Throwable $e) {
// Log the error and return a failure response
Log::error($e->getMessage());
Log::error($e->getLine());
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
}
}
public function simulasiToDraft(Request $request)
{
$bulan = $request->bulan;
if (!$bulan || count($bulan) === 0) {
return response()->json(['error' => 'Sila pilih sekurang-kurangnya satu bulan.'], 400);
}
$simulasi = WTDSimulasi::whereIn('bulan', $bulan)->get();
if ($simulasi->isEmpty()) {
return response()->json(['error' => 'Tiada data untuk bulan yang dipilih.'], 404);
}
// foreach ($simulasi as $data) {
// WTDDraft::create([
// 'nokp' => $data->nokp ?? null,
// 'kodcaw' => $data->kodcaw ?? null,
// 'norujukan' => $data->norujukan ?? null,
// 't_jual' => $data->t_jual ?? null,
// 't_gadai' => $data->t_gadai ?? null,
// 't_lelong' => $data->t_lelong ?? null,
// 'batchno_serah' => $data->simulasi, // Simpan sebagai rujukan
// ]);
// }
//dd($bulan, $simulasi);
return response()->json(['message' => 'Data berjaya dipindahkan ke WTD Draft!']);
}
} }
@@ -396,6 +396,7 @@ class CustomerController extends Controller
// 'nota_arcc'=>$request->nota_arcc, // 'nota_arcc'=>$request->nota_arcc,
'telefon' => $request->telefon1, 'telefon' => $request->telefon1,
'telefon2' => $request->telefon2, 'telefon2' => $request->telefon2,
'telefon2' => $request->telefon2,
)); ));
return response()->json($customer_update, 200); return response()->json($customer_update, 200);
@@ -1102,4 +1103,41 @@ public function AdminsearchCustomerBySAG(Request $request)
return response()->json($results); return response()->json($results);
} }
public function updateTagPdpa(Request $request)
{
$tarikh_cetak_pdpa = Carbon::now()->toDateString();
$no_ic = $request->input('no_ic');
$customer = Customer::where('kplama', $no_ic)
->orWhere('kpbaru', $no_ic)
->first();
if (!$customer) {
return response()->json(['error' => 'Customer not found.'], 404);
}
$customer->tag_pdpa = $request->input('tag_pdpa');
$customer->tarikh_cetak_pdpa = $tarikh_cetak_pdpa;
$customer->save();
return response()->json(['message' => 'Customer PDPA updated successfully.'], 200);
}
// public function createNotaArcc($kodcaw, Request $request)
// {
// $current = Carbon::now();
// $customer = Customer::on('mysql7')->create([
// 'kodcaw' => $kodcaw,
// 'tarikhdaftar' => $current->toDateString(),
// 'nota_arcc' => $request->nota_arcc,
// 'teller' => $request->teller,
// 'aktif' => 'A'
// ]);
// return response()->json($customer, 201);
// }
} }
+5 -1
View File
@@ -145,15 +145,19 @@ class KomuditiController extends Controller
$komuditi_current = KomuditiPurchase::on('mysql7')->where('date_purchase','=',$current->toDateString())->orderBy('created_at','desc')->first(); $komuditi_current = KomuditiPurchase::on('mysql7')->where('date_purchase','=',$current->toDateString())->orderBy('created_at','desc')->first();
$unit_current = $komuditi_current['unit_komuditi']; $unit_current = $komuditi_current['unit_komuditi'];
$update_unit = $unit_current - $request->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);
$update_komuditi = KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([ $update_komuditi = KomuditiPurchase::on('mysql7')->where('id','=',$komuditi_current['id'])->update([
'unit_komuditi' => $update_unit 'unit_komuditi' => $update_unit
]); ]);
$komuditi_transaksi = KomuditiTransaksi::on('mysql7')->create([ $komuditi_transaksi = KomuditiTransaksi::on('mysql7')->create([
'unit_komuditi' => $request->unit_komuditi, 'unit_komuditi' => $request->unit_komuditi,
// 'unit_komuditi' => $unit_komuditi,
'nosijil'=>$komuditi_current['no_sijil'], 'nosijil'=>$komuditi_current['no_sijil'],
'norujukan' => $request->norujukan, 'norujukan' => $request->norujukan,
'kodcaw' => $request->kodcaw , 'kodcaw' => $request->kodcaw ,
+6 -18
View File
@@ -847,7 +847,7 @@ $date = new Carbon($request->tarikh);
$bilangan_tebus = $tebus->count(); $bilangan_tebus = $tebus->count();
// $bilangan_ansuran = $ansuran->count(); // $bilangan_ansuran = $ansuran->count();
//$bilangan_customer_baru = Customer::on($cawangan['mysql'])->where([['tarikhdaftar','>=',$request->mula],['tarikhdaftar','<=',$request->akhir]])->count(); //$bilangan_customer_baru = Customer::on($cawangan['mysql'])->where([['tarikhdaftar','>=',$request->mula],['tarikhdaftar','<=',$request->akhir]])->count();
$bilangan_customer_baru = Customer::on('mysql7')->where([['tarikhdaftar','>=',$request->mula],['tarikhdaftar','<=',$request->akhir]])->count(); $bilangan_customer_baru = Customer::on('mysql7')->whereBetween('tarikhdaftar', [$request->mula, $request->akhir])->where('kodcaw', $request->kodcaw)->count();
//f //f
$data_lelong = Lelong::on($cawangan['mysql'])->where([['t_jual','>=',$request->mula],['t_jual','<=',$request->akhir]])->get(); $data_lelong = Lelong::on($cawangan['mysql'])->where([['t_jual','>=',$request->mula],['t_jual','<=',$request->akhir]])->get();
@@ -975,23 +975,11 @@ $date = new Carbon($request->tarikh);
$gadaiDataKaunter = Gadai::on($cawangan['mysql'])->whereIn('norujukan',$total_keuntunganansuran_data_kaunter)->whereNotNull('hargajualan')->pluck('norujukan'); $gadaiDataKaunter = Gadai::on($cawangan['mysql'])->whereIn('norujukan',$total_keuntunganansuran_data_kaunter)->whereNotNull('hargajualan')->pluck('norujukan');
// $keuntunganansuran_kaunter_query = TransaksiKeuntungan::on('mysql7')
// ->whereIn('norujukan',$gadaiDataKaunter)
// ->whereDate('tarikh_bayaran','>=',$request->mula)
// ->whereDate('tarikh_bayaran','<=',$request->akhir);
// //new update
$keuntunganansuran_kaunter_query = TransaksiKeuntungan::on('mysql7') $keuntunganansuran_kaunter_query = TransaksiKeuntungan::on('mysql7')
->leftJoin('transaction as trans', function($join) ->whereIn('norujukan',$gadaiDataKaunter)
{ ->whereDate('tarikh_bayaran','>=',$request->mula)
$join->on('transaksi_keuntungan.norujukan', '=', 'trans.norujukan'); ->whereDate('tarikh_bayaran','<=',$request->akhir);
$join->on('transaksi_keuntungan.tarikh_bayaran', '=', 'trans.created_at');
})
->whereDate('trans.created_at','>=',$request->mula)
->whereDate('trans.created_at','<=',$request->akhir)
->where('trans.teller','<>','Online')
->where('trans.trans_type','=','A')
->where('trans.kodcaw',$request->kodcaw)
->pluck('transaksi_keuntungan.norujukan');
$total_keuntunganansuran_kaunter = $keuntunganansuran_kaunter_query->sum('bayaran_keuntungan'); $total_keuntunganansuran_kaunter = $keuntunganansuran_kaunter_query->sum('bayaran_keuntungan');
@@ -1257,7 +1245,7 @@ $date = new Carbon($request->tarikh);
array_push($atarray,[ array_push($atarray,[
'tarikh' => $tarikh, 'tarikh' => $tarikh,
'masa' => $masa, 'masa' => $masa,
'users' => isset($at['users']) && $at['users'] !== '' ? $at['users'] : '-', 'users' => $at['users'],
'pelulus' => isset($atnewdata['namapelulus']) ? $atnewdata['namapelulus'] : '-', 'pelulus' => isset($atnewdata['namapelulus']) ? $atnewdata['namapelulus'] : '-',
'actions' => $at['actions'], 'actions' => $at['actions'],
'norujukan' => $at['norujukan'], 'norujukan' => $at['norujukan'],
+8 -2
View File
@@ -203,7 +203,10 @@ class MarhunController extends Controller
$marhun = Marhun::on($cawangan['mysql']) $marhun = Marhun::on($cawangan['mysql'])
->select('marhun.norujukan','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai','marhun.tag_permata', 'marhun.berat_batu_permata') ->select('marhun.norujukan','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai','marhun.tag_permata', 'marhun.berat_batu_permata')
->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan') ->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan')
->rightJoin(config('tennant_database.master_db') . '.customer as customer', 'customer.kpbaru','=','gadai.nokp') ->rightJoin('master_erahn.customer as customer', function($join) {
$join->on('customer.kpbaru', '=', 'gadai.nokp')
->orOn('customer.kplama', '=', 'gadai.nokp');
})
->where('marhun.t_gadai','=',$tarikh) ->where('marhun.t_gadai','=',$tarikh)
->orderBy('marhun.t_gadai') ->orderBy('marhun.t_gadai')
->get(); ->get();
@@ -221,7 +224,10 @@ class MarhunController extends Controller
$marhun = Marhun::on($cawangan['mysql']) $marhun = Marhun::on($cawangan['mysql'])
->select('marhun.norujukan','gadai.sttebus','gadai.pinjaman','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai') ->select('marhun.norujukan','gadai.sttebus','gadai.pinjaman','gadai.nokp','customer.nama','marhun.marhun','marhun.nota','marhun.berat','marhun.karat','marhun.harga','marhun.n_marhun','marhun.t_gadai')
->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan') ->rightJoin('gadai', 'gadai.norujukan', '=', 'marhun.norujukan')
->rightJoin(config('tennant_database.master_db') . '.customer as customer', 'customer.kpbaru','=','gadai.nokp') ->rightJoin('master_erahn.customer as customer', function($join) {
$join->on('customer.kpbaru', '=', 'gadai.nokp')
->orOn('customer.kplama', '=', 'gadai.nokp');
})
->where('marhun.t_gadai','<=',$tarikh) ->where('marhun.t_gadai','<=',$tarikh)
->whereYear('marhun.t_gadai','=',$current->year) ->whereYear('marhun.t_gadai','=',$current->year)
->where('gadai.sttebus','=','') ->where('gadai.sttebus','=','')
@@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers;
use App\Services\PembiayaanOnline\ModalOnlineService;
use Illuminate\Http\Request;
class ModalOnlineController extends Controller
{
protected $service;
public function __construct(ModalOnlineService $service = null)
{
$this->service = $service ?: new ModalOnlineService();
}
protected function respond(array $result)
{
return response()->json($result['payload'], $result['status']);
}
/**
* GET /api/modal-online/{kodcaw}
* Read-only: does not create today's row.
*/
public function get($kodcaw)
{
return $this->respond($this->service->get($kodcaw));
}
/**
* @deprecated Alias of get() — no longer auto-creates RM100k.
*/
public function getOrCreate($kodcaw)
{
return $this->get($kodcaw);
}
/**
* POST /api/modal-online/{kodcaw}/reserve
*/
public function reserve($kodcaw, Request $request)
{
return $this->respond($this->service->reserve($kodcaw, $request->input('amaun', 0)));
}
/**
* GET /api/initiate/modal-online?zone=all|{zone}
*/
public function initiate(Request $request)
{
return $this->respond($this->service->initiate($request->input('zone', 'all')));
}
/**
* POST /api/modal-online/{kodcaw}/topup/request
* PC mohon had (hari pertama) atau tambah modal. Body: amaun, dimohon_oleh, nota?
*/
public function requestTopUp($kodcaw, Request $request)
{
return $this->respond($this->service->requestTopUp(
$kodcaw,
$request->input('amaun', 0),
$request->input('dimohon_oleh', ''),
$request->input('nota')
));
}
/**
* GET /api/modal-online/{kodcaw}/topup/requests?status=&tarikh=
*/
public function listTopUpByCawangan($kodcaw, Request $request)
{
return $this->respond($this->service->listTopUpRequests(
$kodcaw,
$request->input('status'),
$request->input('tarikh')
));
}
/**
* GET /api/modal-online/operasi/topup/requests?status=PENDING&tarikh=
*/
public function listTopUpOperasi(Request $request)
{
$status = $request->input('status', 'PENDING');
if ($status === 'all' || $status === '') {
$status = null;
}
return $this->respond($this->service->listTopUpRequests(
null,
$status,
$request->input('tarikh')
));
}
/**
* POST /api/modal-online/operasi/topup/{id}/approve
* Body: diluluskan_oleh, nota?, amaun? (optional override)
*/
public function approveTopUp($id, Request $request)
{
return $this->respond($this->service->approveTopUp(
$id,
$request->input('diluluskan_oleh', ''),
$request->input('nota'),
null,
$request->input('amaun')
));
}
/**
* POST /api/modal-online/operasi/topup/{id}/reject
* Body: diluluskan_oleh, nota?
*/
public function rejectTopUp($id, Request $request)
{
return $this->respond($this->service->rejectTopUp(
$id,
$request->input('diluluskan_oleh', ''),
$request->input('nota')
));
}
/**
* POST /api/modal-online/{kodcaw}/close
* Body: ditutup_oleh
*/
public function close($kodcaw, Request $request)
{
return $this->respond($this->service->close(
$kodcaw,
$request->input('ditutup_oleh', '')
));
}
/**
* GET /api/modal-online/operasi/eod?zone=all&tarikh=
*/
public function listToday(Request $request)
{
return $this->respond($this->service->listToday(
$request->input('zone', 'all'),
$request->input('tarikh')
));
}
/**
* POST /api/modal-online/operasi/eod/close-all
* Body: ditutup_oleh, zone?
*/
public function closeAll(Request $request)
{
return $this->respond($this->service->closeAll(
$request->input('ditutup_oleh', ''),
$request->input('zone', 'all')
));
}
}
@@ -0,0 +1,204 @@
<?php
namespace App\Http\Controllers;
use App\Services\PembiayaanOnline\PembiayaanOnlineService;
use Illuminate\Http\Request;
class PembiayaanOnlineController extends Controller
{
protected $service;
public function __construct(PembiayaanOnlineService $service = null)
{
$this->service = $service ?: new PembiayaanOnlineService();
}
protected function respond(array $result)
{
return response()->json($result['payload'], $result['status']);
}
/**
* POST /api/pembiayaan-online/{kodcaw}
*/
public function store($kodcaw, Request $request)
{
return $this->respond($this->service->store($kodcaw, $request->all()));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/by-norujukan/{norujukan}
*/
public function show($kodcaw, $norujukan)
{
return $this->respond($this->service->show($kodcaw, $norujukan));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/queue
*/
public function queue($kodcaw, Request $request)
{
return $this->respond($this->service->queue($kodcaw, $request->input('status')));
}
/**
* PUT /api/pembiayaan-online/{kodcaw}/lines/{id}
*/
public function updateLine($kodcaw, $id, Request $request)
{
// Prefer only present keys so "has" semantics match previous controller.
$input = [];
foreach ([
'kodbank', 'noakaun', 'nama_bank', 'nama_akaun', 'nota',
'bukti_akaun_file_id', 'borang_file_id', 'update_customer_bank',
] as $key) {
if ($request->has($key)) {
$input[$key] = $request->input($key);
}
}
return $this->respond($this->service->updateLine($kodcaw, $id, $input));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/batches
*/
public function listBatches($kodcaw, Request $request)
{
return $this->respond($this->service->listBatches(
$kodcaw,
$request->input('tarikh'),
$request->input('status')
));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/batches/{batchId}
*/
public function showBatch($kodcaw, $batchId)
{
return $this->respond($this->service->showBatch($kodcaw, $batchId));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/batches/submit
*/
public function submitBatch($kodcaw, Request $request)
{
return $this->respond($this->service->submitBatch(
$kodcaw,
$request->input('ids', []),
$request->input('pc_submitted_by', ''),
$request->input('slot')
));
}
/**
* GET /api/pembiayaan-online/operasi/batches
*/
public function operasiListBatches(Request $request)
{
return $this->respond($this->service->operasiListBatches(
$request->input('status', 'SUBMITTED'),
$request->input('tarikh')
));
}
/**
* GET /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}
*/
public function operasiShowBatch($kodcaw, $batchId)
{
return $this->showBatch($kodcaw, $batchId);
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/approve
*/
public function operasiApproveLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiApproveLine(
$kodcaw,
$id,
$request->input('ops_by', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject
*/
public function operasiRejectLine($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiRejectLine(
$kodcaw,
$id,
$request->input('ops_by', ''),
$request->input('reject_reason', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve
*/
public function operasiApproveBatch($kodcaw, $batchId, Request $request)
{
return $this->respond($this->service->operasiApproveBatch(
$kodcaw,
$batchId,
$request->input('ops_by', '')
));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/lines/{id}/paid
* Body: ops_by, no_rujukan_bayaran, tarikh_bayaran?,
* bukti_data + bukti_filename + bukti_type OR bukti_file_id
*/
public function operasiMarkPaid($kodcaw, $id, Request $request)
{
return $this->respond($this->service->operasiMarkPaid($kodcaw, $id, $request->all()));
}
/**
* POST /api/pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/paid
*/
public function operasiMarkPaidBatch($kodcaw, $batchId, Request $request)
{
return $this->respond($this->service->operasiMarkPaidBatch($kodcaw, $batchId, $request->all()));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/lines/{id}/bukti
*/
public function getBukti($kodcaw, $id)
{
return $this->respond($this->service->getBukti($kodcaw, $id));
}
/**
* GET /api/pembiayaan-online/{kodcaw}/paid?blast_sent=0
*/
public function paidQueue($kodcaw, Request $request)
{
return $this->respond($this->service->paidQueue(
$kodcaw,
$request->input('blast_sent')
));
}
/**
* POST /api/pembiayaan-online/{kodcaw}/paid/blast
* Body: ids[] (or ids), blast_by?
*/
public function markBlastSent($kodcaw, Request $request)
{
$ids = $request->input('ids', $request->input('id'));
return $this->respond($this->service->markBlastSent(
$kodcaw,
$ids,
$request->input('blast_by')
));
}
}
@@ -48,7 +48,7 @@ class GadaianController extends Controller
$summary_gadaian_data = array_map(function ($db_connection) use ($request) { $summary_gadaian_data = array_map(function ($db_connection) use ($request) {
$query = Gadai::on($db_connection); $query = Gadai::on($db_connection);
$query->select(DB::raw('count(norujukan) as total_gadaian, sum(berat) as sum_berat, sum(nilaimarhun) as sum_nilai_marhun, sum(pinjaman) as sum_pinjaman')); $query->select(DB::raw('count(norujukan) as total_gadaian, sum(berat) as sum_berat, sum(nilaimarhun) as sum_nilai_marhun, sum(pinjaman) as sum_pinjaman, sum(upah12) as sum_upah_12'));
$query->when($request->filled('startDate') and $request->filled('endDate'), function ($q) use ($request) { $query->when($request->filled('startDate') and $request->filled('endDate'), function ($q) use ($request) {
return $q->where('t_gadai', '>=', $request->startDate) return $q->where('t_gadai', '>=', $request->startDate)
+4 -2
View File
@@ -400,12 +400,14 @@ class VaultController extends Controller
} }
$bakiakhirVault = $vault ? $vault['bakiakhir'] : 0;
if($total != 0) if($total != 0)
{ {
$totalbakiakhirtunai = $vault['bakiakhir'] + $total; $totalbakiakhirtunai = $bakiakhirVault + $total;
}else }else
{ {
$totalbakiakhirtunai = $vault['bakiakhir']; $totalbakiakhirtunai = $bakiakhirVault;
} }
return response()->json($totalbakiakhirtunai); return response()->json($totalbakiakhirtunai);
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ModalOnline extends Model
{
protected $table = 'modal_online';
protected $fillable = [
'tarikh',
'kodcaw',
'had',
'digunakan',
'baki',
'tambah',
'kurang',
'ditutup',
'ditutup_oleh',
'ditutup_at',
];
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PembiayaanOnline extends Model
{
protected $table = 'pembiayaan_online';
protected $fillable = [
'norujukan',
'nokp',
'nopelanggan',
'nama_pelanggan',
'kodcaw',
'pinjaman',
'kodbank',
'nama_bank',
'noakaun',
'nama_akaun',
'bukti_akaun_file_id',
'borang_file_id',
'status',
'reject_reason',
'rejected_by',
'rejected_at',
'batch_id',
'bukti_file_id',
'no_rujukan_bayaran',
'tarikh_bayaran',
'paid_at',
'created_by',
'pc_submitted_by',
'ops_by',
'nota',
'blast_sent',
];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class RequestModalOnline extends Model
{
protected $table = 'request_modal_online';
protected $connection = 'mysql7';
protected $fillable = [
'kodcaw',
'tarikh',
'amaun',
'dimohon_oleh',
'status',
'diluluskan_oleh',
'nota',
];
}
@@ -0,0 +1,858 @@
<?php
namespace App\Services\PembiayaanOnline;
use App\Cawangan;
use App\ModalOnline;
use App\RequestModalOnline;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ModalOnlineService
{
/**
* @return array{ok:bool,status:int,payload:mixed,mysql?:string}
*/
public function resolveMysql($kodcaw)
{
$cawangan = Cawangan::on('mysql7')->where('kodcaw', '=', $kodcaw)->first();
if (!$cawangan) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Cawangan tidak dijumpai'],
];
}
return [
'ok' => true,
'status' => 200,
'payload' => null,
'mysql' => $cawangan['mysql'],
];
}
protected function todayDate()
{
return Carbon::now()->toDateString();
}
protected function findPending($kodcaw)
{
return RequestModalOnline::on('mysql7')
->where('kodcaw', $kodcaw)
->where('status', 'PENDING')
->first();
}
protected function pendingPayload($pending)
{
return $pending ? $pending->toArray() : null;
}
protected function missingSnapshot($kodcaw, $tarikh, $pending = null)
{
return [
'tarikh' => $tarikh,
'kodcaw' => $kodcaw,
'had' => 0.00,
'digunakan' => 0.00,
'baki' => 0.00,
'tambah' => 0.00,
'kurang' => 0.00,
'ditutup' => 0,
'missing' => true,
'pending_request' => $this->pendingPayload($pending),
];
}
protected function recalcBaki($modal)
{
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
}
/**
* Get today's modal_online row. Does not create — PC must request had from Operasi.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function get($kodcaw)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$tarikh = $this->todayDate();
$pending = $this->findPending($kodcaw);
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->first();
if (!$modal) {
return [
'ok' => true,
'status' => 200,
'payload' => $this->missingSnapshot($kodcaw, $tarikh, $pending),
];
}
$payload = $modal->toArray();
$payload['missing'] = false;
$payload['pending_request'] = $this->pendingPayload($pending);
return [
'ok' => true,
'status' => 200,
'payload' => $payload,
];
}
/**
* @deprecated Use get(). Kept so existing callers do not auto-seed RM100k.
*/
public function getOrCreate($kodcaw)
{
return $this->get($kodcaw);
}
/**
* Reserve amount inside an existing branch DB connection (caller may wrap in transaction).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function reserveAmountOnConnection($mysql, $kodcaw, $amaun, $tarikh = null)
{
$amaun = round((float) $amaun, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
$tarikh = $tarikh ?: Carbon::now()->toDateString();
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
if (!$modal) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online belum diwujudkan. Sila mohon had daripada Operasi terlebih dahulu.',
'missing' => true,
],
];
}
if ((int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online hari ini sudah ditutup (EOD)',
'modal_online' => $modal,
],
];
}
if ((float) $modal->baki < $amaun) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Baki modal online tidak mencukupi',
'baki' => (float) $modal->baki,
'amaun' => $amaun,
],
];
}
$modal->digunakan = round((float) $modal->digunakan + $amaun, 2);
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
$modal->save();
return [
'ok' => true,
'status' => 200,
'payload' => $modal,
];
}
/**
* Reserve amount from today's online float (transactional).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function reserve($kodcaw, $amaun)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$amaun = round((float) $amaun, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
try {
return DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $amaun) {
return $this->reserveAmountOnConnection($mysql, $kodcaw, $amaun);
});
} catch (\Throwable $e) {
Log::error('ModalOnline reserve failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal reserve modal online'],
];
}
}
/**
* Had is no longer auto-seeded. Returns how many branches already have a row today.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function initiate($zone = 'all')
{
$current = $this->todayDate();
if ($zone == 'all') {
$cawanganAll = Cawangan::on('mysql7')->get();
} else {
$cawanganAll = Cawangan::on('mysql7')->where('zone', $zone)->get();
}
$existing = 0;
$missing = 0;
foreach ($cawanganAll as $cawangan) {
try {
$exists = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', $cawangan['kodcaw'])
->where('tarikh', $current)
->exists();
if ($exists) {
$existing++;
} else {
$missing++;
}
} catch (\Throwable $e) {
Log::warning('initiate modal_online skip ' . $cawangan['kodcaw'] . ': ' . $e->getMessage());
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'tarikh' => $current,
'created' => 0,
'existing' => $existing,
'missing' => $missing,
'message' => 'Modal online tidak di-initiate automatik. PC perlu mohon had daripada Operasi.',
],
];
}
/**
* PC request opening had (or extra top-up) from Operasi.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function requestTopUp($kodcaw, $amaun, $dimohonOleh, $nota = null)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$amaun = round((float) $amaun, 2);
$dimohonOleh = trim((string) $dimohonOleh);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun tidak sah'],
];
}
if ($dimohonOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'dimohon_oleh diperlukan'],
];
}
$tarikh = $this->todayDate();
$pending = $this->findPending($kodcaw);
if ($pending) {
return [
'ok' => false,
'status' => 409,
'payload' => [
'message' => 'Masih ada permohonan modal online PENDING untuk cawangan ini',
'request' => $pending,
],
];
}
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->first();
if ($modal && (int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Modal online hari ini sudah ditutup (EOD). Tidak boleh mohon.',
'modal_online' => $modal,
],
];
}
$jenis = $modal ? 'TOPUP' : 'HAD';
$request = RequestModalOnline::on('mysql7')->create([
'kodcaw' => $kodcaw,
'tarikh' => $tarikh,
'amaun' => $amaun,
'dimohon_oleh' => $dimohonOleh,
'status' => 'PENDING',
'nota' => $nota,
]);
return [
'ok' => true,
'status' => 201,
'payload' => [
'request' => $request,
'jenis' => $jenis,
'modal_online' => $modal ?: $this->missingSnapshot($kodcaw, $tarikh, $request),
],
];
}
/**
* List top-up requests.
* For PC: pass kodcaw. For Operasi: omit kodcaw (all branches).
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function listTopUpRequests($kodcaw = null, $status = null, $tarikh = null)
{
$query = RequestModalOnline::on('mysql7')->newQuery();
if ($kodcaw) {
$query->where('kodcaw', $kodcaw);
}
if ($status) {
$query->where('status', $status);
}
if ($tarikh) {
$query->where('tarikh', $tarikh);
}
$rows = $query->orderBy('id', 'desc')->get();
$cawMap = [];
if (!$kodcaw) {
foreach (Cawangan::on('mysql7')->get() as $caw) {
$cawMap[$caw->kodcaw] = $caw->nama_cawangan;
}
}
$data = [];
foreach ($rows as $row) {
$item = $row->toArray();
if (!$kodcaw) {
$item['cawangan_nama'] = $cawMap[$row->kodcaw] ?? null;
}
$data[] = $item;
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'bilangan' => count($data),
'data' => $data,
],
];
}
/**
* Operasi approve: first request of the day sets had; later requests add tambah.
* Optional $amaunOverride lets operasi lulus a different amount than dimohon.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function approveTopUp($requestId, $diluluskanOleh, $nota = null, $expectedKodcaw = null, $amaunOverride = null)
{
$diluluskanOleh = trim((string) $diluluskanOleh);
if ($diluluskanOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'diluluskan_oleh diperlukan'],
];
}
$req = RequestModalOnline::on('mysql7')->where('id', $requestId)->first();
if (!$req) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($expectedKodcaw && $req->kodcaw !== $expectedKodcaw) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($req->status !== 'PENDING') {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya permohonan PENDING boleh diluluskan',
'status' => $req->status,
],
];
}
$kodcaw = $req->kodcaw;
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$amaunDimohon = round((float) $req->amaun, 2);
$amaun = $amaunDimohon;
if ($amaunOverride !== null && $amaunOverride !== '') {
$amaun = round((float) $amaunOverride, 2);
if ($amaun <= 0) {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Amaun diluluskan tidak sah'],
];
}
}
$tarikh = $this->todayDate();
try {
$applied = DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $tarikh, $amaun) {
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
$jenis = 'TOPUP';
if (!$modal) {
$modal = ModalOnline::on($mysql)->create([
'tarikh' => $tarikh,
'kodcaw' => $kodcaw,
'had' => $amaun,
'digunakan' => 0.00,
'baki' => $amaun,
'tambah' => 0.00,
'kurang' => 0.00,
'ditutup' => 0,
]);
$modal = ModalOnline::on($mysql)
->where('id', $modal->id)
->lockForUpdate()
->first();
$jenis = 'HAD';
} else {
if ((int) ($modal->ditutup ?? 0) === 1) {
throw new \RuntimeException('MODAL_ONLINE_CLOSED');
}
$modal->tambah = round((float) $modal->tambah + $amaun, 2);
$this->recalcBaki($modal);
$modal->save();
}
return ['modal' => $modal, 'jenis' => $jenis];
});
$modal = $applied['modal'];
$jenis = $applied['jenis'];
$req->status = 'APPROVED';
$req->diluluskan_oleh = $diluluskanOleh;
$req->amaun = $amaun;
$notes = [];
if (abs($amaun - $amaunDimohon) >= 0.01) {
$notes[] = 'Amaun dimohon RM ' . number_format($amaunDimohon, 2)
. ', diluluskan RM ' . number_format($amaun, 2);
}
if ($nota !== null && trim((string) $nota) !== '') {
$notes[] = trim((string) $nota);
}
if ($notes) {
$existing = trim((string) $req->nota);
$req->nota = $existing === ''
? implode(' | ', $notes)
: $existing . ' | ' . implode(' | ', $notes);
}
$req->save();
return [
'ok' => true,
'status' => 200,
'payload' => [
'request' => $req->fresh(),
'jenis' => $jenis,
'modal_online' => $modal,
],
];
} catch (\Throwable $e) {
if ($e->getMessage() === 'MODAL_ONLINE_CLOSED') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'Modal online hari ini sudah ditutup (EOD). Tidak boleh lulus permohonan.'],
];
}
Log::error('approveTopUp failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal lulus permohonan modal online'],
];
}
}
/**
* Operasi reject top-up request.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function rejectTopUp($requestId, $diluluskanOleh, $nota = null, $expectedKodcaw = null)
{
$diluluskanOleh = trim((string) $diluluskanOleh);
if ($diluluskanOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'diluluskan_oleh diperlukan'],
];
}
$req = RequestModalOnline::on('mysql7')->where('id', $requestId)->first();
if (!$req) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($expectedKodcaw && $req->kodcaw !== $expectedKodcaw) {
return [
'ok' => false,
'status' => 404,
'payload' => ['message' => 'Permohonan tidak dijumpai'],
];
}
if ($req->status !== 'PENDING') {
return [
'ok' => false,
'status' => 422,
'payload' => [
'message' => 'Hanya permohonan PENDING boleh ditolak',
'status' => $req->status,
],
];
}
$req->status = 'REJECTED';
$req->diluluskan_oleh = $diluluskanOleh;
if ($nota !== null && trim((string) $nota) !== '') {
$req->nota = trim(
trim((string) $req->nota) === ''
? (string) $nota
: $req->nota . ' | ' . $nota
);
}
$req->save();
return [
'ok' => true,
'status' => 200,
'payload' => ['request' => $req],
];
}
/**
* EOD close for one branch: return unused baki via kurang, mark ditutup.
* Also auto-rejects PENDING top-up requests for the branch.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function close($kodcaw, $ditutupOleh)
{
$resolved = $this->resolveMysql($kodcaw);
if (!$resolved['ok']) {
return $resolved;
}
$mysql = $resolved['mysql'];
$ditutupOleh = trim((string) $ditutupOleh);
if ($ditutupOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'ditutup_oleh diperlukan'],
];
}
$tarikh = Carbon::now()->toDateString();
$now = Carbon::now();
try {
$result = DB::connection($mysql)->transaction(function () use ($mysql, $kodcaw, $tarikh, $ditutupOleh, $now) {
$modal = ModalOnline::on($mysql)
->where('kodcaw', $kodcaw)
->where('tarikh', $tarikh)
->lockForUpdate()
->first();
if (!$modal) {
return [
'ok' => false,
'status' => 404,
'payload' => [
'message' => 'Tiada modal online hari ini untuk ditutup. Teller belum mohon / PC belum lulus had.',
'missing' => true,
],
];
}
if ((int) ($modal->ditutup ?? 0) === 1) {
return [
'ok' => false,
'status' => 409,
'payload' => [
'message' => 'Modal online sudah ditutup untuk hari ini',
'modal_online' => $modal,
],
];
}
$unused = round((float) $modal->baki, 2);
$modal->kurang = round((float) $modal->kurang + $unused, 2);
$modal->baki = round(
(float) $modal->had + (float) $modal->tambah - (float) $modal->kurang - (float) $modal->digunakan,
2
);
$modal->ditutup = 1;
$modal->ditutup_oleh = $ditutupOleh;
$modal->ditutup_at = $now;
$modal->save();
return [
'ok' => true,
'status' => 200,
'payload' => [
'modal_online' => $modal,
'dikembalikan' => $unused,
],
];
});
if (!$result['ok']) {
return $result;
}
$pendingRejected = RequestModalOnline::on('mysql7')
->where('kodcaw', $kodcaw)
->where('status', 'PENDING')
->get();
foreach ($pendingRejected as $req) {
$req->status = 'REJECTED';
$req->diluluskan_oleh = $ditutupOleh;
$tag = 'Auto reject: EOD close';
$req->nota = trim((string) $req->nota) === ''
? $tag
: trim($req->nota) . ' | ' . $tag;
$req->save();
}
$result['payload']['pending_topup_ditolak'] = $pendingRejected->count();
return $result;
} catch (\Throwable $e) {
Log::error('modal online close failed: ' . $e->getMessage());
return [
'ok' => false,
'status' => 500,
'payload' => ['message' => 'Gagal tutup modal online'],
];
}
}
/**
* Operasi: list today's modal_online across branches.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function listToday($zone = 'all', $tarikh = null)
{
$tarikh = $tarikh ?: Carbon::now()->toDateString();
if ($zone == 'all') {
$cawanganAll = Cawangan::on('mysql7')->get();
} else {
$cawanganAll = Cawangan::on('mysql7')->where('zone', $zone)->get();
}
$rows = [];
foreach ($cawanganAll as $cawangan) {
try {
$modal = ModalOnline::on($cawangan['mysql'])
->where('kodcaw', $cawangan['kodcaw'])
->where('tarikh', $tarikh)
->first();
$row = $modal ? $modal->toArray() : [
'tarikh' => $tarikh,
'kodcaw' => $cawangan['kodcaw'],
'had' => 0,
'digunakan' => 0,
'baki' => 0,
'tambah' => 0,
'kurang' => 0,
'ditutup' => 0,
'missing' => true,
];
$row['cawangan_nama'] = $cawangan->nama_cawangan;
$row['missing'] = empty($modal);
$rows[] = $row;
} catch (\Throwable $e) {
Log::warning('listToday modal_online skip ' . $cawangan['kodcaw'] . ': ' . $e->getMessage());
}
}
usort($rows, function ($a, $b) {
return strcmp((string) ($a['kodcaw'] ?? ''), (string) ($b['kodcaw'] ?? ''));
});
$open = 0;
$closed = 0;
$missing = 0;
foreach ($rows as $row) {
if (!empty($row['missing'])) {
$missing++;
} elseif ((int) ($row['ditutup'] ?? 0) === 1) {
$closed++;
} else {
$open++;
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'tarikh' => $tarikh,
'bilangan' => count($rows),
'open' => $open,
'closed' => $closed,
'missing' => $missing,
'data' => $rows,
],
];
}
/**
* Operasi bulk EOD close for open branches today.
*
* @return array{ok:bool,status:int,payload:mixed}
*/
public function closeAll($ditutupOleh, $zone = 'all')
{
$ditutupOleh = trim((string) $ditutupOleh);
if ($ditutupOleh === '') {
return [
'ok' => false,
'status' => 422,
'payload' => ['message' => 'ditutup_oleh diperlukan'],
];
}
$list = $this->listToday($zone);
$closed = 0;
$skipped = 0;
$failed = [];
foreach ($list['payload']['data'] as $row) {
if (!empty($row['missing']) || (int) ($row['ditutup'] ?? 0) === 1) {
$skipped++;
continue;
}
$res = $this->close($row['kodcaw'], $ditutupOleh);
if ($res['ok']) {
$closed++;
} else {
$failed[] = [
'kodcaw' => $row['kodcaw'],
'message' => $res['payload']['message'] ?? 'gagal',
];
}
}
return [
'ok' => true,
'status' => 200,
'payload' => [
'closed' => $closed,
'skipped' => $skipped,
'failed' => $failed,
],
];
}
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
/cache
+28
View File
@@ -23,6 +23,33 @@ $app = new Laravel\Lumen\Application(
dirname(__DIR__) dirname(__DIR__)
); );
/*
| Laravel's storage_path() helper resolves app('path.storage'). Lumen does not
| register that binding by default; config files that call storage_path() at
| load time (e.g. config/logging.php) would fail without this.
*/
$app->instance('path.storage', $app->storagePath());
/*
| Laravel's Foundation helpers are loaded before Lumen's and win for names like
| response() and url(). Those helpers resolve Illuminate\Contracts\* from the
| container; Lumen only registers the "url" service, not the contracts. Map the
| contracts to Lumen's implementations so helpers work across the app.
*/
$app->singleton(
\Illuminate\Contracts\Routing\ResponseFactory::class,
function () {
return new \Laravel\Lumen\Http\ResponseFactory();
}
);
$app->singleton(
\Illuminate\Contracts\Routing\UrlGenerator::class,
function ($app) {
return $app->make('url');
}
);
$app->withFacades(); $app->withFacades();
$app->withEloquent(); $app->withEloquent();
@@ -60,6 +87,7 @@ $app->singleton(
*/ */
$app->configure('app'); $app->configure('app');
$app->configure('logging');
$app->configure('tinker'); $app->configure('tinker');
$app->configure('tennant_database'); $app->configure('tennant_database');
$app->configure('report'); $app->configure('report');
+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
return [ return [
'url' => env('APP_URL', url('/')), 'url' => env('APP_URL', 'http://localhost'),
'api_arrahnbid' => env('API_ARRAHNBID', 'http://lelong.test/api/'), 'api_arrahnbid' => env('API_ARRAHNBID', 'http://lelong.test/api/'),
'api_v3' => env('API_V3', 'http://apiv3.localhost/api/') 'api_v3' => env('API_V3', 'http://apiv3.localhost/api/')
]; ];
+103
View File
@@ -0,0 +1,103 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];
@@ -0,0 +1,53 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class CreateModalOnlineTable extends Migration
{
public function getActiveDB()
{
$config_db = array_keys(config('database.connections'));
return array_filter($config_db, 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('modal_online')) {
Schema::connection($dbase)->create('modal_online', function (Blueprint $table) {
$table->bigIncrements('id');
$table->date('tarikh');
$table->string('kodcaw', 20);
$table->decimal('had', 12, 2)->default(100000.00);
$table->decimal('digunakan', 12, 2)->default(0.00);
$table->decimal('baki', 12, 2)->default(100000.00);
$table->decimal('tambah', 12, 2)->default(0.00);
$table->decimal('kurang', 12, 2)->default(0.00);
$table->timestamps();
$table->unique(['tarikh', 'kodcaw']);
});
}
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
Schema::connection($dbase)->dropIfExists('modal_online');
}
}
}
@@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class CreateBatchPembiayaanOnlineTable extends Migration
{
public function getActiveDB()
{
$config_db = array_keys(config('database.connections'));
return array_filter($config_db, 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('batch_pembiayaan_online')) {
Schema::connection($dbase)->create('batch_pembiayaan_online', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('batch_no', 50);
$table->string('kodcaw', 20);
$table->date('tarikh');
$table->string('slot', 20)->nullable()->comment('BATCH1 / BATCH2');
$table->integer('bilangan')->default(0);
$table->decimal('jumlah_keseluruhan', 12, 2)->default(0.00);
$table->string('status', 30)->default('DRAFT');
$table->string('pc_submitted_by', 100)->nullable();
$table->timestamp('submitted_at')->nullable();
$table->timestamps();
$table->index(['kodcaw', 'tarikh', 'status']);
});
}
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
Schema::connection($dbase)->dropIfExists('batch_pembiayaan_online');
}
}
}
@@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class CreatePembiayaanOnlineTable extends Migration
{
public function getActiveDB()
{
$config_db = array_keys(config('database.connections'));
return array_filter($config_db, 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')) {
Schema::connection($dbase)->create('pembiayaan_online', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('norujukan', 50);
$table->string('nokp', 20);
$table->string('nopelanggan', 50)->nullable();
$table->string('nama_pelanggan', 150)->nullable();
$table->string('kodcaw', 20);
$table->decimal('pinjaman', 12, 2);
$table->string('kodbank', 20)->nullable();
$table->string('nama_bank', 100)->nullable();
$table->string('noakaun', 50)->nullable();
$table->string('nama_akaun', 150)->nullable();
$table->unsignedBigInteger('bukti_akaun_file_id')->nullable();
$table->unsignedBigInteger('borang_file_id')->nullable();
$table->string('status', 30)->default('PENDING_PC');
$table->text('reject_reason')->nullable();
$table->string('rejected_by', 100)->nullable();
$table->timestamp('rejected_at')->nullable();
$table->unsignedBigInteger('batch_id')->nullable();
$table->unsignedBigInteger('bukti_file_id')->nullable();
$table->string('no_rujukan_bayaran', 100)->nullable();
$table->date('tarikh_bayaran')->nullable();
$table->timestamp('paid_at')->nullable();
$table->string('created_by', 100)->nullable();
$table->string('pc_submitted_by', 100)->nullable();
$table->string('ops_by', 100)->nullable();
$table->text('nota')->nullable();
$table->tinyInteger('blast_sent')->default(0);
$table->timestamps();
$table->index(['kodcaw', 'status']);
$table->index('norujukan');
$table->index('batch_id');
});
}
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
Schema::connection($dbase)->dropIfExists('pembiayaan_online');
}
}
}
@@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class AddCaraTerimaToGadaiTable extends Migration
{
public function getActiveDB()
{
$config_db = array_keys(config('database.connections'));
return array_filter($config_db, 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('gadai')
&& !Schema::connection($dbase)->hasColumn('gadai', 'cara_terima')
) {
Schema::connection($dbase)->table('gadai', function (Blueprint $table) {
$table->string('cara_terima', 20)->nullable()->after('pinjaman');
});
}
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
if (
Schema::connection($dbase)->hasTable('gadai')
&& Schema::connection($dbase)->hasColumn('gadai', 'cara_terima')
) {
Schema::connection($dbase)->table('gadai', function (Blueprint $table) {
$table->dropColumn('cara_terima');
});
}
}
}
}
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRequestModalOnlineTable extends Migration
{
public function up()
{
if (!Schema::connection('mysql7')->hasTable('request_modal_online')) {
Schema::connection('mysql7')->create('request_modal_online', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('kodcaw', 20);
$table->date('tarikh');
$table->decimal('amaun', 12, 2);
$table->string('dimohon_oleh', 100)->nullable();
$table->string('status', 20)->default('PENDING');
$table->string('diluluskan_oleh', 100)->nullable();
$table->text('nota')->nullable();
$table->timestamps();
$table->index(['kodcaw', 'tarikh', 'status']);
});
}
}
public function down()
{
Schema::connection('mysql7')->dropIfExists('request_modal_online');
}
}
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArrahnFilesTable extends Migration
{
/**
* Master DB (mysql7) — shared file store for CIT / permohonan / bukti pembiayaan online.
* Matches App\ARFiles usage: data (base64), filename, type, tarikhmasa; id used as file reference.
*
* @return void
*/
public function up()
{
if (!Schema::connection('mysql7')->hasTable('arrahn_files')) {
Schema::connection('mysql7')->create('arrahn_files', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('files_id')->nullable()->index();
$table->longText('data')->nullable();
$table->string('filename', 255)->nullable();
$table->string('type', 100)->nullable();
$table->string('tarikhmasa', 50)->nullable();
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::connection('mysql7')->dropIfExists('arrahn_files');
}
}
@@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class AddEodCloseColumnsToModalOnlineTable 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) {
Schema::connection($dbase)->table('modal_online', function (Blueprint $table) {
$table->tinyInteger('ditutup')
->default(0)
->after('kurang');
$table->string('ditutup_oleh', 100)
->nullable()
->after('ditutup');
$table->timestamp('ditutup_at')
->nullable()
->after('ditutup_oleh');
});
}
}
public function down()
{
foreach ($this->getActiveDB() as $dbase) {
Schema::connection($dbase)->table('modal_online', function (Blueprint $table) {
$table->dropColumn([
'ditutup',
'ditutup_oleh',
'ditutup_at',
]);
});
}
}
}
+77
View File
@@ -0,0 +1,77 @@
# Staging stack — isolated from local docker-compose.yml (8080 / 3306)
# and from other apps on the server (unique project name + host ports).
#
# Local:
# docker compose --env-file .env.staging -f docker-compose.staging.yml up -d --build
#
# 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
#
# Host nginx reverse-proxies the public domain to 127.0.0.1:${HTTP_PORT}.
# See docker/nginx/host-proxy.example.conf
#
# If 8090 or 3308 is already taken, set HTTP_PORT / MYSQL_PUBLISH_PORT in .env.staging.
name: api_arrahn_staging
services:
app:
image: ${APP_IMAGE:-api_arrahn}:${IMAGE_TAG:-staging}
build:
context: .
dockerfile: docker/php/Dockerfile
target: staging
container_name: api_arrahn_staging_app
working_dir: /var/www/html
volumes:
- storage_staging:/var/www/html/storage
- ./.env.staging:/var/www/html/.env.staging:ro
env_file:
- .env.staging
environment:
APP_ENV: staging
USE_STAGING_ENV: "true"
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
web:
image: nginx:1.25-alpine
container_name: api_arrahn_staging_web
ports:
- "${HTTP_PORT:-8090}:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: api_arrahn_staging_mysql
command: --default-authentication-plugin=mysql_native_password
ports:
- "127.0.0.1:${MYSQL_PUBLISH_PORT:-3308}:3306"
environment:
MYSQL_DATABASE: erahn_xtra
MYSQL_USER: xtrauser
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql_staging_data:/var/lib/mysql
- ./docker/mysql/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
timeout: 5s
retries: 30
start_period: 20s
restart: unless-stopped
volumes:
mysql_staging_data:
storage_staging:
+40
View File
@@ -0,0 +1,40 @@
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
target: development
working_dir: /var/www/html
volumes:
- ./:/var/www/html:cached
environment:
APP_ENV: local
APP_DEBUG: "true"
depends_on:
- mysql
web:
image: nginx:1.25-alpine
ports:
- "8080:80"
volumes:
- ./:/var/www/html:cached
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
mysql:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: erahn_xtra
MYSQL_USER: xtrauser
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
@@ -0,0 +1,7 @@
CREATE DATABASE IF NOT EXISTS master_erahn;
CREATE DATABASE IF NOT EXISTS kaunter_erahn;
GRANT ALL PRIVILEGES ON master_erahn.* TO 'xtrauser'@'%';
GRANT ALL PRIVILEGES ON kaunter_erahn.* TO 'xtrauser'@'%';
GRANT ALL PRIVILEGES ON erahn_xtra.* TO 'xtrauser'@'%';
FLUSH PRIVILEGES;
+37
View File
@@ -0,0 +1,37 @@
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.php index.html;
charset utf-8;
client_max_body_size 50M;
location / {
rewrite ^/(.*)/$ /$1 last;
try_files $uri @php;
}
location @php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_read_timeout 120s;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_read_timeout 120s;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
+51
View File
@@ -0,0 +1,51 @@
FROM php:7.4-fpm AS base
WORKDIR /var/www/html
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
pkg-config \
unzip \
libonig-dev \
libzip-dev \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" \
pdo_mysql \
mbstring \
bcmath \
zip \
exif \
gd \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini
COPY docker/php/zz-clear-env.conf /usr/local/etc/php-fpm.d/zz-clear-env.conf
COPY docker/php/entrypoint.sh /usr/local/bin/app-entrypoint.sh
RUN chmod +x /usr/local/bin/app-entrypoint.sh \
&& usermod -u 1000 www-data && groupmod -g 1000 www-data
ENTRYPOINT ["app-entrypoint.sh"]
CMD ["php-fpm"]
FROM base AS staging
COPY --chown=www-data:www-data . /var/www/html
RUN composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader \
&& mkdir -p \
storage/logs \
storage/framework/cache/data \
storage/framework/views \
storage/framework/sessions \
storage/app/public \
bootstrap/cache \
&& chown -R www-data:www-data storage bootstrap/cache vendor
FROM base AS development
+30
View File
@@ -0,0 +1,30 @@
#!/bin/sh
set -e
cd /var/www/html
if [ "${USE_STAGING_ENV:-false}" = "true" ] && [ -f .env.staging ]; then
cp .env.staging .env
fi
if [ -f composer.json ]; then
if [ -f vendor/autoload.php ] && [ "${USE_STAGING_ENV:-false}" = "true" ]; then
: # vendor is baked into the staging image
elif [ "${USE_STAGING_ENV:-false}" = "true" ]; then
composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader
else
composer install --no-interaction --prefer-dist
fi
fi
mkdir -p \
storage/logs \
storage/framework/cache/data \
storage/framework/views \
storage/framework/sessions \
storage/app/public \
bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
exec docker-php-entrypoint "$@"
+4
View File
@@ -0,0 +1,4 @@
memory_limit=512M
upload_max_filesize=50M
post_max_size=50M
max_execution_time=120
+3
View File
@@ -0,0 +1,3 @@
[www]
; Pass container env vars (from compose env_file) through to PHP workers.
clear_env = no
+41
View File
@@ -21,6 +21,10 @@ use App\Http\Controllers\TransactionOnlineTrailController;
use App\Http\Controllers\Reports\RingkasanHarianController; use App\Http\Controllers\Reports\RingkasanHarianController;
use App\Transaction; use App\Transaction;
$router->get('/health', function () {
return response()->json(['status' => 'ok']);
});
$router->group(['namespace' => '\Rap2hpoutre\LaravelLogViewer'], function() use ($router) { $router->group(['namespace' => '\Rap2hpoutre\LaravelLogViewer'], function() use ($router) {
$router->get('logs', 'LogViewerController@index'); $router->get('logs', 'LogViewerController@index');
}); });
@@ -62,6 +66,7 @@ $router->group(['prefix'=>'api'], function () use ($router){
$router->get('customer/anggota',['uses'=>'CustomerController@anggotaKoperasi']); $router->get('customer/anggota',['uses'=>'CustomerController@anggotaKoperasi']);
$router->get('customer/eachcawangan/{ic}',['uses'=>'CustomerController@showCustomerByEachCawangan']); $router->get('customer/eachcawangan/{ic}',['uses'=>'CustomerController@showCustomerByEachCawangan']);
$router->put('customers/{noic}/assign-anggota', ['uses' => 'CustomerController@assignAnggota']); $router->put('customers/{noic}/assign-anggota', ['uses' => 'CustomerController@assignAnggota']);
$router->post('/customers/update-tag-pdpa', ['uses' => 'CustomerController@updateTagPdpa']);
//arcc //arcc
$router->get('customer/searchbyname',['uses'=>'CustomerController@searchByName']); $router->get('customer/searchbyname',['uses'=>'CustomerController@searchByName']);
@@ -668,6 +673,42 @@ $router->group(['prefix'=>'api'], function () use ($router){
//initiateTunai //initiateTunai
$router->get('initiate/tunai',['uses'=>'TunaiController@initiateTunai']); $router->get('initiate/tunai',['uses'=>'TunaiController@initiateTunai']);
// Modal Online (pembiayaan atas talian float)
// Operasi top-up routes before {kodcaw} catch-alls
$router->get('modal-online/operasi/topup/requests', ['uses' => 'ModalOnlineController@listTopUpOperasi']);
$router->post('modal-online/operasi/topup/{id}/approve', ['uses' => 'ModalOnlineController@approveTopUp']);
$router->post('modal-online/operasi/topup/{id}/reject', ['uses' => 'ModalOnlineController@rejectTopUp']);
$router->get('modal-online/operasi/eod', ['uses' => 'ModalOnlineController@listToday']);
$router->post('modal-online/operasi/eod/close-all', ['uses' => 'ModalOnlineController@closeAll']);
$router->get('modal-online/{kodcaw}', ['uses' => 'ModalOnlineController@get']);
$router->post('modal-online/{kodcaw}/reserve', ['uses' => 'ModalOnlineController@reserve']);
$router->post('modal-online/{kodcaw}/topup/request', ['uses' => 'ModalOnlineController@requestTopUp']);
$router->get('modal-online/{kodcaw}/topup/requests', ['uses' => 'ModalOnlineController@listTopUpByCawangan']);
$router->post('modal-online/{kodcaw}/close', ['uses' => 'ModalOnlineController@close']);
$router->get('initiate/modal-online', ['uses' => 'ModalOnlineController@initiate']);
// Operasi kelulusan pembiayaan online (must be before {kodcaw} routes)
$router->get('pembiayaan-online/operasi/batches', ['uses' => 'PembiayaanOnlineController@operasiListBatches']);
$router->get('pembiayaan-online/operasi/{kodcaw}/batches/{batchId}', ['uses' => 'PembiayaanOnlineController@operasiShowBatch']);
$router->post('pembiayaan-online/operasi/{kodcaw}/lines/{id}/approve', ['uses' => 'PembiayaanOnlineController@operasiApproveLine']);
$router->post('pembiayaan-online/operasi/{kodcaw}/lines/{id}/reject', ['uses' => 'PembiayaanOnlineController@operasiRejectLine']);
$router->post('pembiayaan-online/operasi/{kodcaw}/lines/{id}/paid', ['uses' => 'PembiayaanOnlineController@operasiMarkPaid']);
$router->post('pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/approve', ['uses' => 'PembiayaanOnlineController@operasiApproveBatch']);
$router->post('pembiayaan-online/operasi/{kodcaw}/batches/{batchId}/paid', ['uses' => 'PembiayaanOnlineController@operasiMarkPaidBatch']);
// Pembiayaan Online (payout line + PC queue / batch)
$router->post('pembiayaan-online/{kodcaw}', ['uses' => 'PembiayaanOnlineController@store']);
$router->get('pembiayaan-online/{kodcaw}/queue', ['uses' => 'PembiayaanOnlineController@queue']);
$router->get('pembiayaan-online/{kodcaw}/paid', ['uses' => 'PembiayaanOnlineController@paidQueue']);
$router->post('pembiayaan-online/{kodcaw}/paid/blast', ['uses' => 'PembiayaanOnlineController@markBlastSent']);
$router->put('pembiayaan-online/{kodcaw}/lines/{id}', ['uses' => 'PembiayaanOnlineController@updateLine']);
$router->get('pembiayaan-online/{kodcaw}/lines/{id}/bukti', ['uses' => 'PembiayaanOnlineController@getBukti']);
$router->get('pembiayaan-online/{kodcaw}/batches', ['uses' => 'PembiayaanOnlineController@listBatches']);
$router->get('pembiayaan-online/{kodcaw}/batches/{batchId}', ['uses' => 'PembiayaanOnlineController@showBatch']);
$router->post('pembiayaan-online/{kodcaw}/batches/submit', ['uses' => 'PembiayaanOnlineController@submitBatch']);
$router->get('pembiayaan-online/{kodcaw}/by-norujukan/{norujukan}', ['uses' => 'PembiayaanOnlineController@show']);
//WTD //WTD
$router->get('check/wtd',['uses'=>'WTDController@getDraftWTD']); $router->get('check/wtd',['uses'=>'WTDController@getDraftWTD']);
$router->get('check/customer',['uses' => 'WTDController@getCustomerWTD']); $router->get('check/customer',['uses' => 'WTDController@getCustomerWTD']);
+2
View File
@@ -0,0 +1,2 @@
/debugbar
/framework/sessions