DONE: add flexible json update saham, yuran, and pelaburan ui
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\v1\Voter;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Util;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Import keanggotaan JSON (match by no_anggota) into voter money columns for the current election.
|
||||
* Expected row keys: no_anggota, pelaburan, pelaburan_2, saham, yuran. Null/empty → 0.
|
||||
*/
|
||||
class ImportKeanggotaanJsonController extends Controller
|
||||
{
|
||||
private const UPDATABLE_FIELDS = [
|
||||
'pelaburan',
|
||||
'pelaburan_2',
|
||||
'saham',
|
||||
'yuran',
|
||||
];
|
||||
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'file' => 'required|file|max:10240',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$extension = strtolower($file->getClientOriginalExtension() ?: '');
|
||||
if ($extension !== 'json') {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Fail mestilah format .json.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$raw = file_get_contents($file->getRealPath());
|
||||
$rows = json_decode($raw, true);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'JSON tidak sah. Jangkaan array objek.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$noAnggota = isset($row['no_anggota']) ? trim((string) $row['no_anggota']) : '';
|
||||
if ($noAnggota === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
foreach (self::UPDATABLE_FIELDS as $field) {
|
||||
if (array_key_exists($field, $row)) {
|
||||
$payload[$field] = $this->cleanNumber($row[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($payload) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$noAnggota] = $payload;
|
||||
}
|
||||
|
||||
if (count($map) === 0) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Tiada baris sah dalam JSON (perlu no_anggota dan sekurang-kurangnya satu medan).',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$electionId = (int) Util::getCurrentElection();
|
||||
if ($electionId <= 0) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Tidak dapat mengenal pasti election semasa.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$stats = [
|
||||
'election_id' => $electionId,
|
||||
'json_rows' => count($map),
|
||||
'matched' => 0,
|
||||
'updated' => 0,
|
||||
'unchanged' => 0,
|
||||
'missing_in_voter' => 0,
|
||||
'missing_in_json' => 0,
|
||||
];
|
||||
|
||||
$matchedKeys = [];
|
||||
|
||||
DB::table('voter')
|
||||
->select(array_merge(['id', 'no_anggota'], self::UPDATABLE_FIELDS))
|
||||
->where('election_id', $electionId)
|
||||
->orderBy('id')
|
||||
->chunkById(500, function ($chunk) use ($map, &$stats, &$matchedKeys) {
|
||||
foreach ($chunk as $voter) {
|
||||
$noAnggota = $voter->no_anggota !== null ? trim((string) $voter->no_anggota) : '';
|
||||
|
||||
if ($noAnggota === '' || !array_key_exists($noAnggota, $map)) {
|
||||
$stats['missing_in_json']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$stats['matched']++;
|
||||
$matchedKeys[$noAnggota] = true;
|
||||
$jsonRow = $map[$noAnggota];
|
||||
|
||||
$update = [];
|
||||
foreach ($jsonRow as $field => $newValue) {
|
||||
$oldValue = $voter->{$field} !== null ? (float) $voter->{$field} : 0.0;
|
||||
if (abs($oldValue - $newValue) >= 0.00001) {
|
||||
$update[$field] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($update) === 0) {
|
||||
$stats['unchanged']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$update['updated_at'] = now();
|
||||
DB::table('voter')->where('id', $voter->id)->update($update);
|
||||
$stats['updated']++;
|
||||
}
|
||||
}, 'id');
|
||||
|
||||
foreach ($map as $noAnggota => $_payload) {
|
||||
if (!isset($matchedKeys[$noAnggota])) {
|
||||
$stats['missing_in_voter']++;
|
||||
}
|
||||
}
|
||||
|
||||
activity()
|
||||
->withProperties([
|
||||
'election_id' => $electionId,
|
||||
'filename' => $file->getClientOriginalName(),
|
||||
'stats' => $stats,
|
||||
'ip' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
])
|
||||
->log('import keanggotaan json');
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Import keanggotaan berjaya.',
|
||||
'data' => $stats,
|
||||
]);
|
||||
}
|
||||
|
||||
private function cleanNumber($value): float
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
$value = str_replace(['RM', ',', ' '], '', $value);
|
||||
|
||||
if (preg_match('/^\((.*)\)$/', $value, $matches)) {
|
||||
$value = $matches[1];
|
||||
}
|
||||
|
||||
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
||||
|
||||
return is_numeric($value) ? (float) $value : 0.0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user