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:
@ -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);
|
||||
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
129
app/Http/Controllers/Api/OrderItemsController.php
Normal file
129
app/Http/Controllers/Api/OrderItemsController.php
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user