diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 47be8ba11873..ac14c8a54574 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -699,7 +699,7 @@ ORDER BY clients.id; invoices ON clients.id=invoices.client_id WHERE invoices.is_deleted = false - AND invoices.status_id > 1 + AND invoices.status_id IN (2,3,4) GROUP BY clients.id HAVING invoice_balance != clients.balance ORDER BY clients.id; @@ -722,21 +722,16 @@ ORDER BY clients.id; { $client = (array)$client; - // $credit_balance = Credit::withTrashed()->where('is_deleted', 0) - // ->where('client_id', $client['client_id']) - // ->where('status_id', '>', 1)->sum('balance'); - - // $invoice_balance = $client['invoice_balance'] - $credit_balance; $invoice_balance = $client['invoice_balance']; - $ledger = CompanyLedger::where('client_id', $client['client_id'])->orderBy('id', 'DESC')->first(); + // $ledger = CompanyLedger::where('client_id', $client['client_id'])->orderBy('id', 'DESC')->first(); - if ($ledger && (string) $invoice_balance != (string) $client['client_balance']) { + if ((string) $invoice_balance != (string) $client['client_balance']) { $this->wrong_paid_to_dates++; $client_object = Client::withTrashed()->find($client['client_id']); - $this->logMessage($client_object->present()->name.' - '.$client_object->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client['client_balance'], '0'). " Ledger balance = {$ledger->balance}"); + $this->logMessage($client_object->present()->name.' - '.$client_object->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client['client_balance'], '0')); if($this->option('ledger_balance')){ @@ -745,10 +740,10 @@ ORDER BY clients.id; $client_object->balance = $invoice_balance; $client_object->save(); - $ledger->adjustment = $invoice_balance; - $ledger->balance = $invoice_balance; - $ledger->notes = 'Ledger Adjustment'; - $ledger->save(); + // $ledger->adjustment = $invoice_balance; + // $ledger->balance = $invoice_balance; + // $ledger->notes = 'Ledger Adjustment'; + // $ledger->save(); } @@ -799,7 +794,7 @@ ORDER BY clients.id; ON invoices.client_id = clients.id WHERE invoices.is_deleted = 0 AND clients.is_deleted = 0 - AND invoices.status_id != 1 + AND invoices.status_id IN (2,3,4) GROUP BY clients.id HAVING(invoices_balance != clients.balance) ORDER BY clients.id; @@ -819,7 +814,7 @@ ORDER BY clients.id; { $client = Client::withTrashed()->find($_client->id); - $invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance'); + $invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3,4])->sum('balance'); $ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first(); @@ -855,7 +850,7 @@ ORDER BY clients.id; $this->wrong_paid_to_dates = 0; foreach (Client::where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) { - $invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance'); + $invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3,4])->sum('balance'); $ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first(); if ($ledger && number_format($ledger->balance, 4) != number_format($client->balance, 4)) { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a279d7824096..c27713e47c94 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,6 +15,7 @@ use App\Jobs\Cron\AutoBillCron; use App\Jobs\Cron\RecurringExpensesCron; use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Cron\SubscriptionCron; +use App\Jobs\Ledger\LedgerBalanceUpdate; use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\CompanySizeCheck; use App\Jobs\Util\DiskCleanup; @@ -54,6 +55,8 @@ class Kernel extends ConsoleKernel $schedule->job(new ReminderJob)->hourly()->withoutOverlapping(); + $schedule->job(new LedgerBalanceUpdate)->everyFiveMinutes()->withoutOverlapping(); + $schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping(); $schedule->job(new UpdateExchangeRates)->daily()->withoutOverlapping(); @@ -84,9 +87,9 @@ class Kernel extends ConsoleKernel $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping(); - $schedule->command('ninja:check-data --database=db-ninja-01')->daily('00:50')->withoutOverlapping(); + $schedule->command('ninja:check-data --database=db-ninja-01')->daily('01:00')->withoutOverlapping(); - $schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('00:55')->withoutOverlapping(); + $schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('01:05')->withoutOverlapping(); $schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping(); diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 3188ad2a8557..b82d43a32134 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -72,6 +72,12 @@ class BaseExport foreach($this->input['report_keys'] as $value){ $key = array_search ($value, $this->entity_keys); + $key = str_replace("item.", "", $key); + $key = str_replace("invoice.", "", $key); + $key = str_replace("client.", "", $key); + $key = str_replace("contact.", "", $key); + + $header[] = ctrans("texts.{$key}"); } diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index da4f0011c404..4193e2fa7b58 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -78,6 +78,46 @@ class ClientExport extends BaseExport 'email' => 'contact.email', ]; + protected array $all_keys = [ + 'client.address1', + 'client.address2', + 'client.balance', + 'client.city', + 'client.country_id', + 'client.credit_balance', + 'client.custom_value1', + 'client.custom_value2', + 'client.custom_value3', + 'client.custom_value4', + 'client.id_number', + 'client.industry_id', + 'client.last_login', + 'client.name', + 'client.number', + 'client.paid_to_date', + 'client.phone', + 'client.postal_code', + 'client.private_notes', + 'client.public_notes', + 'client.shipping_address1', + 'client.shipping_address2', + 'client.shipping_city', + 'client.shipping_country_id', + 'client.shipping_postal_code', + 'client.shipping_state', + 'client.state', + 'client.vat_number', + 'client.website', + 'client.currency', + 'contact.first_name', + 'contact.last_name', + 'contact.phone', + 'contact.custom_value1', + 'contact.custom_value2', + 'contact.custom_value3', + 'contact.custom_value4', + 'contact.email', + ]; private array $decorate_keys = [ 'client.country_id', 'client.shipping_country_id', @@ -105,6 +145,9 @@ class ClientExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); @@ -143,10 +186,10 @@ class ClientExport extends BaseExport $parts = explode(".",$key); $entity[$parts[1]] = ""; - if($parts[0] == 'client') { + if($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) { $entity[$parts[1]] = $transformed_client[$parts[1]]; } - elseif($parts[0] == 'contact') { + elseif($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_client)) { $entity[$parts[1]] = $transformed_contact[$parts[1]]; } @@ -159,16 +202,16 @@ class ClientExport extends BaseExport private function decorateAdvancedFields(Client $client, array $entity) :array { - if(array_key_exists('country_id', $entity)) + if(in_array('country_id', $this->input['report_keys'])) $entity['country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : ""; - if(array_key_exists('shipping_country_id', $entity)) + if(in_array('shipping_country_id', $this->input['report_keys'])) $entity['shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : ""; - if(array_key_exists('currency', $entity)) - $entity['currency'] = $client->currency()->code; + if(in_array('currency', $this->input['report_keys'])) + $entity['currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code; - if(array_key_exists('industry_id', $entity)) + if(in_array('industry_id', $this->input['report_keys'])) $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : ""; return $entity; diff --git a/app/Export/CSV/ContactExport.php b/app/Export/CSV/ContactExport.php index d3f95822b998..8e5030b4b551 100644 --- a/app/Export/CSV/ContactExport.php +++ b/app/Export/CSV/ContactExport.php @@ -74,6 +74,49 @@ class ContactExport extends BaseExport 'email' => 'contact.email', ]; + + protected array $all_keys = [ + 'client.address1', + 'client.address2', + 'client.balance', + 'client.city', + 'client.country_id', + 'client.credit_balance', + 'client.custom_value1', + 'client.custom_value2', + 'client.custom_value3', + 'client.custom_value4', + 'client.id_number', + 'client.industry_id', + 'client.last_login', + 'client.name', + 'client.number', + 'client.paid_to_date', + 'client.phone', + 'client.postal_code', + 'client.private_notes', + 'client.public_notes', + 'client.shipping_address1', + 'client.shipping_address2', + 'client.shipping_city', + 'client.shipping_country_id', + 'client.shipping_postal_code', + 'client.shipping_state', + 'client.state', + 'client.vat_number', + 'client.website', + 'client.currency', + 'contact.first_name', + 'contact.last_name', + 'contact.phone', + 'contact.custom_value1', + 'contact.custom_value2', + 'contact.custom_value3', + 'contact.custom_value4', + 'contact.email', + ]; + + private array $decorate_keys = [ 'client.country_id', 'client.shipping_country_id', @@ -101,6 +144,9 @@ class ContactExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); @@ -115,7 +161,6 @@ class ContactExport extends BaseExport }); - return $this->csv->toString(); } diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index 13ab31ca71da..72dbebc4f470 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -68,6 +68,45 @@ class CreditExport extends BaseExport 'currency' => 'currency' ]; + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'country_id', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'invoice_id', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + 'currency' + ]; + + private array $decorate_keys = [ 'country', 'client', @@ -97,6 +136,9 @@ class CreditExport extends BaseExport //insert the header $this->csv->insertOne($this->buildHeader()); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + $query = Credit::query() ->withTrashed() ->with('client')->where('company_id', $this->company->id) diff --git a/app/Export/CSV/DocumentExport.php b/app/Export/CSV/DocumentExport.php index 74bb8149d571..a782bc1151b1 100644 --- a/app/Export/CSV/DocumentExport.php +++ b/app/Export/CSV/DocumentExport.php @@ -39,6 +39,14 @@ class DocumentExport extends BaseExport 'created_at' => 'created_at', ]; + protected array $all_keys = [ + 'record_type', + 'record_name', + 'name', + 'type', + 'created_at', + ]; + private array $decorate_keys = [ ]; @@ -62,6 +70,9 @@ class DocumentExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/ExpenseExport.php b/app/Export/CSV/ExpenseExport.php index c7b0bb36179a..6d202556ade9 100644 --- a/app/Export/CSV/ExpenseExport.php +++ b/app/Export/CSV/ExpenseExport.php @@ -63,6 +63,40 @@ class ExpenseExport extends BaseExport 'invoice' => 'invoice_id', ]; + protected array $all_keys = [ + 'amount', + 'category_id', + 'client_id', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'currency_id', + 'date', + 'exchange_rate', + 'foreign_amount', + 'invoice_currency_id', + 'payment_date', + 'number', + 'payment_type_id', + 'private_notes', + 'project_id', + 'public_notes', + 'tax_amount1', + 'tax_amount2', + 'tax_amount3', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'transaction_reference', + 'vendor_id', + 'invoice_id', + ]; + + private array $decorate_keys = [ 'client', 'currency', @@ -92,6 +126,9 @@ class ExpenseExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 9ee104dee8d6..79af4a7cf1e9 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -63,13 +63,50 @@ class InvoiceExport extends BaseExport 'tax_rate3' => 'tax_rate3', 'terms' => 'terms', 'total_taxes' => 'total_taxes', - 'currency' => 'client_id' + 'currency_id' => 'currency_id' + ]; + + + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + 'currency_id', ]; private array $decorate_keys = [ 'country', 'client', - 'currency', + 'currency_id', 'status', ]; @@ -92,6 +129,9 @@ class InvoiceExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); @@ -122,7 +162,8 @@ class InvoiceExport extends BaseExport foreach(array_values($this->input['report_keys']) as $key){ - $entity[$key] = $transformed_invoice[$key]; + if(array_key_exists($key, $transformed_invoice)) + $entity[$key] = $transformed_invoice[$key]; } return $this->decorateAdvancedFields($invoice, $entity); @@ -131,13 +172,13 @@ class InvoiceExport extends BaseExport private function decorateAdvancedFields(Invoice $invoice, array $entity) :array { - if(array_key_exists('currency', $entity)) - $entity['currency'] = $invoice->client->currency()->code; + if(in_array('currency_id',$this->input['report_keys'])) + $entity['currency_id'] = $invoice->client->currency()->code ?: $invoice->company->currency()->code; - if(array_key_exists('client_id', $entity)) + if(in_array('client_id',$this->input['report_keys'])) $entity['client_id'] = $invoice->client->present()->name(); - if(array_key_exists('status_id', $entity)) + if(in_array('status_id',$this->input['report_keys'])) $entity['status_id'] = $invoice->stringStatus($invoice->status_id); return $entity; diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 6c060788a9d2..a70cf3328065 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -85,6 +85,61 @@ class InvoiceItemExport extends BaseExport 'invoice4' => 'item.custom_value4', ]; + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + // 'currency_id', + 'item.quantity', + 'item.cost', + 'item.product_key', + 'item.product_cost', + 'item.notes', + 'item.discount', + 'item.is_amount_discount', + 'item.tax_rate1', + 'item.tax_rate2', + 'item.tax_rate3', + 'item.tax_name1', + 'item.tax_name2', + 'item.tax_name3', + 'item.line_total', + 'item.gross_line_total', + 'item.custom_value1', + 'item.custom_value2', + 'item.custom_value3', + 'item.custom_value4', + ]; + private array $decorate_keys = [ 'client', 'currency', @@ -109,6 +164,9 @@ class InvoiceItemExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = ksort($this->all_keys); + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 6638a21eec81..86a8d1be9c67 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -54,6 +54,28 @@ class PaymentExport extends BaseExport 'vendor' => 'vendor_id', ]; + protected array $all_keys = [ + 'amount', + 'applied', + 'client_id', + 'currency_id', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'exchange_currency_id', + 'gateway_type_id', + 'number', + 'private_notes', + 'project_id', + 'refunded', + 'status_id', + 'transaction_reference', + 'type_id', + 'vendor_id', + ]; + private array $decorate_keys = [ 'vendor', 'status', @@ -83,6 +105,9 @@ class PaymentExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/ProductExport.php b/app/Export/CSV/ProductExport.php index bf4c6b5cf1cd..bf02fd1acd63 100644 --- a/app/Export/CSV/ProductExport.php +++ b/app/Export/CSV/ProductExport.php @@ -52,6 +52,26 @@ class ProductExport extends BaseExport 'tax_name3' => 'tax_name3', ]; + protected array $all_keys = [ + 'project_id', + 'vendor_id', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'product_key', + 'notes', + 'cost', + 'price', + 'quantity', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'tax_name1', + 'tax_name2', + 'tax_name3', + ]; + private array $decorate_keys = [ 'vendor', 'project', @@ -76,6 +96,9 @@ class ProductExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/QuoteExport.php b/app/Export/CSV/QuoteExport.php index 71eaec9e593c..f4589e1b15e5 100644 --- a/app/Export/CSV/QuoteExport.php +++ b/app/Export/CSV/QuoteExport.php @@ -67,6 +67,44 @@ class QuoteExport extends BaseExport 'invoice' => 'invoice_id', ]; + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + 'client_id', + 'invoice_id', + ]; + + private array $decorate_keys = [ 'client', 'currency', @@ -92,6 +130,9 @@ class QuoteExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index aa4f15b65579..bb6e499394f0 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -85,6 +85,61 @@ class QuoteItemExport extends BaseExport 'invoice4' => 'item.custom_value4', ]; + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + 'currency_id', + 'item.quantity', + 'item.cost', + 'item.product_key', + 'item.product_cost', + 'item.notes', + 'item.discount', + 'item.is_amount_discount', + 'item.tax_rate1', + 'item.tax_rate2', + 'item.tax_rate3', + 'item.tax_name1', + 'item.tax_name2', + 'item.tax_name3', + 'item.line_total', + 'item.gross_line_total', + 'item.custom_value1', + 'item.custom_value2', + 'item.custom_value3', + 'item.custom_value4', + ]; + private array $decorate_keys = [ 'client', 'currency', @@ -109,6 +164,9 @@ class QuoteItemExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/RecurringInvoiceExport.php b/app/Export/CSV/RecurringInvoiceExport.php index 09e5b1ff142b..81738e80d5d5 100644 --- a/app/Export/CSV/RecurringInvoiceExport.php +++ b/app/Export/CSV/RecurringInvoiceExport.php @@ -68,6 +68,44 @@ class RecurringInvoiceExport extends BaseExport 'project' => 'project_id', ]; + protected array $all_keys = [ + 'amount', + 'balance', + 'client_id', + 'custom_surcharge1', + 'custom_surcharge2', + 'custom_surcharge3', + 'custom_surcharge4', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'date', + 'discount', + 'due_date', + 'exchange_rate', + 'footer', + 'number', + 'paid_to_date', + 'partial', + 'partial_due_date', + 'po_number', + 'private_notes', + 'public_notes', + 'status_id', + 'tax_name1', + 'tax_name2', + 'tax_name3', + 'tax_rate1', + 'tax_rate2', + 'tax_rate3', + 'terms', + 'total_taxes', + 'client_id', + 'vendor_id', + 'project_id', + ]; + private array $decorate_keys = [ 'country', 'client', @@ -96,6 +134,9 @@ class RecurringInvoiceExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Export/CSV/TaskExport.php b/app/Export/CSV/TaskExport.php index fc95b5f8f498..5ccdfa5d54d0 100644 --- a/app/Export/CSV/TaskExport.php +++ b/app/Export/CSV/TaskExport.php @@ -52,6 +52,23 @@ class TaskExport extends BaseExport 'client' => 'client_id', ]; + protected array $all_keys = [ + 'start_date', + 'end_date', + 'duration', + 'rate', + 'number', + 'description', + 'custom_value1', + 'custom_value2', + 'custom_value3', + 'custom_value4', + 'status_id', + 'project_id', + 'invoice_id', + 'client_id', + ]; + private array $decorate_keys = [ 'status', 'project', @@ -83,6 +100,10 @@ class TaskExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); + + if(count($this->input['report_keys']) == 0) + $this->input['report_keys'] = $this->all_keys; + //insert the header $this->csv->insertOne($this->buildHeader()); diff --git a/app/Factory/CompanyFactory.php b/app/Factory/CompanyFactory.php index c3ceb8987e68..d78508f9ffd0 100644 --- a/app/Factory/CompanyFactory.php +++ b/app/Factory/CompanyFactory.php @@ -45,7 +45,7 @@ class CompanyFactory $company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095 $company->default_password_timeout = 1800000; - $company->markdown_email_enabled = true; + $company->markdown_email_enabled = false; return $company; } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index a4ea5f9b35a7..8b60aa001d2c 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -35,6 +35,7 @@ use App\Utils\Traits\UserSessionAttributes; use App\Utils\Traits\User\LoginCache; use App\Utils\TruthSource; use Google_Client; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -210,42 +211,43 @@ class LoginController extends BaseController $user = $user->fresh(); } - $user->setCompany($user->account->default_company); + // $user->setCompany($user->account->default_company); + // $this->setLoginCache($user); - $this->setLoginCache($user); + // $cu = CompanyUser::query() + // ->where('user_id', auth()->user()->id); - $cu = CompanyUser::query() - ->where('user_id', auth()->user()->id); + $cu = $this->hydrateCompanyUser(); if($cu->count() == 0) return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); - $truth = app()->make(TruthSource::class); + // $truth = app()->make(TruthSource::class); - $truth->setCompanyUser($cu->first()); - $truth->setUser(auth()->user()); - $truth->setCompany($user->account->default_company); + // $truth->setCompanyUser($cu->first()); + // $truth->setUser(auth()->user()); + // $truth->setCompany($user->account->default_company); - if(!$cu->exists()) - return response()->json(['message' => 'User not linked to any companies'], 403); + // if(!$cu->exists()) + // return response()->json(['message' => 'User not linked to any companies'], 403); - /* Ensure the user has a valid token */ - if($user->company_users()->count() != $user->tokens()->count()) - { + // /* Ensure the user has a valid token */ + // if($user->company_users()->count() != $user->tokens()->count()) + // { - $user->companies->each(function($company) use($user, $request){ + // $user->companies->each(function($company) use($user, $request){ - if(!CompanyToken::where('user_id', $user->id)->where('company_id', $company->id)->exists()){ + // if(!CompanyToken::where('user_id', $user->id)->where('company_id', $company->id)->exists()){ - CreateCompanyToken::dispatchNow($company, $user, $request->server('HTTP_USER_AGENT')); + // CreateCompanyToken::dispatchNow($company, $user, $request->server('HTTP_USER_AGENT')); - } + // } - }); + // }); - } + // } - $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $user->account->default_company->id)->first()); + // $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $user->account->default_company->id)->first()); /*On the hosted platform, only owners can login for free/pro accounts*/ if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient()) @@ -367,6 +369,50 @@ class LoginController extends BaseController ->header('X-Api-Version', config('ninja.minimum_client_version')); } + private function hydrateCompanyUser() :Builder + { + + $cu = CompanyUser::query()->where('user_id', auth()->user()->id); + + if(CompanyUser::query()->where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company_id)->exists()) + $set_company = auth()->user()->account->default_company; + else{ + $set_company = $cu->first()->company; + } + + auth()->user()->setCompany($set_company); + + $this->setLoginCache(auth()->user()); + + $truth = app()->make(TruthSource::class); + $truth->setCompanyUser($cu->first()); + $truth->setUser(auth()->user()); + $truth->setCompany($set_company); + + if($cu->count() == 0) + return $cu; + + if(auth()->user()->company_users()->count() != auth()->user()->tokens()->count()) + { + + auth()->user()->companies->each(function($company){ + + if(!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()){ + + CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); + + } + + }); + + } + + $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $set_company->id)->first()); + + return $cu; + + } + private function handleGoogleOauth() { $user = false; @@ -377,7 +423,6 @@ class LoginController extends BaseController if (is_array($user)) { - // $query = [ 'oauth_user_id' => $google->harvestSubField($user), 'oauth_provider_id'=> 'google', @@ -389,38 +434,48 @@ class LoginController extends BaseController return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); Auth::login($existing_user, true); - $existing_user->setCompany($existing_user->account->default_company); - $this->setLoginCache($existing_user); + // $cu = CompanyUser::query() + // ->where('user_id', auth()->user()->id) + // ->where('company_id', $existing_user->account->default_company_id); - $cu = CompanyUser::query() - ->where('user_id', auth()->user()->id); + // if($cu->exists()) + // $set_company = $existing_user->account->default_company; + // else{ + // $cu = CompanyUser::query()->where('user_id', auth()->user()->id); + // $set_company = $cu->company; + // } + + // $existing_user->setCompany($set_company); + + // $this->setLoginCache($existing_user); + + $cu = $this->hydrateCompanyUser(); if($cu->count() == 0) return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); - $truth = app()->make(TruthSource::class); - $truth->setCompanyUser($cu->first()); - $truth->setUser($existing_user); - $truth->setCompany($existing_user->account->default_company); + // $truth = app()->make(TruthSource::class); + // $truth->setCompanyUser($cu->first()); + // $truth->setUser($existing_user); + // $truth->setCompany($set_company); + + // if($existing_user->company_users()->count() != $existing_user->tokens()->count()) + // { + + // $existing_user->companies->each(function($company) use($existing_user){ + + // if(!CompanyToken::where('user_id', $existing_user->id)->where('company_id', $company->id)->exists()){ + + // CreateCompanyToken::dispatchNow($company, $existing_user, "Google_O_Auth"); + + // } + + // }); + + // } - - if($existing_user->company_users()->count() != $existing_user->tokens()->count()) - { - - $existing_user->companies->each(function($company) use($existing_user){ - - if(!CompanyToken::where('user_id', $existing_user->id)->where('company_id', $company->id)->exists()){ - - CreateCompanyToken::dispatchNow($company, $existing_user, "Google_O_Auth"); - - } - - }); - - } - - $truth->setCompanyToken(CompanyToken::where('user_id', $existing_user->id)->where('company_id', $existing_user->account->default_company->id)->first()); + // $truth->setCompanyToken(CompanyToken::where('user_id', $existing_user->id)->where('company_id', $set_company->id)->first()); if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient()) @@ -437,45 +492,45 @@ class LoginController extends BaseController return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); Auth::login($existing_login_user, true); - $existing_login_user->setCompany($existing_login_user->account->default_company); - $this->setLoginCache($existing_login_user); + // $existing_login_user->setCompany($existing_login_user->account->default_company); + // $this->setLoginCache($existing_login_user); auth()->user()->update([ 'oauth_user_id' => $google->harvestSubField($user), 'oauth_provider_id'=> 'google', ]); - - $cu = CompanyUser::query() - ->where('user_id', auth()->user()->id); + + $cu = $this->hydrateCompanyUser(); + + // $cu = CompanyUser::query() + // ->where('user_id', auth()->user()->id); if($cu->count() == 0) return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); - $truth = app()->make(TruthSource::class); - $truth->setCompanyUser($cu->first()); - $truth->setUser($existing_login_user); - $truth->setCompany($existing_login_user->account->default_company); + // $truth = app()->make(TruthSource::class); + // $truth->setCompanyUser($cu->first()); + // $truth->setUser($existing_login_user); + // $truth->setCompany($existing_login_user->account->default_company); - if($existing_login_user->company_users()->count() != $existing_login_user->tokens()->count()) - { + // if($existing_login_user->company_users()->count() != $existing_login_user->tokens()->count()) + // { + + // $existing_login_user->companies->each(function($company) use($existing_login_user){ + + // if(!CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $company->id)->exists()){ + + // CreateCompanyToken::dispatchNow($company, $existing_login_user, "Google_O_Auth"); - $existing_login_user->companies->each(function($company) use($existing_login_user){ - - if(!CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $company->id)->exists()){ - - CreateCompanyToken::dispatchNow($company, $existing_login_user, "Google_O_Auth"); - - } - - }); - - } - - $truth->setCompanyToken(CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $existing_login_user->account->default_company->id)->first()); - + // } + + // }); + + // } + // $truth->setCompanyToken(CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $existing_login_user->account->default_company->id)->first()); if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient()) return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); @@ -495,43 +550,45 @@ class LoginController extends BaseController return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); Auth::login($existing_login_user, true); - $existing_login_user->setCompany($existing_login_user->account->default_company); - $this->setLoginCache($existing_login_user); + // $existing_login_user->setCompany($existing_login_user->account->default_company); + // $this->setLoginCache($existing_login_user); auth()->user()->update([ 'oauth_user_id' => $google->harvestSubField($user), 'oauth_provider_id'=> 'google', ]); - - $cu = CompanyUser::query() - ->where('user_id', auth()->user()->id); + + $cu = $this->hydrateCompanyUser(); + + // $cu = CompanyUser::query() + // ->where('user_id', auth()->user()->id); if($cu->count() == 0) return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); - $truth = app()->make(TruthSource::class); - $truth->setCompanyUser($cu->first()); - $truth->setUser($existing_login_user); - $truth->setCompany($existing_login_user->account->default_company); + // $truth = app()->make(TruthSource::class); + // $truth->setCompanyUser($cu->first()); + // $truth->setUser($existing_login_user); + // $truth->setCompany($existing_login_user->account->default_company); - if($existing_login_user->company_users()->count() != $existing_login_user->tokens()->count()) - { + // if($existing_login_user->company_users()->count() != $existing_login_user->tokens()->count()) + // { - $existing_login_user->companies->each(function($company) use($existing_login_user){ + // $existing_login_user->companies->each(function($company) use($existing_login_user){ - if(!CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $company->id)->exists()){ + // if(!CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $company->id)->exists()){ - CreateCompanyToken::dispatchNow($company, $existing_login_user, "Google_O_Auth"); + // CreateCompanyToken::dispatchNow($company, $existing_login_user, "Google_O_Auth"); - } + // } - }); + // }); - } + // } - $truth->setCompanyToken(CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $existing_login_user->account->default_company->id)->first()); + // $truth->setCompanyToken(CompanyToken::where('user_id', $existing_login_user->id)->where('company_id', $existing_login_user->account->default_company->id)->first()); if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient()) return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); @@ -549,8 +606,6 @@ class LoginController extends BaseController 'password' => '', 'email' => $google->harvestEmail($user), 'oauth_user_id' => $google->harvestSubField($user), - // 'oauth_user_token' => $token, - // 'oauth_user_refresh_token' => $refresh_token, 'oauth_provider_id' => 'google', ]; @@ -559,40 +614,39 @@ class LoginController extends BaseController $account = CreateAccount::dispatchNow($new_account, request()->getClientIp()); Auth::login($account->default_company->owner(), true); - auth()->user()->email_verified_at = now(); auth()->user()->save(); - auth()->user()->setCompany(auth()->user()->account->default_company); + // auth()->user()->setCompany(auth()->user()->account->default_company); + // $this->setLoginCache(auth()->user()); + // $cu = CompanyUser::whereUserId(auth()->user()->id); - $this->setLoginCache(auth()->user()); - - $cu = CompanyUser::whereUserId(auth()->user()->id); + $cu = $this->hydrateCompanyUser(); if($cu->count() == 0) return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); - $truth = app()->make(TruthSource::class); - $truth->setCompanyUser($cu->first()); - $truth->setUser(auth()->user()); - $truth->setCompany(auth()->user()->account->default_company); + // $truth = app()->make(TruthSource::class); + // $truth->setCompanyUser($cu->first()); + // $truth->setUser(auth()->user()); + // $truth->setCompany(auth()->user()->account->default_company); - if(auth()->user()->company_users()->count() != auth()->user()->tokens()->count()) - { + // if(auth()->user()->company_users()->count() != auth()->user()->tokens()->count()) + // { + + // auth()->user()->companies->each(function($company) { + + // if(!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()){ + + // CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); - auth()->user()->companies->each(function($company) { - - if(!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()){ - - CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); - - } - - }); - - } + // } + + // }); + + // } - $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company->id)->first()); + // $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company->id)->first()); if(Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient()) diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 264776800b96..f15b1beb8704 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -165,6 +165,8 @@ class InvitationController extends Controller public function routerForDownload(string $entity, string $invitation_key) { + set_time_limit(45); + if(Ninja::isHosted()) return $this->returnRawPdf($entity, $invitation_key); diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 1fa7486f19ad..fe83bfd3a730 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -110,13 +110,24 @@ class SelfUpdateController extends BaseController return response()->json(['message' => ctrans('texts.self_update_not_available')], 403); } + nlog("Test filesystem is writable"); + $this->testWritable(); + + nlog("Clear cache directory"); + $this->clearCacheDir(); + nlog("copying release file"); + copy($this->getDownloadUrl(), storage_path('app/invoiceninja.zip')); + nlog("Finished copying"); + $file = Storage::disk('local')->path('invoiceninja.zip'); + nlog("Extracting zip"); + $zipFile = new \PhpZip\ZipFile(); $zipFile->openFile($file); @@ -125,8 +136,12 @@ class SelfUpdateController extends BaseController $zipFile->close(); + nlog("Finished extracting files"); + unlink($file); + nlog("Deleted release zip file"); + foreach($this->purge_file_list as $purge_file_path) { $purge_file = base_path($purge_file_path); @@ -134,12 +149,16 @@ class SelfUpdateController extends BaseController } + nlog("Removing cache files"); + Artisan::call('clear-compiled'); Artisan::call('route:clear'); Artisan::call('view:clear'); Artisan::call('migrate', ['--force' => true]); Artisan::call('optimize'); + nlog("Called Artisan commands"); + return response()->json(['message' => 'Update completed'], 200); diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index bc5d01c36fe4..d75f91dfda16 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -36,11 +36,9 @@ class SendEmailRequest extends Request public function rules() { return [ - 'template' => 'required', - 'entity' => 'required', - 'entity_id' => 'required', - // 'subject' => 'required', - // 'body' => 'required', + 'template' => 'bail|required', + 'entity' => 'bail|required', + 'entity_id' => 'bail|required', ]; } @@ -58,8 +56,11 @@ class SendEmailRequest extends Request unset($input['template']); } - $input['entity_id'] = $this->decodePrimaryKey($input['entity_id']); - $input['entity'] = "App\Models\\".ucfirst($input['entity']); + if(array_key_exists('entity_id', $input)) + $input['entity_id'] = $this->decodePrimaryKey($input['entity_id']); + + if(array_key_exists('entity', $input)) + $input['entity'] = "App\Models\\".ucfirst($input['entity']); $this->replace($input); } diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index 5ca64b49a2c1..34c85e241dd9 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -43,6 +43,7 @@ use Symfony\Component\HttpFoundation\ParameterBag; class Csv extends BaseImport implements ImportInterface { + public array $entity_count = []; public function import(string $entity) diff --git a/app/Import/Transformer/Wave/InvoiceTransformer.php b/app/Import/Transformer/Wave/InvoiceTransformer.php index f4d4f1eaa5cb..cad4014c2a1d 100644 --- a/app/Import/Transformer/Wave/InvoiceTransformer.php +++ b/app/Import/Transformer/Wave/InvoiceTransformer.php @@ -97,7 +97,7 @@ class InvoiceTransformer extends BaseTransformer { 'quantity' => 1, ]; - if($record['Invoice Paid'] > 0){ + if(array_key_exists('Invoice Paid', $record) && $record['Invoice Paid'] > 0){ $payments[] = [ 'date' => date( 'Y-m-d', strtotime( $record['Last Payment Date'] ) ), 'amount' => $this->getFloat( $record, 'Invoice Paid' ), diff --git a/app/Jobs/Company/CreateCompany.php b/app/Jobs/Company/CreateCompany.php index e9d63984a4b6..6a9ac4f9a25a 100644 --- a/app/Jobs/Company/CreateCompany.php +++ b/app/Jobs/Company/CreateCompany.php @@ -64,7 +64,7 @@ class CreateCompany $company->custom_fields = new \stdClass; $company->default_password_timeout = 1800000; $company->client_registration_fields = ClientRegistrationFields::generate(); - $company->markdown_email_enabled = true; + $company->markdown_email_enabled = false; if(Ninja::isHosted()) $company->subdomain = MultiDB::randomSubdomainGenerator(); diff --git a/app/Jobs/Import/CSVIngest.php b/app/Jobs/Import/CSVIngest.php index 3352b76a8c86..6a7c4e0032a5 100644 --- a/app/Jobs/Import/CSVIngest.php +++ b/app/Jobs/Import/CSVIngest.php @@ -69,6 +69,8 @@ class CSVIngest implements ShouldQueue { MultiDB::setDb( $this->company->db ); + set_time_limit(0); + $engine = $this->bootEngine($this->import_type); foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entity ) { diff --git a/app/Jobs/Ledger/LedgerBalanceUpdate.php b/app/Jobs/Ledger/LedgerBalanceUpdate.php new file mode 100644 index 000000000000..e7fc1bde0157 --- /dev/null +++ b/app/Jobs/Ledger/LedgerBalanceUpdate.php @@ -0,0 +1,77 @@ +check(); + } else { + //multiDB environment, need to + foreach (MultiDB::$dbs as $db) { + MultiDB::setDB($db); + + $this->check(); + } + } + + } + + public function check() + { + + CompanyLedger::where('balance', 0)->cursor()->each(function ($company_ledger){ + + if($company_ledger->balance > 0) + return; + + $last_record = CompanyLedger::where('client_id', $company_ledger->client_id) + ->where('company_id', $company_ledger->company_id) + ->where('balance', '!=', 0) + ->orderBy('id', 'DESC') + ->first(); + + if(!$last_record) + return; + + $company_ledger->balance = $last_record->balance + $company_ledger->adjustment; + $company_ledger->save(); + + }); + + } + +} diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 2f90b46ac115..c128088de30d 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -226,10 +226,10 @@ class NinjaMailerJob implements ShouldQueue if(!$user->oauth_user_token) { $this->company->account->gmailCredentialNotification(); - return; + $this->nmo->settings->email_sending_method = 'default'; + return $this->setMailDriver(); } - /* * Now that our token is refreshed and valid we can boot the * mail driver at runtime and also set the token which will persist @@ -238,6 +238,12 @@ class NinjaMailerJob implements ShouldQueue $token = $user->oauth_user_token->access_token; + if(!$token) { + $this->company->account->gmailCredentialNotification(); + $this->nmo->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + $this->nmo ->mailable ->from($user->email, $user->name()) diff --git a/app/Jobs/Mail/PaymentFailedMailer.php b/app/Jobs/Mail/PaymentFailedMailer.php index 8a9d46785298..3580e38bbff8 100644 --- a/app/Jobs/Mail/PaymentFailedMailer.php +++ b/app/Jobs/Mail/PaymentFailedMailer.php @@ -123,6 +123,7 @@ class PaymentFailedMailer implements ShouldQueue $nmo->settings = $settings; NinjaMailerJob::dispatch($nmo); + } } diff --git a/app/Jobs/Util/WebhookHandler.php b/app/Jobs/Util/WebhookHandler.php index f37aabb3d41d..7c5b25adb18f 100644 --- a/app/Jobs/Util/WebhookHandler.php +++ b/app/Jobs/Util/WebhookHandler.php @@ -88,6 +88,8 @@ class WebhookHandler implements ShouldQueue private function process($subscription) { + $this->entity->refresh(); + // generate JSON data $manager = new Manager(); $manager->setSerializer(new ArraySerializer()); diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php index 200b0d17498c..37228a9f2e50 100644 --- a/app/Mail/Engine/PaymentEmailEngine.php +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -245,6 +245,7 @@ class PaymentEmailEngine extends BaseEmailEngine $data['$invoice'] = ['value' => $this->formatInvoice(), 'label' => ctrans('texts.invoices')]; $data['$invoice.po_number'] = ['value' => $this->formatPoNumber(), 'label' => ctrans('texts.po_number')]; $data['$poNumber'] = &$data['$invoice.po_number']; + $data['$payment.status'] = ['value' => $this->payment->stringStatus($this->payment->status_id), 'label' => ctrans('texts.payment_status')]; $arrKeysLength = array_map('strlen', array_keys($data)); array_multisort($arrKeysLength, SORT_DESC, $data); diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index 3c07eead3f77..7332628533c2 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -67,7 +67,8 @@ class SupportMessageSent extends Mailable $platform = array_key_exists('platform', $this->data) ? $this->data['platform'] : "U"; $migrated = strlen($company->company_key) == 32 ? "M" : ""; $trial = $account->isTrial() ? "T" : ""; - + $plan = str_replace("_", " ", $plan); + if(Ninja::isHosted()) $subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated}{$trial} :: {$plan} :: ".date('M jS, g:ia'); else diff --git a/app/Observers/PaymentObserver.php b/app/Observers/PaymentObserver.php index 28848d6aeab2..efd1ac80f2a2 100644 --- a/app/Observers/PaymentObserver.php +++ b/app/Observers/PaymentObserver.php @@ -30,7 +30,7 @@ class PaymentObserver ->exists(); if ($subscriptions) { - WebhookHandler::dispatch(Webhook::EVENT_CREATE_PAYMENT, $payment, $payment->company, 'invoices,client'); + WebhookHandler::dispatch(Webhook::EVENT_CREATE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(5); } } @@ -57,7 +57,7 @@ class PaymentObserver ->exists(); if ($subscriptions) { - WebhookHandler::dispatch(Webhook::EVENT_DELETE_PAYMENT, $payment, $payment->company, 'invoices,client'); + WebhookHandler::dispatch(Webhook::EVENT_DELETE_PAYMENT, $payment, $payment->company, 'invoices,client')->delay(5); } } diff --git a/app/PaymentDrivers/GoCardless/ACH.php b/app/PaymentDrivers/GoCardless/ACH.php index f10c36566bfc..9c71d41f3f42 100644 --- a/app/PaymentDrivers/GoCardless/ACH.php +++ b/app/PaymentDrivers/GoCardless/ACH.php @@ -190,7 +190,7 @@ class ACH implements MethodInterface if ($payment->status === 'pending_submission') { - return $this->processPendingPayment($payment, ['token' => $token->hashed_id]); + return $this->processPendingPayment($payment); } return $this->processUnsuccessfulPayment($payment); diff --git a/app/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/PaymentDrivers/PayPalExpressPaymentDriver.php index afe824304a37..940f1617ea21 100644 --- a/app/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -185,7 +185,8 @@ class PayPalExpressPaymentDriver extends BaseDriver 'currency' => $this->client->getCurrencyCode(), 'transactionType' => 'Purchase', 'clientIp' => request()->getClientIp(), - 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2), + // 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2), + 'amount' => round($data['total']['amount_with_fee'],2), 'returnUrl' => route('client.payments.response', [ 'company_gateway_id' => $this->company_gateway->id, 'payment_hash' => $this->payment_hash->hash, @@ -218,17 +219,16 @@ class PayPalExpressPaymentDriver extends BaseDriver ]); - if($this->fee > 0.1){ + // if($this->fee > 0.1){ - $items[] = new Item([ - 'name' => " ", - 'description' => ctrans('texts.gateway_fee_description'), - 'price' => $this->fee, - 'quantity' => 1, - ]); + // $items[] = new Item([ + // 'name' => " ", + // 'description' => ctrans('texts.gateway_fee_description'), + // 'price' => $this->fee, + // 'quantity' => 1, + // ]); - - } + // } return $items; diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index f083a6d5784f..2683dd2f0241 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -80,10 +80,13 @@ class Charge 'description' => $description, 'metadata' => [ 'payment_hash' => $payment_hash->hash, - 'gateway_type_id' => GatewayType::CREDIT_CARD, + 'gateway_type_id' => $cgt->gateway_type_id, ], ]; + if($cgt->gateway_type_id == GatewayType::SEPA) + $data['payment_method_types'] = ['sepa_debit']; + $response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth); SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client, $this->stripe->client->company); @@ -132,7 +135,10 @@ class Charge return false; } - $payment_method_type = $response->charges->data[0]->payment_method_details->card->brand; + if($cgt->gateway_type_id == GatewayType::SEPA) + $payment_method_type = PaymentType::SEPA; + else + $payment_method_type = $response->charges->data[0]->payment_method_details->card->brand; $data = [ 'gateway_type_id' => $cgt->gateway_type_id, @@ -174,6 +180,8 @@ class Charge case 'mastercard': return PaymentType::MASTERCARD; break; + case PaymentType::SEPA: + return PaymentType::SEPA; default: return PaymentType::CREDIT_CARD_OTHER; break; diff --git a/app/Services/Ledger/LedgerService.php b/app/Services/Ledger/LedgerService.php index 910c816022b5..df8d9568ead8 100644 --- a/app/Services/Ledger/LedgerService.php +++ b/app/Services/Ledger/LedgerService.php @@ -26,107 +26,50 @@ class LedgerService public function updateInvoiceBalance($adjustment, $notes = '') { - $balance = 0; - // \DB::connection(config('database.default'))->beginTransaction(); + $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); + $company_ledger->client_id = $this->entity->client_id; + $company_ledger->adjustment = $adjustment; + $company_ledger->notes = $notes; + $company_ledger->activity_id = Activity::UPDATE_INVOICE; + $company_ledger->save(); - \DB::connection(config('database.default'))->transaction(function () use($notes, $adjustment, $balance){ - - $company_ledger = $this->ledger(); - - if ($company_ledger) { - $balance = $company_ledger->balance; - } - - $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); - $company_ledger->client_id = $this->entity->client_id; - $company_ledger->adjustment = $adjustment; - $company_ledger->notes = $notes; - $company_ledger->balance = $balance + $adjustment; - $company_ledger->activity_id = Activity::UPDATE_INVOICE; - $company_ledger->save(); - - $this->entity->company_ledger()->save($company_ledger); + $this->entity->company_ledger()->save($company_ledger); - }, 1); - - // \DB::connection(config('database.default'))->commit(); return $this; } public function updatePaymentBalance($adjustment, $notes = '') { - $balance = 0; - - // \DB::connection(config('database.default'))->beginTransaction(); - - \DB::connection(config('database.default'))->transaction(function () use($notes, $adjustment, $balance){ - - /* Get the last record for the client and set the current balance*/ - $company_ledger = $this->ledger(); - - if ($company_ledger) { - $balance = $company_ledger->balance; - } $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); $company_ledger->client_id = $this->entity->client_id; $company_ledger->adjustment = $adjustment; - $company_ledger->balance = $balance + $adjustment; $company_ledger->activity_id = Activity::UPDATE_PAYMENT; $company_ledger->notes = $notes; $company_ledger->save(); $this->entity->company_ledger()->save($company_ledger); - }, 1); - - // \DB::connection(config('database.default'))->commit(); - return $this; } public function updateCreditBalance($adjustment, $notes = '') { - $balance = 0; - - // \DB::connection(config('database.default'))->beginTransaction(); - - \DB::connection(config('database.default'))->transaction(function () use($notes, $adjustment, $balance){ - - $company_ledger = $this->ledger(); - - if ($company_ledger) { - $balance = $company_ledger->balance; - } $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); $company_ledger->client_id = $this->entity->client_id; $company_ledger->adjustment = $adjustment; $company_ledger->notes = $notes; - $company_ledger->balance = $balance + $adjustment; $company_ledger->activity_id = Activity::UPDATE_CREDIT; $company_ledger->save(); $this->entity->company_ledger()->save($company_ledger); - }, 1); - - // \DB::connection(config('database.default'))->commit(); - return $this; } - private function ledger() :?CompanyLedger - { - return CompanyLedger::whereClientId($this->entity->client_id) - ->whereCompanyId($this->entity->company_id) - ->orderBy('id', 'DESC') - ->lockForUpdate() - ->first(); - } - public function save() { $this->entity->save(); diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index dca369383965..ff8332bf1991 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -229,6 +229,10 @@ class Design extends BaseDesign $s_date = $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); return [ + ['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ""], + ['element' => 'th', 'properties' => [], 'content' => "