Uploads for purchase orders

This commit is contained in:
David Bomba 2022-07-06 15:18:41 +10:00
parent ea0ef763bf
commit 3e5e915acc
12 changed files with 265 additions and 1 deletions

View File

@ -291,7 +291,10 @@ class CompanySettings extends BaseSettings
public $email_from_name = '';
public $auto_archive_invoice_cancelled = false;
public $vendor_portal_enable_uploads=false;
public static $casts = [
'vendor_portal_enable_uploads' => 'bool',
'besr_id' => 'string',
'qr_iban' => 'string',
'email_subject_purchase_order' => 'string',

View File

@ -23,21 +23,25 @@ use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UploadPurchaseOrderRequest;
use App\Jobs\Invoice\ZipInvoices;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
use App\Models\Account;
use App\Models\Client;
use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository;
use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class PurchaseOrderController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = PurchaseOrder::class;
protected $entity_transformer = PurchaseOrderTransformer::class;
@ -655,4 +659,69 @@ class PurchaseOrderController extends BaseController
break;
}
}
/**
* Update the specified resource in storage.
*
* @param UploadPurchaseOrderRequest $request
* @param PurchaseOrder $purchase_order
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/purchase_orders/{id}/upload",
* operationId="uploadPurchaseOrder",
* tags={"purchase_orders"},
* summary="Uploads a document to a purchase_orders",
* description="Handles the uploading of a document to a purchase_order",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Purchase Order Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Purchase Order object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Vendor"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
{
if(!$this->checkFeature(Account::FEATURE_DOCUMENTS))
return $this->featureFailure();
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $purchase_order);
return $this->itemResponse($purchase_order->fresh());
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\VendorPortal;
use App\Http\Controllers\Controller;
use App\Http\Requests\VendorPortal\Uploads\StoreUploadRequest;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Response;
class UploadController extends Controller
{
use SavesDocuments;
use MakesHash;
/**
* Main logic behind uploading the files.
*
* @param StoreUploadRequest $request
* @return Response|ResponseFactory
*/
public function upload(StoreUploadRequest $request, PurchaseOrder $purchase_order)
{
$this->saveDocuments($request->getFile(), $purchase_order, true);
return response([], 200);
}
}

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\ClientPortal\Uploads;

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
class UploadPurchaseOrderRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->purchase_order);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\VendorPortal\Uploads;
use Illuminate\Foundation\Http\FormRequest;
class StoreUploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return (bool) auth()->guard('vendor')->user()->vendor->company->getSetting('vendor_portal_enable_uploads');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'file' => ['file', 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'],
];
}
/**
* Since saveDocuments() expects an array of uploaded files,
* we need to convert it to an array before uploading.
*
* @return mixed
*/
public function getFile()
{
if (gettype($this->file) !== 'array') {
return [$this->file];
}
return $this->file;
}
}

View File

@ -14,6 +14,7 @@ namespace App\Transformers;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Transformers\DocumentTransformer;
use App\Utils\Traits\MakesHash;
class PurchaseOrderTransformer extends EntityTransformer
@ -22,6 +23,7 @@ class PurchaseOrderTransformer extends EntityTransformer
protected $defaultIncludes = [
'invitations',
'documents'
];
public function includeInvitations(PurchaseOrder $purchase_order)
@ -31,6 +33,14 @@ class PurchaseOrderTransformer extends EntityTransformer
return $this->includeCollection($purchase_order->invitations, $transformer, PurchaseOrderInvitation::class);
}
public function includeDocuments(PurchaseOrder $purchase_order)
{
$transformer = new DocumentTransformer($this->serializer);
return $this->includeCollection($purchase_order->documents, $transformer, Document::class);
}
public function transform(PurchaseOrder $purchase_order)
{
return [

View File

@ -0,0 +1,20 @@
<link rel="stylesheet" href="{{ asset('vendor/dropzone-5.7.0/dist/min/basic.min.css') }}">
<script src="{{ asset('vendor/dropzone-5.7.0/dist/min/dropzone.min.js') }}"></script>
{{-- TODO: HOSTED --}}
<div class="bg-white rounded shadow p-4 mb-10">
<span class="text-sm mb-4 block text-gray-500 break-words">{{ ctrans('texts.allowed_file_types' )}} png, ai, svg, jpeg, tiff, pdf, gif, psd, txt, doc, xls, ppt, xlsx, docx, pptx</span>
<form action="{{ route('vendor.upload.store',['purchase_order' => $purchase_order->hashed_id]) }}" class="dropzone p-8 border-4 border-dashed border-gray-200 rounded-md" method="post" enctype="multipart/form-data">
@csrf
<div class="fallback">
<input name="file[]" type="file" multiple/>
<input name="purchase_order" type="hidden" value="{{$purchase_order->hashed_id}}">
</div>
</form>
</div>
<script>
Dropzone.prototype.defaultOptions.dictDefaultMessage = '{!! ctrans('texts.dropzone_default_message') !!}';
</script>

View File

@ -12,11 +12,16 @@
@section('body')
@if($purchase_order->company->getSetting('vendor_portal_enable_uploads') || true)
@component('portal.ninja2020.purchase_orders.includes.upload', ['purchase_order' => $purchase_order]) @endcomponent
@endif
@if(in_array($purchase_order->status_id, [\App\Models\PurchaseOrder::STATUS_SENT, \App\Models\PurchaseOrder::STATUS_DRAFT]))
<div class="mb-4">
@include('portal.ninja2020.purchase_orders.includes.actions', ['purchase_order' => $purchase_order])
</div>
@else
<input type="hidden" id="approve-button">
<div class="bg-white shadow sm:rounded-lg mb-4">
<div class="px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between">

View File

@ -83,7 +83,6 @@
@enderror
</div>
</div>
</div>
@ -163,6 +162,15 @@
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-6">
<label for="public_notes" class="input-label w-full">{{ ctrans('texts.notes') }}</label>
<textarea rows="4" id="public_notes" class="block p-2.5 w-full text-sm text-gray-900 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" name="public_notes" value="{{ $vendor->public_notes }}" />{{ $vendor->public_notes}}</textarea>
@error('public_notes')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
</div>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">

View File

@ -212,6 +212,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
Route::resource('purchase_orders', 'PurchaseOrderController');
Route::post('purchase_orders/bulk', 'PurchaseOrderController@bulk')->name('purchase_orders.bulk');
Route::put('purchase_orders/{purchase_order}/upload', 'PurchaseOrderController@upload');
Route::get('purchase_orders/{purchase_order}/{action}', 'PurchaseOrderController@action')->name('purchase_orders.action');
Route::get('users', 'UserController@index');

View File

@ -12,6 +12,7 @@
use App\Http\Controllers\Auth\VendorContactLoginController;
use App\Http\Controllers\VendorPortal\InvitationController;
use App\Http\Controllers\VendorPortal\PurchaseOrderController;
use App\Http\Controllers\VendorPortal\UploadController;
use App\Http\Controllers\VendorPortal\VendorContactController;
use Illuminate\Support\Facades\Route;
@ -39,6 +40,7 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr
Route::post('purchase_orders/bulk', [PurchaseOrderController::class, 'bulk'])->name('purchase_orders.bulk');
Route::get('logout', [VendorContactLoginController::class, 'logout'])->name('logout');
Route::post('purchase_order/upload/{purchase_order}', [UploadController::class,'upload'])->name('upload.store');
});