mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
129 lines
4.7 KiB
PHP
129 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Traits;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\AdjustQuantityRequest;
|
|
use App\Http\Requests\UploadFileRequest;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use DomainException;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Http\Request;
|
|
|
|
/**
|
|
* Shared body of the adjust-quantity controller action. Web and API
|
|
* controllers for Accessory / Consumable / Component all run the same
|
|
* sequence — authorize the update, save an optional receipt attachment,
|
|
* resolve or create an Order + OrderItem for the requested acquisition,
|
|
* call the AdjustsQuantity model trait with the Order's id, and
|
|
* translate a DomainException (would-drop-below-in-use) into the shared
|
|
* error string. Each controller still owns its own response shape
|
|
* (redirect vs JSON) around that shared work.
|
|
*/
|
|
trait HandlesAdjustQuantity
|
|
{
|
|
/**
|
|
* Run the shared adjust-quantity work. Returns null on success or a
|
|
* translated error string on failure, so the caller can wrap the
|
|
* outcome in whichever response shape (RedirectResponse or
|
|
* JsonResponse) is appropriate for the invoking controller.
|
|
*/
|
|
protected function runAdjustQuantity(
|
|
AdjustQuantityRequest $request,
|
|
Model $model,
|
|
string $storageKey,
|
|
): ?string {
|
|
$this->authorize('update', $model);
|
|
|
|
$filename = null;
|
|
if ($request->hasFile('file')) {
|
|
$filename = app(UploadFileRequest::class)->handleFile(
|
|
Controller::getMapStoragePath()[$storageKey],
|
|
Controller::getMapFilePrefix()[$storageKey].'-'.$model->id,
|
|
$request->file('file'),
|
|
);
|
|
}
|
|
|
|
$delta = (int) $request->input('amount');
|
|
$orderId = $this->resolveOrderForAdjustment($request, $model, $delta);
|
|
|
|
try {
|
|
$model->adjustQuantity(
|
|
$delta,
|
|
$request->input('note'),
|
|
$orderId,
|
|
$filename,
|
|
);
|
|
} catch (DomainException) {
|
|
return trans('general.adjust_quantity_below_zero');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find or create an Order from the request payload and append one
|
|
* OrderItem line for the model / delta being adjusted. Returns the
|
|
* Order's id (or null if the request carried no acquisition info,
|
|
* e.g. an audit-only zero-delta submission with no order_number
|
|
* given).
|
|
*
|
|
* Dedupes on (order_number, supplier_id, company_id) so multiple
|
|
* adjust-quantity events that share those fields all land under a
|
|
* single Order row. Never dedupes the OrderItem side — each
|
|
* adjustment is its own line, matching the "one line per
|
|
* acquisition event" semantic.
|
|
*
|
|
* Accepts the base Request rather than AdjustQuantityRequest
|
|
* specifically so the legacy Api\{Accessory,Consumable,Component}
|
|
* Controller::update paths — which run through ImageUploadRequest
|
|
* for their qty-inside-PATCH shape — can call it with the same
|
|
* shape as the dedicated adjust-quantity endpoint.
|
|
*/
|
|
protected function resolveOrderForAdjustment(
|
|
Request $request,
|
|
Model $model,
|
|
int $delta,
|
|
): ?int {
|
|
$orderNumber = trim((string) $request->input('order_number', ''));
|
|
$supplierId = $request->filled('supplier_id') ? (int) $request->input('supplier_id') : null;
|
|
$purchaseDate = $request->filled('purchase_date') ? $request->input('purchase_date') : null;
|
|
|
|
// No acquisition context in the request means don't create an
|
|
// Order. Audit-only submissions (zero delta with no supplier /
|
|
// order number / date) fall through here so we don't accrete
|
|
// meaningless Order rows for pure inventory counts.
|
|
if ($orderNumber === '' && $supplierId === null && $purchaseDate === null) {
|
|
return null;
|
|
}
|
|
|
|
$companyId = $model->company_id ?? null;
|
|
|
|
$order = Order::firstOrCreate(
|
|
[
|
|
'order_number' => $orderNumber !== '' ? $orderNumber : null,
|
|
'supplier_id' => $supplierId,
|
|
'company_id' => $companyId,
|
|
],
|
|
[
|
|
'purchase_date' => $purchaseDate,
|
|
'created_by' => auth()->id(),
|
|
],
|
|
);
|
|
|
|
OrderItem::create([
|
|
'order_id' => $order->id,
|
|
'item_type' => $model::class,
|
|
'item_id' => $model->id,
|
|
// OrderItem.qty is always positive — a decrement adjustment
|
|
// records the absolute number of units the line represents,
|
|
// and the delta sign lives on the sibling action_log.
|
|
'qty' => max(1, abs($delta)),
|
|
'price' => null,
|
|
]);
|
|
|
|
return $order->id;
|
|
}
|
|
}
|