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

Pull item fields out of update

This commit is contained in:
snipe
2026-08-05 16:01:21 +01:00
parent 915f6eb59e
commit 2f22a8754e
15 changed files with 361 additions and 210 deletions

View File

@ -185,13 +185,12 @@ class AccessoriesController extends Controller
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->supplier_id = request('supplier_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
// purchase_cost is create-only on the parent — post-create
// acquisitions record their own price on the OrderItem, so
// the edit form no longer exposes purchase_cost and this
// controller stops overwriting it on update.
// supplier_id, purchase_date, purchase_cost are create-only
// on the parent. Post-create acquisitions live as Orders +
// OrderItems (each with its own supplier / date / price), so
// the edit form no longer exposes any of them and the
// controller stops overwriting the parent values.
$accessory->notes = request('notes');
$accessory->requestable = request('requestable', 0);

View File

@ -294,12 +294,19 @@ class AccessoriesController extends Controller
$qtyRequested = $request->has('qty') ? (int) $request->input('qty') : $qtyBefore;
$qtyDelta = $qtyRequested - $qtyBefore;
// purchase_cost is create-only on the parent — post-create
// acquisitions record their own price on the OrderItem, so an
// update-mode fill drops it. API consumers relying on the old
// "set purchase_cost via PATCH" behavior need to use the
// adjust-quantity endpoint instead.
$accessory->fill($request->except(['qty', 'order_number', 'purchase_cost']));
// supplier_id, purchase_date, purchase_cost, and order_number
// are create-only on the parent. Post-create acquisitions live
// as Orders + OrderItems (each with its own supplier / date /
// price / order number), so update-mode drops all four. API
// consumers relying on the old "set these via PATCH" behavior
// need to use the adjust-quantity endpoint instead.
$accessory->fill($request->except([
'qty',
'order_number',
'purchase_cost',
'purchase_date',
'supplier_id',
]));
$accessory->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$accessory = $request->handleImages($accessory);
@ -537,30 +544,4 @@ class AccessoriesController extends Controller
return response()->json((new ActionlogsTransformer)->transformActionlogs($history, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
/**
* Orders tab datatable: every OrderItem line ever recorded against
* this accessory, with its parent Order's supplier / currency /
* purchase date joined in via eager load.
*/
public function orders(Request $request, Accessory $accessory): JsonResponse|array
{
$this->authorize('view', $accessory);
$ordersQuery = $accessory->orderItems()
->with(['order:id,order_number,supplier_id,currency,purchase_date', 'order.supplier:id,name'])
->orderByDesc('id');
$total = (clone $ordersQuery)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$lines = (clone $ordersQuery)->skip($offset)->take($limit)->get();
return response()->json(
(new \App\Http\Transformers\OrderItemsTransformer)->transformOrderItems($lines, $total),
200,
['Content-Type' => 'application/json;charset=utf8'],
JSON_UNESCAPED_UNICODE,
);
}
}

View File

@ -355,34 +355,4 @@ class AssetModelsController extends Controller
return response()->json((new ActionlogsTransformer)->transformActionlogs($history, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
/**
* Orders tab datatable for an AssetModel: aggregate every OrderItem
* ever recorded against any Asset of this model. Since each Asset
* gets its own OrderItem via AssetObserver::created, one AssetModel
* accumulates one line per asset instance. Useful for "how many of
* this model have we bought over time, from whom, at what price".
*/
public function orders(Request $request, AssetModel $model): JsonResponse|array
{
$this->authorize('view', $model);
$ordersQuery = \App\Models\OrderItem::query()
->where('item_type', \App\Models\Asset::class)
->whereIn('item_id', $model->assets()->select('id'))
->with(['order:id,order_number,supplier_id,currency,purchase_date', 'order.supplier:id,name'])
->orderByDesc('id');
$total = (clone $ordersQuery)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$lines = (clone $ordersQuery)->skip($offset)->take($limit)->get();
return response()->json(
(new \App\Http\Transformers\OrderItemsTransformer)->transformOrderItems($lines, $total),
200,
['Content-Type' => 'application/json;charset=utf8'],
JSON_UNESCAPED_UNICODE,
);
}
}

View File

@ -236,9 +236,16 @@ class ComponentsController extends Controller
$qtyRequested = $request->has('qty') ? (int) $request->input('qty') : $qtyBefore;
$qtyDelta = $qtyRequested - $qtyBefore;
// purchase_cost is create-only on the parent — post-create
// acquisitions record their own price on the OrderItem.
$component->fill($request->except(['qty', 'order_number', 'purchase_cost']));
// supplier_id, purchase_date, purchase_cost, and order_number
// are create-only on the parent. Post-create acquisitions live
// as Orders + OrderItems, so update-mode drops all four.
$component->fill($request->except([
'qty',
'order_number',
'purchase_cost',
'purchase_date',
'supplier_id',
]));
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component = $request->handleImages($component);
@ -524,30 +531,4 @@ class ComponentsController extends Controller
return response()->json((new ActionlogsTransformer)->transformActionlogs($history, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
/**
* Orders tab datatable: every OrderItem line ever recorded against
* this component, with its parent Order's supplier / currency /
* purchase date joined in via eager load.
*/
public function orders(Request $request, Component $component): JsonResponse|array
{
$this->authorize('view', $component);
$ordersQuery = $component->orderItems()
->with(['order:id,order_number,supplier_id,currency,purchase_date', 'order.supplier:id,name'])
->orderByDesc('id');
$total = (clone $ordersQuery)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$lines = (clone $ordersQuery)->skip($offset)->take($limit)->get();
return response()->json(
(new \App\Http\Transformers\OrderItemsTransformer)->transformOrderItems($lines, $total),
200,
['Content-Type' => 'application/json;charset=utf8'],
JSON_UNESCAPED_UNICODE,
);
}
}

View File

@ -223,9 +223,16 @@ class ConsumablesController extends Controller
$qtyRequested = $request->has('qty') ? (int) $request->input('qty') : $qtyBefore;
$qtyDelta = $qtyRequested - $qtyBefore;
// purchase_cost is create-only on the parent — post-create
// acquisitions record their own price on the OrderItem.
$consumable->fill($request->except(['qty', 'order_number', 'purchase_cost']));
// supplier_id, purchase_date, purchase_cost, and order_number
// are create-only on the parent. Post-create acquisitions live
// as Orders + OrderItems, so update-mode drops all four.
$consumable->fill($request->except([
'qty',
'order_number',
'purchase_cost',
'purchase_date',
'supplier_id',
]));
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable = $request->handleImages($consumable);
@ -484,30 +491,4 @@ class ConsumablesController extends Controller
return response()->json((new ActionlogsTransformer)->transformActionlogs($history, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
/**
* Orders tab datatable: every OrderItem line ever recorded against
* this consumable, with its parent Order's supplier / currency /
* purchase date joined in via eager load.
*/
public function orders(Request $request, Consumable $consumable): JsonResponse|array
{
$this->authorize('view', $consumable);
$ordersQuery = $consumable->orderItems()
->with(['order:id,order_number,supplier_id,currency,purchase_date', 'order.supplier:id,name'])
->orderByDesc('id');
$total = (clone $ordersQuery)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$lines = (clone $ordersQuery)->skip($offset)->take($limit)->get();
return response()->json(
(new \App\Http\Transformers\OrderItemsTransformer)->transformOrderItems($lines, $total),
200,
['Content-Type' => 'application/json;charset=utf8'],
JSON_UNESCAPED_UNICODE,
);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Transformers\OrderItemsTransformer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\OrderItem;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Standard REST endpoint for OrderItems. Backs the Orders tab on
* accessory / consumable / component / model view pages, and any
* external API consumer that needs to page / filter / sort acquisition
* lines directly rather than through their parent inventory item.
*
* Filters:
* item_type fully-qualified model class ("App\Models\Accessory")
* item_id id of that item
* asset_model_id aggregate: all OrderItems for every Asset of this model
*
* Sort covers OrderItem-native columns and cross-table columns
* (order_number, purchase_date, supplier, currency) via a leftJoin
* onto orders + suppliers. Search hits order_number, purchase_order,
* and suppliers.name across the same joined query.
*/
class OrderItemsController extends Controller
{
public function index(Request $request): JsonResponse
{
// Authorization: if the request narrows to a specific parent,
// require view permission on THAT parent. Otherwise fall back
// to the OrderItem-level policy so a global listing is gated
// to superusers or the equivalent.
$itemType = $request->input('item_type');
$itemId = $request->input('item_id');
$assetModelId = $request->input('asset_model_id');
if ($assetModelId) {
$this->authorize('view', AssetModel::findOrFail($assetModelId));
} elseif ($itemType && $itemId && class_exists($itemType)) {
$this->authorize('view', $itemType::findOrFail($itemId));
} elseif (! auth()->user()?->isSuperUser()) {
// Unfiltered "list every OrderItem" is a superuser-only
// capability until an OrderPolicy exists.
abort(403);
}
$query = OrderItem::query()
->with([
'admin:id,first_name,last_name,username',
'order:id,order_number,supplier_id,currency,purchase_date,created_by',
'order.supplier:id,name',
'order.admin:id,first_name,last_name,username',
]);
if ($assetModelId) {
$query->where('order_items.item_type', Asset::class)
->whereIn('order_items.item_id', Asset::where('model_id', $assetModelId)->select('id'));
} elseif ($itemType && $itemId) {
$query->where('order_items.item_type', $itemType)
->where('order_items.item_id', $itemId);
}
// leftJoin onto orders + suppliers so cross-table sort / search
// can hit those columns. Select order_items.* so the model
// hydrates cleanly and the joined columns don't leak into the
// OrderItem attributes.
$query->leftJoin('orders', 'orders.id', '=', 'order_items.order_id')
->leftJoin('suppliers', 'suppliers.id', '=', 'orders.supplier_id')
->select('order_items.*');
if ($request->filled('search')) {
$needle = '%'.$request->input('search').'%';
$query->where(function ($q) use ($needle) {
$q->where('orders.order_number', 'like', $needle)
->orWhere('suppliers.name', 'like', $needle);
});
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($request->input('sort')) {
case 'order_number':
$query->orderBy('orders.order_number', $order);
break;
case 'purchase_date':
$query->orderBy('orders.purchase_date', $order);
break;
case 'currency':
$query->orderBy('orders.currency', $order);
break;
case 'supplier':
$query->orderBy('suppliers.name', $order);
break;
case 'qty':
$query->orderBy('order_items.qty', $order);
break;
case 'unit_cost':
$query->orderBy('order_items.price', $order);
break;
case 'total_cost':
// qty * price sort is computed rather than a stored column.
$query->orderByRaw('(order_items.qty * COALESCE(order_items.price, 0)) '.$order);
break;
case 'created_at':
$query->orderBy('order_items.created_at', $order);
break;
case 'created_by':
$query->orderBy('order_items.created_by', $order);
break;
default:
$query->orderBy('order_items.id', 'desc');
}
$total = (clone $query)->count();
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value');
$lines = (clone $query)->skip($offset)->take($limit)->get();
return response()->json(
(new OrderItemsTransformer)->transformOrderItems($lines, $total),
200,
['Content-Type' => 'application/json;charset=utf8'],
JSON_UNESCAPED_UNICODE,
);
}
}

View File

@ -42,12 +42,6 @@
categoryType="accessory"
/>
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
<x-input.manufacturer-select
:label="trans('general.manufacturer')"
name="manufacturer_id"
@ -66,37 +60,58 @@
name="model_number"
/>
{{-- order_number and qty are create-only. After creation,
on-hand qty is managed via the adjust-quantity modal so
every change becomes a QuantityAdjust action_log entry
(with its own note + order_number) instead of a silent
overwrite. Correcting a create-time order_number typo
isn't supported today; a dedicated Orders model built
out from action_log history is the plan if it's asked
for. --}}
{{-- Acquisition metadata is create-only. Post-create qty
changes flow through the adjust-quantity modal so each
change becomes a QuantityAdjust action_log entry (with
its own note / order_number / supplier / date / price)
instead of a silent parent-column overwrite. Every
later acquisition lives on its own Order + OrderItem. --}}
@if (! $item->id)
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
<x-form.row
:label="trans('general.order_number')"
:$item
name="order_number"
/>
@endif
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-form.row
:label="trans('general.currency')"
name="currency"
input_div_class="col-md-3"
:help_text="trans('general.currency_prefilled_hint')"
>
<x-slot:input>
<input
type="text"
class="form-control"
id="currency"
name="currency"
maxlength="10"
value="{{ old('currency', $item->location->currency ?? $snipeSettings->default_currency) }}"
aria-label="{{ trans('general.currency') }}"
/>
</x-slot:input>
</x-form.row>
@if (! $item->id)
<x-input.quantity :item="$item" min="0" />
@endif

View File

@ -27,6 +27,7 @@
<x-tabs.checkedout-tab :item="$accessory" count="{{ $accessory->checkouts_count }}" />
<x-tabs.files-tab :item="$accessory" count="{{ $accessory->uploads()->count() }}"/>
<x-tabs.history-tab count="{{ $accessory->history()->count() }}" :model="$accessory"/>
<x-tabs.orders-tab count="{{ $accessory->ordersCount() }}"/>
<x-tabs.upload-tab :item="$accessory"/>
</x-slot:tabnav>
@ -49,10 +50,16 @@
<!-- start history tab pane -->
<x-tabs.pane name="history">
<x-table.history :model="$accessory" :route="route('api.accessories.history', $accessory)"/>
<x-table.history :model="$accessory" :route="route('api.accessories.history', $accessory)" :hide_fields="['serial']"/>
</x-tabs.pane>
<!-- end history tab pane -->
<!-- start orders tab pane -->
<x-tabs.pane name="orders">
<x-table.orders :route="route('api.order-items.index', ['item_type' => \App\Models\Accessory::class, 'item_id' => $accessory->id])"/>
</x-tabs.pane>
<!-- end orders tab pane -->
<!-- start files tab pane -->
<x-tabs.pane name="files">
<x-table.files object_type="accessories" :object="$accessory"/>
@ -72,6 +79,7 @@
<x-button.clone :item="$accessory" :route="route('clone/accessories', $accessory->id)"/>
<x-button.checkout permission="checkout" :item="$accessory" :route="route('accessories.checkout.show', $accessory->id)" />
@can('update', $accessory)
@php $lastOrder = $accessory->lastOrderDefaults(); @endphp
<button type="button"
class="btn btn-sm btn-primary adjust-quantity"
data-tooltip="true"
@ -79,6 +87,8 @@
data-adjust-url="{{ route('accessories.adjust-quantity', $accessory) }}"
data-item-name="{{ e($accessory->name) }}"
data-available="{{ (int) $accessory->numRemaining() }}"
@if ($lastOrder && $lastOrder['unit_cost'] !== null) data-last-unit-cost="{{ $lastOrder['unit_cost'] }}" @endif
@if ($lastOrder && $lastOrder['currency'] !== null) data-last-currency="{{ e($lastOrder['currency']) }}" @endif
>
<x-icon type="plus-minus" class="fa-fw" />
<span class="sr-only">{{ trans('general.adjust_quantity') }}</span>

View File

@ -57,9 +57,15 @@
<div class="form-group">
<label for="adjustQuantityPurchaseDate">{{ trans('general.purchase_date') }}</label>
{{-- Pre-populated with today's date because most --}}
{{-- adjust events happen the day they are recorded. --}}
{{-- Editable, and snipeit.js resets to today on --}}
{{-- every modal open so a stale date can't bleed --}}
{{-- across sessions. --}}
<x-input.datepicker
id="adjustQuantityPurchaseDate"
name="purchase_date"
:value="now()->toDateString()"
:end_date="'0d'"
/>
</div>
@ -99,6 +105,14 @@
>
</div>
</div>
{{-- Shown by snipeit.js when the modal opens with --}}
{{-- pre-populated cost/currency from the trigger's --}}
{{-- data-last-* attrs. Hidden as soon as the user --}}
{{-- edits either field, so it disappears the moment --}}
{{-- the pre-fill stops being authoritative. --}}
<p class="help-block" id="adjustQuantityCostHint" style="display: none; margin-top: -5px;">
{{ trans('general.adjust_quantity_prefilled_from_last_order') }}
</p>
<div class="form-group">
<label for="adjustQuantityNote">{{ trans('general.notes') }}</label>

View File

@ -42,12 +42,6 @@
categoryType="component"
/>
{{-- qty is create-only. After creation, on-hand qty is
managed via the adjust-quantity modal. --}}
@if (! $item->id)
<x-input.quantity :item="$item" />
@endif
<x-input.minimum-quantity :item="$item" />
<x-form.row
@ -74,35 +68,59 @@
:selected="old('location_id', $item->location_id)"
/>
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
{{-- Create-only. See accessories/edit for rationale. --}}
{{-- Acquisition metadata + qty are all create-only. See
accessories/edit for the full rationale. Post-create
changes flow through the adjust-quantity modal and
land as Order + OrderItem rows. --}}
@if (! $item->id)
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
<x-form.row
:label="trans('general.order_number')"
:$item
name="order_number"
/>
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-form.row
:label="trans('general.currency')"
name="currency"
input_div_class="col-md-3"
:help_text="trans('general.currency_prefilled_hint')"
>
<x-slot:input>
<input
type="text"
class="form-control"
id="currency"
name="currency"
maxlength="10"
value="{{ old('currency', $item->location->currency ?? $snipeSettings->default_currency) }}"
aria-label="{{ trans('general.currency') }}"
/>
</x-slot:input>
</x-form.row>
<x-input.quantity :item="$item" />
@endif
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-form.row
:label="trans('general.notes')"
:$item

View File

@ -26,6 +26,7 @@
<x-tabs.files-tab :item="$snipe_component" count="{{ $snipe_component->uploads()->count() }}"/>
<x-tabs.history-tab count="{{ $snipe_component->history()->count() }}" :model="$snipe_component"/>
<x-tabs.orders-tab count="{{ $snipe_component->ordersCount() }}"/>
<x-tabs.upload-tab :item="$snipe_component"/>
</x-slot:tabnav>
@ -55,6 +56,11 @@
<x-table.history :model="$snipe_component" :route="route('api.components.history', $snipe_component)"/>
</x-tabs.pane>
<!-- start orders tab pane -->
<x-tabs.pane name="orders">
<x-table.orders :route="route('api.order-items.index', ['item_type' => \App\Models\Component::class, 'item_id' => $snipe_component->id])"/>
</x-tabs.pane>
</x-slot:tabpanes>
</x-tabs>
</x-page-column>
@ -68,6 +74,7 @@
<x-button.clone :item="$snipe_component" :route="route('components.clone.create', $snipe_component->id)"/>
<x-button.checkout :item="$snipe_component" :route="route('components.checkout.show', $snipe_component->id)" />
@can('update', $snipe_component)
@php $lastOrder = $snipe_component->lastOrderDefaults(); @endphp
<button type="button"
class="btn btn-sm btn-primary adjust-quantity"
data-tooltip="true"
@ -75,6 +82,8 @@
data-adjust-url="{{ route('components.adjust-quantity', $snipe_component) }}"
data-item-name="{{ e($snipe_component->name) }}"
data-available="{{ (int) $snipe_component->numRemaining() }}"
@if ($lastOrder && $lastOrder['unit_cost'] !== null) data-last-unit-cost="{{ $lastOrder['unit_cost'] }}" @endif
@if ($lastOrder && $lastOrder['currency'] !== null) data-last-currency="{{ e($lastOrder['currency']) }}" @endif
>
<x-icon type="plus-minus" class="fa-fw" />
<span class="sr-only">{{ trans('general.adjust_quantity') }}</span>

View File

@ -42,20 +42,8 @@
categoryType="consumable"
/>
{{-- qty is create-only: after creation, on-hand qty is
managed via the adjust-quantity modal. --}}
@if (! $item->id)
<x-input.quantity :item="$item"/>
@endif
<x-input.minimum-quantity :item="$item"/>
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
<x-input.manufacturer-select
:label="trans('general.manufacturer')"
name="manufacturer_id"
@ -80,29 +68,59 @@
name="item_no"
/>
{{-- Create-only. See accessories/edit for rationale. --}}
{{-- Acquisition metadata + qty are all create-only. See
accessories/edit for the full rationale. Post-create
changes flow through the adjust-quantity modal and
land as Order + OrderItem rows. --}}
@if (! $item->id)
<x-input.supplier-select
:label="trans('general.supplier')"
name="supplier_id"
:selected="old('supplier_id', $item->supplier_id)"
/>
<x-form.row
:label="trans('general.order_number')"
:$item
name="order_number"
/>
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-form.row
:label="trans('general.currency')"
name="currency"
input_div_class="col-md-3"
:help_text="trans('general.currency_prefilled_hint')"
>
<x-slot:input>
<input
type="text"
class="form-control"
id="currency"
name="currency"
maxlength="10"
value="{{ old('currency', $item->location->currency ?? $snipeSettings->default_currency) }}"
aria-label="{{ trans('general.currency') }}"
/>
</x-slot:input>
</x-form.row>
<x-input.quantity :item="$item"/>
@endif
<x-form.row
:label="trans('general.purchase_date')"
name="purchase_date"
type="datepicker"
:item="$item"
input_div_class="col-md-4"
/>
<x-input.purchase-cost
:label="trans('general.unit_cost')"
:item="$item"
:currencyType="$item->location->currency ?? null"
/>
<x-form.row
:label="trans('general.notes')"
:$item

View File

@ -30,6 +30,7 @@
<x-tabs.files-tab :item="$consumable" count="{{ $consumable->uploads()->count() }}"/>
<x-tabs.history-tab count="{{ $consumable->history()->count() }}" :model="$consumable"/>
<x-tabs.orders-tab count="{{ $consumable->ordersCount() }}"/>
<x-tabs.upload-tab :item="$consumable"/>
</x-slot:tabnav>
@ -51,10 +52,16 @@
<!-- start history tab pane -->
<x-tabs.pane name="history">
<x-table.history :model="$consumable" :route="route('api.consumables.history', $consumable)"/>
<x-table.history :model="$consumable" :route="route('api.consumables.history', $consumable)" :hide_fields="['serial']"/>
</x-tabs.pane>
<!-- end history tab pane -->
<!-- start orders tab pane -->
<x-tabs.pane name="orders">
<x-table.orders :route="route('api.order-items.index', ['item_type' => \App\Models\Consumable::class, 'item_id' => $consumable->id])"/>
</x-tabs.pane>
<!-- end orders tab pane -->
</x-slot:tabpanes>
</x-tabs>
@ -68,6 +75,7 @@
<x-button.edit :item="$consumable" :route="route('consumables.edit', $consumable->id)"/>
<x-button.clone :item="$consumable" :route="route('consumables.clone.create', $consumable->id)"/>
@can('update', $consumable)
@php $lastOrder = $consumable->lastOrderDefaults(); @endphp
<button type="button"
class="btn btn-sm btn-primary adjust-quantity"
data-tooltip="true"
@ -75,6 +83,8 @@
data-adjust-url="{{ route('consumables.adjust-quantity', $consumable) }}"
data-item-name="{{ e($consumable->name) }}"
data-available="{{ (int) $consumable->numRemaining() }}"
@if ($lastOrder && $lastOrder['unit_cost'] !== null) data-last-unit-cost="{{ $lastOrder['unit_cost'] }}" @endif
@if ($lastOrder && $lastOrder['currency'] !== null) data-last-currency="{{ e($lastOrder['currency']) }}" @endif
>
<x-icon type="plus-minus" class="fa-fw" />
<span class="sr-only">{{ trans('general.adjust_quantity') }}</span>

View File

@ -29,6 +29,7 @@
<x-tabs.asset-tab count="{{ $model->assets()->AssetsForShow()->count() }}" />
<x-tabs.files-tab :item="$model" count="{{ $model->uploads()->count() }}"/>
<x-tabs.history-tab count="{{ $model->history()->count() }}" :model="$model"/>
<x-tabs.orders-tab count="{{ $model->ordersCount() }}"/>
<x-tabs.upload-tab :item="$model"/>
</x-slot:tabnav>
@ -44,6 +45,12 @@
</x-tabs.pane>
<!-- end history tab pane -->
<!-- start orders tab pane -->
<x-tabs.pane name="orders">
<x-table.orders :route="route('api.order-items.index', ['asset_model_id' => $model->id])"/>
</x-tabs.pane>
<!-- end orders tab pane -->
<x-tabs.pane name="files">
<x-table.files :object="$model" object_type="models" />
</x-tabs.pane>

View File

@ -43,6 +43,15 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
]);
});
/**
* OrderItems (acquisition line-items). Standard REST index, filter
* by item_type + item_id for a specific parent, or asset_model_id
* for the AssetModel aggregate.
*/
Route::get('order-items',
[Api\OrderItemsController::class, 'index']
)->name('api.order-items.index');
/**
* Account routes
*/