220 lines
7.3 KiB
PHP
220 lines
7.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\V1\Admin;
|
|
|
|
use App\Http\Controllers\Api\V1\ApiController;
|
|
use App\Http\Requests\Qr\GenerateBatchRequest;
|
|
use App\Http\Resources\QrCodeBatchResource;
|
|
use App\Http\Resources\QrCodeResource;
|
|
use App\Models\Household;
|
|
use App\Models\QrCode;
|
|
use App\Models\QrCodeBatch;
|
|
use App\Models\ServiceArea;
|
|
use App\Services\Qr\BatchGenerator;
|
|
use Barryvdh\DomPDF\Facade\Pdf;
|
|
use Carbon\Carbon;
|
|
use Endroid\QrCode\Builder\Builder;
|
|
use Endroid\QrCode\ErrorCorrectionLevel;
|
|
use Endroid\QrCode\Writer\PngWriter;
|
|
use Endroid\QrCode\Writer\SvgWriter;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Picqer\Barcode\BarcodeGeneratorPNG;
|
|
use Picqer\Barcode\BarcodeGeneratorSVG;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class AdminQrBatchController extends ApiController
|
|
{
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'purpose' => ['nullable', 'in:free_allocation,store_inventory,promotional'],
|
|
'household_id' => ['nullable', 'integer'],
|
|
'q' => ['nullable', 'string', 'max:100'],
|
|
'per_page' => ['nullable', 'integer', 'min:1', 'max:500'],
|
|
]);
|
|
|
|
$perPage = (int) $request->input('per_page', 25);
|
|
|
|
$batches = QrCodeBatch::query()
|
|
->with(['household.head', 'createdBy'])
|
|
->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'),
|
|
fn ($q) => $q->where('household_id', $request->integer('household_id')),
|
|
)
|
|
->when(
|
|
$request->filled('q'),
|
|
fn ($q) => $q->where('batch_number', 'like', '%'.$request->string('q').'%'),
|
|
)
|
|
->orderByDesc('id')
|
|
->paginate($perPage);
|
|
|
|
return $this->ok(
|
|
QrCodeBatchResource::collection($batches),
|
|
null,
|
|
[
|
|
'page' => $batches->currentPage(),
|
|
'per_page' => $batches->perPage(),
|
|
'total' => $batches->total(),
|
|
'last_page' => $batches->lastPage(),
|
|
],
|
|
);
|
|
}
|
|
|
|
public function store(GenerateBatchRequest $request, BatchGenerator $generator): JsonResponse
|
|
{
|
|
$data = $request->validated();
|
|
$household = Household::where('uuid', $data['household_id'])->firstOrFail();
|
|
|
|
$batch = $generator->generate(
|
|
quantity: (int) $data['quantity'],
|
|
purpose: $data['purpose'],
|
|
targetArea: null,
|
|
targetStoreId: $data['target_store_id'] ?? null,
|
|
createdBy: $request->user(),
|
|
expiresAt: isset($data['expires_at']) ? Carbon::parse($data['expires_at']) : null,
|
|
notes: $data['notes'] ?? null,
|
|
householdId: $household->id,
|
|
);
|
|
|
|
$batch->load(['household.head', 'createdBy', 'codes']);
|
|
|
|
return $this->created(new QrCodeBatchResource($batch), 'Batch generated');
|
|
}
|
|
|
|
public function show(QrCodeBatch $qrCodeBatch): JsonResponse
|
|
{
|
|
$qrCodeBatch->load(['household.head', 'createdBy', 'codes']);
|
|
|
|
return $this->ok(new QrCodeBatchResource($qrCodeBatch));
|
|
}
|
|
|
|
public function markPrinted(QrCodeBatch $qrCodeBatch): JsonResponse
|
|
{
|
|
if ($qrCodeBatch->printed_at) {
|
|
return $this->fail('Batch already marked as printed', null, 422);
|
|
}
|
|
|
|
$qrCodeBatch->forceFill(['printed_at' => now()])->save();
|
|
|
|
return $this->ok(
|
|
new QrCodeBatchResource($qrCodeBatch->fresh()->load(['targetArea', 'createdBy', 'codes'])),
|
|
'Batch marked as printed',
|
|
);
|
|
}
|
|
|
|
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()
|
|
->with(['batch.targetArea', 'household.head', 'usedAtDropOff', 'scannedBy'])
|
|
->where('serial', $serial)
|
|
->firstOrFail();
|
|
|
|
return $this->ok(new QrCodeResource($code));
|
|
}
|
|
|
|
public function printPdf(QrCodeBatch $qrCodeBatch): Response
|
|
{
|
|
$codes = $qrCodeBatch->codes()->orderBy('id')->get();
|
|
|
|
$rendered = $codes->map(function ($c) {
|
|
return [
|
|
'serial' => $c->serial,
|
|
'qr_data_uri' => $this->qrDataUri($c->serial),
|
|
'barcode_data_uri' => $this->barcodeDataUri($c->serial),
|
|
];
|
|
})->all();
|
|
|
|
$perPage = 24;
|
|
$pages = array_chunk($rendered, $perPage);
|
|
|
|
$pdf = Pdf::loadView('pdf.qr-batch', [
|
|
'batch' => $qrCodeBatch,
|
|
'pages' => $pages,
|
|
])->setPaper('a4');
|
|
|
|
$filename = "verde-{$qrCodeBatch->batch_number}.pdf";
|
|
|
|
return $pdf->download($filename);
|
|
}
|
|
|
|
private function qrDataUri(string $serial): string
|
|
{
|
|
try {
|
|
$result = (new Builder(
|
|
writer: new PngWriter,
|
|
data: $serial,
|
|
errorCorrectionLevel: ErrorCorrectionLevel::Medium,
|
|
size: 140,
|
|
margin: 4,
|
|
))->build();
|
|
|
|
return $result->getDataUri();
|
|
} catch (\Throwable $e) {
|
|
if (class_exists(SvgWriter::class)) {
|
|
$result = (new Builder(
|
|
writer: new SvgWriter,
|
|
data: $serial,
|
|
errorCorrectionLevel: ErrorCorrectionLevel::Medium,
|
|
size: 140,
|
|
margin: 4,
|
|
))->build();
|
|
|
|
return 'data:image/svg+xml;base64,'.base64_encode($result->getString());
|
|
}
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private function barcodeDataUri(string $serial): string
|
|
{
|
|
try {
|
|
$generator = new BarcodeGeneratorPNG;
|
|
$png = $generator->getBarcode($serial, BarcodeGeneratorPNG::TYPE_CODE_128, 1, 28);
|
|
|
|
return 'data:image/png;base64,'.base64_encode($png);
|
|
} catch (\Throwable $e) {
|
|
if (class_exists(BarcodeGeneratorSVG::class)) {
|
|
$generator = new BarcodeGeneratorSVG;
|
|
$svg = $generator->getBarcode($serial, BarcodeGeneratorSVG::TYPE_CODE_128, 1, 28);
|
|
|
|
return 'data:image/svg+xml;base64,'.base64_encode($svg);
|
|
}
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|