Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ PAYMONGO_SUCCESS_URL="${APP_URL}/checkout/success"
PAYMONGO_FAILED_URL="${APP_URL}/checkout/failed"

GEMINI_API_KEY=

RFID_API_SECRET=
46 changes: 46 additions & 0 deletions app/Http/Controllers/Api/RfidController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\Rfid\RfidScanRequest;
use App\Services\ItemService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class RfidController extends Controller
{
/**
* Inject Item Service
*
* @param ItemService $itemService
*/
public function __construct(
protected ItemService $itemService
) {}

/**
* Handle RFID scan and adjust item quantity.
*/
public function scan(RfidScanRequest $request): JsonResponse
{
$result = $this->itemService->adjustQuantityByCode(
$request->validated()['item_code'],
$request->validated()['action'],
$request->validated()['quantity']
);

if (! $result['success']) {
return response()->json([
'status' => 'error',
'message' => $result['message']
], 422);
}

return response()->json([
'status' => 'success',
'message' => $result['message'],
'data' => $result['item'],
], 200);
}
}
29 changes: 29 additions & 0 deletions app/Http/Middleware/ApiSecretMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ApiSecretMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$secret = config('app.rfid_api_secret');

if (! $secret || $request->header('X-API-Secret') !== $secret) {
return response()->json([
'status' => 'error',
'message' => 'Unauthorized'
], 401);
}

return $next($request);
}
}
30 changes: 30 additions & 0 deletions app/Http/Requests/Rfid/RfidScanRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Http\Requests\Rfid;

use Illuminate\Foundation\Http\FormRequest;

class RfidScanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // Middleware handles authorization
}

/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'item_code' => 'required|string',
'action' => 'required|string|in:add,deduct',
'quantity' => 'required|integer|min:1'
];
}
}
1 change: 1 addition & 0 deletions app/Repositories/Interfaces/ItemRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ interface ItemRepositoryInterface extends BaseRepositoryInterface
{
public function latestByCategory(string $category): ?Item;
public function getLowStockItems(): Collection;
public function findByCode(string $itemCode): ?Item;
}
13 changes: 13 additions & 0 deletions app/Repositories/ItemRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,17 @@ public function getLowStockItems(): Collection
->orderBy('quantity', 'asc')
->get();
}

/**
* Find an item by its item_code
*
* @param string $itemCode
* @return Item|null
*/
public function findByCode(string $itemCode): ?Item
{
return $this->query()
->where('item_code', $itemCode)
->first();
}
}
40 changes: 40 additions & 0 deletions app/Services/ItemService.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,44 @@ protected function deleteImages(Item $item)
}
}
}

/**
* Adjust item quantity via RFID scan.
* Use action 'add' to increase and 'deduct' to decrease.
*
* @param string $itemCode
* @param string $action 'add' | 'deduct'
* @param int $quantity
* @return array{success: bool, message: string, item?: Item}
*/
public function adjustQuantityByCode(string $itemCode, string $action, int $quantity): array
{
$item = $this->itemRepo->findByCode($itemCode);

if (! $item) {
return ['success' => false, 'message' => 'Item not found'];
}

if ($action === 'deduct') {
if ($item->quantity < $quantity) {
return ['success' => false, 'message' => 'Insufficient stock'];
}
$newQuantity = $item->quantity - $quantity;
} elseif ($action === 'add') {
$newQuantity = $item->quantity + $quantity;
} else {
return ['success' => false, 'message' => 'Invalid action. Use "add" or "deduct"'];
}

$this->itemRepo->update($item->id, ['quantity' => $newQuantity]);

// Re-fetch for fresh data
$item->refresh();

return [
'success' => true,
'message' => 'Quantity updated successfully',
'item' => $item
];
}
}
3 changes: 3 additions & 0 deletions bootstrap/app.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Http\Middleware\ApiSecretMiddleware;
use App\Http\Middleware\HandleAppearance;
use App\Http\Middleware\HandleInertiaRequests;
use App\Http\Middleware\RoleMiddleware;
Expand All @@ -11,6 +12,7 @@
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
channels: __DIR__.'/../routes/channels.php',
health: '/up',
Expand All @@ -26,6 +28,7 @@

$middleware->alias([
'role' => RoleMiddleware::class,
'api.secret' => ApiSecretMiddleware::class
]);
})
->withExceptions(function (Exceptions $exceptions): void {
Expand Down
10 changes: 10 additions & 0 deletions config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,14 @@
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],

/*
|--------------------------------------------------------------------------
| RFID API Secret
|--------------------------------------------------------------------------
|
| This secret key is used to authenticate requests from the Python RFID
| scanner. It must match the X-API-Secret header sent by the scanner.
|
*/
'rfid_api_secret' => env('RFID_API_SECRET'),
];
12 changes: 12 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

use Illuminate\Support\Facades\Route;

Route::get('/health', function () {
return response()->json([
'status' => 'ok',
'message' => 'API is running',
]);
});

require __DIR__ . '/groups/rfid.php';
14 changes: 14 additions & 0 deletions routes/groups/rfid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

use App\Http\Controllers\Api\RfidController;
use Illuminate\Support\Facades\Route;

Route::middleware(['api.secret'])
->prefix('rfid')
->name('rfid.')
->group(function () {

// Scan item by item_code
Route::post('/scan', [RfidController::class, 'scan'])
->name('scan');
});
Loading