Replace variables in Terms for user acceptance in Client Portal

This commit is contained in:
David Bomba 2024-01-29 15:43:40 +11:00
parent f18ac57545
commit 50676394e0
15 changed files with 118 additions and 67 deletions

View File

@ -22,6 +22,7 @@ use App\Models\Invoice;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use App\Utils\HtmlEngine;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Number; use App\Utils\Number;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
@ -68,11 +69,14 @@ class InvoiceController extends Controller
event(new InvoiceWasViewed($invitation, $invoice->company, Ninja::eventVars())); event(new InvoiceWasViewed($invitation, $invoice->company, Ninja::eventVars()));
} }
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice,
'invitation' => $invitation ?: $invoice->invitations->first(), 'invitation' => $invitation ?: $invoice->invitations->first(),
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'hash' => $hash, 'hash' => $hash,
'variables' => $variables,
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {
@ -217,13 +221,21 @@ class InvoiceController extends Controller
//if there is only one payment method -> lets return straight to the payment page //if there is only one payment method -> lets return straight to the payment page
$settings = auth()->guard('contact')->user()->client->getMergedSettings();
$variables = false;
if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms)
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
$data = [ $data = [
'settings' => auth()->guard('contact')->user()->client->getMergedSettings(), 'settings' => $settings,
'invoices' => $invoices, 'invoices' => $invoices,
'formatted_total' => $formatted_total, 'formatted_total' => $formatted_total,
'payment_methods' => $payment_methods, 'payment_methods' => $payment_methods,
'hashed_ids' => $invoices->pluck('hashed_id'), 'hashed_ids' => $invoices->pluck('hashed_id'),
'total' => $total, 'total' => $total,
'variables' => $variables,
]; ];
return $this->render('invoices.payment', $data); return $this->render('invoices.payment', $data);

View File

@ -12,21 +12,22 @@
namespace App\Http\Controllers\ClientPortal; namespace App\Http\Controllers\ClientPortal;
use App\Events\Misc\InvitationWasViewed; use App\Utils\Ninja;
use App\Models\Quote;
use App\Utils\HtmlEngine;
use Illuminate\View\View;
use Illuminate\Http\Request;
use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use App\Events\Quote\QuoteWasViewed; use App\Events\Quote\QuoteWasViewed;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest; use App\Jobs\Invoice\InjectSignature;
use Illuminate\Contracts\View\Factory;
use App\Events\Misc\InvitationWasViewed;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use App\Http\Requests\ClientPortal\Quotes\ShowQuoteRequest; use App\Http\Requests\ClientPortal\Quotes\ShowQuoteRequest;
use App\Http\Requests\ClientPortal\Quotes\ShowQuotesRequest; use App\Http\Requests\ClientPortal\Quotes\ShowQuotesRequest;
use App\Jobs\Invoice\InjectSignature; use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class QuoteController extends Controller class QuoteController extends Controller
{ {
@ -54,11 +55,13 @@ class QuoteController extends Controller
/* If the quote is expired, convert the status here */ /* If the quote is expired, convert the status here */
$invitation = $quote->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first(); $invitation = $quote->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_quote_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [ $data = [
'quote' => $quote, 'quote' => $quote,
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'invitation' => $invitation 'invitation' => $invitation,
'variables' => $variables,
]; ];
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) { if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
@ -212,8 +215,18 @@ class QuoteController extends Controller
->withSuccess('Quote(s) approved successfully.'); ->withSuccess('Quote(s) approved successfully.');
} }
$variables = false;
if($invitation = $quotes->first()->invitations()->first() ?? false) {
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
}
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_quote_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
return $this->render('quotes.approve', [ return $this->render('quotes.approve', [
'quotes' => $quotes, 'quotes' => $quotes,
'variables' => $variables,
]); ]);
} }
} }

View File

@ -101,7 +101,9 @@ class PurchaseOrderController extends Controller
'settings' => $purchase_order->company->settings, 'settings' => $purchase_order->company->settings,
'sidebar' => $this->sidebarMenu(), 'sidebar' => $this->sidebarMenu(),
'company' => $purchase_order->company, 'company' => $purchase_order->company,
'invitation' => $invitation 'invitation' => $invitation,
'variables' => false,
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {

View File

@ -58,55 +58,54 @@ class EmailPayment implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
if ($this->company->is_disabled) { if ($this->company->is_disabled || ($this->contact->email ?? false)) {
return; return;
} }
if ($this->contact->email) { MultiDB::setDb($this->company->db);
MultiDB::setDb($this->company->db);
$this->payment->load('invoices'); $this->payment->load('invoices');
if (!$this->contact) { if (!$this->contact) {
$this->contact = $this->payment->client->contacts()->orderBy('is_primary', 'desc')->first(); $this->contact = $this->payment->client->contacts()->orderBy('is_primary', 'desc')->first();
}
$this->contact->load('client');
$email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build();
if($this->payment->client->getSetting('payment_email_all_contacts') && $this->payment->invoices && $this->payment->invoices->count() >= 1) {
$this->emailAllContacts($email_builder);
return;
}
$invitation = null;
$nmo = new NinjaMailerObject();
if ($this->payment->invoices && $this->payment->invoices->count() >= 1) {
if($this->contact) {
$invitation = $this->payment->invoices->first()->invitations()->where('client_contact_id', $this->contact->id)->first();
} else {
$invitation = $this->payment->invoices->first()->invitations()->first();
}
if($invitation) {
$nmo->invitation = $invitation;
}
}
$nmo->mailable = new TemplateEmail($email_builder, $this->contact, $invitation);
$nmo->to_user = $this->contact;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
$nmo->entity = $this->payment;
(new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
} }
$this->contact->load('client');
$email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build();
if($this->payment->client->getSetting('payment_email_all_contacts') && $this->payment->invoices && $this->payment->invoices->count() >= 1) {
$this->emailAllContacts($email_builder);
return;
}
$invitation = null;
$nmo = new NinjaMailerObject();
if ($this->payment->invoices && $this->payment->invoices->count() >= 1) {
if($this->contact) {
$invitation = $this->payment->invoices->first()->invitations()->where('client_contact_id', $this->contact->id)->first();
} else {
$invitation = $this->payment->invoices->first()->invitations()->first();
}
if($invitation) {
$nmo->invitation = $invitation;
}
}
$nmo->mailable = new TemplateEmail($email_builder, $this->contact, $invitation);
$nmo->to_user = $this->contact;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
$nmo->entity = $this->payment;
(new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
} }
private function emailAllContacts($email_builder): void private function emailAllContacts($email_builder): void

View File

@ -316,4 +316,24 @@ class BaseModel extends Model
return "data:application/pdf;base64,".base64_encode((new CreateRawPdf($invitation))->handle()); return "data:application/pdf;base64,".base64_encode((new CreateRawPdf($invitation))->handle());
} }
/**
* Takes a entity prop as first argument
* along with an array of variables and performs
* a string replace on the prop.
*
* @param string $field
* @param array $variables
* @return string
*/
public function parseHtmlVariables(string $field, array $variables): string
{
if(!$this->{$field})
return '';
$section = strtr($this->{$field}, $variables['labels']);
return strtr($section, $variables['values']);
}
} }

View File

@ -401,6 +401,10 @@ trait DesignHelpers
); );
} }
/**
* @todo - this is being called directl, - not through the calling class!!!!
* @design_flaw
*/
public static function parseMarkdownToHtml(string $markdown): ?string public static function parseMarkdownToHtml(string $markdown): ?string
{ {
// Use setting to determinate if parsing should be done. // Use setting to determinate if parsing should be done.

View File

@ -25,7 +25,6 @@ return new class extends Migration
->cursor() ->cursor()
->each(function (Invoice $invoice) { ->each(function (Invoice $invoice) {
$line_items = $invoice->line_items; $line_items = $invoice->line_items;
if(is_array($line_items)) if(is_array($line_items))

File diff suppressed because one or more lines are too long

View File

@ -240,7 +240,7 @@
"src": "resources/js/setup/setup.js" "src": "resources/js/setup/setup.js"
}, },
"resources/sass/app.scss": { "resources/sass/app.scss": {
"file": "assets/app-b3536ce6.css", "file": "assets/app-ec429add.css",
"isEntry": true, "isEntry": true,
"src": "resources/sass/app.scss" "src": "resources/sass/app.scss"
} }

View File

@ -4,7 +4,7 @@
</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 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="">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-xl leading-6 font-medium text-gray-900"> <h3 class="text-xl leading-6 font-medium text-gray-900">
{{ ctrans('texts.terms') }} {{ ctrans('texts.terms') }}
@ -13,7 +13,9 @@
@foreach($entities as $entity) @foreach($entities as $entity)
<div class="mb-4"> <div class="mb-4">
<p class="text-sm leading-6 font-medium text-gray-500">{{ $entity_type }} {{ $entity->number }}:</p> <p class="text-sm leading-6 font-medium text-gray-500">{{ $entity_type }} {{ $entity->number }}:</p>
@if($entity->terms) @if($variables && $entity->terms)
<h5 data-ref="entity-terms"{!! $entity->parseHtmlVariables('terms', $variables) !!}</h5>
@elseif($entity->terms)
<h5 data-ref="entity-terms" class="text-sm leading-5 text-gray-900">{!! $entity->terms !!}</h5> <h5 data-ref="entity-terms" class="text-sm leading-5 text-gray-900">{!! $entity->terms !!}</h5>
@else @else
<i class="text-sm leading-5 text-gray-500">{{ ctrans('texts.not_specified') }}</i> <i class="text-sm leading-5 text-gray-500">{{ ctrans('texts.not_specified') }}</i>

View File

@ -150,7 +150,7 @@
</div> </div>
</form> </form>
@include('portal.ninja2020.invoices.includes.terms', ['entities' => $invoices, 'entity_type' => ctrans('texts.invoice')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => $invoices, 'variables' => $variables, 'entity_type' => ctrans('texts.invoice')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -101,7 +101,7 @@
@section('footer') @section('footer')
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'variables' => $variables, 'entity_type' => ctrans('texts.invoice')])
@endsection @endsection
@push('head') @push('head')

View File

@ -51,7 +51,7 @@
@endsection @endsection
@section('footer') @section('footer')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'variables' => $variables, 'entity_type' => ctrans('texts.purchase_order')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -87,7 +87,7 @@
@section('footer') @section('footer')
@include('portal.ninja2020.quotes.includes.user-input') @include('portal.ninja2020.quotes.includes.user-input')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => $quotes, 'entity_type' => ctrans('texts.quote')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => $quotes, 'variables' => $variables, 'entity_type' => ctrans('texts.quote')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection

View File

@ -106,7 +106,7 @@
@section('footer') @section('footer')
@include('portal.ninja2020.quotes.includes.user-input') @include('portal.ninja2020.quotes.includes.user-input')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')]) @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'variables' => $variables, 'entity_type' => ctrans('texts.quote')])
@include('portal.ninja2020.invoices.includes.signature') @include('portal.ninja2020.invoices.includes.signature')
@endsection @endsection