From cfb09fd5fbfe142330a87bed21d9290449588c48 Mon Sep 17 00:00:00 2001 From: Developer Date: Tue, 30 Jun 2026 15:03:25 +0800 Subject: [PATCH] feat: add void and delete endpoints for qr batches, fix batch list status counts --- .../Api/V1/Admin/AdminQrBatchController.php | 33 ++++++++++++++++++- app/Http/Resources/QrCodeBatchResource.php | 13 ++++++-- routes/api.php | 2 ++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/Admin/AdminQrBatchController.php b/app/Http/Controllers/Api/V1/Admin/AdminQrBatchController.php index c92c4b0..8f10205 100644 --- a/app/Http/Controllers/Api/V1/Admin/AdminQrBatchController.php +++ b/app/Http/Controllers/Api/V1/Admin/AdminQrBatchController.php @@ -38,7 +38,12 @@ class AdminQrBatchController extends ApiController $batches = QrCodeBatch::query() ->with(['household.head', 'createdBy']) - ->withCount('codes') + ->withCount([ + 'codes', + 'codes as active_codes_count' => fn ($q) => $q->where('status', 'active'), + 'codes as used_codes_count' => fn ($q) => $q->where('status', 'used'), + 'codes as voided_codes_count' => fn ($q) => $q->where('status', 'voided'), + ]) ->when($request->filled('purpose'), fn ($q) => $q->where('purpose', $request->string('purpose'))) ->when( $request->filled('household_id'), @@ -105,6 +110,32 @@ class AdminQrBatchController extends ApiController ); } + public function voidBatch(QrCodeBatch $qrCodeBatch): JsonResponse + { + // Update all codes that aren't already used + $qrCodeBatch->codes() + ->whereNotIn('status', ['used']) + ->update(['status' => 'voided']); + + return $this->ok( + new QrCodeBatchResource($qrCodeBatch->fresh()->load(['household.head', 'createdBy', 'codes'])), + 'Batch voided successfully', + ); + } + + public function destroy(QrCodeBatch $qrCodeBatch): JsonResponse + { + // Delete any unused/unassigned codes to free up space + $qrCodeBatch->codes() + ->whereNotIn('status', ['used', 'active']) + ->delete(); + + // Soft delete the batch itself + $qrCodeBatch->delete(); + + return $this->ok(null, 'Batch deleted successfully'); + } + public function showCode(string $serial): JsonResponse { $code = QrCode::query() diff --git a/app/Http/Resources/QrCodeBatchResource.php b/app/Http/Resources/QrCodeBatchResource.php index c442ea8..d262a29 100644 --- a/app/Http/Resources/QrCodeBatchResource.php +++ b/app/Http/Resources/QrCodeBatchResource.php @@ -10,11 +10,18 @@ class QrCodeBatchResource extends JsonResource { public function toArray(Request $request): array { - $statusCounts = $this->whenLoaded('codes', function () { - return $this->codes->groupBy(fn (QrCode $c) => (string) $c->status) + $statusCounts = [ + 'active' => $this->active_codes_count ?? 0, + 'used' => $this->used_codes_count ?? 0, + 'voided' => $this->voided_codes_count ?? 0, + ]; + + // If the relation is loaded (like on the show endpoint), compute it accurately just in case + if ($this->relationLoaded('codes') && ! isset($this->active_codes_count)) { + $statusCounts = $this->codes->groupBy(fn (QrCode $c) => (string) $c->status) ->map(fn ($group) => $group->count()) ->all(); - }); + } return [ 'batch_number' => $this->batch_number, diff --git a/routes/api.php b/routes/api.php index 523fb86..19e7ad6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -236,6 +236,8 @@ Route::prefix('admin/qr-batches') Route::post('/', [AdminQrBatchController::class, 'store'])->name('store'); Route::get('/{qr_code_batch}', [AdminQrBatchController::class, 'show'])->name('show'); Route::post('/{qr_code_batch}/mark-printed', [AdminQrBatchController::class, 'markPrinted'])->name('mark-printed'); + Route::post('/{qr_code_batch}/void', [AdminQrBatchController::class, 'voidBatch'])->name('void'); + Route::delete('/{qr_code_batch}', [AdminQrBatchController::class, 'destroy'])->name('destroy'); }); Route::get('admin/qr-batches/{qr_code_batch}/print.pdf', [AdminQrBatchController::class, 'printPdf'])