Fixes #1044 - adds suppliers and image to accessories (#4266)

* Ignore accesories uploads

* API: Allow searching accessories by supplier id

* Adds suppliers and image upload to accessories

* Allow sorting by counts for suppliers

* Validate supplier image uploads

* Remove purchase_date from protected accessory array, it was converting it to datetime in datepicker
This commit is contained in:
snipe 2017-10-19 16:25:24 -07:00 committed by GitHub
parent 1f247ff541
commit 4215a3257b
17 changed files with 209 additions and 22 deletions

1
.gitignore vendored
View file

@ -27,6 +27,7 @@ public/uploads/logo.png
public/uploads/logo.svg
public/uploads/models/*
public/uploads/suppliers/*
public/uploads/accessories/*
public/uploads/users/*
storage/app/private_uploads/users/*
storage/debugbar/

View file

@ -18,6 +18,8 @@ use Illuminate\Http\Request;
use Slack;
use Str;
use View;
use Image;
use App\Http\Requests\ImageUploadRequest;
/** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application.
@ -57,6 +59,7 @@ class AccessoriesController extends Controller
->with('item', new Accessory)
->with('category_list', Helper::categoryList('accessory'))
->with('company_list', Helper::companyList())
->with('supplier_list', Helper::suppliersList())
->with('location_list', Helper::locationsList())
->with('manufacturer_list', Helper::manufacturerList());
}
@ -68,7 +71,7 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return Redirect
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize(Accessory::class);
// create a new model instance
@ -87,6 +90,28 @@ class AccessoriesController extends Controller
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
if ($request->hasFile('image')) {
if (!config('app.lock_passwords')) {
$image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = "accessory-".str_random(18).'.'.$ext;
$path = public_path('/uploads/accessories');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(null, 250, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
}
$accessory->image = $file_name;
}
}
// Was the accessory created?
if ($accessory->save()) {
@ -116,6 +141,7 @@ class AccessoriesController extends Controller
->with('category_list', Helper::categoryList('accessory'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList())
->with('supplier_list', Helper::suppliersList())
->with('manufacturer_list', Helper::manufacturerList());
}
@ -127,7 +153,7 @@ class AccessoriesController extends Controller
* @param int $accessoryId
* @return Redirect
*/
public function update(Request $request, $accessoryId = null)
public function update(ImageUploadRequest $request, $accessoryId = null)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
@ -144,11 +170,38 @@ class AccessoriesController extends Controller
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
// Was the accessory updated?
if ($request->hasFile('image')) {
if (!config('app.lock_passwords')) {
$image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = "accessory-".str_random(18).'.'.$ext;
$path = public_path('/uploads/accessories');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(null, 250, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
}
if (($accessory->image) && (file_exists($path.'/'.$accessory->image))) {
unlink($path.'/'.$accessory->image);
}
$accessory->image = $file_name;
}
}
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}

View file

@ -38,6 +38,10 @@ class AccessoriesController extends Controller
$accessories->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
if ($request->has('supplier_id')) {
$accessories->where('supplier_id','=',$request->input('supplier_id'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View file

@ -20,11 +20,11 @@ class SuppliersController extends Controller
public function index(Request $request)
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['id','name','address','phone','contact','fax','email'];
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count'];
$suppliers = Supplier::select(
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at')
)->withCount('assets')->withCount('licenses')->whereNull('deleted_at');
)->withCount('assets')->withCount('licenses')->withCount('accessories')->whereNull('deleted_at');
if ($request->has('search')) {

View file

@ -13,6 +13,7 @@ use Str;
use View;
use Auth;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
@ -56,7 +57,7 @@ class SuppliersController extends Controller
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
// Create a new supplier
$supplier = new Supplier;
@ -135,7 +136,7 @@ class SuppliersController extends Controller
* @param int $supplierId
* @return \Illuminate\Http\RedirectResponse
*/
public function update($supplierId = null, Request $request)
public function update($supplierId = null, ImageUploadRequest $request)
{
// Check if the supplier exists
if (is_null($supplier = Supplier::find($supplierId))) {
@ -158,18 +159,17 @@ class SuppliersController extends Controller
$supplier->url = $supplier->addhttp(request('url'));
$supplier->notes = request('notes');
if (Input::file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$file_name = 'suppliers-'.str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/suppliers/'.$file_name);
Image::make($image->getRealPath())->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$supplier->image = $file_name;
}
if (request('image_delete') == 1 && $request->file('image') == "") {
} elseif (request('image_delete') == 1) {
$supplier->image = null;
}

View file

@ -25,6 +25,7 @@ class AccessoriesTransformer
'name' => e($accessory->name),
'company' => ($accessory->company) ? ['id' => $accessory->company->id,'name'=> e($accessory->company->name)] : null,
'manufacturer' => ($accessory->manufacturer) ? ['id' => $accessory->manufacturer->id,'name'=> e($accessory->manufacturer->name)] : null,
'supplier' => ($accessory->supplier) ? ['id' => $accessory->supplier->id,'name'=> e($accessory->supplier->name)] : null,
'model_number' => ($accessory->model_number) ? e($accessory->model_number) : null,
'category' => ($accessory->category) ? ['id' => $accessory->category->id,'name'=> e($accessory->category->name)] : null,
'location' => ($accessory->location) ? ['id' => $accessory->location->id,'name'=> e($accessory->location->name)] : null,
@ -35,6 +36,7 @@ class AccessoriesTransformer
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => $accessory->numRemaining(),
'image' => ($accessory->image) ? url('/').'/uploads/accessories/'.e($accessory->image) : null,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),

View file

@ -36,8 +36,9 @@ class SuppliersTransformer
'email' => ($supplier->email) ? e($supplier->email) : null,
'contact' => ($supplier->contact) ? e($supplier->contact) : null,
'assets_count' => (int) $supplier->assets_count,
'accessories_count' => (int) $supplier->accessories_count,
'licenses_count' => (int) $supplier->licenses_count,
'image' => ($supplier->image) ? e($supplier->image) : null,
'image' => ($supplier->image) ? url('/').'/uploads/suppliers/'.e($supplier->image) : null,
'notes' => ($supplier->notes) ? e($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'),
@ -46,7 +47,7 @@ class SuppliersTransformer
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Supplier::class) ? true : false,
'delete' => Gate::allows('delete', Supplier::class) ? true : false,
'delete' => (Gate::allows('delete', Supplier::class) && ($supplier->assets_count == 0) && ($supplier->licenses_count == 0) && ($supplier->accessories_count == 0)) ? true : false,
];
$array += $permissions_array;

View file

@ -17,7 +17,7 @@ class Accessory extends SnipeModel
use Loggable, Presentable;
use SoftDeletes;
protected $dates = ['deleted_at', 'purchase_date'];
protected $dates = ['deleted_at'];
protected $table = 'accessories';
protected $casts = [
'requestable' => 'boolean'
@ -61,10 +61,19 @@ class Accessory extends SnipeModel
'purchase_date',
'model_number',
'manufacturer_id',
'supplier_id',
'image',
'qty',
'requestable'
];
public function supplier()
{
return $this->belongsTo('\App\Models\Supplier', 'supplier_id');
}
public function setRequestableAttribute($value)
{
if ($value == '') {

View file

@ -68,6 +68,11 @@ class Supplier extends SnipeModel
return $this->hasMany('\App\Models\Asset', 'supplier_id');
}
public function accessories()
{
return $this->hasMany('\App\Models\Accessory', 'supplier_id');
}
public function asset_maintenances()
{
return $this->hasMany('\App\Models\AssetMaintenance', 'supplier_id');

View file

@ -31,6 +31,14 @@ class AccessoryPresenter extends Presenter
"switchable" => true,
"title" => trans('general.id'),
"visible" => false
],[
"field" => "image",
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('admin/hardware/table.image'),
"visible" => true,
"formatter" => "imageFormatter"
], [
"field" => "company",
"searchable" => true,
@ -63,6 +71,14 @@ class AccessoryPresenter extends Presenter
"sortable" => true,
"title" => trans('general.manufacturer'),
"formatter" => "manufacturersLinkObjFormatter",
], [
"field" => "supplier",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.supplier'),
"visible" => false,
"formatter" => "suppliersLinkObjFormatter"
], [
"field" => "location",
"searchable" => true,

View file

@ -24,7 +24,8 @@ $factory->state(App\Models\Accessory::class, 'apple-bt-keyboard', function ($fak
'category_id' => 8,
'manufacturer_id' => 1,
'qty' => 10,
'min_amt' => 2
'min_amt' => 2,
'supplier_id' => rand(1,5)
];
});
@ -36,7 +37,8 @@ $factory->state(App\Models\Accessory::class, 'apple-usb-keyboard', function ($fa
'category_id' => 8,
'manufacturer_id' => 1,
'qty' => 15,
'min_amt' => 2
'min_amt' => 2,
'supplier_id' => rand(1,5)
];
});
@ -48,7 +50,8 @@ $factory->state(App\Models\Accessory::class, 'apple-mouse', function ($faker) {
'category_id' => 9,
'manufacturer_id' => 1,
'qty' => 13,
'min_amt' => 2
'min_amt' => 2,
'supplier_id' => rand(1,5)
];
});
@ -56,7 +59,7 @@ $factory->state(App\Models\Accessory::class, 'apple-mouse', function ($faker) {
$factory->state(App\Models\Accessory::class, 'microsoft-mouse', function ($faker) {
return [
'name' => 'Sculpt Comfort Mouse\'',
'name' => 'Sculpt Comfort Mouse',
'category_id' => 9,
'manufacturer_id' => 2,
'qty' => 13,

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddImageAndSupplierToAccessories extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accessories', function (Blueprint $table) {
$table->string('image')->nullable()->default(null);
$table->integer('supplier_id')->nullable()->default(null);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accessories', function (Blueprint $table) {
$table->dropColumn('image');
$table->dropColumn('supplier_id');
});
}
}

1
public/uploads/accessories/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
!.gitignore

View file

View file

@ -12,6 +12,7 @@
@include ('partials.forms.edit.company')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/accessories/general.accessory_name')])
@include ('partials.forms.edit.category')
@include ('partials.forms.edit.supplier')
@include ('partials.forms.edit.manufacturer')
@include ('partials.forms.edit.location')
@include ('partials.forms.edit.model_number')
@ -21,4 +22,20 @@
@include ('partials.forms.edit.quantity')
@include ('partials.forms.edit.minimum_quantity')
<!-- Image -->
<div class="form-group {{ $errors->has('image') ? ' has-error' : '' }}">
{{ Form::label('image', trans('general.image_upload'), array('class' => 'col-md-3 control-label')) }}
<div class="col-md-7">
@if (config('app.lock_passwords'))
<p class="help-block">{{ trans('general.lock_passwords') }}</p>
@else
{{ Form::file('image') }}
{!! $errors->first('image', '<span class="alert-msg">:message</span>') !!}
@endif
</div>
</div>
@stop

View file

@ -30,14 +30,16 @@
<thead>
<tr>
<th data-sortable="true" data-field="id" data-visible="false">{{ trans('admin/suppliers/table.id') }}</th>
<th data-formatter="imageFormatter" data-sortable="true" data-field="image" data-visible="false" data-searchable="false">Image</th>
<th data-sortable="true" data-field="name" data-formatter="suppliersLinkFormatter">{{ trans('admin/suppliers/table.name') }}</th>
<th data-sortable="true" data-field="address">{{ trans('admin/suppliers/table.address') }}</th>
<th data-searchable="true" data-sortable="true" data-field="contact">{{ trans('admin/suppliers/table.contact') }}</th>
<th data-searchable="true" data-sortable="true" data-field="email" data-formatter="emailFormatter">{{ trans('admin/suppliers/table.email') }}</th>
<th data-searchable="true" data-sortable="true" data-field="phone">{{ trans('admin/suppliers/table.phone') }}</th>
<th data-searchable="true" data-sortable="true" data-field="fax" data-visible="false">{{ trans('admin/suppliers/table.fax') }}</th>
<th data-searchable="false" data-sortable="false" data-field="assets_count">{{ trans('admin/suppliers/table.assets') }}</th>
<th data-searchable="false" data-sortable="false" data-field="licenses_count">{{ trans('admin/suppliers/table.licenses') }}</th>
<th data-searchable="false" data-sortable="true" data-field="assets_count">{{ trans('admin/suppliers/table.assets') }}</th>
<th data-searchable="false" data-sortable="true" data-field="accessories_count">{{ trans('general.accessories') }}</th>
<th data-searchable="false" data-sortable="true" data-field="licenses_count">{{ trans('admin/suppliers/table.licenses') }}</th>
<th data-switchable="false" data-formatter="suppliersActionsFormatter" data-searchable="false" data-sortable="false" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>

View file

@ -137,6 +137,40 @@
<div class="row">
<div class="col-md-9">
<div class="box box-default">
<div class="box-header with-border">
<div class="box-heading">
<h3 class="box-title">Accessories</h3>
</div>
</div><!-- /.box-header -->
<div class="box-body">
<div class="table-responsive">
<table
name="suppliersAccessories"
id="table"
class="snipe-table"
data-url="{{ route('api.accessories.index', ['supplier_id' => $supplier->id]) }}"
data-cookie="true"
data-export-options='{"fileName": "testo"}'
data-click-to-select="true"
data-cookie-id-table="suppliersAccessories-{{ config('version.hash_version') }}">
<thead>
<tr>
<th class="col-md-4" data-field="name" data-formatter="accessoriesLinkFormatter">Name</th>
<th class="col-md-4" data-field="model_number">Model Number</th>
<th class="col-md-4" data-field="purchase_cost" data-footer-formatter="sumFormatter">Purchase_cost</th>
<th class="col-md-4" data-field="actions" data-formatter="accessoriesActionsFormatter">Actions</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<div class="box box-default">
@if ($supplier->id)
@ -239,3 +273,8 @@
</div> <!-- /.row-->
@stop
@section('moar_scripts')
@include ('partials.bootstrap-table', [
'showFooter' => true,
])
@stop