mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-02 22:57:00 +00:00
Updates for rejection
This commit is contained in:
@@ -90,6 +90,10 @@ class QuoteController extends Controller
|
||||
return $this->approve((array) $transformed_ids, $request->has('process'));
|
||||
}
|
||||
|
||||
if ($request->action == 'reject') {
|
||||
return $this->reject((array) $transformed_ids, $request->has('process'));
|
||||
}
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
@@ -171,6 +175,44 @@ class QuoteController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
protected function reject(array $ids, $process = false)
|
||||
{
|
||||
$quotes = Quote::query()
|
||||
->whereIn('id', $ids)
|
||||
->where('client_id', auth()->guard('contact')->user()->client_id)
|
||||
->where('company_id', auth()->guard('contact')->user()->company_id)
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
if (! $quotes || $quotes->count() == 0) {
|
||||
return redirect()
|
||||
->route('client.quotes.index')
|
||||
->with('message', ctrans('texts.quotes_with_status_sent_can_be_rejected'));
|
||||
}
|
||||
|
||||
if ($process) {
|
||||
foreach ($quotes as $quote) {
|
||||
|
||||
$quote->service()->reject(auth()->guard('contact')->user(), request()->input('user_input', ''))->save();
|
||||
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('client.quotes.index')
|
||||
->withSuccess('Quote(s) rejected successfully.');
|
||||
}
|
||||
|
||||
|
||||
$variables = false;
|
||||
|
||||
return $this->render('quotes.reject', [
|
||||
'quotes' => $quotes,
|
||||
'variables' => $variables,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
protected function approve(array $ids, $process = false)
|
||||
{
|
||||
$quotes = Quote::query()
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace App\Http\Requests\ClientPortal\Quotes;
|
||||
use App\Http\ViewComposers\PortalComposer;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
use function auth;
|
||||
|
||||
class ProcessQuotesInBulkRequest extends FormRequest
|
||||
{
|
||||
public function authorize()
|
||||
|
||||
@@ -384,6 +384,8 @@ class Quote extends BaseModel
|
||||
return '<h5><span class="badge badge-danger">'.ctrans('texts.expired').'</span></h5>';
|
||||
case self::STATUS_CONVERTED:
|
||||
return '<h5><span class="badge badge-light">'.ctrans('texts.converted').'</span></h5>';
|
||||
case self::STATUS_REJECTED:
|
||||
return '<h5><span class="badge badge-danger">'.ctrans('texts.rejected').'</span></h5>';
|
||||
default:
|
||||
return '<h5><span class="badge badge-light">'.ctrans('texts.draft').'</span></h5>';
|
||||
}
|
||||
@@ -402,6 +404,8 @@ class Quote extends BaseModel
|
||||
return ctrans('texts.expired');
|
||||
case self::STATUS_CONVERTED:
|
||||
return ctrans('texts.converted');
|
||||
case self::STATUS_REJECTED:
|
||||
return ctrans('texts.rejected');
|
||||
default:
|
||||
return ctrans('texts.draft');
|
||||
|
||||
@@ -422,6 +426,15 @@ class Quote extends BaseModel
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isRejected(): bool
|
||||
{
|
||||
if ($this->status_id === $this::STATUS_REJECTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getValidUntilAttribute()
|
||||
{
|
||||
return $this->due_date;
|
||||
|
||||
@@ -223,6 +223,7 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
|
||||
use App\Listeners\Invoice\InvoiceReversedActivity;
|
||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||
use App\Listeners\Quote\QuoteApprovedNotification;
|
||||
use App\Listeners\Quote\QuoteRejectedNotification;
|
||||
use SocialiteProviders\Apple\AppleExtendSocialite;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
use App\Events\Subscription\SubscriptionWasCreated;
|
||||
@@ -574,6 +575,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
],
|
||||
QuoteWasRejected::class => [
|
||||
QuoteRejectedActivity::class,
|
||||
QuoteRejectedNotification::class,
|
||||
],
|
||||
QuoteWasUpdated::class => [
|
||||
QuoteUpdatedActivity::class,
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use App\Exceptions\QuoteConversion;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Events\Quote\QuoteWasRejected;
|
||||
use App\Services\Invoice\LocationData;
|
||||
use App\Services\Quote\UpdateReminder;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
@@ -148,6 +149,24 @@ class QuoteService
|
||||
}
|
||||
|
||||
|
||||
public function reject($contact = null, ?string $notes = null): self
|
||||
{
|
||||
|
||||
if($this->quote->status_id != Quote::STATUS_SENT) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->setStatus(Quote::STATUS_REJECTED)->save();
|
||||
|
||||
if (! $contact) {
|
||||
$contact = $this->quote->invitations->first()->contact;
|
||||
}
|
||||
|
||||
event(new QuoteWasRejected($contact, $this->quote, $this->quote->company, $notes ?? '', Ninja::eventVars()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function approveWithNoCoversion($contact = null): self
|
||||
{
|
||||
|
||||
@@ -5635,7 +5635,6 @@ $lang = array(
|
||||
'einvoice_received_subject' => 'E-Invoice/s Received',
|
||||
'einvoice_received_body' => 'You have received :count new E-Invoice/s.<br><br>Login to view.',
|
||||
'download_files_too_large' => 'Some files were too large to attach directly to the email. Please use the links below to download these individually.',
|
||||
|
||||
'restore_disabled_verifactu' => 'You cannot restore an invoice once it has been deleted',
|
||||
'delete_disabled_verifactu' => 'You cannot delete an invoice once it has been cancelled or modified',
|
||||
'rectify' => 'Rectificar',
|
||||
@@ -5676,6 +5675,13 @@ $lang = array(
|
||||
'activity_156' => 'Invoice cancellation for :invoice sent to AEAT successfully',
|
||||
'activity_157' => 'Invoice cancellation for :invoice failed to send to AEAT :notes',
|
||||
'activity_158' => 'Quote :quote was rejected by :client :notes',
|
||||
'quotes_with_status_sent_can_be_rejected' => 'Only quotes with "Sent" status can be rejected.',
|
||||
'reject' => 'Reject',
|
||||
'rejected' => 'Rejected',
|
||||
'reject_quote' => 'Reject Quote',
|
||||
'reject_quote_confirmation' => 'Are you sure you want to reject this quote?',
|
||||
'reason' => 'Reason',
|
||||
'enter_reason' => 'Enter a reason...',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
||||
File diff suppressed because one or more lines are too long
109
public/build/assets/app-aa93be80.js
vendored
109
public/build/assets/app-aa93be80.js
vendored
File diff suppressed because one or more lines are too long
104
public/build/assets/app-e5ec2fdc.js
vendored
Normal file
104
public/build/assets/app-e5ec2fdc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
public/build/assets/reject-dae85928.js
vendored
Normal file
9
public/build/assets/reject-dae85928.js
vendored
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
|
||||
*/class o{constructor(){}submitForm(){document.getElementById("reject-form").submit()}displayRejectModal(){let e=document.getElementById("displayRejectModal");e&&e.removeAttribute("style")}hideRejectModal(){let e=document.getElementById("displayRejectModal");e&&(e.style.display="none")}handle(){const e=document.getElementById("reject-button");if(!e)return;e.addEventListener("click",()=>{e.disabled=!0,setTimeout(()=>{e.disabled=!1},2e3),this.displayRejectModal()});const t=document.getElementById("reject-confirm-button");t&&t.addEventListener("click",()=>{const n=document.getElementById("reject_reason");if(n){const c=document.querySelector('#reject-form input[name="user_input"]');c&&(c.value=n.value)}this.hideRejectModal(),this.submitForm()});const d=document.getElementById("reject-close-button");d&&d.addEventListener("click",()=>{this.hideRejectModal()})}}new o().handle();
|
||||
@@ -12,7 +12,7 @@
|
||||
"file": "assets/wait-8f4ae121.js"
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-aa93be80.js",
|
||||
"file": "assets/app-e5ec2fdc.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
@@ -360,6 +360,11 @@
|
||||
"isEntry": true,
|
||||
"src": "resources/js/clients/quotes/approve.js"
|
||||
},
|
||||
"resources/js/clients/quotes/reject.js": {
|
||||
"file": "assets/reject-dae85928.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/clients/quotes/reject.js"
|
||||
},
|
||||
"resources/js/clients/shared/multiple-downloads.js": {
|
||||
"file": "assets/multiple-downloads-2f8b7e95.js",
|
||||
"isEntry": true,
|
||||
@@ -385,7 +390,7 @@
|
||||
"src": "resources/js/setup/setup.js"
|
||||
},
|
||||
"resources/sass/app.scss": {
|
||||
"file": "assets/app-55cdafc9.css",
|
||||
"file": "assets/app-2350ca5d.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/sass/app.scss"
|
||||
}
|
||||
|
||||
82
resources/js/clients/quotes/reject.js
vendored
Normal file
82
resources/js/clients/quotes/reject.js
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 Reject {
|
||||
constructor() {
|
||||
// Always show rejection modal for confirmation
|
||||
// (Siempre mostrar modal de rechazo para confirmación)
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
document.getElementById('reject-form').submit();
|
||||
}
|
||||
|
||||
displayRejectModal() {
|
||||
let displayRejectModal = document.getElementById("displayRejectModal");
|
||||
if (displayRejectModal) {
|
||||
displayRejectModal.removeAttribute("style");
|
||||
}
|
||||
}
|
||||
|
||||
hideRejectModal() {
|
||||
let displayRejectModal = document.getElementById("displayRejectModal");
|
||||
if (displayRejectModal) {
|
||||
displayRejectModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
handle() {
|
||||
const rejectButton = document.getElementById('reject-button');
|
||||
if (!rejectButton) return;
|
||||
|
||||
rejectButton.addEventListener('click', () => {
|
||||
rejectButton.disabled = true;
|
||||
|
||||
// Re-enable the reject button after 2 seconds (Rehabilitar botón de rechazo después de 2 segundos)
|
||||
setTimeout(() => {
|
||||
rejectButton.disabled = false;
|
||||
}, 2000);
|
||||
|
||||
// Always display the rejection modal for confirmation
|
||||
// (Siempre mostrar el modal de rechazo para confirmación)
|
||||
this.displayRejectModal();
|
||||
});
|
||||
|
||||
// Handle confirm rejection button (Manejar botón de confirmar rechazo)
|
||||
const rejectConfirmButton = document.getElementById('reject-confirm-button');
|
||||
if (rejectConfirmButton) {
|
||||
rejectConfirmButton.addEventListener('click', () => {
|
||||
const rejectReason = document.getElementById('reject_reason');
|
||||
|
||||
// Set user input value if provided (optional)
|
||||
// (Establecer valor de entrada del usuario si se proporciona - opcional)
|
||||
if (rejectReason) {
|
||||
const userInputField = document.querySelector('#reject-form input[name="user_input"]');
|
||||
if (userInputField) {
|
||||
userInputField.value = rejectReason.value;
|
||||
}
|
||||
}
|
||||
|
||||
this.hideRejectModal();
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle close/cancel button (Manejar botón de cerrar/cancelar)
|
||||
const rejectCloseButton = document.getElementById('reject-close-button');
|
||||
if (rejectCloseButton) {
|
||||
rejectCloseButton.addEventListener('click', () => {
|
||||
this.hideRejectModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Reject().handle();
|
||||
@@ -1,33 +1,47 @@
|
||||
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="approve-form">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<input type="hidden" name="action" value="approve" id="quote-action">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
<input type="hidden" name="signature">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
</form>
|
||||
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="reject-form">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
</form>
|
||||
|
||||
<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') }}
|
||||
{{ ctrans('texts.approve') }} / {{ ctrans('texts.reject') }}
|
||||
</h3>
|
||||
</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">
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="button"
|
||||
<div class="inline-flex rounded-md shadow-sm mr-2">
|
||||
<button type="button"
|
||||
onclick="document.querySelector('#approve-form [name=action]').value='approve'; setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary"
|
||||
id="approve-button">{{ ctrans('texts.approve') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm ">
|
||||
<button type="button"
|
||||
onclick="document.querySelector('#reject-form [name=action]').value='reject'; setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-secondary bg-red-500 text-white hover:bg-red-600"
|
||||
id="reject-button">{{ ctrans('texts.reject') }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{{-- Rejection Confirmation Modal (Modal de confirmación de rechazo) --}}
|
||||
<div style="display: none;" id="displayRejectModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center z-50">
|
||||
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity">
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
||||
<h3 class="text-xl leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.reject_quote') }}
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
{{ ctrans('texts.reject_quote_confirmation') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="reject_reason" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ ctrans('texts.reason') }} ({{ ctrans('texts.optional') }})
|
||||
</label>
|
||||
<textarea
|
||||
name="reject_reason"
|
||||
id="reject_reason"
|
||||
rows="3"
|
||||
class="block w-full rounded-md border-gray-300 bg-gray-100 focus:border-primary-300 focus:bg-white focus:ring focus:ring-primary-200 focus:ring-opacity-50"
|
||||
placeholder="{{ ctrans('texts.enter_reason') }}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||
<button type="button" id="reject-confirm-button" class="button button-danger bg-red-500 hover:bg-red-600 text-white w-full sm:w-auto">
|
||||
{{ ctrans('texts.reject') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||
<button type="button" class="button button-secondary w-full sm:w-auto" id="reject-close-button">
|
||||
{{ ctrans('texts.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary" name="action"
|
||||
value="approve">{{ ctrans('texts.approve') }}</button>
|
||||
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary" name="action"
|
||||
value="reject">{{ ctrans('texts.reject') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
90
resources/views/portal/ninja2020/quotes/reject.blade.php
Normal file
90
resources/views/portal/ninja2020/quotes/reject.blade.php
Normal file
@@ -0,0 +1,90 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.reject'))
|
||||
|
||||
@push('head')
|
||||
<meta name="accept-user-input" content="true">
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="reject-form">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
|
||||
@foreach($quotes as $quote)
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
@endforeach
|
||||
</form>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
<div class="flex justify-end">
|
||||
<div class="flex justify-end mb-2">
|
||||
<div class="relative inline-block text-left">
|
||||
<div>
|
||||
<div class="rounded-md shadow-sm">
|
||||
<button type="button" id="reject-button" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:ring-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.reject') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@foreach($quotes as $quote)
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.quote') }}
|
||||
<a class="button-link text-primary" href="{{ route('client.quote.show', $quote->hashed_id) }}">
|
||||
({{ $quote->number }})
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<dl>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.quote_number') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $quote->number }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.quote_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $quote->translateDate($quote->date, $quote->client->date_format(), $quote->client->locale()) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($quote->amount, $quote->client) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
@include('portal.ninja2020.quotes.includes.user-input')
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
@vite('resources/js/clients/quotes/reject.js')
|
||||
@endpush
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@section('body')
|
||||
|
||||
@if(!$quote->isApproved() && $client->getSetting('custom_message_unapproved_quote'))
|
||||
@if(!$quote->isApproved() && !$quote->isRejected() && $client->getSetting('custom_message_unapproved_quote'))
|
||||
@component('portal.ninja2020.components.message')
|
||||
<pre>{{ $client->getSetting('custom_message_unapproved_quote') }}</pre>
|
||||
@endcomponent
|
||||
@@ -31,13 +31,13 @@
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@if($quote->invoice()->exists())
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 flex justify-end">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<a class="button button-primary bg-primary" href="/client/invoices/{{ $quote->invoice->hashed_id }}">{{ ctrans('texts.view_invoice') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if($quote->invoice()->exists())
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 flex justify-end">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<a class="button button-primary bg-primary" href="/client/invoices/{{ $quote->invoice->hashed_id }}">{{ ctrans('texts.view_invoice') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,7 +55,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($quote->status_id == \App\Models\Quote::STATUS_REJECTED)
|
||||
|
||||
<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.rejected') }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
@@ -78,13 +90,14 @@
|
||||
|
||||
@section('footer')
|
||||
@include('portal.ninja2020.quotes.includes.user-input')
|
||||
@include('portal.ninja2020.quotes.includes.reject-input')
|
||||
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'variables' => $variables, 'entity_type' => ctrans('texts.quote')])
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
@endsection
|
||||
|
||||
@push('head')
|
||||
@vite('resources/js/clients/quotes/approve.js')
|
||||
|
||||
@vite('resources/js/clients/quotes/reject.js')
|
||||
<script type="text/javascript" defer>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ export default defineConfig({
|
||||
'resources/js/clients/payments/checkout-credit-card.js',
|
||||
'resources/js/clients/quotes/action-selectors.js',
|
||||
'resources/js/clients/quotes/approve.js',
|
||||
'resources/js/clients/quotes/reject.js',
|
||||
'resources/js/clients/payments/stripe-credit-card.js',
|
||||
'resources/js/setup/setup.js',
|
||||
'resources/js/clients/shared/pdf.js',
|
||||
|
||||
Reference in New Issue
Block a user