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.partial_due_date'
),
'custom_surcharge1' => $this->getString(
'custom_surcharge1' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge1'
),
'custom_surcharge2' => $this->getString(
'custom_surcharge2' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge2'
),
'custom_surcharge3' => $this->getString(
'custom_surcharge3' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge3'
),
'custom_surcharge4' => $this->getString(
'custom_surcharge4' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge4'
),

View File

@ -324,7 +324,10 @@ class NinjaMailerJob implements ShouldQueue
$this->mailer = 'mailgun';
$this->setMailgunMailer();
return $this;
case 'smtp':
$this->mailer = 'smtp';
$this->configureSmtpMailer();
return $this;
default:
break;
}
@ -336,6 +339,48 @@ class NinjaMailerJob implements ShouldQueue
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
* per company for use by self hosted users

View File

@ -12,15 +12,17 @@
namespace App\Livewire;
use App\Models\Client;
use App\Models\Invoice;
use Livewire\Component;
use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use App\Models\ClientContact;
use App\Models\CompanyGateway;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Livewire\Attributes\Computed;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Livewire\Component;
class RequiredClientInfo extends Component
{
@ -31,10 +33,7 @@ class RequiredClientInfo extends Component
*/
public $show_terms = false;
/**
* @var array
*/
public $invoice;
public $invoice_terms;
/**
* @var bool
@ -49,18 +48,40 @@ class RequiredClientInfo extends Component
/**
* @var ClientContact
*/
public $contact;
public $contact_id;
/**
* @var \App\Models\Client
*/
public $client;
public $client_id;
/**
* @var array
*/
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,
* right side is column in database.
@ -113,50 +134,96 @@ class RequiredClientInfo extends Component
];
protected $rules = [
'client.address1' => '',
'client.address2' => '',
'client.city' => '',
'client.state' => '',
'client.postal_code' => '',
'client.country_id' => '',
'client.shipping_address1' => '',
'client.shipping_address2' => '',
'client.shipping_city' => '',
'client.shipping_state' => '',
'client.shipping_postal_code' => '',
'client.shipping_country_id' => '',
'contact.first_name' => '',
'contact.last_name' => '',
'contact.email' => '',
'client.name' => '',
'client.website' => '',
'client.phone' => '',
'client.custom_value1' => '',
'client.custom_value2' => '',
'client.custom_value3' => '',
'client.custom_value4' => '',
// 'client.address1' => '',
// 'client.address2' => '',
// 'client.city' => '',
// 'client.state' => '',
// 'client.postal_code' => '',
// 'client.country_id' => '',
// 'client.shipping_address1' => '',
// 'client.shipping_address2' => '',
// 'client.shipping_city' => '',
// 'client.shipping_state' => '',
// 'client.shipping_postal_code' => '',
// 'client.shipping_country_id' => '',
// 'contact.first_name' => '',
// 'contact.last_name' => '',
// 'contact.email' => '',
// 'client.name' => '',
// 'client.website' => '',
// 'client.phone' => '',
// 'client.custom_value1' => '',
// 'client.custom_value2' => '',
// 'client.custom_value3' => '',
// '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 $company;
public $company_id;
public $company_gateway_id;
public $db;
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->terms_accepted = false;
$this->show_form = true;
$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
@ -164,6 +231,24 @@ class RequiredClientInfo extends Component
: $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()
{
$this->terms_accepted = !$this->terms_accepted;
@ -171,6 +256,10 @@ class RequiredClientInfo extends Component
public function handleSubmit(array $data): bool
{
MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->find($this->contact_id);
$rules = [];
collect($this->fields)->map(function ($field) use (&$rules) {
@ -192,7 +281,7 @@ class RequiredClientInfo extends Component
if ($this->updateClientDetails($data)) {
$this->dispatch(
'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.
@ -209,6 +298,11 @@ class RequiredClientInfo extends Component
$client = [];
$contact = [];
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($data as $field => $value) {
if (Str::startsWith($field, 'client_')) {
$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)
->push();
$client_update = $this->contact->client
$client_update = $_contact->client
->fill($client)
->push();
if ($contact_update && $client_update) {
if ($_contact) {
/** @var \App\Models\CompanyGateway $cg */
$cg = CompanyGateway::find($this->company_gateway_id);
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")) {
$payment_gateway->updateCustomer();
@ -247,11 +364,15 @@ class RequiredClientInfo extends Component
public function checkFields()
{
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($this->fields as $index => $field) {
$_field = $this->mappings[$field['name']];
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;
} else {
$this->fields[$index]['filled'] = true;
@ -259,7 +380,7 @@ class RequiredClientInfo extends Component
}
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;
} else {
$this->fields[$index]['filled'] = true;
@ -289,19 +410,23 @@ class RequiredClientInfo extends Component
public function handleCopyBilling(): void
{
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
$this->dispatch(
'update-shipping-data',
client_shipping_address_line_1: $this->contact->client->address1,
client_shipping_address_line_2: $this->contact->client->address2,
client_shipping_city: $this->contact->client->city,
client_shipping_state: $this->contact->client->state,
client_shipping_postal_code: $this->contact->client->postal_code,
client_shipping_country_id: $this->contact->client->country_id,
client_shipping_address_line_1: $_contact->client->address1,
client_shipping_address_line_2: $_contact->client->address2,
client_shipping_city: $_contact->client->city,
client_shipping_state: $_contact->client->state,
client_shipping_postal_code: $_contact->client->postal_code,
client_shipping_country_id: $_contact->client->country_id,
);
}
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->setMailgunMailer();
return $this;
case 'smtp':
$this->mailer = 'smtp';
$this->configureSmtpMailer();
return $this;
default:
$this->mailer = config('mail.default');
return $this;
@ -562,6 +565,43 @@ class Email implements ShouldQueue
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
* per company for use by self hosted users

View File

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

View File

@ -15,7 +15,7 @@
@if(!array_key_exists('filled', $field))
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
@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>
@foreach($countries as $country)
@ -25,7 +25,7 @@
@endforeach
</select>
@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
@if(session()->has('validation_errors') && array_key_exists($field['name'], session('validation_errors')))
@ -85,7 +85,7 @@
</h3>
<div class="mt-2">
<p class="text-sm leading-5 text-gray-500 bg-opacity-100">
{!! nl2br($invoice->terms) !!}
{!! nl2br($invoice_terms) !!}
</p>
</div>
</div>

View File

@ -11,7 +11,7 @@
@endpush
@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="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$",
"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) {
$this->assertEquals($key, Number::parseFloat($value));
// $this->assertEquals($key, Number::parseFloat2($value));
}