3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 03:06:23 +00:00

Moved JSON response into trait

This commit is contained in:
snipe
2026-08-07 11:58:43 +01:00
parent ff10b8139a
commit a2afbfb911
10 changed files with 60 additions and 270 deletions

View File

@ -263,22 +263,12 @@ class AccessoriesController extends Controller
}
/**
* Apply an on-hand quantity delta (+/-) and log the change. The
* shared body lives on the HandlesAdjustQuantity trait; this method
* only owns the redirect-response shape.
* Apply an on-hand quantity delta (+/-) and log the change. Route
* exists here so route-model binding resolves against Accessory,
* everything else lives on HandlesAdjustQuantity.
*/
public function adjustQuantity(AdjustQuantityRequest $request, Accessory $accessory): RedirectResponse
{
$error = $this->runAdjustQuantity($request, $accessory, 'accessories');
if ($error) {
return redirect()->back()->with('error', $error);
}
// adjustQuantityRedirect keeps show-page opens landing on
// #history for the confirmation loop, and sends index-page
// opens back to the index so bulk adjust flows aren't broken
// by a forced navigation to the item detail page.
return $this->adjustQuantityRedirect($request, route('accessories.show', $accessory));
return $this->adjustQuantityAsRedirect($request, $accessory);
}
}

View File

@ -363,18 +363,7 @@ class AccessoriesController extends Controller
*/
public function adjustQuantity(AdjustQuantityRequest $request, Accessory $accessory): JsonResponse
{
$error = $this->runAdjustQuantity($request, $accessory, 'accessories');
if ($error !== null) {
return response()->json(
Helper::formatStandardApiResponse('error', null, $error),
422,
);
}
return response()->json(
Helper::formatStandardApiResponse('success', $accessory->fresh(), trans('general.adjust_quantity_success')),
);
return $this->adjustQuantityAsJson($request, $accessory);
}
/**

View File

@ -296,18 +296,7 @@ class ComponentsController extends Controller
*/
public function adjustQuantity(AdjustQuantityRequest $request, Component $component): JsonResponse
{
$error = $this->runAdjustQuantity($request, $component, 'components');
if ($error !== null) {
return response()->json(
Helper::formatStandardApiResponse('error', null, $error),
422,
);
}
return response()->json(
Helper::formatStandardApiResponse('success', $component->fresh(), trans('general.adjust_quantity_success')),
);
return $this->adjustQuantityAsJson($request, $component);
}
/**

View File

@ -281,18 +281,7 @@ class ConsumablesController extends Controller
*/
public function adjustQuantity(AdjustQuantityRequest $request, Consumable $consumable): JsonResponse
{
$error = $this->runAdjustQuantity($request, $consumable, 'consumables');
if ($error !== null) {
return response()->json(
Helper::formatStandardApiResponse('error', null, $error),
422,
);
}
return response()->json(
Helper::formatStandardApiResponse('success', $consumable->fresh(), trans('general.adjust_quantity_success')),
);
return $this->adjustQuantityAsJson($request, $consumable);
}
/**

View File

@ -259,20 +259,12 @@ class ComponentsController extends Controller
}
/**
* Apply an on-hand quantity delta (+/-) and log the change. The
* shared body lives on the HandlesAdjustQuantity trait; this method
* only owns the redirect-response shape.
* Apply an on-hand quantity delta (+/-) and log the change. Route
* exists here so route-model binding resolves against Component,
* everything else lives on HandlesAdjustQuantity.
*/
public function adjustQuantity(AdjustQuantityRequest $request, Component $component): RedirectResponse
{
$error = $this->runAdjustQuantity($request, $component, 'components');
if ($error) {
return redirect()->back()->with('error', $error);
}
// See AccessoriesController::adjustQuantity — helper picks
// between show-page-#history and back-to-referer.
return $this->adjustQuantityRedirect($request, route('components.show', $component));
return $this->adjustQuantityAsRedirect($request, $component);
}
}

View File

@ -261,20 +261,12 @@ class ConsumablesController extends Controller
}
/**
* Apply an on-hand quantity delta (+/-) and log the change. The
* shared body lives on the HandlesAdjustQuantity trait; this method
* only owns the redirect-response shape.
* Apply an on-hand quantity delta (+/-) and log the change. Route
* exists here so route-model binding resolves against Consumable,
* everything else lives on HandlesAdjustQuantity.
*/
public function adjustQuantity(AdjustQuantityRequest $request, Consumable $consumable): RedirectResponse
{
$error = $this->runAdjustQuantity($request, $consumable, 'consumables');
if ($error) {
return redirect()->back()->with('error', $error);
}
// See AccessoriesController::adjustQuantity — helper picks
// between show-page-#history and back-to-referer.
return $this->adjustQuantityRedirect($request, route('consumables.show', $consumable));
return $this->adjustQuantityAsRedirect($request, $consumable);
}
}

View File

@ -10,6 +10,7 @@ use App\Models\Order;
use App\Models\OrderItem;
use DomainException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -63,9 +64,9 @@ trait HandlesAdjustQuantity
/**
* Land the post-save redirect on the page the operator was on when
* they opened the modal. From the item's show page the show page
* they opened the modal. From the item's show page: the show page
* with the `#history` fragment so the newly-written log entry is
* visible as confirmation. From an index / listing page back to
* visible as confirmation. From an index / listing page: back to
* that listing so bulk-adjust flows don't force the operator to
* back out and re-navigate for every item. Referer is validated
* as same-origin to prevent open-redirect abuse; missing or
@ -83,6 +84,48 @@ trait HandlesAdjustQuantity
return redirect()->to($itemShowUrl)->withFragment('history')->with('success', $success);
}
/**
* Full web controller flow. Runs the shared work and wraps the
* outcome in the standard redirect-with-flash shape. Route target
* derives from Controller::$map_class_url_segment so a new inventory
* model that adopts HandlesAdjustQuantity only needs an entry in
* that map, not a per-model method or a controller-side segment
* string.
*/
protected function adjustQuantityAsRedirect(AdjustQuantityRequest $request, Model $model): RedirectResponse
{
$segment = Controller::getMapClassUrlSegment()[$model::class];
$error = $this->runAdjustQuantity($request, $model, $segment);
if ($error) {
return redirect()->back()->with('error', $error);
}
return $this->adjustQuantityRedirect($request, route("$segment.show", $model));
}
/**
* Full API controller flow. Runs the shared work and wraps the
* outcome in the standard Snipe-IT JSON envelope. 422 on validation
* / floor errors, 200 with the refreshed model on success.
*/
protected function adjustQuantityAsJson(AdjustQuantityRequest $request, Model $model): JsonResponse
{
$segment = Controller::getMapClassUrlSegment()[$model::class];
$error = $this->runAdjustQuantity($request, $model, $segment);
if ($error !== null) {
return response()->json(
Helper::formatStandardApiResponse('error', null, $error),
422,
);
}
return response()->json(
Helper::formatStandardApiResponse('success', $model->fresh(), trans('general.adjust_quantity_success')),
);
}
/**
* Find or create an Order from the request payload and append one
* OrderItem line for the model / delta being adjusted. Returns the

View File

@ -464,73 +464,6 @@ class Accessory extends SnipeModel
$accessory_checkout->limit(1)->delete();
}
/**
* Sum every OrderItem's line total (qty × price) grouped by the
* parent Order's currency, so mixed-currency acquisitions render
* as a per-currency breakdown instead of a single misleading total.
*
* Falls back to `qty × parent.purchase_cost` under the system's
* default_currency when the item has no OrderItems yet (legacy
* rows uncaught by backfill, or brand-new items with a purchase
* cost set but no acquisitions recorded).
*
* Returns [] when both paths are empty. The info-panel skips the
* "Total cost" line entirely in that case rather than showing 0.
*
* @return array<string, float> currency code => sum in that currency
*/
public function totalCostSumByCurrency(): array
{
$lines = $this->orderItems()->with('order:id,currency')->get();
$totals = $lines->reduce(function (array $carry, OrderItem $line) {
if ($line->price === null) {
return $carry;
}
$currency = $line->order?->currency
?? Setting::getSettings()?->default_currency
?? '';
$carry[$currency] = ($carry[$currency] ?? 0) + ($line->qty * (float) $line->price);
return $carry;
}, []);
// No fallback for unaccounted qty. Orders / OrderItems is the
// single source of truth for acquisition cost. If lines don't
// cover the current on-hand qty, the display honestly shows
// "we don't know the cost of those units" (empty totals).
return $totals;
}
/**
* Naive cross-currency sum, kept for backwards compatibility with
* external callers. New code should prefer totalCostSumByCurrency()
* so mixed-currency totals stay disambiguated.
*/
public function totalCostSum()
{
return array_sum($this->totalCostSumByCurrency()) ?: null;
}
/**
* True when every recorded acquisition for this item came from the
* same supplier. Compares distinct supplier_ids across every Order
* linked via OrderItems. The info-panel's supplier row hides itself
* when this returns false so a single supplier name doesn't
* misrepresent multi-supplier history.
*/
public function hasConsistentSupplier(): bool
{
return $this->orderItems()
->with('order:id,supplier_id')
->get()
->map(fn (OrderItem $line) => $line->order?->supplier_id)
->filter()
->unique()
->count() <= 1;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS

View File

@ -417,69 +417,6 @@ class Component extends SnipeModel
return $this->qty - $this->numCheckedOut();
}
/**
* Sum every OrderItem's line total (qty × price) grouped by the
* parent Order's currency, so mixed-currency acquisitions render
* as a per-currency breakdown instead of a single misleading total.
*
* Falls back to `qty × parent.purchase_cost` under the system's
* default_currency when the item has no OrderItems yet (legacy
* rows uncaught by backfill, or brand-new items with a purchase
* cost set but no acquisitions recorded).
*
* Returns [] when both paths are empty. The info-panel skips the
* "Total cost" line entirely in that case rather than showing 0.
*
* @return array<string, float> currency code => sum in that currency
*/
public function totalCostSumByCurrency(): array
{
$lines = $this->orderItems()->with('order:id,currency')->get();
$totals = $lines->reduce(function (array $carry, OrderItem $line) {
if ($line->price === null) {
return $carry;
}
$currency = $line->order?->currency
?? Setting::getSettings()?->default_currency
?? '';
$carry[$currency] = ($carry[$currency] ?? 0) + ($line->qty * (float) $line->price);
return $carry;
}, []);
// Orders / OrderItems is the single source of truth. No
// fallback to legacy_* columns (those will be dropped in a
// later version). See Accessory::totalCostSumByCurrency.
return $totals;
}
/**
* Naive cross-currency sum, kept for backwards compatibility with
* external callers. New code should prefer totalCostSumByCurrency()
* so mixed-currency totals stay disambiguated.
*/
public function totalCostSum()
{
return array_sum($this->totalCostSumByCurrency()) ?: null;
}
/**
* True when every recorded acquisition for this item came from the
* same supplier. Info-panel supplier row hides when false so a
* single supplier name doesn't misrepresent multi-supplier history.
*/
public function hasConsistentSupplier(): bool
{
return $this->orderItems()
->with('order:id,supplier_id')
->get()
->map(fn (OrderItem $line) => $line->order?->supplier_id)
->filter()
->unique()
->count() <= 1;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS

View File

@ -385,70 +385,6 @@ class Consumable extends SnipeModel
return $remaining;
}
/**
* Sum every OrderItem's line total (qty × price) grouped by the
* parent Order's currency, so mixed-currency acquisitions render
* as a per-currency breakdown instead of a single misleading total.
*
* Falls back to `qty × parent.purchase_cost` under the system's
* default_currency when the item has no OrderItems yet (legacy
* rows uncaught by backfill, or brand-new items with a purchase
* cost set but no acquisitions recorded).
*
* Returns [] when both paths are empty. The info-panel skips the
* "Total cost" line entirely in that case rather than showing 0.
*
* @return array<string, float> currency code => sum in that currency
*/
public function totalCostSumByCurrency(): array
{
$lines = $this->orderItems()->with('order:id,currency')->get();
$totals = $lines->reduce(function (array $carry, OrderItem $line) {
if ($line->price === null) {
return $carry;
}
$currency = $line->order?->currency
?? Setting::getSettings()?->default_currency
?? '';
$carry[$currency] = ($carry[$currency] ?? 0) + ($line->qty * (float) $line->price);
return $carry;
}, []);
// Orders / OrderItems is the single source of truth. No
// fallback to legacy_* columns (those will be dropped in a
// later version). See Accessory::totalCostSumByCurrency.
return $totals;
}
/**
* Naive cross-currency sum, kept for backwards compatibility with
* external callers. New code should prefer totalCostSumByCurrency()
* so mixed-currency totals stay disambiguated.
*/
public function totalCostSum()
{
return array_sum($this->totalCostSumByCurrency()) ?: null;
}
/**
* True when every recorded acquisition for this item came from the
* same supplier. Info-panel supplier row hides when false so a
* single supplier name doesn't misrepresent multi-supplier history.
*/
public function hasConsistentSupplier(): bool
{
return $this->orderItems()
->with('order:id,supplier_id')
->get()
->map(fn (OrderItem $line) => $line->order?->supplier_id)
->filter()
->unique()
->count() <= 1;
}
/**
* Get the list of checkouts for this consumable
*