diff --git a/VERSION.txt b/VERSION.txt
index 25285daaef52..71c2c6e276ca 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-5.7.30
\ No newline at end of file
+5.7.33
\ No newline at end of file
diff --git a/app/DataMapper/Settings/SettingsData.php b/app/DataMapper/Settings/SettingsData.php
new file mode 100644
index 000000000000..b76379806ff6
--- /dev/null
+++ b/app/DataMapper/Settings/SettingsData.php
@@ -0,0 +1,515 @@
+ $value) {
+
+ try{
+ settype($object->{$key}, gettype($this->{$key}));
+ }
+ catch(\Exception | \Error | \Throwable $e){
+
+ if(property_exists($this, $key))
+ $object->{$key} = $this->{$key};
+ else
+ unset($object->{$key});
+
+ }
+
+ // if(!property_exists($this, $key)) {
+ // unset($object->{$key});
+ // }
+ // elseif(is_array($object->{$key}) && gettype($this->{$key} != 'array')){
+ // $object->{$key} = $this->{$key};
+ // }
+ // else {
+ // settype($object->{$key}, gettype($this->{$key}));
+ // }
+ }
+ }
+ $this->object = $object;
+
+ return $this;
+ }
+
+ public function toObject(): object
+ {
+ return (object)$this->object;
+ }
+
+ public function toArray(): array
+ {
+ return (array)$this->object;
+ }
+}
\ No newline at end of file
diff --git a/app/DataMapper/Tax/AU/Rule.php b/app/DataMapper/Tax/AU/Rule.php
index d6536e1a7177..c1d9e7ed218e 100644
--- a/app/DataMapper/Tax/AU/Rule.php
+++ b/app/DataMapper/Tax/AU/Rule.php
@@ -62,7 +62,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxByType($item): self
{
- if ($this->client->is_tax_exempt) {
+ if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
}
diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php
index 36635429a6d7..ba8169734547 100644
--- a/app/DataMapper/Tax/BaseRule.php
+++ b/app/DataMapper/Tax/BaseRule.php
@@ -297,7 +297,7 @@ class BaseRule implements RuleInterface
public function tax($item = null): self
{
- if ($this->client->is_tax_exempt) {
+ if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
diff --git a/app/DataMapper/Tax/DE/Rule.php b/app/DataMapper/Tax/DE/Rule.php
index c82f701496f4..8214188c369f 100644
--- a/app/DataMapper/Tax/DE/Rule.php
+++ b/app/DataMapper/Tax/DE/Rule.php
@@ -63,7 +63,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxByType($item): self
{
- if ($this->client->is_tax_exempt) {
+ if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
}
diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php
index e0344792895c..fa6ab80ef33f 100644
--- a/app/Export/CSV/BaseExport.php
+++ b/app/Export/CSV/BaseExport.php
@@ -88,6 +88,7 @@ class BaseExport
protected array $client_report_keys = [
"name" => "client.name",
+ "number" => "client.number",
"user" => "client.user",
"assigned_user" => "client.assigned_user",
"balance" => "client.balance",
@@ -168,6 +169,7 @@ class BaseExport
'tax_rate1' => 'invoice.tax_rate1',
'tax_rate2' => 'invoice.tax_rate2',
'tax_rate3' => 'invoice.tax_rate3',
+ 'recurring_invoice' => 'invoice.recurring_id',
];
protected array $recurring_invoice_report_keys = [
@@ -230,7 +232,7 @@ class BaseExport
'po_number' => 'purchase_order.po_number',
'private_notes' => 'purchase_order.private_notes',
'public_notes' => 'purchase_order.public_notes',
- 'status' => 'purchase_order.status_id',
+ 'status' => 'purchase_order.status',
'tax_name1' => 'purchase_order.tax_name1',
'tax_name2' => 'purchase_order.tax_name2',
'tax_name3' => 'purchase_order.tax_name3',
@@ -377,6 +379,7 @@ class BaseExport
"custom_value4" => "payment.custom_value4",
"user" => "payment.user_id",
"assigned_user" => "payment.assigned_user_id",
+
];
protected array $expense_report_keys = [
@@ -429,6 +432,14 @@ class BaseExport
'project' => 'task.project_id',
];
+ protected array $forced_client_fields = [
+ "client.name",
+ ];
+
+ protected array $forced_vendor_fields = [
+ "vendor.name",
+ ];
+
protected function filterByClients($query)
{
if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') {
@@ -1144,9 +1155,9 @@ class BaseExport
$clean_row[$key]['entity'] = $report_keys[0];
$clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0];
$clean_row[$key]['hashed_id'] = $report_keys[0] == $entity ? null : $resource->{$report_keys[0]}->hashed_id ?? null;
- $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]];
+ $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value];
$clean_row[$key]['identifier'] = $value;
- $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]];
+ $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value];
}
diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php
index e99434b19863..16f9ca154d70 100644
--- a/app/Export/CSV/ClientExport.php
+++ b/app/Export/CSV/ClientExport.php
@@ -189,7 +189,7 @@ class ClientExport extends BaseExport
$clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0];
$clean_row[$key]['hashed_id'] = $report_keys[0] == 'client' ? null : $resource->{$report_keys[0]}->hashed_id ?? null;
$clean_row[$key]['value'] = $row[$column_key];
- $clean_row[$key]['identifier'] = $key;
+ $clean_row[$key]['identifier'] = $value;
if(in_array($clean_row[$key]['id'], ['paid_to_date', 'balance', 'credit_balance','payment_balance']))
$clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $resource);
diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php
index 29afbfa16100..c5c05f3c23e2 100644
--- a/app/Export/CSV/CreditExport.php
+++ b/app/Export/CSV/CreditExport.php
@@ -93,6 +93,8 @@ class CreditExport extends BaseExport
$this->input['report_keys'] = array_values($this->credit_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Credit::query()
->withTrashed()
->with('client')
diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php
index 0a8f3188f2c3..4dc4ee3d8b94 100644
--- a/app/Export/CSV/InvoiceExport.php
+++ b/app/Export/CSV/InvoiceExport.php
@@ -50,6 +50,8 @@ class InvoiceExport extends BaseExport
$this->input['report_keys'] = array_values($this->invoice_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Invoice::query()
->withTrashed()
->with('client')
@@ -142,6 +144,11 @@ class InvoiceExport extends BaseExport
if (in_array('invoice.status', $this->input['report_keys'])) {
$entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
}
+
+ if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
+ $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
+ }
+
return $entity;
}
diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php
index 74c1a64c25b0..ba2dc00da4f5 100644
--- a/app/Export/CSV/InvoiceItemExport.php
+++ b/app/Export/CSV/InvoiceItemExport.php
@@ -62,6 +62,8 @@ class InvoiceItemExport extends BaseExport
$this->input['report_keys'] = array_values($this->mergeItemsKeys('invoice_report_keys'));
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Invoice::query()
->withTrashed()
->with('client')
@@ -135,16 +137,16 @@ class InvoiceItemExport extends BaseExport
if (str_contains($key, "item.")) {
- $key = str_replace("item.", "", $key);
+ $tmp_key = str_replace("item.", "", $key);
- if($key == 'type_id')
- $key = 'type';
+ if($tmp_key == 'type_id')
+ $tmp_key = 'type';
- if($key == 'tax_id')
- $key = 'tax_category';
+ if($tmp_key == 'tax_id')
+ $tmp_key = 'tax_category';
- if (property_exists($item, $key)) {
- $item_array[$key] = $item->{$key};
+ if (property_exists($item, $tmp_key)) {
+ $item_array[$key] = $item->{$tmp_key};
}
else {
$item_array[$key] = '';
@@ -154,6 +156,8 @@ class InvoiceItemExport extends BaseExport
$transformed_items = array_merge($transformed_invoice, $item_array);
$entity = $this->decorateAdvancedFields($invoice, $transformed_items);
+
+ $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity);
$this->storage_array[] = $entity;
@@ -200,6 +204,27 @@ class InvoiceItemExport extends BaseExport
$entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
}
+ if (in_array('invoice.country_id', $this->input['report_keys'])) {
+ $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : '';
+ }
+
+ if (in_array('invoice.currency_id', $this->input['report_keys'])) {
+ $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
+ }
+
+ if (in_array('invoice.client_id', $this->input['report_keys'])) {
+ $entity['invoice.client_id'] = $invoice->client->present()->name();
+ }
+
+ if (in_array('invoice.status', $this->input['report_keys'])) {
+ $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
+ }
+
+ if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
+ $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
+ }
+
+
return $entity;
}
diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php
index 01c8641b0725..7f77563f9b92 100644
--- a/app/Export/CSV/PaymentExport.php
+++ b/app/Export/CSV/PaymentExport.php
@@ -48,6 +48,8 @@ class PaymentExport extends BaseExport
$this->input['report_keys'] = array_values($this->payment_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Payment::query()
->withTrashed()
->where('company_id', $this->company->id)
@@ -69,6 +71,8 @@ class PaymentExport extends BaseExport
return ['identifier' => $key, 'display_value' => $headerdisplay[$value]];
})->toArray();
+ nlog($header);
+
$report = $query->cursor()
->map(function ($resource) {
$row = $this->buildRow($resource);
diff --git a/app/Export/CSV/PurchaseOrderExport.php b/app/Export/CSV/PurchaseOrderExport.php
index 88145822b10a..45e8a6adb1b7 100644
--- a/app/Export/CSV/PurchaseOrderExport.php
+++ b/app/Export/CSV/PurchaseOrderExport.php
@@ -54,7 +54,7 @@ class PurchaseOrderExport extends BaseExport
'po_number' => 'purchase_order.po_number',
'private_notes' => 'purchase_order.private_notes',
'public_notes' => 'purchase_order.public_notes',
- 'status' => 'purchase_order.status_id',
+ 'status' => 'purchase_order.status',
'tax_name1' => 'purchase_order.tax_name1',
'tax_name2' => 'purchase_order.tax_name2',
'tax_name3' => 'purchase_order.tax_name3',
@@ -95,6 +95,9 @@ class PurchaseOrderExport extends BaseExport
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = array_values($this->purchase_order_report_keys);
}
+
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_vendor_fields, $this->input['report_keys']));
+
$query = PurchaseOrder::query()
->withTrashed()
->with('vendor')
@@ -181,8 +184,8 @@ class PurchaseOrderExport extends BaseExport
$entity['vendor'] = $purchase_order->vendor->present()->name();
}
- if (in_array('status_id', $this->input['report_keys'])) {
- $entity['status'] = $purchase_order->stringStatus($purchase_order->status_id);
+ if (in_array('purchase_order.status', $this->input['report_keys'])) {
+ $entity['purchase_order.status'] = $purchase_order->stringStatus($purchase_order->status_id);
}
return $entity;
diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php
index 9ebf2991c95b..3738ec5b6dd0 100644
--- a/app/Export/CSV/PurchaseOrderItemExport.php
+++ b/app/Export/CSV/PurchaseOrderItemExport.php
@@ -55,6 +55,8 @@ class PurchaseOrderItemExport extends BaseExport
$this->input['report_keys'] = array_values($this->mergeItemsKeys('purchase_order_report_keys'));
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_vendor_fields, $this->input['report_keys']));
+
$query = PurchaseOrder::query()
->withTrashed()
->with('vendor')->where('company_id', $this->company->id)
@@ -125,18 +127,18 @@ class PurchaseOrderItemExport extends BaseExport
if (str_contains($key, "item.")) {
- $key = str_replace("item.", "", $key);
+ $tmp_key = str_replace("item.", "", $key);
- if($key == 'type_id') {
- $keyval = 'type';
+ if($tmp_key == 'type_id') {
+ $tmp_key = 'type';
}
- if($key == 'tax_id') {
- $keyval = 'tax_category';
+ if($tmp_key == 'tax_id') {
+ $tmp_key = 'tax_category';
}
- if (property_exists($item, $key)) {
- $item_array[$key] = $item->{$key};
+ if (property_exists($item, $tmp_key)) {
+ $item_array[$key] = $item->{$tmp_key};
} else {
$item_array[$key] = '';
}
@@ -145,6 +147,7 @@ class PurchaseOrderItemExport extends BaseExport
$transformed_items = array_merge($transformed_purchase_order, $item_array);
$entity = $this->decorateAdvancedFields($purchase_order, $transformed_items);
+ $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity);
$this->storage_array[] = $entity;
}
diff --git a/app/Export/CSV/QuoteExport.php b/app/Export/CSV/QuoteExport.php
index 8083c0a71e7a..eba6adbcef26 100644
--- a/app/Export/CSV/QuoteExport.php
+++ b/app/Export/CSV/QuoteExport.php
@@ -56,6 +56,8 @@ class QuoteExport extends BaseExport
$this->input['report_keys'] = array_values($this->quote_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Quote::query()
->withTrashed()
->with('client')
diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php
index f85a82dd4c12..0db08629c664 100644
--- a/app/Export/CSV/QuoteItemExport.php
+++ b/app/Export/CSV/QuoteItemExport.php
@@ -57,6 +57,8 @@ class QuoteItemExport extends BaseExport
$this->input['report_keys'] = array_values($this->mergeItemsKeys('quote_report_keys'));
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Quote::query()
->withTrashed()
->with('client')->where('company_id', $this->company->id)
@@ -131,16 +133,16 @@ class QuoteItemExport extends BaseExport
if (str_contains($key, "item.")) {
- $key = str_replace("item.", "", $key);
+ $tmp_key = str_replace("item.", "", $key);
- if($key == 'type_id')
- $key = 'type';
+ if($tmp_key == 'type_id')
+ $tmp_key = 'type';
- if($key == 'tax_id')
- $key = 'tax_category';
+ if($tmp_key == 'tax_id')
+ $tmp_key = 'tax_category';
- if (property_exists($item, $key)) {
- $item_array[$key] = $item->{$key};
+ if (property_exists($item, $tmp_key)) {
+ $item_array[$key] = $item->{$tmp_key};
}
else {
$item_array[$key] = '';
@@ -150,6 +152,7 @@ class QuoteItemExport extends BaseExport
$transformed_items = array_merge($transformed_quote, $item_array);
$entity = $this->decorateAdvancedFields($quote, $transformed_items);
+ $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity);
$this->storage_array[] = $entity;
}
diff --git a/app/Export/CSV/RecurringInvoiceExport.php b/app/Export/CSV/RecurringInvoiceExport.php
index d30359510c80..3234b0674337 100644
--- a/app/Export/CSV/RecurringInvoiceExport.php
+++ b/app/Export/CSV/RecurringInvoiceExport.php
@@ -48,6 +48,8 @@ class RecurringInvoiceExport extends BaseExport
$this->input['report_keys'] = array_values($this->recurring_invoice_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = RecurringInvoice::query()
->withTrashed()
->with('client')
@@ -135,8 +137,8 @@ class RecurringInvoiceExport extends BaseExport
$entity['client'] = $invoice->client->present()->name();
}
- if (in_array('status_id', $this->input['report_keys'])) {
- $entity['status'] = $invoice->stringStatus($invoice->status_id);
+ if (in_array('recurring_invoice.status', $this->input['report_keys'])) {
+ $entity['recurring_invoice.status'] = $invoice->stringStatus($invoice->status_id);
}
if (in_array('project_id', $this->input['report_keys'])) {
diff --git a/app/Export/CSV/TaskExport.php b/app/Export/CSV/TaskExport.php
index 87834c6aee4f..b2eb6425c527 100644
--- a/app/Export/CSV/TaskExport.php
+++ b/app/Export/CSV/TaskExport.php
@@ -60,6 +60,8 @@ class TaskExport extends BaseExport
$this->input['report_keys'] = array_values($this->task_report_keys);
}
+ $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys']));
+
$query = Task::query()
->withTrashed()
->where('company_id', $this->company->id)
diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php
index d31a0019b2c9..9b15037f625c 100644
--- a/app/Factory/UserFactory.php
+++ b/app/Factory/UserFactory.php
@@ -27,7 +27,8 @@ class UserFactory
$user->last_login = now();
$user->failed_logins = 0;
$user->signature = '';
- $user->theme_id = 0;
+ $user->theme_id = 0;
+ $user->user_logged_in_notification = true;
return $user;
}
diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php
index 19ff1974c64d..ceb1417c7530 100644
--- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php
+++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php
@@ -140,7 +140,7 @@ class AccountTransformer implements AccountTransformerInterface
'id' => $account->id,
'account_type' => $account->CONTAINER,
// 'account_name' => $account->accountName,
- 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname,
+ 'account_name' => property_exists($account, 'accountName') ? $account->accountName : ($account->nickname ?? 'Unknown Account'),
'account_status' => $account_status,
'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 62d61a0a15bb..965e2dad1f13 100644
--- a/app/Helpers/Epc/EpcQrGenerator.php
+++ b/app/Helpers/Epc/EpcQrGenerator.php
@@ -15,6 +15,7 @@ use App\Models\Company;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Utils\Ninja;
+use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
@@ -58,10 +59,15 @@ class EpcQrGenerator
", "", $this->build_email->getSubject()))
diff --git a/app/Mail/VendorTemplateEmail.php b/app/Mail/VendorTemplateEmail.php
index 2ac0987b8045..bdfb818b0091 100644
--- a/app/Mail/VendorTemplateEmail.php
+++ b/app/Mail/VendorTemplateEmail.php
@@ -11,10 +11,11 @@
namespace App\Mail;
+use App\Utils\Ninja;
use App\Models\VendorContact;
-use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
-use App\Utils\VendorHtmlEngine;
use Illuminate\Mail\Mailable;
+use App\Utils\VendorHtmlEngine;
+use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
class VendorTemplateEmail extends Mailable
{
@@ -102,8 +103,19 @@ class VendorTemplateEmail extends Mailable
$this->from(config('mail.from.address'), $email_from_name);
if (strlen($settings->bcc_email) > 1) {
- $this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email)));
- }//remove whitespace if any has been inserted.
+
+ if (Ninja::isHosted()) {
+
+ if($this->company->account->isPaid()) {
+ $bccs = explode(',', str_replace(' ', '', $settings->bcc_email));
+ $this->bcc(array_slice($bccs, 0, 5));
+ }
+
+ } else {
+ $this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email)));
+ }
+
+ }
$this->subject($this->build_email->getSubject())
->text('email.template.text', [
diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php
index ac4b184d94e1..aed92c0eb8c9 100644
--- a/app/Models/Presenters/ClientPresenter.php
+++ b/app/Models/Presenters/ClientPresenter.php
@@ -23,7 +23,7 @@ class ClientPresenter extends EntityPresenter
*/
public function name()
{
- if ($this->entity->name) {
+ if (strlen($this->entity->name) > 1) {
return $this->entity->name;
}
diff --git a/app/Models/User.php b/app/Models/User.php
index 22e5130d0df1..aec9b0e787d5 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -73,7 +73,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
* @property int|null $deleted_at
* @property string|null $oauth_user_refresh_token
* @property string|null $last_confirmed_email_address
- * @property int $has_password
+ * @property bool $has_password
+ * @property bool $user_logged_in_notification
* @property Carbon|null $oauth_user_token_expiry
* @property string|null $sms_verification_code
* @property bool $verified_phone_number
@@ -140,6 +141,7 @@ class User extends Authenticatable implements MustVerifyEmail
*
*/
protected $fillable = [
+ 'user_logged_in_notification',
'first_name',
'last_name',
'email',
diff --git a/app/PaymentDrivers/Eway/CreditCard.php b/app/PaymentDrivers/Eway/CreditCard.php
index 11dbafdcfe2b..d4203d0eab9f 100644
--- a/app/PaymentDrivers/Eway/CreditCard.php
+++ b/app/PaymentDrivers/Eway/CreditCard.php
@@ -251,7 +251,7 @@ class CreditCard
$response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
- if ($response->TransactionStatus) {
+ if ($response->TransactionStatus ?? false) {
$this->logResponse($response, true);
$payment = $this->storePayment($response);
} else {
diff --git a/app/PaymentDrivers/PayTrace/CreditCard.php b/app/PaymentDrivers/PayTrace/CreditCard.php
index 720d5f0b02be..350fc6948b87 100644
--- a/app/PaymentDrivers/PayTrace/CreditCard.php
+++ b/app/PaymentDrivers/PayTrace/CreditCard.php
@@ -183,7 +183,7 @@ class CreditCard
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data);
- if ($response->success) {
+ if ($response->success ?? false) {
$this->paytrace->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace->payment_hash], SystemLog::TYPE_PAYTRACE);
return $this->processSuccessfulPayment($response);
diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php
index 5f33ea843e3a..b55e8857c169 100644
--- a/app/PaymentDrivers/PaytracePaymentDriver.php
+++ b/app/PaymentDrivers/PaytracePaymentDriver.php
@@ -198,7 +198,7 @@ class PaytracePaymentDriver extends BaseDriver
$auth_data = json_decode($response);
- if (! property_exists($auth_data, 'access_token')) {
+ if (!isset($auth_data) || ! property_exists($auth_data, 'access_token')) {
throw new SystemError('Error authenticating with PayTrace');
}
diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php
index c83b73fd4765..67d656e8c926 100644
--- a/app/PaymentDrivers/Square/SquareWebhook.php
+++ b/app/PaymentDrivers/Square/SquareWebhook.php
@@ -134,8 +134,8 @@ class SquareWebhook implements ShouldQueue
nlog("Searching by payment hash");
- $payment_hash_id = $apiResponse->getPayment()->getReferenceId() ?? false;
- $square_payment = $apiResponse->getPayment()->jsonSerialize();
+ $payment_hash_id = $apiResponse->getResult()->getPayment()->getReferenceId() ?? false;
+ $square_payment = $apiResponse->getResult()->getPayment()->jsonSerialize();
$payment_hash = PaymentHash::query()->where('hash', $payment_hash_id)->firstOrFail();
$payment_hash->data = array_merge((array) $payment_hash->data, (array)$square_payment);
diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php
index cebb8b7b6f79..c539d075d445 100644
--- a/app/Services/Email/Email.php
+++ b/app/Services/Email/Email.php
@@ -140,7 +140,7 @@ class Email implements ShouldQueue
$this->email_object->client_id ? $this->email_object->settings = $this->email_object->client->getMergedSettings() : $this->email_object->settings = $this->company->settings;
- $this->email_object->client_id ? nlog("client settings") : nlog("company settings ");
+ // $this->email_object->client_id ? nlog("client settings") : nlog("company settings ");
$this->email_object->whitelabel = $this->company->account->isPaid() ? true : false;
@@ -413,6 +413,14 @@ class Email implements ShouldQueue
if ($address_object->address == " ") {
return true;
}
+
+ if ($address_object->address == "") {
+ return true;
+ }
+
+ if($address_object->name == " " || $address_object->name == "") {
+ return true;
+ }
}
diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php
index 401060b6c7b4..1f78facaa1e5 100644
--- a/app/Services/Email/EmailDefaults.php
+++ b/app/Services/Email/EmailDefaults.php
@@ -255,8 +255,8 @@ class EmailDefaults
if (strlen($this->email->email_object->settings->bcc_email) > 1) {
if (Ninja::isHosted() && $this->email->company->account->isPaid()) {
- $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 2);
- } elseif (Ninja::isSelfHost()) {
+ $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 5);
+ } else {
$bccs = (explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)));
}
}
diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php
index 0585f888bd66..19a366fdc507 100644
--- a/app/Services/Invoice/AutoBillInvoice.php
+++ b/app/Services/Invoice/AutoBillInvoice.php
@@ -120,7 +120,7 @@ class AutoBillInvoice extends AbstractService
/* Build payment hash */
$payment_hash = PaymentHash::create([
- 'hash' => Str::random(64),
+ 'hash' => Str::random(32),
'data' => [
'amount_with_fee' => $amount + $fee,
'invoices' => [
diff --git a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php
index 6b7dbe884350..539e78f6e270 100644
--- a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php
+++ b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php
@@ -75,7 +75,7 @@ class ZugferdEInvoice extends AbstractService
} else {
$this->xrechnung->setDocumentBuyerReference($client->routing_id);
}
- if (!empty($client->shipping_address1)){
+ if (!empty($client->shipping_address1) && $client->shipping_country->exists()){
$this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state);
}
diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php
index 1e803faf10cd..8884dbdef622 100644
--- a/app/Services/Payment/DeletePayment.php
+++ b/app/Services/Payment/DeletePayment.php
@@ -109,7 +109,11 @@ class DeletePayment
if ($paymentable_invoice->balance == $paymentable_invoice->amount) {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
- } else {
+ }
+ elseif($paymentable_invoice->balance == 0){
+ $paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save();
+ }
+ else {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
}
} else {
diff --git a/app/Services/Tax/Providers/TaxProvider.php b/app/Services/Tax/Providers/TaxProvider.php
index 9598813b80f9..00b540bbc0a4 100644
--- a/app/Services/Tax/Providers/TaxProvider.php
+++ b/app/Services/Tax/Providers/TaxProvider.php
@@ -225,6 +225,8 @@ class TaxProvider
*/
private function configureEuTax(): self
{
+ throw new \Exception("No tax region defined for this country");
+
$this->provider = EuTax::class;
return $this;
diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php
index 711c9cfec5ba..4cfb7742b3e4 100644
--- a/app/Transformers/ClientTransformer.php
+++ b/app/Transformers/ClientTransformer.php
@@ -17,6 +17,7 @@ use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\CompanyLedger;
use App\Models\Document;
+use App\Models\GroupSetting;
use App\Models\SystemLog;
use App\Utils\Traits\MakesHash;
use League\Fractal\Resource\Collection;
@@ -42,6 +43,7 @@ class ClientTransformer extends EntityTransformer
'activities',
'ledger',
'system_logs',
+ 'group_settings',
];
/**
@@ -96,6 +98,16 @@ class ClientTransformer extends EntityTransformer
return $this->includeCollection($client->system_logs, $transformer, SystemLog::class);
}
+ public function includeGroupSettings(Client $client)
+ {
+ if (!$client->group_settings)
+ return null;
+
+ $transformer = new GroupSettingTransformer($this->serializer);
+
+ return $this->includeItem($client->group_settings, $transformer, GroupSetting::class);
+ }
+
/**
* @param Client $client
*
diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php
index ffe029fbb0fe..0971c7dc2b02 100644
--- a/app/Transformers/UserTransformer.php
+++ b/app/Transformers/UserTransformer.php
@@ -64,6 +64,7 @@ class UserTransformer extends EntityTransformer
'oauth_user_token' => empty($user->oauth_user_token) ? '' : '***',
'verified_phone_number' => (bool) $user->verified_phone_number,
'language_id' => (string) $user->language_id ?? '',
+ 'user_logged_in_notification' => (bool) $user->user_logged_in_notification,
];
}
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index f96d37be5df8..01d2d7d6fe43 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -670,16 +670,16 @@ class HtmlEngine
$data['$payment.transaction_reference'] = ['value' => '', 'label' => ctrans('texts.transaction_reference')];
- if ($this->entity_string == 'invoice' && $this->entity->payments()->exists()) {
+ if ($this->entity_string == 'invoice' && $this->entity->net_payments()->exists()) {
$payment_list = '
';
- foreach ($this->entity->payments as $payment) {
+ foreach ($this->entity->net_payments as $payment) {
$payment_list .= ctrans('texts.payment_subject') . ": " . $this->formatDate($payment->date, $this->client->date_format()) . " :: " . Number::formatMoney($payment->amount, $this->client) ." :: ". GatewayType::getAlias($payment->gateway_type_id) . "
";
}
$data['$payments'] = ['value' => $payment_list, 'label' => ctrans('texts.payments')];
- $payment = $this->entity->payments()->first();
+ $payment = $this->entity->net_payments()->first();
$data['$payment.custom1'] = ['value' => $payment->custom_value1, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')];
$data['$payment.custom2'] = ['value' => $payment->custom_value2, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')];
@@ -1014,17 +1014,17 @@ html {
*/
protected function generateEntityImagesMarkup()
{
- // if (!$this->client->getSetting('embed_documents') && !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
- // return '';
- // }
+ if (!$this->client->getSetting('embed_documents') || !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
+ return '';
+ }
$dom = new \DOMDocument('1.0', 'UTF-8');
$container = $dom->createElement('div');
$container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;');
- foreach ($this->entity->documents as $document) {
- if (!$document->isImage()) {
+ foreach ($this->entity->documents()->where('is_public',true)->get() as $document) {
+ if (!$document->isImage()) {
continue;
}
diff --git a/app/Utils/Traits/MakesHash.php b/app/Utils/Traits/MakesHash.php
index 63c151b93530..dc8d6d158a40 100644
--- a/app/Utils/Traits/MakesHash.php
+++ b/app/Utils/Traits/MakesHash.php
@@ -62,8 +62,27 @@ trait MakesHash
return $hashids->encode($value);
}
- public function decodePrimaryKey($value)
+ public function decodePrimaryKey($value, $return_string_failure = false)
{
+
+ try {
+ $hashids = new Hashids(config('ninja.hash_salt'), 10);
+
+ $decoded_array = $hashids->decode($value);
+
+ if(isset($decoded_array[0]) ?? false) {
+ return $decoded_array[0];
+ } elseif($return_string_failure) {
+ return "Invalid Primary Key";
+ } else {
+ throw new \Exception('Invalid Primary Key');
+ }
+
+ } catch (\Exception $e) {
+ return response()->json(['error'=>'Invalid primary key'], 400);
+ }
+
+ /*
try {
$hashids = new Hashids(config('ninja.hash_salt'), 10);
@@ -77,6 +96,7 @@ trait MakesHash
} catch (\Exception $e) {
return response()->json(['error'=>'Invalid primary key'], 400);
}
+ */
}
public function transformKeys($keys)
diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php
index eb1f595584e5..7bd05d75924c 100644
--- a/app/Utils/VendorHtmlEngine.php
+++ b/app/Utils/VendorHtmlEngine.php
@@ -12,18 +12,19 @@
namespace App\Utils;
-use App\Models\Country;
-use App\Models\CreditInvitation;
-use App\Models\InvoiceInvitation;
-use App\Models\PurchaseOrderInvitation;
-use App\Models\QuoteInvitation;
-use App\Models\RecurringInvoiceInvitation;
-use App\Utils\Traits\AppSetup;
-use App\Utils\Traits\DesignCalculator;
-use App\Utils\Traits\MakesDates;
use Exception;
+use App\Models\Account;
+use App\Models\Country;
+use App\Utils\Traits\AppSetup;
+use App\Models\QuoteInvitation;
+use App\Models\CreditInvitation;
+use App\Utils\Traits\MakesDates;
+use App\Models\InvoiceInvitation;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
+use App\Utils\Traits\DesignCalculator;
+use App\Models\PurchaseOrderInvitation;
+use App\Models\RecurringInvoiceInvitation;
/**
* Note the premise used here is that any currencies will be formatted back to the company currency and not
@@ -775,31 +776,37 @@ html {
*/
protected function generateEntityImagesMarkup()
{
- if ($this->company->getSetting('embed_documents') === false) {
+
+ if (!$this->vendor->getSetting('embed_documents') || !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
return '';
}
$dom = new \DOMDocument('1.0', 'UTF-8');
$container = $dom->createElement('div');
- $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);');
-
- foreach ($this->entity->documents as $document) {
+ $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;');
+
+ foreach ($this->entity->documents()->where('is_public',true)->get() as $document) {
if (!$document->isImage()) {
continue;
}
$image = $dom->createElement('img');
- $image->setAttribute('src', $document->generateUrl());
- $image->setAttribute('style', 'max-height: 100px; margin-top: 20px;');
+ $image->setAttribute('src', "data:image/png;base64,".base64_encode($document->getFile()));
+ $image->setAttribute('style', 'max-width: 50%; margin-top: 20px;');
$container->appendChild($image);
}
$dom->appendChild($container);
- return $dom->saveHTML();
+ $html = $dom->saveHTML();
+
+ $dom = null;
+
+ return $html;
+
}
/**
diff --git a/config/ninja.php b/config/ninja.php
index 7ef548ab7efa..d5f584da4e29 100644
--- a/config/ninja.php
+++ b/config/ninja.php
@@ -15,8 +15,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
- 'app_version' => env('APP_VERSION','5.7.30'),
- 'app_tag' => env('APP_TAG','5.7.30'),
+ 'app_version' => env('APP_VERSION','5.7.33'),
+ 'app_tag' => env('APP_TAG','5.7.33'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
diff --git a/database/factories/RecurringInvoiceInvitationFactory.php b/database/factories/RecurringInvoiceInvitationFactory.php
new file mode 100644
index 000000000000..fe7db151f11d
--- /dev/null
+++ b/database/factories/RecurringInvoiceInvitationFactory.php
@@ -0,0 +1,30 @@
+ Str::random(40),
+ ];
+ }
+}
diff --git a/database/migrations/2023_10_18_061415_add_user_notification_suppression.php b/database/migrations/2023_10_18_061415_add_user_notification_suppression.php
new file mode 100644
index 000000000000..4c01624a02ac
--- /dev/null
+++ b/database/migrations/2023_10_18_061415_add_user_notification_suppression.php
@@ -0,0 +1,43 @@
+boolean('user_logged_in_notification')->default(true);
+ });
+
+
+ $cur = Currency::find(120);
+
+ if(!$cur) {
+ $cur = new \App\Models\Currency();
+ $cur->id = 120;
+ $cur->code = 'TOP';
+ $cur->name = "Tongan Pa'anga";
+ $cur->symbol = 'T$';
+ $cur->thousand_separator = ',';
+ $cur->decimal_separator = '.';
+ $cur->precision = 2;
+ $cur->save();
+ }
+
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ //
+ }
+};
diff --git a/database/seeders/CurrenciesSeeder.php b/database/seeders/CurrenciesSeeder.php
index b2c8b35af751..e6976622ee86 100644
--- a/database/seeders/CurrenciesSeeder.php
+++ b/database/seeders/CurrenciesSeeder.php
@@ -142,6 +142,7 @@ class CurrenciesSeeder extends Seeder
['id' => 117, 'name' => 'Gold Troy Ounce', 'code' => 'XAU', 'symbol' => 'XAU', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 118, 'name' => 'Nicaraguan Córdoba', 'code' => 'NIO', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 119, 'name' => 'Malagasy ariary', 'code' => 'MGA', 'symbol' => 'Ar', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
+ ['id' => 120, 'name' => "Tongan Pa anga", 'code' => 'TOP', 'symbol' => 'T$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {
diff --git a/lang/en/texts.php b/lang/en/texts.php
index 49eb412a6202..48cfe30beb3a 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -2402,6 +2402,9 @@ $LANG = array(
'currency_libyan_dinar' => 'Libyan Dinar',
'currency_silver_troy_ounce' => 'Silver Troy Ounce',
'currency_gold_troy_ounce' => 'Gold Troy Ounce',
+ 'currency_nicaraguan_córdoba' => 'Nicaraguan Córdoba',
+ 'currency_malagasy_ariary' => 'Malagasy ariary',
+ "currency_tongan_pa_anga" => "Tongan Pa'anga",
'review_app_help' => 'We hope you\'re enjoying using the app.
If you\'d consider :link we\'d greatly appreciate it!',
'writing_a_review' => 'writing a review',
@@ -3679,9 +3682,9 @@ $LANG = array(
'send_date' => 'Send Date',
'auto_bill_on' => 'Auto Bill On',
'minimum_under_payment_amount' => 'Minimum Under Payment Amount',
- 'allow_over_payment' => 'Allow Over Payment',
+ 'allow_over_payment' => 'Allow Overpayment',
'allow_over_payment_help' => 'Support paying extra to accept tips',
- 'allow_under_payment' => 'Allow Under Payment',
+ 'allow_under_payment' => 'Allow Underpayment',
'allow_under_payment_help' => 'Support paying at minimum the partial/deposit amount',
'test_mode' => 'Test Mode',
'calculated_rate' => 'Calculated Rate',
@@ -3978,8 +3981,8 @@ $LANG = array(
'account_balance' => 'Account Balance',
'thanks' => 'Thanks',
'minimum_required_payment' => 'Minimum required payment is :amount',
- 'under_payments_disabled' => 'Company doesn\'t support under payments.',
- 'over_payments_disabled' => 'Company doesn\'t support over payments.',
+ 'under_payments_disabled' => 'Company doesn\'t support underpayments.',
+ 'over_payments_disabled' => 'Company doesn\'t support overpayments.',
'saved_at' => 'Saved at :time',
'credit_payment' => 'Credit applied to Invoice :invoice_number',
'credit_subject' => 'New credit :number from :account',
@@ -4654,8 +4657,8 @@ $LANG = array(
'search_purchase_order' => 'Search Purchase Order',
'search_purchase_orders' => 'Search Purchase Orders',
'login_url' => 'Login URL',
- 'enable_applying_payments' => 'Enable Applying Payments',
- 'enable_applying_payments_help' => 'Support separately creating and applying payments',
+ 'enable_applying_payments' => 'Manual Overpayments',
+ 'enable_applying_payments_help' => 'Support adding an overpayment amount manually on a payment',
'stock_quantity' => 'Stock Quantity',
'notification_threshold' => 'Notification Threshold',
'track_inventory' => 'Track Inventory',
@@ -5180,6 +5183,8 @@ $LANG = array(
'upcoming' => 'Upcoming',
'client_contact' => 'Client Contact',
'uncategorized' => 'Uncategorized',
+ 'login_notification' => 'Login Notification',
+ 'login_notification_help' => 'Sends an email notifying that a login has taken place.'
);
return $LANG;
diff --git a/resources/views/portal/ninja2020/invoices/payment.blade.php b/resources/views/portal/ninja2020/invoices/payment.blade.php
index 8d5df833d326..a0d786ff4610 100644
--- a/resources/views/portal/ninja2020/invoices/payment.blade.php
+++ b/resources/views/portal/ninja2020/invoices/payment.blade.php
@@ -64,7 +64,7 @@
{{ ctrans('texts.public_notes') }}