Merge remote-tracking branch 'upstream/v5-develop' into v5-update-email-templates

This commit is contained in:
Benjamin Beganović 2021-06-15 15:45:14 +02:00
commit dfdb4b1cd9
116 changed files with 169913 additions and 169168 deletions

View File

@ -1,11 +1,21 @@
# Release notes
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
## Added:
- Timezone Offset: Schedule emails based on timezone and time offsets.
- Force client country to system country if none is set.
- GMail Oauth via web
## Fixed:
- Add Cache-control: no-cache to prevent overaggressive caching of assets
- Improved labelling in the settings (client portal)
- Client portal: Multiple accounts access improvements (#5703)
- Client portal: "Credits" updates (#5734)
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
- Inject small delay into emails to allow all resources to be produced (ie PDFs) prior to sending
- Fixes for endless reminders not firing
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
## Fixed:

88
LICENSE
View File

@ -1,47 +1,47 @@
Copyright (c) 2021 by David Bomba
Invoice Ninja * https://www.invoiceninja.com
"CREATE. SEND. GET PAID"
Elastic License 2.0 (ELv2)
Elastic License
All Rights Reserved
ATTRIBUTION ASSURANCE LICENSE (adapted from the original BSD license)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the conditions below are met.
These conditions require a modest attribution to InvoiceNinja.com (the
"Author"), who hopes that its promotional value may help justify the
thousands of dollars in otherwise billable time invested in writing
this and other freely available, open-source software.
Acceptance
By using the software, you agree to all of the terms and conditions below.
1. Redistributions of source code, in whole or part and with or without
modification (the "Code"), must prominently display this GPG-signed
text in verifiable form.
2. Redistributions of the Code in binary form must be accompanied by
this GPG-signed text in any documentation and, each time the resulting
executable program or a program dependent thereon is launched, a
prominent display (e.g., splash screen or banner text) of the Author's
attribution information, which includes:
(a) Name ("Hillel Coren"),
(b) Professional identification ("Invoice Ninja"), and
(c) URL ("https://www.invoiceninja.com").
3. Neither the name nor any trademark of the Author may be used to
endorse or promote products derived from this software without specific
prior written permission.
4. Users are entirely responsible, to the exclusion of the Author and
any other persons, for compliance with (1) regulations set by owners or
administrators of employed equipment, (2) licensing terms of any other
software, and (3) local regulations regarding use, including those
regarding import, export, and use of encryption software.
Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below
THIS FREE SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR OR ANY CONTRIBUTOR BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
EFFECTS OF UNAUTHORIZED OR MALICIOUS NETWORK ACCESS;
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Limitations
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.
Patents
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
No Other Rights
These terms do not imply any licenses other than those expressly granted in these terms.
Termination
If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
No Liability
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
Definitions
The licensor is the entity offering these terms, and the software is the software the licensor makes available under these terms, including any portion of it.
you refers to the individual or entity agreeing to these terms.
your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
your licenses are all the licenses granted to you for the software under these terms.
use means anything you do with the software requiring one of your licenses.
trademark means trademarks, service marks, and similar rights.
For more information regarding the interpretation of this license please see here: https://invoiceninja.github.io/docs/legal/license/

View File

@ -1 +1 @@
5.1.71
5.2.4

View File

@ -311,17 +311,17 @@ class CheckData extends Command
Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($wrong_paid_to_dates, $credit_total_applied) {
$total_invoice_payments = 0;
foreach ($client->invoices->where('is_deleted', false)->where('status_id', '>', 1) as $invoice) {
foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {
$total_amount = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.refunded');
$total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');
$total_invoice_payments += ($total_amount - $total_refund);
}
// 10/02/21
foreach ($client->payments as $payment) {
$credit_total_applied += $payment->paymentables->where('paymentable_type', App\Models\Credit::class)->sum(DB::raw('amount'));
$credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(DB::raw('amount'));
}
if ($credit_total_applied < 0) {
@ -347,10 +347,11 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
Client::cursor()->where('is_deleted', 0)->each(function ($client) use ($wrong_balances) {
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($wrong_balances, $client) {
$total_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('amount');
$total_amount = $invoice->payments()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits()->get()->sum('amount');
$total_paid = $total_amount - $total_refund;
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
@ -363,6 +364,7 @@ class CheckData extends Command
$this->isValid = false;
}
});
});
$this->logMessage("{$wrong_balances} clients with incorrect invoice balances");
@ -408,8 +410,8 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get()->sum('balance');
$credit_balance = $client->credits()->where('is_deleted', false)->get()->sum('balance');
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount

View File

@ -0,0 +1,165 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands;
use App;
use App\Factory\ClientContactFactory;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyLedger;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\Contact;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Gateway;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\Paymentable;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\Subscription;
use App\Models\SystemLog;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\Webhook;
use App\Utils\Ninja;
use DB;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Mail;
use Symfony\Component\Console\Input\InputOption;
/**
* Class CheckDb.
*/
class CheckDb extends Command
{
/**
* @var string
*/
protected $signature = 'ninja:check-db';
/**
* @var string
*/
protected $description = 'Check MultiDB';
protected $log = '';
private $entities = [
Account::class,
Activity::class,
Backup::class,
Client::class,
ClientContact::class,
ClientGatewayToken::class,
Company::class,
CompanyGateway::class,
CompanyLedger::class,
CompanyToken::class,
CompanyUser::class,
Credit::class,
CreditInvitation::class,
Design::class,
Document::class,
Expense::class,
ExpenseCategory::class,
Gateway::class,
GroupSetting::class,
Invoice::class,
InvoiceInvitation::class,
Payment::class,
Paymentable::class,
PaymentHash::class,
Product::class,
Project::class,
Quote::class,
QuoteInvitation::class,
RecurringInvoice::class,
RecurringInvoiceInvitation::class,
Subscription::class,
SystemLog::class,
Task::class,
TaskStatus::class,
TaxRate::class,
User::class,
Vendor::class,
VendorContact::class,
WebHook::class,
];
public function handle()
{
$this->LogMessage("Checking - V5_DB1");
foreach($this->entities as $entity) {
$count_db_1 = $entity::on('db-ninja-01')->count();
$count_db_2 = $entity::on('db-ninja-02a')->count();
$diff = $count_db_1 - $count_db_2;
if($diff != 0)
$this->logMessage("{$entity} DB1: {$count_db_1} - DB2: {$count_db_2} - diff = {$diff}");
}
$this->LogMessage("Checking - V5_DB2");
foreach($this->entities as $entity) {
$count_db_1 = $entity::on('db-ninja-02')->count();
$count_db_2 = $entity::on('db-ninja-01a')->count();
$diff = $count_db_1 - $count_db_2;
if($diff != 0)
$this->logMessage("{$entity} DB1: {$count_db_1} - DB2: {$count_db_2} - diff = {$diff}");
}
}
private function logMessage($str)
{
$str = date('Y-m-d h:i:s').' '.$str;
$this->info($str);
$this->log .= $str."\n";
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Console\Command;
class HostedUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:sync-users';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Syncs Invoice Ninja Users';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Company::on('db-ninja-01')->each(function ($company){
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow([], $company);
});
Company::on('db-ninja-02')->each(function ($company){
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow([], $company);
});
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use App\Models\Company;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class S3Cleanup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:s3-cleanup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove orphan folders';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$c1 = Company::on('db-ninja-01')->pluck('company_key');
$c2 = Company::on('db-ninja-02')->pluck('company_key');
$merged = $c1->merge($c2)->toArray();
$directories = Storage::disk(config('filesystems.default'))->directories();
$this->LogMessage("Disk Cleanup");
foreach($directories as $dir)
{
if(!in_array($dir, $merged))
{
$this->logMessage("Deleting $dir");
Storage::disk(config('filesystems.default'))->deleteDirectory($dir);
}
}
$this->logMessage("exiting");
}
private function logMessage($str)
{
$str = date('Y-m-d h:i:s').' '.$str;
$this->info($str);
$this->log .= $str."\n";
}
}

View File

@ -89,7 +89,7 @@ class SendRemindersCron extends Command
->cursor();
$invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
WebhookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
});
@ -99,7 +99,7 @@ class SendRemindersCron extends Command
->cursor();
$quotes->each(function ($quote) {
WebHookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
WebhookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
});
}

View File

@ -19,7 +19,6 @@ use App\Factory\InvoiceInvitationFactory;
use App\Jobs\Invoice\CreateEntityPdf;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\DownloadInvoices;
use App\Mail\Migration\MaxCompanies;
use App\Mail\TemplateEmail;
use App\Models\Account;
@ -92,107 +91,11 @@ class SendTestEmails extends Command
]);
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadInvoices('https://google.com', $user->account->companies()->first());
$nmo->mailable = new MaxCompanies($user->account->companies()->first());
$nmo->company = $user->account->companies()->first();
$nmo->settings = $user->account->companies()->first()->settings;
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
// $this->sendTemplateEmails('plain');
// $this->sendTemplateEmails('light');
// $this->sendTemplateEmails('dark');
}
private function sendTemplateEmails($template)
{
$faker = Factory::create();
$message = [
'title' => 'Invoice XJ-3838',
'body' => '<div>"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"</div>',
'subject' => 'The Test Subject',
'footer' => 'Lovely Footer Texts',
];
$user = User::whereEmail('user@example.com')->first();
if (! $user) {
$account = Account::factory()->create();
$user = User::factory()->create([
'account_id' => $account->id,
'confirmation_code' => '123',
'email' => $faker->safeEmail,
'first_name' => 'John',
'last_name' => 'Doe',
]);
$company = Company::factory()->create([
'account_id' => $account->id,
]);
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => '',
'notifications' => CompanySettings::notificationDefaults(),
//'settings' => DefaultSettings::userSettings(),
'settings' => null,
]);
} else {
$company = $user->company_users->first()->company;
$account = $company->account;
}
$client = Client::all()->first();
if (! $client) {
$client = ClientFactory::create($company->id, $user->id);
$client->save();
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1,
'send_email' => true,
'email' => $faker->safeEmail,
]);
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'send_email' => true,
'email' => $faker->safeEmail,
]);
}
$invoice = InvoiceFactory::create($company->id, $user->id);
$invoice->client_id = $client->id;
$invoice->setRelation('client', $client);
$invoice->save();
$ii = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
$ii->invoice_id = $invoice->id;
$ii->client_contact_id = $client->primary_contact()->first()->id;
$ii->save();
$invoice->setRelation('invitations', $ii);
$invoice->service()->markSent()->save();
CreateEntityPdf::dispatch($invoice->invitations()->first());
$cc_emails = [config('ninja.testvars.test_email')];
$bcc_emails = [config('ninja.testvars.test_email')];
$email_builder->setFooter($message['footer'])
->setSubject($message['subject'])
->setBody($message['body']);
NinjaMailerJob::dispatchNow($nmo);
}
}

View File

@ -52,7 +52,7 @@ class Kernel extends ConsoleKernel
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
$schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping();
@ -75,7 +75,7 @@ class Kernel extends ConsoleKernel
}
if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled')) {
if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
$schedule->command('queue:work')->everyMinute()->withoutOverlapping();
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();

View File

@ -65,6 +65,8 @@ class CompanySettings extends BaseSettings
public $auto_convert_quote = true; //@implemented
public $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send?
public $entity_send_time = 0;
public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented
@ -266,6 +268,7 @@ class CompanySettings extends BaseSettings
public $hide_empty_columns_on_pdf = false;
public static $casts = [
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',

View File

@ -74,14 +74,12 @@ class GmailTransport extends Transport
}
}
$this->gmail->send();
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
}

View File

@ -1,47 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Helpers\Mail;
use App\Libraries\MultiDB;
use App\Mail\SupportMessageSent;
use App\Models\User;
use App\Providers\MailServiceProvider;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Laravel\Socialite\Facades\Socialite;
/**
* GmailTransportConfig.
*/
class GmailTransportConfig
{
public function test()
{
/********************* We may need to fetch a new token on behalf of the client ******************************/
$query = [
'email' => 'david@invoiceninja.com',
];
$user = MultiDB::hasUser($query);
// $oauth_user = Socialite::driver('google')->stateless()->userFromToken($user->oauth_user_token);
// $user->oauth_user_token = $oauth_user->refreshToken;
// $user->save();
Config::set('mail.driver', 'gmail');
Config::set('services.gmail.token', $user->oauth_user_token);
(new MailServiceProvider(app()))->register();
Mail::to('david@romulus.com.au')
->send(new SupportMessageSent('a cool message'));
}
}

View File

@ -213,15 +213,19 @@ class LoginController extends BaseController
if(!$cu->exists())
return response()->json(['message' => 'User not linked to any companies'], 403);
$cu->first()->account->companies->each(function ($company) use($cu, $request){
/* Ensure the user has a valid token */
$user->company_users->each(function ($company_user) use($request){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
if($company_user->tokens->count() == 0){
CreateCompanyToken::dispatchNow($company_user->company, $company_user->user, $request->server('HTTP_USER_AGENT'));
}
});
/*On the hosted platform, only owners can login for free/pro accounts*/
if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
@ -309,6 +313,9 @@ class LoginController extends BaseController
if($request->has('current_company') && $request->input('current_company') == 'true')
$cu->where("company_id", $company_token->company_id);
if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->refreshResponse($cu);
}
@ -370,6 +377,9 @@ class LoginController extends BaseController
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
@ -398,6 +408,9 @@ class LoginController extends BaseController
}
});
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);
return $this->timeConstrainedResponse($cu);
}
@ -430,6 +443,9 @@ class LoginController extends BaseController
}
});
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);
return $this->timeConstrainedResponse($cu);
}
@ -469,6 +485,9 @@ class LoginController extends BaseController
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
@ -480,38 +499,54 @@ class LoginController extends BaseController
public function redirectToProvider(string $provider)
{
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
$scopes = [];
$parameters = [];
if($provider == 'google'){
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
$parameters = ['access_type' => 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/google"];
}
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->with(['redirect_uri' => config('ninja.app_url')."/auth/google"])->scopes($scopes)->redirect();
return Socialite::driver($provider)->with($parameters)->scopes($scopes)->redirect();
}
}
public function handleProviderCallback(string $provider)
{
$socialite_user = Socialite::driver($provider)
->user();
$socialite_user = Socialite::driver($provider)->user();
$oauth_user_token = '';
if($socialite_user->refreshToken){
$client = new Google_Client();
$client->setClientId(config('ninja.auth.google.client_id'));
$client->setClientSecret(config('ninja.auth.google.client_secret'));
$client->fetchAccessTokenWithRefreshToken($socialite_user->refreshToken);
$oauth_user_token = $client->getAccessToken();
}
if($user = OAuth::handleAuth($socialite_user, $provider))
{
nlog('found user and updating their user record');
$name = OAuth::splitName($socialite_user->getName());
$update_user = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $socialite_user->refreshToken,
'oauth_user_token' => $oauth_user_token,
'oauth_user_refresh_token' => $socialite_user->refreshToken
];
$user->update($update_user);

View File

@ -379,6 +379,15 @@ class ClientController extends BaseController
$client->load('contacts', 'primary_contact');
/* Set the client country to the company if none is set */
if(!$client->country_id && strlen($client->company->settings->country_id) > 1){
$client->country_id = $client->company->settings->country_id;
$client->save();
}
$this->uploadLogo($request->file('company_logo'), $client->company, $client);
event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -55,18 +55,21 @@ class InvitationController extends Controller
->firstOrFail();
/* Return early if we have the correct client_hash embedded */
$client_contact = $invitation->contact;
if(empty($client_contact->email))
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
auth()->guard('contact')->login($client_contact, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
//If no contact password is set - this will cause a 401 error - instead redirect to the client.login route
$this->middleware('auth:contact');
return redirect()->route('client.login');
} else {
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
nlog("else - default - login contact");
auth()->guard('contact')->login($client_contact, true);
}

View File

@ -164,8 +164,9 @@ class InvoiceController extends Controller
//if only 1 pdf, output to buffer for download
if ($invoices->count() == 1) {
$file = $invoices->first()->pdf_file_path();
$invoice = $invoices->first();
$invitation = $invoice->invitations->first();
$file = $invoice->pdf_file_path($invitation);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
}

View File

@ -132,7 +132,7 @@ class EmailController extends BaseController
$entity_obj->service()->markSent()->save();
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data)
->delay(now()->addSeconds(5));
->delay(now()->addSeconds(30));
}

View File

@ -66,9 +66,11 @@ class ImportJsonController extends BaseController
$hash = Str::random(32);
nlog($hash);
Cache::put( $hash, base64_encode( $contents ), 3600 );
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'));
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
return response()->json(['message' => 'Processing'], 200);

View File

@ -30,6 +30,7 @@ use App\Transformers\RecurringInvoiceTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -205,6 +206,10 @@ class RecurringInvoiceController extends BaseController
event(new RecurringInvoiceWasCreated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
$offset = $recurring_invoice->client->timezone_offset();
$recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
$recurring_invoice->save();
return $this->itemResponse($recurring_invoice);
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Credit;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -24,6 +25,13 @@ class CreditsTable extends Component
public $per_page = 10;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = Credit::query()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -25,8 +26,13 @@ class DocumentsTable extends Component
public $per_page = 10;
public $company;
public function mount($client)
{
MultiDB::setDb($this->company->db);
$this->client = $client;
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Utils\Traits\WithSorting;
use Carbon\Carbon;
@ -26,8 +27,12 @@ class InvoicesTable extends Component
public $status = [];
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->sort_asc = false;
$this->sort_field = 'date';

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use Livewire\Component;
class PayNowDropdown extends Component
@ -20,8 +21,12 @@ class PayNowDropdown extends Component
public $methods;
public $company;
public function mount(int $total)
{
MultiDB::setDb($this->company->db);
$this->total = $total;
$this->methods = auth()->user()->client->service()->getPaymentMethods($total);

View File

@ -5,6 +5,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientGatewayToken;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -16,10 +17,16 @@ class PaymentMethodsTable extends Component
use WithSorting;
public $per_page = 10;
public $client;
public $company;
public function mount($client)
{
MultiDB::setDb($this->company->db);
$this->client = $client;
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Payment;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -23,11 +24,17 @@ class PaymentsTable extends Component
use WithPagination;
public $per_page = 10;
public $user;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->user = auth()->user();
}
public function render()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Quote;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -23,8 +24,17 @@ class QuotesTable extends Component
use WithPagination;
public $per_page = 10;
public $status = [];
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = Quote::query()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\RecurringInvoice;
use Livewire\Component;
@ -22,6 +23,18 @@ class RecurringInvoiceCancellation extends Component
*/
public $invoice;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
return render('components.livewire.recurring-invoice-cancellation');
}
public function processCancellation()
{
if ($this->invoice->subscription) {
@ -31,8 +44,5 @@ class RecurringInvoiceCancellation extends Component
return redirect()->route('client.recurring_invoices.request_cancellation', ['recurring_invoice' => $this->invoice->hashed_id]);
}
public function render()
{
return render('components.livewire.recurring-invoice-cancellation');
}
}

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
@ -65,7 +66,12 @@ class RequiredClientInfo extends Component
public $show_form = false;
public function mount() {}
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function handleSubmit(array $data): bool
{

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Subscription;
use Illuminate\Support\Facades\Cache;
@ -71,8 +72,12 @@ class SubscriptionPlanSwitch extends Component
*/
public $hash;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->total = $this->amount;
$this->methods = $this->contact->client->service()->getPaymentMethods($this->amount);

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\RecurringInvoice;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -24,6 +25,13 @@ class SubscriptionRecurringInvoicesTable extends Component
public $per_page = 10;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = RecurringInvoice::query()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Task;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -24,6 +25,13 @@ class TasksTable extends Component
public $per_page = 10;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = Task::query()

View File

@ -46,7 +46,7 @@ class ContactKeyLogin
if($client_contact = ClientContact::where('email', $contact_email)->where('company_id', $payload['company_id'])->first()){
if(empty($client_contact->email))
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);

View File

@ -58,6 +58,9 @@ class PasswordProtection
$google = new Google();
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
nlog("user");
nlog($user);
if (is_array($user)) {
$query = [
@ -65,14 +68,20 @@ class PasswordProtection
'oauth_provider_id'=> 'google'
];
nlog($query);
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
nlog("existing user with password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->has_password){
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->company()->oauth_password_required){
nlog("existing user without password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);

View File

@ -49,7 +49,7 @@ class StoreCompanyRequest extends Request
} else {
if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())];
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())];
}
else
$rules['subdomain'] = 'nullable|alpha_num';

View File

@ -50,7 +50,7 @@ class UpdateCompanyRequest extends Request
} else {
if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())];
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())];
}
else
$rules['subdomain'] = 'nullable|alpha_num';

View File

@ -14,10 +14,12 @@ namespace App\Http\Requests\User;
use App\DataMapper\DefaultSettings;
use App\Factory\UserFactory;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Ninja\CanAddUserRule;
use App\Http\ValidationRules\User\AttachableUser;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Libraries\MultiDB;
use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Validation\Rule;
class StoreUserRequest extends Request
@ -45,8 +47,7 @@ class StoreUserRequest extends Request
$rules['email'] = ['email', new AttachableUser()];
}
if (auth()->user()->company()->account->isFreeHostedClient()) {
if (Ninja::isHosted()) {
$rules['hosted_users'] = new CanAddUserRule(auth()->user()->company()->account);
}

View File

@ -104,7 +104,10 @@ class CreateAccount
//todo implement SLACK notifications
//$sp035a66->notification(new NewAccountCreated($spaa9f78, $sp035a66))->ninja();
VersionCheck::dispatchNow();
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66);
VersionCheck::dispatch();
LightLogs::create(new AnalyticsAccountCreated())
->increment()
@ -118,10 +121,6 @@ class CreateAccount
if(Ninja::isHosted() && Cache::get('currencies'))
{
//&& $data = unserialize(@file_get_contents('http://www.geoplugin.net/php.gp?ip=' . $this->client_ip))
// $currency_code = strtolower($data['geoplugin_currencyCode']);
// $country_code = strtolower($data['geoplugin_countryCode']);
$currency = Cache::get('currencies')->filter(function ($item) use ($currency_code) {
return strtolower($item->code) == $currency_code;
})->first();
@ -146,8 +145,6 @@ class CreateAccount
$settings->language_id = (string)$language->id;
}
//$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
if($timezone) {
$settings->timezone_id = (string)$timezone->id;
}

View File

@ -480,7 +480,11 @@ class CompanyExport implements ShouldQueue
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
Storage::makeDirectory(public_path('storage/backups/'), 0775);
$path = 'backups';
if(!Storage::disk(config('filesystems.default'))->exists($path))
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
$zip_path = public_path('storage/backups/'.$file_name);
$zip = new \ZipArchive();

View File

@ -19,6 +19,7 @@ use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Mail\DownloadBackup;
use App\Mail\DownloadInvoices;
use App\Mail\Import\CompanyImportFailure;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
@ -88,6 +89,14 @@ class CompanyImport implements ShouldQueue
private $request_array = [];
public $message = '';
public $pre_flight_checks_pass = true;
public $force_user_coalesce = false;
public $company_owner;
private $importables = [
// 'company',
'users',
@ -136,6 +145,7 @@ class CompanyImport implements ShouldQueue
public function __construct(Company $company, User $user, string $hash, array $request_array)
{
$this->company = $company;
$this->user = $user;
$this->hash = $hash;
$this->request_array = $request_array;
$this->current_app_version = config('ninja.app_version');
@ -147,6 +157,10 @@ class CompanyImport implements ShouldQueue
$this->company = Company::where('company_key', $this->company->company_key)->firstOrFail();
$this->account = $this->company->account;
$this->company_owner = $this->company->owner();
nlog("Company ID = {$this->company->id}");
nlog("Hash ID = {$this->hash}");
$this->backup_file = Cache::get($this->hash);
@ -156,21 +170,112 @@ class CompanyImport implements ShouldQueue
$this->backup_file = json_decode(base64_decode($this->backup_file));
// nlog($this->backup_file);
$this->checkUserCount();
if(array_key_exists('import_settings', $this->request_array) && $this->request_array['import_settings'] == 'true') {
$this->preFlightChecks()->importSettings();
}
if(array_key_exists('import_data', $this->request_array) && $this->request_array['import_data'] == 'true') {
try{
$this->preFlightChecks()
->purgeCompanyData()
->importData();
}
catch(\Exception $e){
info($e->getMessage());
}
}
}
/**
* On the hosted platform we cannot allow the
* import to start if there are users > plan number
* due to entity user_id dependencies
*
* @return bool
*/
private function checkUserCount()
{
if(Ninja::isSelfHost())
$this->pre_flight_checks_pass = true;
$backup_users = $this->backup_file->users;
$company_users = $this->company->users;
nlog("This is a free account");
nlog("Backup user count = ".count($backup_users));
if(count($backup_users) > 1){
// $this->message = 'Only one user can be in the import for a Free Account';
// $this->pre_flight_checks_pass = false;
//$this->force_user_coalesce = true;
}
nlog("backup users email = " . $backup_users[0]->email);
if(count($backup_users) == 1 && $this->company_owner->email != $backup_users[0]->email) {
// $this->message = 'Account emails do not match. Account owner email must match backup user email';
// $this->pre_flight_checks_pass = false;
// $this->force_user_coalesce = true;
}
$backup_users_emails = array_column($backup_users, 'email');
$company_users_emails = $company_users->pluck('email')->toArray();
$existing_user_count = count(array_intersect($backup_users_emails, $company_users_emails));
nlog("existing user count = {$existing_user_count}");
if($existing_user_count > 1){
if($this->account->plan == 'pro'){
// $this->message = 'Pro plan is limited to one user, you have multiple users in the backup file';
// $this->pre_flight_checks_pass = false;
// $this->force_user_coalesce = true;
}
if($this->account->plan == 'enterprise'){
$total_import_users = count($backup_users_emails);
$account_plan_num_user = $this->account->num_users;
if($total_import_users > $account_plan_num_user){
$this->message = "Total user count ({$total_import_users}) greater than your plan allows ({$account_plan_num_user})";
$this->pre_flight_checks_pass = false;
}
}
}
if($this->company->account->isFreeHostedClient() && count($this->backup_file->clients) > config('ninja.quotas.free.clients')){
nlog("client quota busted");
$client_count = count($this->backup_file->clients);
$client_limit = config('ninja.quotas.free.clients');
$this->message = "You are attempting to import ({$client_count}) clients, your current plan allows a total of ({$client_limit})";
$this->pre_flight_checks_pass = false;
}
return $this;
}
//check if this is a complete company import OR if it is selective
/*
@ -186,6 +291,18 @@ class CompanyImport implements ShouldQueue
//perform some magic here
}
if($this->pre_flight_checks_pass === false)
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new CompanyImportFailure($this->company, $this->message);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();
NinjaMailerJob::dispatchNow($nmo);
nlog($this->message);
throw new \Exception($this->message);
}
return $this;
}
@ -239,10 +356,14 @@ class CompanyImport implements ShouldQueue
$method = "import_{$import}";
nlog($method);
$this->{$method}();
}
nlog("finished importing company data");
return $this;
}
@ -281,6 +402,8 @@ class CompanyImport implements ShouldQueue
$obj_array,
);
$new_obj->company_id = $this->company->id;
$new_obj->user_id = $user_id;
$new_obj->save(['timestamps' => false]);
}
@ -1095,11 +1218,22 @@ class CompanyImport implements ShouldQueue
return implode(",", $tmp_arr);
}
/* Transform all IDs from old to new
*
* In the case of users - we need to check if the system
* is attempting to migrate resources above their quota,
*
* ie. > 50 clients or more than 1 user
*/
private function transformId(string $resource, ?string $old): ?int
{
if(empty($old))
return null;
if ($resource == 'users' && $this->force_user_coalesce){
return $this->company_owner->id;
}
if (! array_key_exists($resource, $this->ids)) {
// nlog($this->ids);
throw new \Exception("Resource {$resource} not available.");

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Company;
use App\Libraries\MultiDB;
use App\Models\TaskStatus;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable;
@ -44,6 +45,9 @@ class CreateCompanyTaskStatuses
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$task_statuses = [
['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1],
['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],

View File

@ -102,12 +102,11 @@ class CreateEntityPdf implements ShouldQueue
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
// nlog($this->entity->client->getMergedSettings());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$this->entity->service()->deletePdf();
/*This line of code hurts... it deletes ALL $entity PDFs... this causes a race condition when trying to send an email*/
// $this->entity->service()->deletePdf();
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation);
@ -116,16 +115,16 @@ class CreateEntityPdf implements ShouldQueue
$entity_design_id = '';
if ($this->entity instanceof Invoice) {
$path = $this->entity->client->invoice_filepath();
$path = $this->entity->client->invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id';
} elseif ($this->entity instanceof Quote) {
$path = $this->entity->client->quote_filepath();
$path = $this->entity->client->quote_filepath($this->invitation);
$entity_design_id = 'quote_design_id';
} elseif ($this->entity instanceof Credit) {
$path = $this->entity->client->credit_filepath();
$path = $this->entity->client->credit_filepath($this->invitation);
$entity_design_id = 'credit_design_id';
} elseif ($this->entity instanceof RecurringInvoice) {
$path = $this->entity->client->recurring_invoice_filepath();
$path = $this->entity->client->recurring_invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id';
}
@ -195,6 +194,11 @@ class CreateEntityPdf implements ShouldQueue
try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
nlog($file_path);
Storage::disk($this->disk)->put($file_path, $pdf);
}

View File

@ -109,7 +109,6 @@ class EmailEntity implements ShouldQueue
App::setLocale($this->invitation->contact->preferredLocale());
$nmo = new NinjaMailerObject;
$nmo->mailable = new TemplateEmail($this->email_entity_builder, $this->invitation->contact, $this->invitation);
$nmo->company = $this->company;

View File

@ -78,13 +78,16 @@ class ZipInvoices implements ShouldQueue
// create a new zipstream object
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
$path = $this->invoices->first()->client->invoice_filepath();
$invoice = $this->invoices->first();
$invitation = $invoice->invitations->first();
$path = $invoice->client->invoice_filepath($invitation);
$zip = new ZipStream($file_name, $options);
foreach ($this->invoices as $invoice) {
//$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
$zip->addFileFromPath(basename($invoice->pdf_file_path()), $invoice->pdf_file_path());
$zip->addFileFromPath(basename($invoice->pdf_file_path($invitation)), $invoice->pdf_file_path());
}
$zip->finish();

View File

@ -14,7 +14,7 @@ namespace App\Jobs\Ninja;
use App\DataMapper\InvoiceItem;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Util\WebHookHandler;
use App\Jobs\Util\WebhookHandler;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\Invoice;
@ -84,7 +84,7 @@ class SendReminders implements ShouldQueue
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'endless_reminder'])) {
$this->sendReminder($invoice, $reminder_template);
WebHookHandler::dispatch(Webhook::EVENT_REMIND_INVOICE, $invoice, $invoice->company);
WebhookHandler::dispatch(Webhook::EVENT_REMIND_INVOICE, $invoice, $invoice->company);
}
});
}
@ -128,9 +128,9 @@ class SendReminders implements ShouldQueue
$set_reminder3 = false;
if ((int)$settings->schedule_reminder1 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder1, (int)$settings->num_days_reminder1);
$next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder1, (int)$settings->num_days_reminder1);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date);
if (!$invoice->reminder1_sent) {
@ -139,20 +139,20 @@ class SendReminders implements ShouldQueue
}
if ((int)$settings->num_days_reminder2 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder2, (int)$settings->num_days_reminder2);
$next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder2, (int)$settings->num_days_reminder2);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date);
if (!$invoice->reminder2_sent) {
$set_reminder3 = true;
$set_reminder2 = true;
}
}
if ((int)$settings->num_days_reminder3 > 0) {
$next_reminder_date = $this->calculateScheduledDate($invoice, (int)$settings->schedule_reminder3, (int)$settings->num_days_reminder3);
$next_reminder_date = $this->calculateScheduledDate($invoice, $settings->schedule_reminder3, (int)$settings->num_days_reminder3);
if ($next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
if ($next_reminder_date && $next_reminder_date->gt(Carbon::parse($invoice->last_sent_date)));
$dates->push($next_reminder_date);
if (!$invoice->reminder3_sent) {
@ -178,15 +178,17 @@ class SendReminders implements ShouldQueue
*/
private function calculateScheduledDate($invoice, $schedule_reminder, $num_days_reminder) :?Carbon
{
$offset = $invoice->client->timezone_offset();
switch ($schedule_reminder) {
case 'after_invoice_date':
return Carbon::parse($invoice->date)->addDays($num_days_reminder)->startOfDay();
return Carbon::parse($invoice->date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break;
case 'before_due_date':
return Carbon::parse($invoice->due_date)->subDays($num_days_reminder)->startOfDay();
return Carbon::parse($invoice->due_date)->subDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break;
case 'after_due_date':
return Carbon::parse($invoice->due_date)->addDays($num_days_reminder)->startOfDay();
return Carbon::parse($invoice->due_date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset);
break;
default:
return null;
@ -212,6 +214,7 @@ class SendReminders implements ShouldQueue
nlog("firing email");
EmailEntity::dispatchNow($invitation, $invitation->company, $template);
}
});
@ -226,8 +229,9 @@ class SendReminders implements ShouldQueue
if (in_array($template, ['reminder1', 'reminder2', 'reminder3'])) {
$invoice->{$template."_sent"} = now();
}
$invoice->service()->touchReminder($template)->save();
$invoice->save();
// $invoice->save();
}
/**

View File

@ -70,9 +70,9 @@ class SendRecurring implements ShouldQueue
nlog("updating recurring invoice dates");
/* Set next date here to prevent a recurring loop forming */
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate()->format('Y-m-d');
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
$this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles();
$this->recurring_invoice->last_sent_date = date('Y-m-d');
$this->recurring_invoice->last_sent_date = now();
/* Set completed if we don't have any more cycles remaining*/
if ($this->recurring_invoice->remaining_cycles == 0) {

View File

@ -212,7 +212,7 @@ class Import implements ShouldQueue
}
// if(Ninja::isHosted() && array_key_exists('ninja_tokens', $data))
// $this->processNinjaTokens($data['ninja_tokens']);
$this->processNinjaTokens($data['ninja_tokens']);
$this->setInitialCompanyLedgerBalances();
@ -929,7 +929,7 @@ class Import implements ShouldQueue
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
if(array_key_exists('invoice_id', $resource) && $this->tryTransformingId('invoices', $resource['invoice_id']))
if(array_key_exists('invoice_id', $resource) && isset($resource['invoice_id']) && $this->tryTransformingId('invoices', $resource['invoice_id']))
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
$modified['user_id'] = $this->processUserId($resource);
@ -1659,8 +1659,10 @@ class Import implements ShouldQueue
private function processNinjaTokens(array $data)
{
nlog("attempting to process Ninja Tokens");
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatch($data, $this->company);
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);
}

View File

@ -53,7 +53,11 @@ class ReminderJob implements ShouldQueue
private function processReminders()
{
Invoice::where('next_send_date', Carbon::today()->format('Y-m-d'))->with('invitations')->cursor()->each(function ($invoice) {
Invoice::whereDate('next_send_date', '<=', now())
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice');

View File

@ -8,6 +8,7 @@
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Util;
use App\Jobs\Util\SystemLogger;

View File

@ -34,11 +34,11 @@ class OAuth
* @param Socialite $user
* @return bool|\App\Models\User|\App\Libraries\App\Models\User|null
*/
public static function handleAuth(Socialite $user)
public static function handleAuth($socialite_user, $provider)
{
/** 1. Ensure user arrives on the correct provider **/
$query = [
'oauth_user_id' =>$user->getId(),
'oauth_user_id' =>$socialite_user->getId(),
'oauth_provider_id'=>$provider,
];

View File

@ -47,6 +47,9 @@ class CreditEmailEngine extends BaseEmailEngine
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];
} else {
@ -98,9 +101,9 @@ class CreditEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted())
$this->setAttachments([$this->credit->pdf_file_path(null, 'url', true)]);
$this->setAttachments([$this->credit->pdf_file_path($this->invitation, 'url', true)]);
else
$this->setAttachments([$this->credit->pdf_file_path()]);
$this->setAttachments([$this->credit->pdf_file_path($this->invitation)]);
}

View File

@ -50,6 +50,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];
} elseif (strlen($this->client->getSetting('email_template_'.$this->reminder_template)) > 0) {
@ -109,9 +112,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted())
$this->setAttachments([$this->invoice->pdf_file_path(null, 'url', true)]);
$this->setAttachments([$this->invoice->pdf_file_path($this->invitation, 'url', true)]);
else
$this->setAttachments([$this->invoice->pdf_file_path()]);
$this->setAttachments([$this->invoice->pdf_file_path($this->invitation)]);
// $this->setAttachments(['path' => $this->invoice->pdf_file_path(), 'name' => basename($this->invoice->pdf_file_path())]);

View File

@ -77,7 +77,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$this->payment->invoices->each(function ($invoice){
$this->setAttachments([$invoice->pdf_file_path()]);
$this->setAttachments([$invoice->pdf_file_path($invoice->invitations->first())]);
});

View File

@ -48,6 +48,9 @@ class QuoteEmailEngine extends BaseEmailEngine
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if($this->reminder_template == 'endless_reminder')
$this->reminder_template = 'reminder_endless';
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];
} else {
@ -100,9 +103,9 @@ class QuoteEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted())
$this->setAttachments([$this->quote->pdf_file_path(null, 'url', true)]);
$this->setAttachments([$this->quote->pdf_file_path($this->invitation, 'url', true)]);
else
$this->setAttachments([$this->quote->pdf_file_path()]);
$this->setAttachments([$this->quote->pdf_file_path($this->invitation)]);
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Mail\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CompanyImportFailure extends Mailable
{
// use Queueable, SerializesModels;
public $company;
public $settings;
public $logo;
public $title;
public $message;
public $whitelabel;
public $user_message;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user_message)
{
$this->company = $company;
$this->user_message = $user_message;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$this->settings = $this->company->settings;
$this->logo = $this->company->present()->logo();
$this->title = ctrans('texts.company_import_failure_subject', ['company' => $this->company->present()->name()]);
$this->whitelabel = $this->company->account->isPaid();
nlog($this->user_message);
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.company_import_failure_subject', ['company' => $this->company->present()->name()]))
->view('email.import.import_failure', ['user_message' => $this->user_message, 'title' => $this->title]);
}
}

View File

@ -41,9 +41,6 @@ class MigrationCompleted extends Mailable
$result = $this->from(config('mail.from.address'), config('mail.from.name'))
->view('email.import.completed', $data);
// if($this->company->invoices->count() >=1)
// $result->attach($this->company->invoices->first()->pdf_file_path());
return $result;
}
}

View File

@ -105,9 +105,6 @@ class Account extends BaseModel
return $this->hasOne(Company::class, 'id', 'default_company_id');
}
/**
* @return BelongsTo
*/
public function payment()
{
return $this->belongsTo(Payment::class)->withTrashed();
@ -207,7 +204,7 @@ class Account extends BaseModel
return false;
}
return $this->plan == 'free';
return $this->plan == 'free' || is_null($this->plan);
}
public function isEnterpriseClient()
@ -323,4 +320,5 @@ class Account extends BaseModel
];
}
}
}

View File

@ -15,6 +15,7 @@ use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Models\Presenters\ClientPresenter;
use App\Services\Client\ClientService;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
@ -32,6 +33,7 @@ class Client extends BaseModel implements HasLocalePreference
use SoftDeletes;
use Filterable;
use GeneratesCounter;
use AppSetup;
protected $presenter = ClientPresenter::class;
@ -230,13 +232,16 @@ class Client extends BaseModel implements HasLocalePreference
public function language()
{
//return Language::find($this->getSetting('language_id'));
$languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id');
})->first();
}
public function locale()
@ -257,6 +262,9 @@ class Client extends BaseModel implements HasLocalePreference
{
$currencies = Cache::get('currencies');
if(!$currencies)
$this->buildCache(true);
return $currencies->filter(function ($item) {
return $item->id == $this->getSetting('currency_id');
})->first();
@ -622,29 +630,36 @@ class Client extends BaseModel implements HasLocalePreference
{
$languages = Cache::get('languages');
if(!$languages)
$this->buildCache(true);
return $languages->filter(function ($item) {
return $item->id == $this->getSetting('language_id');
})->first()->locale;
}
public function invoice_filepath()
public function invoice_filepath($invitation)
{
return $this->company->company_key.'/'.$this->client_hash.'/invoices/';
$contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/invoices/';
}
public function quote_filepath()
public function quote_filepath($invitation)
{
return $this->company->company_key.'/'.$this->client_hash.'/quotes/';
$contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/quotes/';
}
public function credit_filepath()
public function credit_filepath($invitation)
{
return $this->company->company_key.'/'.$this->client_hash.'/credits/';
$contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/credits/';
}
public function recurring_invoice_filepath()
public function recurring_invoice_filepath($invitation)
{
return $this->company->company_key.'/'.$this->client_hash.'/recurring_invoices/';
$contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/recurring_invoices/';
}
public function company_filepath()
@ -684,4 +699,21 @@ class Client extends BaseModel implements HasLocalePreference
{
return $this->hasMany(Payment::class);
}
public function timezone_offset()
{
$offset = 0;
$entity_send_time = $this->getSetting('entity_send_time');
if($entity_send_time == 0)
return 0;
$timezone = $this->company->timezone();
$offset -= $timezone->utc_offset;
$offset += ($entity_send_time * 3600);
return $offset;
}
}

View File

@ -267,7 +267,7 @@ class Credit extends BaseModel
if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->credit_filepath().$this->numberFormatter().'.pdf';
$file_path = $this->client->credit_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path);

View File

@ -126,9 +126,9 @@ class CreditInvitation extends BaseModel
public function pdf_file_path()
{
$storage_path = Storage::url($this->credit->client->quote_filepath().$this->credit->numberFormatter().'.pdf');
$storage_path = Storage::url($this->credit->client->quote_filepath($this).$this->credit->numberFormatter().'.pdf');
if (! Storage::exists($this->credit->client->credit_filepath().$this->credit->numberFormatter().'.pdf')) {
if (! Storage::exists($this->credit->client->credit_filepath($this).$this->credit->numberFormatter().'.pdf')) {
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this);
}

View File

@ -409,7 +409,7 @@ class Invoice extends BaseModel
if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->invoice_filepath().$this->numberFormatter().'.pdf';
$file_path = $this->client->invoice_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path);

View File

@ -142,7 +142,7 @@ class InvoiceInvitation extends BaseModel
{
$storage_path = Storage::url($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf');
if (! Storage::exists($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf')) {
if (! Storage::exists($this->invoice->client->invoice_filepath($this).$this->invoice->numberFormatter().'.pdf')) {
event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this);
}

View File

@ -219,7 +219,7 @@ class Quote extends BaseModel
if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->quote_filepath().$this->numberFormatter().'.pdf';
$file_path = $this->client->quote_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path);

View File

@ -130,9 +130,9 @@ class QuoteInvitation extends BaseModel
public function pdf_file_path()
{
$storage_path = Storage::url($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf');
$storage_path = Storage::url($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf');
if (! Storage::exists($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf')) {
if (! Storage::exists($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf')) {
event(new QuoteWasUpdated($this->quote, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this);
}

View File

@ -220,34 +220,35 @@ class RecurringInvoice extends BaseModel
{
if (!$this->next_send_date) {
return null;
// $this->next_send_date = now()->format('Y-m-d');
}
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->addDay();
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY:
return Carbon::parse($this->next_send_date)->addWeek();
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($this->next_send_date)->addWeeks(2);
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($this->next_send_date)->addWeeks(4);
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY:
return Carbon::parse($this->next_send_date)->addMonthNoOverflow();
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(2);
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(3);
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(4);
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($this->next_send_date)->addMonthsNoOverflow(6);
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY:
return Carbon::parse($this->next_send_date)->addYear();
return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($this->next_send_date)->addYears(2);
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($this->next_send_date)->addYears(3);
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}
@ -255,31 +256,33 @@ class RecurringInvoice extends BaseModel
public function nextDateByFrequency($date)
{
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay();
return Carbon::parse($date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek();
return Carbon::parse($date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->addWeeks(2);
return Carbon::parse($date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->addWeeks(4);
return Carbon::parse($date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow();
return Carbon::parse($date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(2);
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(3);
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(4);
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6);
return Carbon::parse($date)->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear();
return Carbon::parse($date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->addYears(2);
return Carbon::parse($date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->addYears(3);
return Carbon::parse($date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}

View File

@ -52,11 +52,6 @@ class InvoiceObserver
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company);
}
// if($invoice->isDirty('date') || $invoice->isDirty('due_date'))
// $invoice->service()->setReminder()->save();
// UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath() . $invoice->numberFormatter().'.pdf');
}
/**

View File

@ -98,7 +98,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
$message = [
'server_response' => $response->getMessage(),
'data' => $this->checkout->payment_hash->data,
'data' => $this->payment_hash->data,
];
SystemLogger::dispatch(
@ -187,7 +187,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
'cancelUrl' => $this->client->company->domain() . '/client/invoices',
'description' => implode(',', collect($this->payment_hash->data->invoices)
->map(function ($invoice) {
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->number);
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
})->toArray()),
'transactionId' => $this->payment_hash->hash . '-' . time(),
'ButtonSource' => 'InvoiceNinja_SP',

View File

@ -56,7 +56,7 @@ class ImportCustomers
}
/* Now call the update payment methods handler*/
$this->stripe->updateAllPaymentMethods();
// $this->stripe->updateAllPaymentMethods();
}

View File

@ -58,7 +58,7 @@ class UpdatePaymentMethods
// }
private function updateMethods(Customer $customer, Client $client)
public function updateMethods(Customer $customer, Client $client)
{
$card_methods = PaymentMethod::all([
'customer' => $customer->id,
@ -145,7 +145,7 @@ class UpdatePaymentMethods
}
private function buildPaymentMethodMeta(PaymentMethod $method, GatewayType $type_id)
private function buildPaymentMethodMeta(PaymentMethod $method, $type_id)
{
switch ($type_id) {

View File

@ -508,10 +508,10 @@ class StripePaymentDriver extends BaseDriver
* the respective tokens in the system.
*
*/
// public function updateAllPaymentMethods()
// {
// return (new UpdatePaymentMethods($this))->run();
// }
public function updateAllPaymentMethods()
{
return (new UpdatePaymentMethods($this))->run();
}
/**
* Imports stripe customers and their payment methods

View File

@ -140,7 +140,11 @@ class CreditService
public function deletePdf()
{
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath() . $this->credit->numberFormatter().'.pdf');
$this->credit->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath($invitation) . $this->credit->numberFormatter().'.pdf');
});
return $this;
}

View File

@ -37,7 +37,7 @@ class GetCreditPdf extends AbstractService
$this->contact = $this->credit->client->primary_contact()->first();
}
$path = $this->credit->client->credit_filepath();
$path = $this->credit->client->credit_filepath($this->invitation);
$file_path = $path.$this->credit->numberFormatter().'.pdf';

View File

@ -60,14 +60,15 @@ class GenerateDeliveryNote
? $this->invoice->design_id
: $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id'));
$file_path = sprintf('%s%s_delivery_note.pdf', $this->invoice->client->invoice_filepath(), $this->invoice->number);
$invitation = $this->invoice->invitations->first();
$file_path = sprintf('%s%s_delivery_note.pdf', $this->invoice->client->invoice_filepath($invitation), $this->invoice->number);
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invoice->invitations->first());
}
$design = Design::find($design_id);
$html = new HtmlEngine($this->invoice->invitations->first());
$html = new HtmlEngine($invitation);
if ($design->is_custom) {
$options = ['custom_partials' => json_decode(json_encode($design->design), true)];
@ -105,6 +106,9 @@ class GenerateDeliveryNote
info($maker->getCompiledHTML());
}
if(!Storage::disk($this->disk)->exists($this->invoice->client->invoice_filepath($invitation)))
Storage::disk($this->disk)->makeDirectory($this->invoice->client->invoice_filepath($invitation), 0775);
Storage::disk($this->disk)->put($file_path, $pdf);
return Storage::disk($this->disk)->path($file_path);

View File

@ -35,7 +35,7 @@ class GetInvoicePdf extends AbstractService
$invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->invoice->client->invoice_filepath();
$path = $this->invoice->client->invoice_filepath($invitation);
$file_path = $path.$this->invoice->numberFormatter().'.pdf';

View File

@ -307,13 +307,16 @@ class InvoiceService
public function deletePdf()
{
//UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
$this->invoice->invitations->each(function ($invitation){
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
if(Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
}
});
return $this;
}
@ -351,8 +354,17 @@ class InvoiceService
* PDF when it is updated etc.
* @return InvoiceService
*/
public function touchPdf()
public function touchPdf($force = false)
{
if($force){
$this->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatchNow($invitation);
});
return $this;
}
$this->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation);
});
@ -376,9 +388,12 @@ class InvoiceService
$this->invoice->reminder3_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
break;
case 'endless_reminder':
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
break;
default:
// code...
$this->invoice->reminder1_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
break;
}

View File

@ -41,90 +41,95 @@ class UpdateReminder extends AbstractService
return $this->invoice; //exit early
}
$offset = $this->invoice->client->timezone_offset();
$date_collection = collect();
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' &&
$this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder1);
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder1);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder1);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' &&
$this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder2);
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder2);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder2);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' &&
$this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->date)->addDays($this->settings->num_days_reminder3);
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->subDays($this->settings->num_days_reminder3);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$reminder_date = Carbon::parse($this->invoice->due_date)->addDays($this->settings->num_days_reminder3);
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date->format('Y-m-d'));
$date_collection->push($reminder_date);
}
if($date_collection->count() >=1 && $date_collection->sort()->first()->gte(now()))
$this->invoice->next_send_date = $date_collection->sort()->first();
else
$this->invoice->next_send_date = null;
return $this->invoice;
}

View File

@ -35,7 +35,7 @@ class GetQuotePdf extends AbstractService
$invitation = $this->quote->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->quote->client->quote_filepath();
$path = $this->quote->client->quote_filepath($invitation);
$file_path = $path.$this->quote->numberFormatter().'.pdf';

View File

@ -178,7 +178,11 @@ class QuoteService
public function deletePdf()
{
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath() . $this->quote->numberFormatter().'.pdf');
$this->quote->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath($invitation) . $this->quote->numberFormatter().'.pdf');
});
return $this;
}

View File

@ -37,7 +37,7 @@ class GetInvoicePdf extends AbstractService
$invitation = $this->entity->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->entity->client->recurring_invoice_filepath();
$path = $this->entity->client->recurring_invoice_filepath($invitation);
$file_path = $path.$this->entity->hashed_id.'.pdf';

View File

@ -87,7 +87,13 @@ class RecurringService
public function deletePdf()
{
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath() . $this->recurring_entity->numberFormatter().'.pdf');
$this->recurring_entity->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf');
});
return $this;
}

View File

@ -630,7 +630,7 @@ class SubscriptionService
*/
public function triggerWebhook($context)
{
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || empty($this->subscription->webhook_configuration['post_purchase_rest_method'])) {
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
return ["message" => "Success", "status_code" => 200];
}

View File

@ -15,6 +15,7 @@ use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyUser;
use App\Models\User;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
/**

View File

@ -51,7 +51,7 @@ class CompanyUserTransformer extends EntityTransformer
'archived_at' => (int) $company_user->deleted_at,
'created_at' => (int) $company_user->created_at,
'permissions_updated_at' => (int) $company_user->permissions_updated_at,
//'number_years_active' => (int) $company_user->number_years_active,
'ninja_portal_url' => (string) $company_user->ninja_portal_url,
];
}

View File

@ -181,6 +181,7 @@ class HtmlEngine
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
$data['$quote.total'] = &$data['$total'];
$data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.invoice_total')];
$data['$invoice_total_raw'] = ['value' => $this->entity_calc->getTotal(), 'label' => ctrans('texts.invoice_total')];
$data['$invoice.amount'] = &$data['$total'];
$data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.quote_total')];
$data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.credit_total')];

View File

@ -62,19 +62,19 @@ class Phantom
$entity_obj = $invitation->{$entity};
if ($entity == 'invoice') {
$path = $entity_obj->client->invoice_filepath();
$path = $entity_obj->client->invoice_filepath($invitation);
}
if ($entity == 'quote') {
$path = $entity_obj->client->quote_filepath();
$path = $entity_obj->client->quote_filepath($invitation);
}
if ($entity == 'credit') {
$path = $entity_obj->client->credit_filepath();
$path = $entity_obj->client->credit_filepath($invitation);
}
if ($entity == 'recurring_invoice') {
$path = $entity_obj->client->recurring_invoice_filepath();
$path = $entity_obj->client->recurring_invoice_filepath($invitation);
}
$file_path = $path.$entity_obj->numberFormatter().'.pdf';
@ -90,6 +90,9 @@ class Phantom
$this->checkMime($pdf, $invitation, $entity);
if(!Storage::disk(config('filesystems.default'))->exists($path))
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
return $file_path;
@ -118,8 +121,6 @@ class Phantom
$finfo = new \finfo(FILEINFO_MIME);
nlog($pdf);
if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
{
SystemLogger::dispatch(

View File

@ -51,20 +51,20 @@ trait MakesReminders
if ($this->inReminderWindow(
$client->getSetting('schedule_reminder1'),
$client->getSetting('num_days_reminder1')
)) {
) && !$this->reminder1_sent) {
return 'reminder1';
} elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder2'),
$client->getSetting('num_days_reminder2')
)) {
) && !$this->reminder2_sent) {
return 'reminder2';
} elseif ($this->inReminderWindow(
$client->getSetting('schedule_reminder3'),
$client->getSetting('num_days_reminder3')
)) {
) && !$this->reminder3_sent) {
return 'reminder3';
} elseif ($this->checkEndlessReminder(
$this->last_sent_date,
$this->reminder_last_sent,
$client->getSetting('endless_reminder_frequency_id')
)) {
return 'endless_reminder';
@ -77,6 +77,10 @@ trait MakesReminders
private function checkEndlessReminder($last_sent_date, $endless_reminder_frequency_id) :bool
{
nlog("endless date match = ".$this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id));
nlog("Endless reminder bool = ");
nlog(Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id)));
if (Carbon::now()->startOfDay()->eq($this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id))) {
return true;
}
@ -86,10 +90,13 @@ trait MakesReminders
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{
if (!$date)
return null;
switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS:

View File

@ -99,6 +99,24 @@ return [
// ),
],
'db-ninja-01a' => [
'driver' => 'mysql',
'host' => env('DB_HOST1', env('DB_HOST', '127.0.0.1')),
'database' => env('DB_DATABASE2', env('DB_DATABASE', 'forge')),
'username' => env('DB_USERNAME2', env('DB_USERNAME', 'forge')),
'password' => env('DB_PASSWORD2', env('DB_PASSWORD', '')),
'port' => env('DB_PORT1', env('DB_PORT', '3306')),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
// 'options' => array(
// PDO::ATTR_EMULATE_PREPARES => true
// ),
],
'db-ninja-02' => [
'driver' => 'mysql',
'host' => env('DB_HOST2', env('DB_HOST', '127.0.0.1')),
@ -116,6 +134,24 @@ return [
// PDO::ATTR_EMULATE_PREPARES => true
// ),
],
'db-ninja-02a' => [
'driver' => 'mysql',
'host' => env('DB_HOST2', env('DB_HOST', '127.0.0.1')),
'database' => env('DB_DATABASE1', env('DB_DATABASE', 'forge')),
'username' => env('DB_USERNAME1', env('DB_USERNAME', 'forge')),
'password' => env('DB_PASSWORD1', env('DB_PASSWORD', '')),
'port' => env('DB_PORT2', env('DB_PORT', '3306')),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
// 'options' => array(
// PDO::ATTR_EMULATE_PREPARES => true
// ),
],
],
/*

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.1.71',
'app_tag' => '5.1.71-release',
'app_version' => '5.2.4',
'app_tag' => '5.2.4-release',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNinjaPortalColumnToAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('company_user', function (Blueprint $table) {
$table->text('ninja_portal_url')->default('');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -5,7 +5,7 @@ const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "e63018de16a321755f090480820081ff",
"main.dart.js": "33f9288e9a8ba68d21b46b8afda06fbb",
"/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

166703
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

170743
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4264,6 +4264,8 @@ $LANG = array(
'client_email_company_contact_label' => 'If you have any questions please contact us, we\'re here to help!',
'quote_was_approved_label' => 'Quote was approved',
'quote_was_approved' => 'We would like to inform you that quote was approved.',
'company_import_failure_subject' => 'Error importing :company',
'company_import_failure_body' => 'There was an error importing the company data, the error message was:',
);
return $LANG;

View File

@ -0,0 +1,22 @@
@component('email.template.master', ['design' => 'light', 'settings' => $settings])
@slot('header')
@include('email.components.header', ['logo' => $logo])
@endslot
<h2>{{ $title }}</h2>
<p>{{ctrans('texts.company_import_failure_body')}}</p>
@if($user_message)
<p>{{ $user_message }}</p>
@endif
@if(isset($whitelabel) && !$whitelabel)
@slot('footer')
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '&copy; InvoiceNinja'])
For any info, please visit InvoiceNinja.
@endcomponent
@endslot
@endif
@endcomponent

View File

@ -4,12 +4,11 @@
<p>{{ ctrans('texts.migration_failed') }} {{ $company->present()->name() }}</p>
<pre>
@if(\App\Utils\Ninja::isHosted())
@if(\App\Utils\Ninja::isSelfHost())
{!! $exception->getMessage() !!}
{!! $content !!}
@else
{!! $exception->getMessage() !!}
{!! $content !!}
<p>Please contact us at contact@invoiceninja.com for more information on this error.</p>
@endif
</pre>
</div>

View File

@ -13,6 +13,6 @@
@section('body')
<div class="flex flex-col">
@livewire('credits-table')
@livewire('credits-table', ['company' => $company])
</div>
@endsection

View File

@ -14,5 +14,5 @@
@csrf
</form>
@livewire('documents-table', ['client' => $client])
@livewire('documents-table', ['client' => $client, 'company' => $company])
@endsection

View File

@ -20,6 +20,6 @@
</form>
</div>
<div class="flex flex-col mt-4">
@livewire('invoices-table')
@livewire('invoices-table', ['company' => $company])
</div>
@endsection

Some files were not shown because too many files have changed in this diff Show More