D unused files
This commit is contained in:
parent
fe41475c84
commit
d6a03f413a
|
|
@ -1,190 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Blax\Shop\Http\Controllers\Concerns;
|
|
||||||
|
|
||||||
use Blax\Shop\Exceptions\NotEnoughStockException;
|
|
||||||
use Blax\Shop\Http\Requests\StoreLoanRequest;
|
|
||||||
use Blax\Shop\Http\Resources\LoanResource;
|
|
||||||
use Blax\Shop\Models\ProductPurchase;
|
|
||||||
use Blax\Workkit\Services\MiscService;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop this trait on a host controller to expose the full loan lifecycle —
|
|
||||||
* list / create / show / extend / return — without re-implementing any of
|
|
||||||
* the standard machinery (atomic stock decrement, ownership guard, event
|
|
||||||
* dispatch, status filtering, pagination, resource envelopes).
|
|
||||||
*
|
|
||||||
* Required override:
|
|
||||||
* - {@see loanableModel()} — return the host model class name (e.g.
|
|
||||||
* Book::class). Must implement Cartable +
|
|
||||||
* Purchasable; usually a {@see \Blax\Shop\Models\Product}
|
|
||||||
* subclass using {@see \Blax\Shop\Traits\IsLoanableProduct}.
|
|
||||||
*
|
|
||||||
* Optional overrides:
|
|
||||||
* - {@see loanResource()} — the JsonResource subclass to serialise
|
|
||||||
* ProductPurchase rows. Defaults to
|
|
||||||
* {@see LoanResource}; override to point at
|
|
||||||
* a host subclass that customises the
|
|
||||||
* purchasable resource (e.g. BookResource).
|
|
||||||
* - {@see storeRequest()} — FormRequest class used by store(). Defaults
|
|
||||||
* to {@see StoreLoanRequest} which validates
|
|
||||||
* a `loanable_id` field. Override if you
|
|
||||||
* want the body key to be `book_id` etc.
|
|
||||||
*
|
|
||||||
* Wire endpoints in your routes file (per-method) or use the
|
|
||||||
* `Route::shopLoans()` macro to register all five at once.
|
|
||||||
*/
|
|
||||||
trait HandlesLoans
|
|
||||||
{
|
|
||||||
/** Required — name of the Cartable+Purchasable model being loaned. */
|
|
||||||
abstract protected function loanableModel(): string;
|
|
||||||
|
|
||||||
/** Override to customise the response shape (purchasable subresource). */
|
|
||||||
protected function loanResource(): string
|
|
||||||
{
|
|
||||||
return LoanResource::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Override to swap the FormRequest used by store(). */
|
|
||||||
protected function storeRequest(): string
|
|
||||||
{
|
|
||||||
return StoreLoanRequest::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
$query = ProductPurchase::query()
|
|
||||||
->where('purchaser_type', $user::class)
|
|
||||||
->where('purchaser_id', $user->getKey())
|
|
||||||
->where('purchasable_type', $this->loanableModel())
|
|
||||||
->with('purchasable')
|
|
||||||
->orderByDesc('from');
|
|
||||||
|
|
||||||
match ($request->query('status')) {
|
|
||||||
'active' => $query->activeLoans(),
|
|
||||||
'returned' => $query->returned(),
|
|
||||||
'overdue' => $query->overdue(),
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
$perPage = min(max((int) $request->query('per_page', 25), 1), 100);
|
|
||||||
|
|
||||||
return response()->json(MiscService::apiPaginated(
|
|
||||||
$query->paginate($perPage),
|
|
||||||
$this->loanResource(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// Resolve the request class lazily so subclass overrides win.
|
|
||||||
/** @var StoreLoanRequest $validated */
|
|
||||||
$validated = app($this->storeRequest());
|
|
||||||
$validated->setContainer(app())->setRedirector(app('redirect'));
|
|
||||||
$validated->initialize(
|
|
||||||
$request->query(),
|
|
||||||
$request->request->all(),
|
|
||||||
$request->attributes->all(),
|
|
||||||
$request->cookies->all(),
|
|
||||||
$request->files->all(),
|
|
||||||
$request->server->all(),
|
|
||||||
$request->getContent(),
|
|
||||||
);
|
|
||||||
$validated->setUserResolver($request->getUserResolver());
|
|
||||||
$validated->validateResolved();
|
|
||||||
|
|
||||||
$model = $this->loanableModel();
|
|
||||||
/** @var \Blax\Shop\Models\Product $item */
|
|
||||||
$item = $model::query()->findOrFail($validated->loanableId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
$purchase = $item->checkOutTo($request->user());
|
|
||||||
} catch (NotEnoughStockException) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
$validated->validationKey() => ['No copies of this item are currently available.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(
|
|
||||||
MiscService::apiItem($purchase->load('purchasable'), $this->loanResource()),
|
|
||||||
Response::HTTP_CREATED,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function show(Request $request, ProductPurchase $purchase): JsonResponse
|
|
||||||
{
|
|
||||||
$this->ensureLoanOwner($request, $purchase);
|
|
||||||
|
|
||||||
return response()->json(MiscService::apiItem(
|
|
||||||
$purchase->load('purchasable'),
|
|
||||||
$this->loanResource(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function extend(Request $request, ProductPurchase $purchase): JsonResponse
|
|
||||||
{
|
|
||||||
$this->ensureLoanOwner($request, $purchase);
|
|
||||||
|
|
||||||
if ($purchase->isReturned()) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'loan' => ['This loan has already been returned.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $purchase->canExtend()) {
|
|
||||||
$message = $purchase->isOverdue()
|
|
||||||
? 'Overdue loans cannot be extended — please return the item first.'
|
|
||||||
: 'This loan has reached the maximum number of extensions.';
|
|
||||||
|
|
||||||
throw ValidationException::withMessages(['loan' => [$message]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$purchase->extend();
|
|
||||||
|
|
||||||
return response()->json(MiscService::apiItem(
|
|
||||||
$purchase->load('purchasable'),
|
|
||||||
$this->loanResource(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function returnLoan(Request $request, ProductPurchase $purchase): JsonResponse
|
|
||||||
{
|
|
||||||
$this->ensureLoanOwner($request, $purchase);
|
|
||||||
|
|
||||||
if ($purchase->isReturned()) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'loan' => ['This loan has already been returned.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$purchase->markReturned();
|
|
||||||
|
|
||||||
$purchase->load('purchasable');
|
|
||||||
if ($purchase->purchasable !== null && method_exists($purchase->purchasable, 'increaseStock')) {
|
|
||||||
$purchase->purchasable->increaseStock(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(MiscService::apiItem($purchase, $this->loanResource()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 403 unless the authenticated user is the loan's purchaser.
|
|
||||||
*/
|
|
||||||
protected function ensureLoanOwner(Request $request, ProductPurchase $purchase): void
|
|
||||||
{
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
$isOwner = $purchase->purchaser_type === $user::class
|
|
||||||
&& (string) $purchase->purchaser_id === (string) $user->getKey();
|
|
||||||
|
|
||||||
if (! $isOwner) {
|
|
||||||
abort(Response::HTTP_FORBIDDEN, 'This loan does not belong to you.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Blax\Shop\Http\Requests;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic store-loan validation. Accepts a `loanable_id` referencing a row
|
|
||||||
* in the products table. Host apps that want a different request key
|
|
||||||
* (e.g. `book_id`) can subclass and override rules() / prepareForValidation().
|
|
||||||
*/
|
|
||||||
class StoreLoanRequest extends FormRequest
|
|
||||||
{
|
|
||||||
public function authorize(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'loanable_id' => [
|
|
||||||
'required',
|
|
||||||
'uuid',
|
|
||||||
'exists:'.config('shop.tables.products', 'products').',id',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The validated id of the model to be checked out. Subclasses that
|
|
||||||
* rename the field can override this to keep HandlesLoans agnostic.
|
|
||||||
*/
|
|
||||||
public function loanableId(): string
|
|
||||||
{
|
|
||||||
return $this->validated($this->validationKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The body / validation key used in `rules()`. HandlesLoans uses this
|
|
||||||
* to attach the "no copies available" error to the correct field, so
|
|
||||||
* subclasses that rename `loanable_id` should override here too.
|
|
||||||
*/
|
|
||||||
public function validationKey(): string
|
|
||||||
{
|
|
||||||
return 'loanable_id';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -93,13 +93,16 @@ class ShopServiceProvider extends ServiceProvider
|
||||||
* concisely. Currently provides:
|
* concisely. Currently provides:
|
||||||
*
|
*
|
||||||
* Route::shopLoans('loans', \App\Http\Controllers\LoanController::class)
|
* Route::shopLoans('loans', \App\Http\Controllers\LoanController::class)
|
||||||
* → POST {prefix} index of caller's loans
|
* → GET {prefix} index of caller's loans
|
||||||
* → POST {prefix} check out a new loan
|
* → POST {prefix} check out a new loan
|
||||||
* → GET {prefix}/{purchase} show a single loan
|
* → GET {prefix}/{purchase} show a single loan
|
||||||
* → POST {prefix}/{purchase}/extend extend the due date
|
* → POST {prefix}/{purchase}/extend extend the due date
|
||||||
* → POST {prefix}/{purchase}/return return the item
|
* → POST {prefix}/{purchase}/return return the item
|
||||||
*
|
*
|
||||||
* The controller is expected to use {@see \Blax\Shop\Http\Controllers\Concerns\HandlesLoans}.
|
* The controller must expose matching methods: index, store, show,
|
||||||
|
* extend, returnLoan. The {purchase} route parameter is passed as a
|
||||||
|
* raw UUID string — controllers typically resolve it through the
|
||||||
|
* authenticated user's loans relation so ownership falls out for free.
|
||||||
*/
|
*/
|
||||||
protected function registerRouteMacros(): void
|
protected function registerRouteMacros(): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue