mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Accept a purchase order
This commit is contained in:
parent
45a6daf347
commit
1e30bf4bdc
@ -222,6 +222,9 @@ class Handler extends ExceptionHandler
|
||||
case 'user':
|
||||
$login = 'login';
|
||||
break;
|
||||
case 'vendor':
|
||||
$login = 'vendor.catchall';
|
||||
break;
|
||||
default:
|
||||
$login = 'default';
|
||||
break;
|
||||
|
@ -172,7 +172,12 @@ class BaseController extends Controller
|
||||
*/
|
||||
public function notFoundClient()
|
||||
{
|
||||
abort(404, 'Page not found in client portal.');
|
||||
abort(404, 'Page not found in the client portal.');
|
||||
}
|
||||
|
||||
public function notFoundVendor()
|
||||
{
|
||||
abort(404, 'Page not found in the vendor portal.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,17 +118,38 @@ class PurchaseOrderController extends Controller
|
||||
|
||||
public function bulk(ProcessPurchaseOrdersInBulkRequest $request)
|
||||
{
|
||||
|
||||
$transformed_ids = $this->transformKeys($request->purchase_orders);
|
||||
|
||||
if ($request->input('action') == 'download') {
|
||||
return $this->downloadInvoices((array) $transformed_ids);
|
||||
}
|
||||
elseif ($request->input('action') == 'accept'){
|
||||
return $this->acceptPurchaseOrder($request->all());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('message', ctrans('texts.no_action_provided'));
|
||||
}
|
||||
|
||||
public function acceptPurchaseOrder($data)
|
||||
{
|
||||
$purchase_orders = PurchaseOrder::query()
|
||||
->whereIn('id', $this->transformKeys($data['purchase_orders']))
|
||||
->where('company_id', auth()->guard('vendor')->user()->vendor->company_id)
|
||||
->whereIn('status_id', [PurchaseOrder::STATUS_DRAFT, PurchaseOrder::STATUS_SENT]);
|
||||
|
||||
$purchase_orders->update(['status_id' => PurchaseOrder::STATUS_ACCEPTED]);
|
||||
|
||||
if($purchase_orders->count() == 1)
|
||||
return redirect()->route('vendor.purchase_order.show', ['purchase_order' => $purchase_orders->first()->hashed_id]);
|
||||
else
|
||||
return redirect()->route('vendor.purchase_orders.index');
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function downloadInvoices($ids)
|
||||
{
|
||||
|
||||
|
2
public/js/clients/purchase_orders/accept.js
vendored
Normal file
2
public/js/clients/purchase_orders/accept.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see accept.js.LICENSE.txt */
|
||||
(()=>{function e(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}var t=function(){function t(e,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.shouldDisplaySignature=e,this.shouldDisplayTerms=n,this.termsAccepted=!1}var n,a,r;return n=t,(a=[{key:"submitForm",value:function(){document.getElementById("approve-form").submit()}},{key:"displaySignature",value:function(){document.getElementById("displaySignatureModal").removeAttribute("style");var e=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});this.signaturePad=e}},{key:"displayTerms",value:function(){document.getElementById("displayTermsModal").removeAttribute("style")}},{key:"handle",value:function(){var e=this;document.getElementById("approve-button").addEventListener("click",(function(){e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.termsAccepted=!0,e.submitForm()}))}))),e.shouldDisplaySignature&&!e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.submitForm()}))),!e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplaySignature||e.shouldDisplayTerms||e.submitForm()}))}}])&&e(n.prototype,a),r&&e(n,r),Object.defineProperty(n,"prototype",{writable:!1}),t}(),n=document.querySelector('meta[name="require-purchase_order-signature"]').content,a=document.querySelector('meta[name="show-purchase_order-terms"]').content;new t(Boolean(+n),Boolean(+a)).handle()})();
|
9
public/js/clients/purchase_orders/accept.js.LICENSE.txt
Normal file
9
public/js/clients/purchase_orders/accept.js.LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
@ -5,6 +5,7 @@
|
||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=7bed15f51bca764378d9a3aa605b8664",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=d4f86ddee4e8a1d6e9719010aa0fe62b",
|
||||
"/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=160b8161599fc2429b449b0970d3ba6c",
|
||||
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=2b5fed3ae34a6fd4db171a77ba72496e",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=b88ad7c8881cc87df07b129c5a7c76df",
|
||||
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=1c5493a4c53a5b862d07ee1818179ea9",
|
||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0274ab4f8d2b411f2a2fe5142301e7af",
|
||||
|
103
resources/js/clients/purchase_orders/accept.js
vendored
Normal file
103
resources/js/clients/purchase_orders/accept.js
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
class Accept {
|
||||
constructor(displaySignature, displayTerms) {
|
||||
this.shouldDisplaySignature = displaySignature;
|
||||
this.shouldDisplayTerms = displayTerms;
|
||||
this.termsAccepted = false;
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
document.getElementById('approve-form').submit();
|
||||
}
|
||||
|
||||
displaySignature() {
|
||||
let displaySignatureModal = document.getElementById(
|
||||
'displaySignatureModal'
|
||||
);
|
||||
displaySignatureModal.removeAttribute('style');
|
||||
|
||||
const signaturePad = new SignaturePad(
|
||||
document.getElementById('signature-pad'),
|
||||
{
|
||||
penColor: 'rgb(0, 0, 0)',
|
||||
}
|
||||
);
|
||||
|
||||
this.signaturePad = signaturePad;
|
||||
}
|
||||
|
||||
displayTerms() {
|
||||
let displayTermsModal = document.getElementById("displayTermsModal");
|
||||
displayTermsModal.removeAttribute("style");
|
||||
}
|
||||
|
||||
handle() {
|
||||
document
|
||||
.getElementById('approve-button')
|
||||
.addEventListener('click', () => {
|
||||
if (this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||
this.displaySignature();
|
||||
|
||||
document
|
||||
.getElementById('signature-next-step')
|
||||
.addEventListener('click', () => {
|
||||
this.displayTerms();
|
||||
|
||||
document
|
||||
.getElementById('accept-terms-button')
|
||||
.addEventListener('click', () => {
|
||||
document.querySelector(
|
||||
'input[name="signature"'
|
||||
).value = this.signaturePad.toDataURL();
|
||||
this.termsAccepted = true;
|
||||
this.submitForm();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||
this.displaySignature();
|
||||
|
||||
document
|
||||
.getElementById('signature-next-step')
|
||||
.addEventListener('click', () => {
|
||||
document.querySelector(
|
||||
'input[name="signature"'
|
||||
).value = this.signaturePad.toDataURL();
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||
this.displayTerms();
|
||||
|
||||
document
|
||||
.getElementById('accept-terms-button')
|
||||
.addEventListener('click', () => {
|
||||
this.termsAccepted = true;
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||
this.submitForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const signature = document.querySelector('meta[name="require-purchase_order-signature"]')
|
||||
.content;
|
||||
|
||||
const terms = document.querySelector('meta[name="show-purchase_order-terms"]').content;
|
||||
|
||||
new Accept(Boolean(+signature), Boolean(+terms)).handle();
|
@ -30,7 +30,7 @@
|
||||
{{ ctrans('texts.profile') }}
|
||||
</a>
|
||||
|
||||
<a href="{{ route('client.logout') }}"
|
||||
<a href="{{ route('vendor.logout') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.logout') }}
|
||||
</a>
|
||||
|
@ -0,0 +1,40 @@
|
||||
<form action="{{ route('vendor.purchase_orders.bulk') }}" method="post" id="approve-form" />
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="action" value="accept">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="purchase_orders[]" value="{{ $purchase_order->hashed_id }}">
|
||||
<input type="hidden" name="signature">
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.approve') }}
|
||||
</h3>
|
||||
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!">
|
||||
<div class="flex text-sm leading-6 font-medium text-gray-500">
|
||||
<p class="mr-2">{{url("vendor/purchase_order/{$key}")}}</p>
|
||||
<p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
@yield('quote-not-approved-right-side')
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="button"
|
||||
class="button button-primary bg-primary"
|
||||
id="approve-button">{{ ctrans('texts.accept') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -2,7 +2,8 @@
|
||||
@section('meta_title', ctrans('texts.view_purchase_order'))
|
||||
|
||||
@push('head')
|
||||
<meta name="require-invoice-signature" content="{{ $purchase_order->vendor->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_purchase_order_signature }}">
|
||||
<meta name="show-purchase_order-terms" content="false">
|
||||
<meta name="require-purchase_order-signature" content="{{ $purchase_order->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_purchase_order_signature }}">
|
||||
@include('portal.ninja2020.components.no-cache')
|
||||
|
||||
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
|
||||
@ -11,56 +12,24 @@
|
||||
|
||||
@section('body')
|
||||
|
||||
@if($purchase_order)
|
||||
<form action="{{ ($settings->client_portal_allow_under_payment || $settings->client_portal_allow_over_payment) ? route('client.invoices.bulk') : route('client.payments.process') }}" method="post" id="payment-form">
|
||||
@csrf
|
||||
<input type="hidden" name="signature">
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4" translate>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.purchase_order_number_placeholder', ['purchase_order' => $purchase_order->number])}}
|
||||
- {{ ctrans('texts.unpaid') }}
|
||||
</h3>
|
||||
|
||||
@if($key)
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!">
|
||||
<div class="flex text-sm leading-6 font-medium text-gray-500">
|
||||
<p class="mr-2">{{url("vendor/purchase_order/{$key}")}}</p>
|
||||
<p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 flex justify-end">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<input type="hidden" name="purchase_orders[]" value="{{ $purchase_order->hashed_id }}">
|
||||
<input type="hidden" name="action" value="payment">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@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
|
||||
<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">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $purchase_order->number])}}
|
||||
{{ ctrans('texts.purchase_order_number_placeholder', ['purchase_order' => $purchase_order->number])}}
|
||||
- {{ \App\Models\PurchaseOrder::stringStatus($purchase_order->status_id) }}
|
||||
</h3>
|
||||
|
||||
@if($key)
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("client/invoice/{$key}")}}" aria-label="Copied!">
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!">
|
||||
<div class="flex text-sm leading-6 font-medium text-gray-500">
|
||||
<p class="pr-10">{{url("client/invoice/{$key}")}}</p>
|
||||
<p class="pr-10">{{url("vendor/purchase_order/{$key}")}}</p>
|
||||
<p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,7 +47,7 @@
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
<script src="{{ asset('js/clients/invoices/payment.js') }}"></script>
|
||||
<script src="{{ asset('js/clients/purchase_orders/accept.js') }}"></script>
|
||||
<script src="{{ asset('vendor/clipboard.min.js') }}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -87,3 +56,4 @@
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
|
@ -32,7 +32,9 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr
|
||||
Route::get('purchase_orders/{purchase_order}', [PurchaseOrderController::class, 'show'])->name('purchase_order.show');
|
||||
|
||||
Route::get('profile/{vendor_contact}/edit', [PurchaseOrderController::class, 'index'])->name('profile.edit');
|
||||
Route::post('invoices/payment', [PurchaseOrderController::class, 'bulk'])->name('purchase_orders.bulk');
|
||||
Route::get('logout', [VendorContactLoginController::class, 'logout'])->name('logout');
|
||||
Route::post('purchase_orders/bulk', [PurchaseOrderController::class, 'bulk'])->name('purchase_orders.bulk');
|
||||
Route::post('logout', [VendorContactLoginController::class, 'logout'])->name('logout');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Route::fallback('BaseController@notFoundVendor');
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -22,6 +22,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
"resources/js/clients/purchase_orders/action-selectors.js",
|
||||
"public/js/clients/purchase_orders/action-selectors.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/purchase_orders/accept.js",
|
||||
"public/js/clients/purchase_orders/accept.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/invoices/payment.js",
|
||||
"public/js/clients/invoices/payment.js"
|
||||
|
Loading…
x
Reference in New Issue
Block a user