mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
293 lines
11 KiB
PHP
Executable File
293 lines
11 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Accessories;
|
|
|
|
use App\Helpers\Helper;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\AdjustQuantityRequest;
|
|
use App\Http\Requests\ImageUploadRequest;
|
|
use App\Http\Traits\HandlesAdjustQuantity;
|
|
use App\Models\Accessory;
|
|
use App\Models\Company;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
/** This controller handles all actions related to Accessories for
|
|
* the Snipe-IT Asset Management application.
|
|
*
|
|
* @version v1.0
|
|
*/
|
|
class AccessoriesController extends Controller
|
|
{
|
|
use HandlesAdjustQuantity;
|
|
|
|
/**
|
|
* Returns a view that invokes the ajax tables which actually contains
|
|
* the content for the accessories listing, which is generated in getDatatable.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @see AccessoriesController::getDatatable() method that generates the JSON response
|
|
* @since [v1.0]
|
|
*/
|
|
public function index(): View
|
|
{
|
|
$this->authorize('index', Accessory::class);
|
|
|
|
return view('accessories.index');
|
|
}
|
|
|
|
/**
|
|
* Returns a view with a form to create a new Accessory.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*/
|
|
public function create(): View
|
|
{
|
|
$this->authorize('create', Accessory::class);
|
|
$category_type = 'accessory';
|
|
|
|
return view('accessories/edit')->with('category_type', $category_type)
|
|
->with('item', new Accessory);
|
|
}
|
|
|
|
/**
|
|
* Validate and save new Accessory from form post
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*/
|
|
public function store(ImageUploadRequest $request): RedirectResponse
|
|
{
|
|
$this->authorize(Accessory::class);
|
|
|
|
// create a new model instance
|
|
$accessory = new Accessory;
|
|
|
|
// Update the accessory data
|
|
$accessory->name = request('name');
|
|
$accessory->category_id = request('category_id');
|
|
$accessory->location_id = request('location_id');
|
|
$accessory->min_amt = request('min_amt');
|
|
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
|
// order_number + currency don't live on the Accessory parent
|
|
// column any more — the AccessoryObserver::created hook writes
|
|
// the initial Order + OrderItem, and we enrich that Order below
|
|
// (after save) with the form-supplied order_number / currency.
|
|
$accessory->manufacturer_id = request('manufacturer_id');
|
|
$accessory->model_number = request('model_number');
|
|
$accessory->qty = request('qty');
|
|
$accessory->created_by = auth()->id();
|
|
$accessory->notes = request('notes');
|
|
$accessory->requestable = request('requestable', 0);
|
|
// Seed the template supplier from the initial-acquisition
|
|
// supplier on the create form so newly-created items already
|
|
// have a "typical" supplier pre-populated. Editable afterwards.
|
|
$accessory->default_supplier_id = request('default_supplier_id', request('supplier_id'));
|
|
|
|
if ($request->has('use_cloned_image')) {
|
|
$cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id'));
|
|
if ($cloned_model_img) {
|
|
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
|
|
$new_image = 'accessories/'.$new_image_name;
|
|
Storage::disk('public')->copy('accessories/'.$cloned_model_img->image, $new_image);
|
|
$accessory->image = $new_image_name;
|
|
}
|
|
|
|
} else {
|
|
$accessory = $request->handleImages($accessory);
|
|
}
|
|
|
|
if ($request->input('redirect_option') === 'back') {
|
|
session()->put(['redirect_option' => 'index']);
|
|
} else {
|
|
session()->put(['redirect_option' => $request->input('redirect_option')]);
|
|
}
|
|
|
|
// Was the accessory created?
|
|
if ($accessory->save()) {
|
|
$this->enrichInitialOrderFromRequest($request, $accessory);
|
|
|
|
// Redirect to the new accessory page
|
|
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
|
->with('success', trans('admin/accessories/message.create.success'));
|
|
}
|
|
|
|
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
|
}
|
|
|
|
/**
|
|
* Return view for the Accessory update form, prepopulated with existing data
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @param int $accessoryId
|
|
*/
|
|
public function edit(Accessory $accessory): View|RedirectResponse
|
|
{
|
|
$this->authorize('update', $accessory);
|
|
if ($safeReferer = Helper::sameOriginUrl(url()->previous())) {
|
|
session()->put('url.intended', $safeReferer);
|
|
}
|
|
|
|
return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
|
|
}
|
|
|
|
/**
|
|
* Returns a view that presents a form to clone an accessory.
|
|
*
|
|
* @author [J. Vinsmoke]
|
|
*
|
|
* @param int $accessoryId
|
|
*
|
|
* @since [v6.0]
|
|
*/
|
|
public function getClone(Accessory $accessory): View|RedirectResponse
|
|
{
|
|
|
|
$this->authorize('create', $accessory);
|
|
$cloned = clone $accessory;
|
|
$accessory_to_clone = $accessory;
|
|
$cloned->id = null;
|
|
$cloned->deleted_at = '';
|
|
|
|
// Restore the pre-Orders clone-as-fast-entry workflow: carry
|
|
// the source item's most recent acquisition context onto the
|
|
// cloned entry's create form so operators cloning a stock row
|
|
// to restock don't have to copy-paste supplier / order # /
|
|
// date / price from another tab. Field names match the
|
|
// create-form input names so enrichInitialOrderFromRequest
|
|
// in store() picks them up on save and writes them onto the
|
|
// observer-created initial Order + OrderItem for the new row.
|
|
// Explicit assignments (not a foreach) so each typed value from
|
|
// lastOrderPrefill() lands on the matching typed model property
|
|
// without going through a mixed intermediate that would fail
|
|
// larastan's assign.propertyType check.
|
|
$prefill = $accessory->lastOrderPrefill();
|
|
$cloned->supplier_id = $prefill['supplier_id'];
|
|
$cloned->purchase_date = $prefill['purchase_date'];
|
|
$cloned->purchase_cost = $prefill['purchase_cost'];
|
|
$cloned->order_number = $prefill['order_number'];
|
|
|
|
return view('accessories/edit')
|
|
->with('cloned_model', $accessory_to_clone)
|
|
->with('item', $cloned);
|
|
|
|
}
|
|
|
|
/**
|
|
* Save edited Accessory from form post
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @param int $accessoryId
|
|
*/
|
|
public function update(ImageUploadRequest $request, Accessory $accessory): RedirectResponse
|
|
{
|
|
$this->authorize('update', $accessory);
|
|
|
|
if ($accessory = Accessory::find($accessory->id)) {
|
|
|
|
// qty and order_number are intentionally NOT accepted on
|
|
// update. On-hand qty is managed via the adjust-quantity
|
|
// modal (see AdjustsQuantity trait); each change becomes a
|
|
// QuantityAdjust action_log entry rather than a silent
|
|
// overwrite. order_number is captured onto the create
|
|
// action_log at create time and onto QuantityAdjust log
|
|
// entries thereafter — a single value on multi-batch
|
|
// inventory is misleading, so the model accessor hides it.
|
|
// supplier_id remains editable (imperfect single-value
|
|
// semantics accepted for the info-panel display).
|
|
$accessory->name = request('name');
|
|
$accessory->location_id = request('location_id');
|
|
$accessory->min_amt = request('min_amt');
|
|
$accessory->category_id = request('category_id');
|
|
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
|
|
$accessory->manufacturer_id = request('manufacturer_id');
|
|
$accessory->model_number = request('model_number');
|
|
// 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).
|
|
// default_supplier_id remains editable — it's the parent's
|
|
// "typical supplier" template that seeds new Orders when
|
|
// the item has no order history yet.
|
|
$accessory->default_supplier_id = request('default_supplier_id');
|
|
$accessory->notes = request('notes');
|
|
$accessory->requestable = request('requestable', 0);
|
|
|
|
$accessory = $request->handleImages($accessory);
|
|
|
|
if ($request->input('redirect_option') === 'back') {
|
|
session()->put(['redirect_option' => 'index']);
|
|
} else {
|
|
session()->put(['redirect_option' => $request->input('redirect_option')]);
|
|
}
|
|
|
|
if ($accessory->save()) {
|
|
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
|
|
->with('success', trans('admin/accessories/message.update.success'));
|
|
}
|
|
} else {
|
|
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
|
}
|
|
|
|
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
|
|
}
|
|
|
|
/**
|
|
* Delete the given accessory.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @param int $accessoryId
|
|
*/
|
|
public function destroy(Accessory $accessory): RedirectResponse
|
|
{
|
|
$this->authorize('delete', $accessory);
|
|
$accessory->loadCount('checkouts as checkouts_count');
|
|
|
|
if ($accessory->isDeletable()) {
|
|
// Note: the image file is deliberately preserved across this
|
|
// soft-delete. Snipe-IT's `snipeit:purge` command permanently
|
|
// removes it later when the row is force-deleted. Keeping
|
|
// the file here means a restored soft-deleted row still has
|
|
// its image.
|
|
$accessory->delete();
|
|
|
|
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
|
|
}
|
|
|
|
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
|
|
}
|
|
|
|
/**
|
|
* Returns a view that invokes the ajax table which contains
|
|
* the content for the accessory detail view, which is generated in getDataView.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
*
|
|
* @param int $accessoryID
|
|
*
|
|
* @see AccessoriesController::getDataView() method that generates the JSON response
|
|
* @since [v1.0]
|
|
*/
|
|
public function show(Accessory $accessory): View|RedirectResponse
|
|
{
|
|
$this->authorize('view', $accessory);
|
|
$accessory->loadCount('checkouts as checkouts_count');
|
|
$accessory->load(['adminuser' => fn ($query) => $query->withTrashed()]);
|
|
|
|
return view('accessories.view', compact('accessory'));
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
return $this->adjustQuantityAsRedirect($request, $accessory);
|
|
}
|
|
}
|