Fixes for required client invoice + SMTP transport

This commit is contained in:
David Bomba 2024-03-11 13:35:16 +11:00
parent b7305a8538
commit f0ad4175aa
8 changed files with 274 additions and 63 deletions

View File

@ -98,19 +98,19 @@ class InvoiceTransformer extends BaseTransformer
$invoice_data, $invoice_data,
'invoice.partial_due_date' 'invoice.partial_due_date'
), ),
'custom_surcharge1' => $this->getString( 'custom_surcharge1' => $this->getFloat(
$invoice_data, $invoice_data,
'invoice.custom_surcharge1' 'invoice.custom_surcharge1'
), ),
'custom_surcharge2' => $this->getString( 'custom_surcharge2' => $this->getFloat(
$invoice_data, $invoice_data,
'invoice.custom_surcharge2' 'invoice.custom_surcharge2'
), ),
'custom_surcharge3' => $this->getString( 'custom_surcharge3' => $this->getFloat(
$invoice_data, $invoice_data,
'invoice.custom_surcharge3' 'invoice.custom_surcharge3'
), ),
'custom_surcharge4' => $this->getString( 'custom_surcharge4' => $this->getFloat(
$invoice_data, $invoice_data,
'invoice.custom_surcharge4' 'invoice.custom_surcharge4'
), ),

View File

@ -324,7 +324,10 @@ class NinjaMailerJob implements ShouldQueue
$this->mailer = 'mailgun'; $this->mailer = 'mailgun';
$this->setMailgunMailer(); $this->setMailgunMailer();
return $this; return $this;
case 'smtp':
$this->mailer = 'smtp';
$this->configureSmtpMailer();
return $this;
default: default:
break; break;
} }
@ -336,6 +339,48 @@ class NinjaMailerJob implements ShouldQueue
return $this; return $this;
} }
private function configureSmtpMailer(): void
{
$company = $this->company;
$smtp_host = $company->smtp_host;
$smtp_port = $company->smtp_port;
$smtp_username = $company->smtp_username;
$smtp_password = $company->smtp_password;
$smtp_encryption = $company->smtp_encryption ?? 'tls';
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
$smtp_verify_peer = $company->smtp_verify_peer ?? true;
config([
'mail.mailers.smtp' => [
'transport' => 'smtp',
'host' => $smtp_host,
'port' => $smtp_port,
'username' => $smtp_username,
'password' => $smtp_password,
'encryption' => $smtp_encryption,
'local_domain' => $smtp_local_domain,
'verify_peer' => $smtp_verify_peer,
'timeout' => 30,
],
]);
if (property_exists($this->nmo->settings, 'email_from_name') && strlen($this->nmo->settings->email_from_name) > 1) {
$email_from_name = $this->nmo->settings->email_from_name;
} else {
$email_from_name = $this->company->present()->name();
}
$user = $this->resolveSendingUser();
$sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$this->nmo
->mailable
->from($sending_email, $email_from_name);
}
/** /**
* Allows configuration of multiple mailers * Allows configuration of multiple mailers
* per company for use by self hosted users * per company for use by self hosted users

View File

@ -12,15 +12,17 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Client;
use App\Models\Invoice;
use Livewire\Component;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Livewire\Attributes\Computed;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Livewire\Component;
class RequiredClientInfo extends Component class RequiredClientInfo extends Component
{ {
@ -31,10 +33,7 @@ class RequiredClientInfo extends Component
*/ */
public $show_terms = false; public $show_terms = false;
/** public $invoice_terms;
* @var array
*/
public $invoice;
/** /**
* @var bool * @var bool
@ -49,18 +48,40 @@ class RequiredClientInfo extends Component
/** /**
* @var ClientContact * @var ClientContact
*/ */
public $contact; public $contact_id;
/** /**
* @var \App\Models\Client * @var \App\Models\Client
*/ */
public $client; public $client_id;
/** /**
* @var array * @var array
*/ */
public $countries; public $countries;
public $client_name;
public $contact_first_name;
public $contact_last_name;
public $contact_email;
public $client_phone;
public $client_address_line_1;
public $client_city;
public $client_state;
public $client_country_id;
public $client_postal_code;
public $client_shipping_address_line_1;
public $client_shipping_city;
public $client_shipping_state;
public $client_shipping_postal_code;
public $client_shipping_country_id;
public $client_custom_value1;
public $client_custom_value2;
public $client_custom_value3;
public $client_custom_value4;
/** /**
* Mappings for updating the database. Left side is mapping from gateway, * Mappings for updating the database. Left side is mapping from gateway,
* right side is column in database. * right side is column in database.
@ -113,50 +134,96 @@ class RequiredClientInfo extends Component
]; ];
protected $rules = [ protected $rules = [
'client.address1' => '', // 'client.address1' => '',
'client.address2' => '', // 'client.address2' => '',
'client.city' => '', // 'client.city' => '',
'client.state' => '', // 'client.state' => '',
'client.postal_code' => '', // 'client.postal_code' => '',
'client.country_id' => '', // 'client.country_id' => '',
'client.shipping_address1' => '', // 'client.shipping_address1' => '',
'client.shipping_address2' => '', // 'client.shipping_address2' => '',
'client.shipping_city' => '', // 'client.shipping_city' => '',
'client.shipping_state' => '', // 'client.shipping_state' => '',
'client.shipping_postal_code' => '', // 'client.shipping_postal_code' => '',
'client.shipping_country_id' => '', // 'client.shipping_country_id' => '',
'contact.first_name' => '', // 'contact.first_name' => '',
'contact.last_name' => '', // 'contact.last_name' => '',
'contact.email' => '', // 'contact.email' => '',
'client.name' => '', // 'client.name' => '',
'client.website' => '', // 'client.website' => '',
'client.phone' => '', // 'client.phone' => '',
'client.custom_value1' => '', // 'client.custom_value1' => '',
'client.custom_value2' => '', // 'client.custom_value2' => '',
'client.custom_value3' => '', // 'client.custom_value3' => '',
'client.custom_value4' => '', // 'client.custom_value4' => '',
'client_name' => '',
'client_website' => '',
'client_phone' => '',
'client_address_line_1' => '',
'client_address_line_2' => '',
'client_city' => '',
'client_state' => '',
'client_postal_code' => '',
'client_country_id' => '',
'client_shipping_address_line_1' => '',
'client_shipping_address_line_2' => '',
'client_shipping_city' => '',
'client_shipping_state' => '',
'client_shipping_postal_code' => '',
'client_shipping_country_id' => '',
'client_custom_value1' => '',
'client_custom_value2' => '',
'client_custom_value3' => '',
'client_custom_value4' => '',
'contact_first_name' => '',
'contact_last_name' => '',
'contact_email' => '',
]; ];
public $show_form = false; public $show_form = false;
public $company; public $company_id;
public $company_gateway_id; public $company_gateway_id;
public $db;
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->find($this->contact_id);
$company = $contact->company;
$this->client = $this->contact->client; $this->client_name = $contact->client->name;
$this->contact_first_name = $contact->first_name;
$this->contact_last_name = $contact->last_name;
$this->contact_email = $contact->email;
$this->client_phone = $contact->client->phone;
$this->client_address_line_1 = $contact->client->address1;
$this->client_city = $contact->client->city ;
$this->client_state = $contact->client->state;
$this->client_country_id = $contact->client->country_id;
$this->client_postal_code = $contact->client->postal_code;
$this->client_shipping_address_line_1 = $contact->client->shipping_address1;
$this->client_shipping_city = $contact->client->shipping_city;
$this->client_shipping_state = $contact->client->shipping_state;
$this->client_shipping_postal_code = $contact->client->shipping_postal_code;
$this->client_shipping_country_id = $contact->client->shipping_country_id;
$this->client_custom_value1 = $contact->client->custom_value1;
$this->client_custom_value2 = $contact->client->custom_value2;
$this->client_custom_value3 = $contact->client->custom_value3;
$this->client_custom_value4 = $contact->client->custom_value4;
if ($this->company->settings->show_accept_invoice_terms && request()->query('hash')) { // $this->client = $this->contact->client;
if ($company->settings->show_accept_invoice_terms && request()->query('hash')) {
$this->show_terms = true; $this->show_terms = true;
$this->terms_accepted = false; $this->terms_accepted = false;
$this->show_form = true; $this->show_form = true;
$hash = Cache::get(request()->input('hash')); $hash = Cache::get(request()->input('hash'));
$this->invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id'])); $this->invoice_terms = Invoice::find($this->decodePrimaryKey($hash['invoice_id']))->terms;
} }
count($this->fields) > 0 || $this->show_terms count($this->fields) > 0 || $this->show_terms
@ -164,6 +231,24 @@ class RequiredClientInfo extends Component
: $this->show_form = false; : $this->show_form = false;
} }
#[Computed]
public function contact()
{
MultiDB::setDb($this->db);
return ClientContact::withTrashed()->find($this->contact_id);
}
#[Computed]
public function client()
{
MultiDB::setDb($this->db);
return ClientContact::withTrashed()->find($this->contact_id)->client;
}
public function toggleTermsAccepted() public function toggleTermsAccepted()
{ {
$this->terms_accepted = !$this->terms_accepted; $this->terms_accepted = !$this->terms_accepted;
@ -171,6 +256,10 @@ class RequiredClientInfo extends Component
public function handleSubmit(array $data): bool public function handleSubmit(array $data): bool
{ {
MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->find($this->contact_id);
$rules = []; $rules = [];
collect($this->fields)->map(function ($field) use (&$rules) { collect($this->fields)->map(function ($field) use (&$rules) {
@ -192,7 +281,7 @@ class RequiredClientInfo extends Component
if ($this->updateClientDetails($data)) { if ($this->updateClientDetails($data)) {
$this->dispatch( $this->dispatch(
'passed-required-fields-check', 'passed-required-fields-check',
client_postal_code: $this->contact->client->postal_code client_postal_code: $contact->client->postal_code
); );
//if stripe is enabled, we want to update the customer at this point. //if stripe is enabled, we want to update the customer at this point.
@ -209,6 +298,11 @@ class RequiredClientInfo extends Component
$client = []; $client = [];
$contact = []; $contact = [];
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($data as $field => $value) { foreach ($data as $field => $value) {
if (Str::startsWith($field, 'client_')) { if (Str::startsWith($field, 'client_')) {
$client[$this->mappings[$field]] = $value; $client[$this->mappings[$field]] = $value;
@ -219,20 +313,43 @@ class RequiredClientInfo extends Component
} }
} }
$contact_update = $this->contact
$_contact->first_name = $this->contact_first_name;
$_contact->last_name = $this->contact_last_name;
$_contact->client->name = $this->client_name;
$_contact->email = $this->contact_email;
$_contact->client->phone = $this->client_phone;
$_contact->client->address1 = $this->client_address_line_1;
$_contact->client->city = $this->client_city;
$_contact->client->state = $this->client_state;
$_contact->client->country_id = $this->client_country_id;
$_contact->client->postal_code = $this->client_postal_code;
$_contact->client->shipping_address1 = $this->client_shipping_address_line_1;
$_contact->client->shipping_city = $this->client_shipping_city;
$_contact->client->shipping_state = $this->client_shipping_state;
$_contact->client->shipping_postal_code = $this->client_shipping_postal_code;
$_contact->client->shipping_country_id = $this->client_shipping_country_id;
$_contact->client->custom_value1 = $this->client_custom_value1;
$_contact->client->custom_value2 = $this->client_custom_value2;
$_contact->client->custom_value3 = $this->client_custom_value3;
$_contact->client->custom_value4 = $this->client_custom_value4;
$_contact->push();
$contact_update = $_contact
->fill($contact) ->fill($contact)
->push(); ->push();
$client_update = $this->contact->client $client_update = $_contact->client
->fill($client) ->fill($client)
->push(); ->push();
if ($contact_update && $client_update) { if ($_contact) {
/** @var \App\Models\CompanyGateway $cg */ /** @var \App\Models\CompanyGateway $cg */
$cg = CompanyGateway::find($this->company_gateway_id); $cg = CompanyGateway::find($this->company_gateway_id);
if ($cg && $cg->update_details) { if ($cg && $cg->update_details) {
$payment_gateway = $cg->driver($this->client)->init(); $payment_gateway = $cg->driver($_contact->client)->init();
if (method_exists($payment_gateway, "updateCustomer")) { if (method_exists($payment_gateway, "updateCustomer")) {
$payment_gateway->updateCustomer(); $payment_gateway->updateCustomer();
@ -247,11 +364,15 @@ class RequiredClientInfo extends Component
public function checkFields() public function checkFields()
{ {
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($this->fields as $index => $field) { foreach ($this->fields as $index => $field) {
$_field = $this->mappings[$field['name']]; $_field = $this->mappings[$field['name']];
if (Str::startsWith($field['name'], 'client_')) { if (Str::startsWith($field['name'], 'client_')) {
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || in_array($_field, $this->client_address_array)) { if (empty($_contact->client->{$_field}) || is_null($_contact->client->{$_field}) || in_array($_field, $this->client_address_array)) {
$this->show_form = true; $this->show_form = true;
} else { } else {
$this->fields[$index]['filled'] = true; $this->fields[$index]['filled'] = true;
@ -259,7 +380,7 @@ class RequiredClientInfo extends Component
} }
if (Str::startsWith($field['name'], 'contact_')) { if (Str::startsWith($field['name'], 'contact_')) {
if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field}) || str_contains($this->contact->{$_field}, '@example.com')) { if (empty($_contact->{$_field}) || is_null($_contact->{$_field}) || str_contains($_contact->{$_field}, '@example.com')) {
$this->show_form = true; $this->show_form = true;
} else { } else {
$this->fields[$index]['filled'] = true; $this->fields[$index]['filled'] = true;
@ -289,19 +410,23 @@ class RequiredClientInfo extends Component
public function handleCopyBilling(): void public function handleCopyBilling(): void
{ {
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
$this->dispatch( $this->dispatch(
'update-shipping-data', 'update-shipping-data',
client_shipping_address_line_1: $this->contact->client->address1, client_shipping_address_line_1: $_contact->client->address1,
client_shipping_address_line_2: $this->contact->client->address2, client_shipping_address_line_2: $_contact->client->address2,
client_shipping_city: $this->contact->client->city, client_shipping_city: $_contact->client->city,
client_shipping_state: $this->contact->client->state, client_shipping_state: $_contact->client->state,
client_shipping_postal_code: $this->contact->client->postal_code, client_shipping_postal_code: $_contact->client->postal_code,
client_shipping_country_id: $this->contact->client->country_id, client_shipping_country_id: $_contact->client->country_id,
); );
} }
public function render() public function render()
{ {
return render('components.livewire.required-client-info'); return render('components.livewire.required-client-infox');
} }
} }

View File

@ -549,7 +549,10 @@ class Email implements ShouldQueue
$this->mailer = 'mailgun'; $this->mailer = 'mailgun';
$this->setMailgunMailer(); $this->setMailgunMailer();
return $this; return $this;
case 'smtp':
$this->mailer = 'smtp';
$this->configureSmtpMailer();
return $this;
default: default:
$this->mailer = config('mail.default'); $this->mailer = config('mail.default');
return $this; return $this;
@ -562,6 +565,43 @@ class Email implements ShouldQueue
return $this; return $this;
} }
private function configureSmtpMailer(): void
{
$company = $this->company;
$smtp_host = $company->smtp_host;
$smtp_port = $company->smtp_port;
$smtp_username = $company->smtp_username;
$smtp_password = $company->smtp_password;
$smtp_encryption = $company->smtp_encryption ?? 'tls';
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
$smtp_verify_peer = $company->smtp_verify_peer ?? true;
config([
'mail.mailers.smtp' => [
'transport' => 'smtp',
'host' => $smtp_host,
'port' => $smtp_port,
'username' => $smtp_username,
'password' => $smtp_password,
'encryption' => $smtp_encryption,
'local_domain' => $smtp_local_domain,
'verify_peer' => $smtp_verify_peer,
'timeout' => 30,
],
]);
$user = $this->resolveSendingUser();
$sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email;
$sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name();
$this->mailable
->from($sending_email, $sending_user);
}
/** /**
* Allows configuration of multiple mailers * Allows configuration of multiple mailers
* per company for use by self hosted users * per company for use by self hosted users

View File

@ -181,7 +181,7 @@ class Number
return (float) $value; return (float) $value;
} }
//comma first = traditional thousan separator //comma first = traditional thousand separator
$value = str_replace(',', '', $value); $value = str_replace(',', '', $value);
return (float)$value; return (float)$value;

View File

@ -15,7 +15,7 @@
@if(!array_key_exists('filled', $field)) @if(!array_key_exists('filled', $field))
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']]) @component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
@if($field['name'] == 'client_country_id' || $field['name'] == 'client_shipping_country_id') @if($field['name'] == 'client_country_id' || $field['name'] == 'client_shipping_country_id')
<select id="client_country" class="input w-full form-select bg-white" name="{{ $field['name'] }}" wire:model="{{ str_replace(["client_","_line_","contact_"], ["client.","","contact."], $field['name']) }}"> <select id="client_country" class="input w-full form-select bg-white" name="{{ $field['name'] }}" wire:model="{{ $field['name'] }}">
<option value="none"></option> <option value="none"></option>
@foreach($countries as $country) @foreach($countries as $country)
@ -25,7 +25,7 @@
@endforeach @endforeach
</select> </select>
@else @else
<input class="input w-full" type="{{ $field['type'] ?? 'text' }}" name="{{ $field['name'] }}" wire:model="{{ str_replace(["client_","_line_","contact_"], ["client.","","contact."], $field['name']) }}"> <input class="input w-full" type="{{ $field['type'] ?? 'text' }}" name="{{ $field['name'] }}" wire:model="{{ $field['name'] }}">
@endif @endif
@if(session()->has('validation_errors') && array_key_exists($field['name'], session('validation_errors'))) @if(session()->has('validation_errors') && array_key_exists($field['name'], session('validation_errors')))
@ -85,7 +85,7 @@
</h3> </h3>
<div class="mt-2"> <div class="mt-2">
<p class="text-sm leading-5 text-gray-500 bg-opacity-100"> <p class="text-sm leading-5 text-gray-500 bg-opacity-100">
{!! nl2br($invoice->terms) !!} {!! nl2br($invoice_terms) !!}
</p> </p>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@
@endpush @endpush
@section('body') @section('body')
@livewire('required-client-info', ['fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact' => auth()->guard('contact')->user(), 'countries' => $countries, 'company' => $company, 'company_gateway_id' => $gateway->company_gateway ? $gateway->company_gateway->id : $gateway->id]) @livewire('required-client-info', ['db' => $company->db, 'fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact_id' => auth()->guard('contact')->user()->id, 'countries' => $countries, 'company_id' => $company->id, 'company_gateway_id' => $gateway->company_gateway ? $gateway->company_gateway->id : $gateway->id])
<div class="container mx-auto grid grid-cols-12 opacity-25 pointer-events-none" data-ref="gateway-container"> <div class="container mx-auto grid grid-cols-12 opacity-25 pointer-events-none" data-ref="gateway-container">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 bg-white shadow rounded-lg"> <div class="col-span-12 lg:col-span-6 lg:col-start-4 bg-white shadow rounded-lg">

View File

@ -55,13 +55,14 @@ class NumberTest extends TestCase
"1000.02" =>"1'000,02 EUR", "1000.02" =>"1'000,02 EUR",
"1000.02" =>"1 000.02$", "1000.02" =>"1 000.02$",
"1000.02" =>"1,000.02$", "1000.02" =>"1,000.02$",
"1000.02" =>"1.000,02 EURO" "1000.02" =>"1.000,02 EURO",
"9.975" => "9.975"
]; ];
foreach($floatvals as $key => $value) { foreach($floatvals as $key => $value) {
$this->assertEquals($key, Number::parseFloat($value)); // $this->assertEquals($key, Number::parseFloat2($value));
} }