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; } }