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:
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user