374 lines
11 KiB
PHP
374 lines
11 KiB
PHP
<?php
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\DB;
|
|
use App\Models\BorrowTransaction;
|
|
use App\Models\IotMode;
|
|
use PhpMqtt\Client\MqttClient;
|
|
use App\Http\Controllers\Admin\PeminjamanController;
|
|
use App\Http\Controllers\User\UserPeminjamanController;
|
|
use App\Jobs\ProcessPeminjamanJob;
|
|
use App\Jobs\ProcessPengembalianJob;
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| API Routes - Nawasena IoT Library System (Synchronized)
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/* ================= HELPERS ================= */
|
|
|
|
if (!function_exists('cleanData')) {
|
|
function cleanData($data) {
|
|
return strtoupper(str_replace(' ', '', trim($data)));
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mqttPublish')) {
|
|
function mqttPublish($topic, $message) {
|
|
try {
|
|
// Menggunakan Unique Client ID untuk menghindari konflik koneksi
|
|
$mqtt = new MqttClient('127.0.0.1', 1883, 'laravel-api-pub-' . uniqid());
|
|
$mqtt->connect(null, false);
|
|
$payload = is_array($message) ? json_encode($message) : (string)$message;
|
|
$mqtt->publish($topic, $payload, 0);
|
|
$mqtt->disconnect();
|
|
} catch (\Exception $e) {
|
|
\Log::error("MQTT Error: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================= 1. VERIFIKASI RFID (IDENTIFIKASI SEMENTARA) ================= */
|
|
|
|
Route::get('/esp/check-member', function (Request $request) {
|
|
$uid = cleanData($request->query('uid'));
|
|
|
|
if (!$uid) return response()->json(['status' => 'error', 'message' => 'UID Kosong'], 400);
|
|
|
|
$iot = IotMode::find(1);
|
|
|
|
// Tambahan: Cegah proses jika sistem sedang dalam state lock 'failed'
|
|
if ($iot && $iot->mode === 'wait') {
|
|
return response()->json(['status' => 'error', 'message' => 'Sistem terkunci sementara'], 423);
|
|
}
|
|
|
|
$anggota = DB::table('anggota')->where('uid', $uid)->orWhere('uid_clean', $uid)->first();
|
|
|
|
if (!$anggota) {
|
|
// Jika kartu salah, ubah mode jadi failed untuk mengunci scan ganda
|
|
if ($iot) $iot->update(['mode' => 'wait']);
|
|
|
|
mqttPublish('rfid/status', 'failed');
|
|
mqttPublish('rfid/ui/member', ['status' => 'invalid']);
|
|
return response()->json(['status' => 'gagal', 'message' => "Kartu $uid tidak terdaftar"], 404);
|
|
}
|
|
|
|
if ($iot && $iot->book_id && ($iot->mode === 'wait' || $iot->mode === 'identified')) {
|
|
$iot->update([
|
|
'mode' => 'identified',
|
|
'user_id' => $anggota->id
|
|
]);
|
|
|
|
mqttPublish('rfid/status', 'valid');
|
|
mqttPublish('rfid/ui/member', [
|
|
'status' => 'valid',
|
|
'nama' => $anggota->nama,
|
|
'uid' => $uid
|
|
]);
|
|
|
|
return response()->json([
|
|
'status' => 'berhasil',
|
|
'id' => $anggota->id,
|
|
'nama' => $anggota->nama,
|
|
'message'=> 'Anggota teridentifikasi, silakan klik lanjutkan di layar.'
|
|
], 200);
|
|
}
|
|
|
|
return response()->json(['status' => 'error', 'message' => 'Sistem tidak siap'], 400);
|
|
});
|
|
/* ================= 2. KONFIRMASI PEMINJAMAN REAL ================= */
|
|
|
|
Route::get('/esp/konfirmasi-pinjam', function () {
|
|
|
|
$iot = IotMode::find(1);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Validasi State
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
if (
|
|
!$iot ||
|
|
!$iot->book_id ||
|
|
!$iot->user_id
|
|
) {
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => 'Data tidak lengkap'
|
|
], 400);
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Pastikan Mode Sudah Identified
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
if ($iot->mode !== 'identified') {
|
|
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => 'Sistem belum siap'
|
|
], 400);
|
|
}
|
|
|
|
try {
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Lock State
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
$iot->update([
|
|
'mode' => 'processing'
|
|
]);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| MQTT Feedback ke ESP32
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
mqttPublish('rfid/status', 'processing');
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Dispatch Queue
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
ProcessPeminjamanJob::dispatch(
|
|
$iot->book_id,
|
|
$iot->user_id
|
|
);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Response Cepat ke Frontend
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
return response()->json([
|
|
'status' => 'queued',
|
|
'message' => 'Peminjaman masuk queue'
|
|
]);
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Rollback State
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
$iot->update([
|
|
'mode' => 'wait'
|
|
]);
|
|
|
|
mqttPublish('rfid/status', 'failed');
|
|
|
|
\Log::error(
|
|
'Dispatch Queue Error: ' . $e->getMessage()
|
|
);
|
|
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => 'Queue gagal diproses'
|
|
], 500);
|
|
}
|
|
});
|
|
|
|
/* ================= 3. SCAN BUKU (PINJAM VS KEMBALI) ================= */
|
|
|
|
Route::post('/esp/scan-buku', function (Request $request) {
|
|
$barcode = cleanData($request->barcode);
|
|
if (!$barcode) return response()->json(['status' => 'barcode_kosong']);
|
|
|
|
$book = DB::table('koleksis')
|
|
->where('kode_eksemplar', $barcode)
|
|
->orWhere('barcode', $barcode)
|
|
->first();
|
|
|
|
if (!$book) return response()->json(['status' => 'tidak_ditemukan']);
|
|
|
|
$isBorrowed = DB::table('borrow_transactions')
|
|
->where('koleksi_id', $book->biblio_id)
|
|
->where('status', 'dipinjam')
|
|
->first();
|
|
|
|
if ($isBorrowed) {
|
|
$peminjam = DB::table('anggota')->where('id', $isBorrowed->anggota_id)->first();
|
|
|
|
IotMode::updateOrCreate(['id' => 1], [
|
|
'mode' => 'return_confirm',
|
|
'book_id' => $book->biblio_id,
|
|
'user_id' => $peminjam->id
|
|
]);
|
|
|
|
return response()->json([
|
|
'status' => 'konfirmasi_kembali',
|
|
'book' => [
|
|
'judul' => $book->judul_koleksi ?? $book->title,
|
|
'barcode' => $book->kode_eksemplar ?? $book->barcode,
|
|
'peminjam_saat_ini' => $peminjam ? $peminjam->nama : 'Anggota',
|
|
'tanggal_pinjam' => $isBorrowed->tanggal_pinjam
|
|
]
|
|
]);
|
|
}
|
|
|
|
IotMode::updateOrCreate(['id' => 1], [
|
|
'mode' => 'wait',
|
|
'book_id' => $book->biblio_id,
|
|
'user_id' => null
|
|
]);
|
|
|
|
mqttPublish('rfid/mode', 'wait');
|
|
|
|
return response()->json([
|
|
'status' => 'ok',
|
|
'book' => [
|
|
'judul' => $book->judul_koleksi ?? $book->title,
|
|
'barcode' => $book->kode_eksemplar ?? $book->barcode
|
|
]
|
|
]);
|
|
});
|
|
/* ================= 4. PROSES PENGEMBALIAN ================= */
|
|
|
|
Route::get('/esp/proses-kembali', function () {
|
|
|
|
$iot = IotMode::find(1);
|
|
|
|
if (
|
|
!$iot ||
|
|
$iot->mode !== 'return_confirm' ||
|
|
!$iot->book_id
|
|
) {
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => 'Sistem tidak dalam mode pengembalian'
|
|
], 400);
|
|
}
|
|
|
|
try {
|
|
|
|
// LOCK MODE
|
|
$iot->update([
|
|
'mode' => 'processing_return'
|
|
]);
|
|
|
|
// DISPATCH JOB KE QUEUE
|
|
ProcessPengembalianJob::dispatch([
|
|
'book_id' => $iot->book_id,
|
|
'user_id' => $iot->user_id
|
|
]);
|
|
|
|
// MQTT FEEDBACK CEPAT KE ESP32
|
|
mqttPublish('rfid/status', 'processing');
|
|
|
|
return response()->json([
|
|
'status' => 'queued',
|
|
'message' => 'Pengembalian diproses queue'
|
|
]);
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
logger()->error($e);
|
|
|
|
mqttPublish('rfid/status', 'error');
|
|
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
});
|
|
|
|
/* ================= 5. STATE, RESET, & BATAL ================= */
|
|
|
|
Route::get('/esp/state', function () {
|
|
$iot = IotMode::find(1);
|
|
if (!$iot) return response()->json(['status' => 'empty', 'mode' => 'standby']);
|
|
|
|
$book = $iot->book_id ? DB::table('koleksis')->where('biblio_id', $iot->book_id)->first() : null;
|
|
$user = $iot->user_id ? DB::table('anggota')->where('id', $iot->user_id)->first() : null;
|
|
|
|
return response()->json([
|
|
'status' => 'ok',
|
|
'mode' => $iot->mode,
|
|
'book' => $book ? [
|
|
'judul' => $book->judul_koleksi ?? $book->title,
|
|
'barcode' => $book->kode_eksemplar ?? $book->barcode
|
|
] : null,
|
|
'user' => $user ? [
|
|
'nama' => $user->nama
|
|
] : null
|
|
]);
|
|
});
|
|
|
|
Route::get('/esp/batalkan-transaksi', function () {
|
|
// Reset SQL ke mode standby
|
|
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
|
|
|
|
// Sinkronkan ke Hardware
|
|
mqttPublish('rfid/mode', 'standby');
|
|
mqttPublish('rfid/status', 'idle');
|
|
mqttPublish('rfid/cancel', 'reset'); // Signal tambahan jika Backbone MQTT memantau topik rfid/cancel
|
|
|
|
return response()->json(['status' => 'ok', 'message' => 'Transaksi dibatalkan']);
|
|
});
|
|
|
|
Route::get('/esp/selesai-transaksi', function() {
|
|
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
|
|
mqttPublish('rfid/mode', 'standby');
|
|
mqttPublish('rfid/status', 'idle');
|
|
return response()->json(['status' => 'ok']);
|
|
});
|
|
|
|
Route::get('/esp/reset', function () {
|
|
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
|
|
mqttPublish('rfid/mode', 'standby');
|
|
mqttPublish('rfid/status', 'idle');
|
|
return response()->json(['status' => 'ok']);
|
|
});
|
|
|
|
/* ================= 6. ADMIN REGISTER MODE ================= */
|
|
|
|
Route::get('/esp/start-register/{user_id}', function ($user_id) {
|
|
try {
|
|
IotMode::updateOrCreate(['id' => 1], [
|
|
'mode' => 'admin',
|
|
'user_id' => $user_id,
|
|
'book_id' => null
|
|
]);
|
|
|
|
mqttPublish('rfid/mode', 'admin');
|
|
|
|
return response()->json([
|
|
'status' => 'ready',
|
|
'message' => 'Alat dalam mode pendaftaran. Silakan tap kartu.'
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json(['status' => 'error'], 500);
|
|
}
|
|
});
|
|
|
|
/* ================= 7. CONTROLLER BINDINGS ================= */
|
|
|
|
Route::get('/esp/konfirmasi-kembali/{id}', [UserPeminjamanController::class, 'kembalikan']);
|
|
Route::get('/esp/konfirmasi-kembali', [PeminjamanController::class, 'konfirmasiKembali']);
|
|
|