diff --git a/VERSION.txt b/VERSION.txt index 8a55abf0b000..5c41e37e4728 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.41 \ No newline at end of file +5.5.42 \ No newline at end of file diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index 3673ce79b8ea..b94b5e858924 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -87,7 +87,8 @@ class AccountTransformer implements AccountTransformerInterface return [ 'id' => $account->id, 'account_type' => $account->CONTAINER, - 'account_name' => $account->accountName, + // 'account_name' => $account->accountName, + 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname, 'account_status' => $account->accountStatus, 'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '', 'provider_account_id' => $account->providerAccountId, diff --git a/app/Helpers/Epc/EpcQrGenerator.php b/app/Helpers/Epc/EpcQrGenerator.php index a63446f24caf..d308f7188ba9 100644 --- a/app/Helpers/Epc/EpcQrGenerator.php +++ b/app/Helpers/Epc/EpcQrGenerator.php @@ -49,9 +49,12 @@ class EpcQrGenerator $this->validateFields(); try { - $qr = $writer->writeString($this->encodeMessage()); + $qr = $writer->writeString($this->encodeMessage(), 'utf-8'); } - catch(BaconQrCode\Exception\WriterException $e){ + catch(\Throwable $e){ + return ''; + } + catch(\Exception $e){ return ''; } return " diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index 706689d99771..488c0d1a623c 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -87,10 +87,10 @@ class SwissQrGenerator $qrBill->setUltimateDebtor( QrBill\DataGroup\Element\StructuredAddress::createWithStreet( substr($this->client->present()->name(), 0 , 70), - $this->client->address1 ? substr($this->client->address1, 0 , 70) : '', - $this->client->address2 ? substr($this->client->address2, 0 , 16) : '', - $this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '', - $this->client->city ? substr($this->client->city, 0, 35) : '', + $this->client->address1 ? substr($this->client->address1, 0 , 70) : '_', + $this->client->address2 ? substr($this->client->address2, 0 , 16) : '_', + $this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '_', + $this->client->city ? substr($this->client->city, 0, 35) : '_', 'CH' )); diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index a8ec2da2d0bc..f523c91b34da 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -293,7 +293,7 @@ class CompanyExport implements ShouldQueue $this->export_data['payments'] = $this->company->payments()->orderBy('number', 'DESC')->cursor()->map(function ($payment){ $payment = $this->transformBasicEntities($payment); - $payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']); + $payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id', 'transaction_id']); $payment->paymentables = $this->transformPaymentable($payment); @@ -456,7 +456,6 @@ class CompanyExport implements ShouldQueue })->all(); - $this->export_data['purchase_order_invitations'] = PurchaseOrderInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($purchase_order){ $purchase_order = $this->transformArrayOfKeys($purchase_order, ['company_id', 'user_id', 'vendor_contact_id', 'purchase_order_id']); @@ -466,6 +465,21 @@ class CompanyExport implements ShouldQueue })->all(); + $this->export_data['bank_integrations'] = $this->company->bank_integrations()->orderBy('id', 'ASC')->cursor()->map(function ($bank_integration){ + + $bank_integration = $this->transformArrayOfKeys($bank_integration, ['account_id','company_id', 'user_id']); + + return $bank_integration->makeVisible(['id','user_id','company_id','account_id']); + + })->all(); + + $this->export_data['bank_transactions'] = $this->company->bank_transactions()->orderBy('id', 'ASC')->cursor()->map(function ($bank_transaction){ + + $bank_transaction = $this->transformArrayOfKeys($bank_transaction, ['company_id', 'user_id','bank_integration_id','expense_id','category_id','ninja_category_id','vendor_id']); + + return $bank_transaction->makeVisible(['id','user_id','company_id']); + + })->all(); //write to tmp and email to owner(); @@ -516,9 +530,6 @@ class CompanyExport implements ShouldQueue $file_name = date('Y-m-d').'_'.str_replace([" ", "/"],["_",""], $this->company->present()->name() . '_' . $this->company->company_key .'.zip'); $path = 'backups'; - - // if(!Storage::disk(config('filesystems.default'))->exists($path)) - // Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775); $zip_path = public_path('storage/backups/'.$file_name); $zip = new \ZipArchive(); diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 67964eefa18f..2910fb720b70 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -24,6 +24,8 @@ use App\Mail\Import\CompanyImportFailure; use App\Mail\Import\ImportCompleted; use App\Models\Activity; use App\Models\Backup; +use App\Models\BankIntegration; +use App\Models\BankTransaction; use App\Models\Client; use App\Models\ClientContact; use App\Models\ClientGatewayToken; @@ -142,15 +144,16 @@ class CompanyImport implements ShouldQueue 'expenses', 'tasks', 'payments', - // 'activities', - // 'backups', 'company_ledger', 'designs', 'documents', 'webhooks', 'system_logs', 'purchase_orders', - 'purchase_order_invitations' + 'purchase_order_invitations', + 'bank_integrations', + 'bank_transactions', + 'payments', ]; private $company_properties = [ @@ -527,6 +530,37 @@ class CompanyImport implements ShouldQueue } + private function import_bank_integrations() + { + $this->genericImport(BankIntegration::class, + ['assigned_user_id','account_id', 'company_id', 'id', 'hashed_id'], + [ + ['users' => 'user_id'], + ], + 'bank_integrations', + 'description'); + + return $this; + } + + private function import_bank_transactions() + { + $this->genericImport(BankTransaction::class, + ['assigned_user_id','company_id', 'id', 'hashed_id', 'user_id'], + [ + ['users' => 'user_id'], + ['expenses' => 'expense_id'], + ['vendors' => 'vendor_id'], + ['expense_categories' => 'ninja_category_id'], + ['expense_categories' => 'category_id'], + ['bank_integrations' => 'bank_integration_id'] + ], + 'bank_transactions', + null); + + return $this; + } + private function import_recurring_expenses() { //unset / transforms / object_property / match_key @@ -979,6 +1013,7 @@ class CompanyImport implements ShouldQueue ['vendors' => 'vendor_id'], ['invoice_invitations' => 'invitation_id'], ['company_gateways' => 'company_gateway_id'], + ['bank_transactions' => 'transaction_id'], ], 'payments', 'number'); @@ -1569,6 +1604,28 @@ class CompanyImport implements ShouldQueue $obj_array, ); } + elseif($class == 'App\Models\BankIntegration'){ + $new_obj = new BankIntegration(); + $new_obj->company_id = $this->company->id; + $new_obj->account_id = $this->account->id; + $new_obj->fill($obj_array); + $new_obj->save(['timestamps' => false]); + } + elseif($class == 'App\Models\BankTransaction'){ + + $new_obj = new BankTransaction(); + $new_obj->company_id = $this->company->id; + + $obj_array['invoice_ids'] = collect(explode(",",$obj_array['invoice_ids']))->map(function ($id) { + return $this->transformId('invoices', $id); + })->map(function ($encodeable){ + return $this->encodePrimaryKey($encodeable); + })->implode(","); + + + $new_obj->fill($obj_array); + $new_obj->save(['timestamps' => false]); + } else{ $new_obj = $class::withTrashed()->firstOrNew( [$match_key => $obj->{$match_key}, 'company_id' => $this->company->id], diff --git a/app/PaymentDrivers/Braintree/PayPal.php b/app/PaymentDrivers/Braintree/PayPal.php index 6d24013e7c4a..76b630d00fb1 100644 --- a/app/PaymentDrivers/Braintree/PayPal.php +++ b/app/PaymentDrivers/Braintree/PayPal.php @@ -111,7 +111,10 @@ class PayPal 'paymentMethodNonce' => $gateway_response->nonce, ]); - return $payment_method->paymentMethod->token; + if($payment_method->success) + return $payment_method->paymentMethod->token; + else + throw new PaymentFailed(property_exists($payment_method, 'message') ? $payment_method->message : 'Undefined error storing payment token.', 0); } /** diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 6801d8f0ff41..daa9aa7fcd68 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -306,10 +306,53 @@ class CheckoutComPaymentDriver extends BaseDriver try { $response = $this->gateway->getCustomersClient()->create($request); - } catch (\Exception $e) { - // API error - throw new PaymentFailed($e->getMessage(), $e->getCode()); } + catch (CheckoutApiException $e) { + // API error + $request_id = $e->request_id; + $http_status_code = $e->http_status_code; + $error_details = $e->error_details; + + if(is_array($error_details)) { + $error_details = end($e->error_details['error_codes']); + } + + $human_exception = $error_details ? new \Exception($error_details, 400) : $e; + + + throw new PaymentFailed($human_exception); + } catch (CheckoutArgumentException $e) { + // Bad arguments + + $error_details = $e->error_details; + + if(is_array($error_details)) { + $error_details = end($e->error_details['error_codes']); + } + + $human_exception = $error_details ? new \Exception($error_details, 400) : $e; + + throw new PaymentFailed($human_exception); + } catch (CheckoutAuthorizationException $e) { + // Bad Invalid authorization + + $error_details = $e->error_details; + + if(is_array($error_details)) { + $error_details = end($e->error_details['error_codes']); + } + + $human_exception = $error_details ? new \Exception($error_details, 400) : $e; + + throw new PaymentFailed($human_exception); + } + + + + // catch (\Exception $e) { + // // API error + // throw new PaymentFailed($e->getMessage(), $e->getCode()); + // } return $response; } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 0b70b4c9f855..e81b7a932f5a 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -104,7 +104,6 @@ class DeletePayment $client = $this->payment ->client - ->fresh() ->service() ->updateBalance($net_deletable) ->save(); @@ -136,9 +135,8 @@ class DeletePayment }); } - $client = $this->payment->client->fresh(); - - $client + $this->payment + ->client ->service() ->updatePaidToDate(($this->payment->amount - $this->payment->refunded) * -1) ->save(); @@ -146,7 +144,7 @@ class DeletePayment $transaction = [ 'invoice' => [], 'payment' => [], - 'client' => $client->transaction_event(), + 'client' => $this->payment->client->transaction_event(), 'credit' => [], 'metadata' => [], ]; diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index 4efbeddb715c..f3cb3debfe6a 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -62,12 +62,14 @@ class UpdateInvoicePayment $paid_amount = $paid_invoice->amount; } - $client->service()->updateBalanceAndPaidToDate($paid_amount*-1, $paid_amount); + $client->service()->updatePaidToDate($paid_amount); //always use the payment->amount /* Need to determine here is we have an OVER payment - if YES only apply the max invoice amount */ if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance) $paid_amount = $invoice->balance; + $client->service()->updateBalance($paid_amount*-1); //only ever use the amount applied to the invoice + /*Improve performance here - 26-01-2022 - also change the order of events for invoice first*/ //caution what if we amount paid was less than partial - we wipe it! $invoice->balance -= $paid_amount; diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index 88f64e12c535..c8629c9f70f1 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -95,10 +95,14 @@ class SystemHealth if(strlen(config('ninja.currency_converter_api_key')) == 0){ + try{ $cs = DB::table('clients') ->select('settings->currency_id as id') ->get(); - + } + catch(\Exception $e){ + return true; //fresh installs, there may be no DB connection, nor migrations could have run yet. + } $currency_count = $cs->unique('id')->filter(function ($value){ return !is_null($value->id); diff --git a/config/ninja.php b/config/ninja.php index edfc4c68b0f2..b850fef4ff1e 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.41', - 'app_tag' => '5.5.41', + 'app_version' => '5.5.42', + 'app_tag' => '5.5.42', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 1f9fcebbd282..dac7dcfe60b7 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -257,7 +257,7 @@ overflow-wrap: break-word; } - .stamp { + .stamp { transform: rotate(12deg); color: #555; font-size: 3rem; diff --git a/resources/views/portal/ninja2020/payment_methods/show.blade.php b/resources/views/portal/ninja2020/payment_methods/show.blade.php index f928bfbd530f..dbba4777fce1 100644 --- a/resources/views/portal/ninja2020/payment_methods/show.blade.php +++ b/resources/views/portal/ninja2020/payment_methods/show.blade.php @@ -31,7 +31,7 @@ {{ ctrans('texts.type') }}
- {{ $payment_method->meta?->brand }} + {{ property_exists($payment_method->meta, 'brand') ? $payment_method->meta?->brand : ''}} {{ property_exists($payment_method->meta, 'scheme') ? $payment_method->meta?->scheme : '' }}