mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-16 16:13:20 +01:00
Merge branch 'v5-develop' into v5-stable
This commit is contained in:
commit
ace639d956
@ -1 +1 @@
|
||||
5.1.65
|
||||
5.1.66
|
@ -65,7 +65,7 @@ class CheckData extends Command
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'ninja:check-data';
|
||||
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=}';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
@ -34,6 +34,7 @@ use App\Models\Project;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
@ -109,6 +110,14 @@ class CreateSingleAccount extends Command
|
||||
'portal_domain' => 'http://ninja.test:8000',
|
||||
]);
|
||||
|
||||
$settings = $company->settings;
|
||||
$settings->invoice_terms = 'Default company invoice terms';
|
||||
$settings->quote_terms = 'Default company quote terms';
|
||||
$settings->invoice_footer = 'Default invoice footer';
|
||||
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
|
||||
$account->default_company_id = $company->id;
|
||||
$account->save();
|
||||
|
||||
@ -146,6 +155,29 @@ class CreateSingleAccount extends Command
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
|
||||
TaxRate::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'name' => 'GST',
|
||||
'rate' => 10
|
||||
]);
|
||||
|
||||
TaxRate::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'name' => 'VAT',
|
||||
'rate' => 17.5
|
||||
]);
|
||||
|
||||
TaxRate::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'name' => 'CA Sales Tax',
|
||||
'rate' => 5
|
||||
]);
|
||||
|
||||
|
||||
$this->info('Creating '.$this->count.' clients');
|
||||
|
||||
for ($x = 0; $x < $this->count; $x++) {
|
||||
|
@ -11,8 +11,9 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Cron\AutoBillCron;
|
||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Util\ReminderJob;
|
||||
@ -46,7 +47,7 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new VersionCheck)->daily();
|
||||
|
||||
$schedule->command('ninja:check-data')->daily()->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new ReminderJob)->daily()->withoutOverlapping();
|
||||
|
||||
@ -58,6 +59,8 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SchedulerCheck)->everyFiveMinutes();
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
@ -65,6 +68,7 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
|
||||
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
|
||||
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@ class CompanySettings extends BaseSettings
|
||||
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
|
||||
public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
|
||||
|
||||
public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders @TODO
|
||||
public $reminder_send_time = 0; //number of seconds from UTC +0 to send reminders @TODO
|
||||
|
||||
public $late_fee_amount1 = 0; //@implemented
|
||||
public $late_fee_amount2 = 0; //@implemented
|
||||
@ -667,8 +667,9 @@ class CompanySettings extends BaseSettings
|
||||
'$custom_surcharge4',
|
||||
'$total_taxes',
|
||||
'$line_taxes',
|
||||
'$paid_to_date',
|
||||
'$total',
|
||||
'$paid_to_date',
|
||||
'$outstanding',
|
||||
],
|
||||
];
|
||||
|
||||
|
10
app/Exceptions/ImportCompanyFailed.php
Normal file
10
app/Exceptions/ImportCompanyFailed.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ImportCompanyFailed extends Exception
|
||||
{
|
||||
// ..
|
||||
}
|
@ -12,7 +12,9 @@
|
||||
namespace App\Factory;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class CompanyFactory
|
||||
@ -33,7 +35,12 @@ class CompanyFactory
|
||||
$company->db = config('database.default');
|
||||
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
|
||||
$company->custom_fields = (object) [];
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
else
|
||||
$company->subdomain = '';
|
||||
|
||||
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
|
||||
$company->default_password_timeout = 1800000;
|
||||
|
||||
|
@ -142,7 +142,7 @@ class AccountController extends BaseController
|
||||
*/
|
||||
public function store(CreateAccountRequest $request)
|
||||
{
|
||||
$account = CreateAccount::dispatchNow($request->all());
|
||||
$account = CreateAccount::dispatchNow($request->all(), $request->getClientIp());
|
||||
|
||||
if (! ($account instanceof Account)) {
|
||||
return $account;
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
@ -50,11 +51,15 @@ class ContactForgotPasswordController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function showLinkRequestForm()
|
||||
public function showLinkRequestForm(Request $request)
|
||||
{
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
|
||||
return $this->render('auth.passwords.request', [
|
||||
'title' => 'Client Password Reset',
|
||||
'passwordEmailRoute' => 'client.password.email',
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Events\Contact\ContactLoggedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Account;
|
||||
use App\Models\ClientContact;
|
||||
use App\Utils\Ninja;
|
||||
use Auth;
|
||||
@ -31,9 +32,13 @@ class ContactLoginController extends Controller
|
||||
$this->middleware('guest:contact', ['except' => ['logout']]);
|
||||
}
|
||||
|
||||
public function showLoginForm()
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
return $this->render('auth.login');
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
|
||||
return $this->render('auth.login', ['account' => $account]);
|
||||
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
|
@ -24,7 +24,7 @@ class ContactRegisterController extends Controller
|
||||
|
||||
$company = Company::where('company_key', $key)->firstOrFail();
|
||||
|
||||
return render('auth.register', ['company' => $company]);
|
||||
return render('auth.register', ['company' => $company, 'account' => $company->account]);
|
||||
}
|
||||
|
||||
public function register(RegisterRequest $request)
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Account;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
@ -62,8 +63,11 @@ class ContactResetPasswordController extends Controller
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
|
||||
return $this->render('auth.passwords.reset')->with(
|
||||
['token' => $token, 'email' => $request->email]
|
||||
['token' => $token, 'email' => $request->email, 'account' => $account]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
@ -104,9 +105,8 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request)
|
||||
{
|
||||
// MultiDB::userFindAndSetDb($request->input('email'));
|
||||
|
||||
// $user = MultiDB::hasUser(['email' => $request->input('email')]);
|
||||
MultiDB::userFindAndSetDb($request->input('email'));
|
||||
$user = MultiDB::hasUser(['email' => $request->input('email')]);
|
||||
|
||||
$this->validateEmail($request);
|
||||
|
||||
|
@ -31,6 +31,7 @@ use App\Models\User;
|
||||
use App\Transformers\CompanyUserTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use App\Utils\Traits\User\LoginCache;
|
||||
use Google_Client;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
@ -55,6 +56,7 @@ class LoginController extends BaseController
|
||||
|
||||
use AuthenticatesUsers;
|
||||
use UserSessionAttributes;
|
||||
use LoginCache;
|
||||
|
||||
protected $entity_type = CompanyUser::class;
|
||||
|
||||
@ -178,8 +180,7 @@ class LoginController extends BaseController
|
||||
|
||||
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
|
||||
|
||||
//if user has 2fa enabled - lets check this now:
|
||||
|
||||
//2FA
|
||||
if($user->google_2fa_secret && $request->has('one_time_password'))
|
||||
{
|
||||
$google2fa = new Google2FA();
|
||||
@ -203,14 +204,7 @@ class LoginController extends BaseController
|
||||
|
||||
$user->setCompany($user->account->default_company);
|
||||
|
||||
$timeout = $user->company()->default_password_timeout;
|
||||
|
||||
if($timeout == 0)
|
||||
$timeout = 30*60*1000*1000;
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
Cache::put($user->hashed_id.'_'.$user->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
$this->setLoginCache($user);
|
||||
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', auth()->user()->id);
|
||||
@ -228,7 +222,7 @@ class LoginController extends BaseController
|
||||
});
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
// return $this->listResponse($cu);
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
@ -351,6 +345,7 @@ class LoginController extends BaseController
|
||||
|
||||
if (is_array($user)) {
|
||||
|
||||
//
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
@ -361,14 +356,7 @@ class LoginController extends BaseController
|
||||
Auth::login($existing_user, true);
|
||||
$existing_user->setCompany($existing_user->account->default_company);
|
||||
|
||||
$timeout = $existing_user->company()->default_password_timeout;
|
||||
|
||||
if($timeout == 0)
|
||||
$timeout = 30*60*1000*1000;
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
Cache::put($existing_user->hashed_id.'_'.$existing_user->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
$this->setLoginCache($existing_user);
|
||||
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', auth()->user()->id);
|
||||
@ -384,10 +372,68 @@ class LoginController extends BaseController
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
|
||||
}
|
||||
|
||||
//If this is a result user/email combo - lets add their OAuth details details
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
|
||||
{
|
||||
Auth::login($existing_login_user, true);
|
||||
$existing_login_user->setCompany($existing_login_user->account->default_company);
|
||||
|
||||
$this->setLoginCache($existing_login_user);
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
]);
|
||||
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', auth()->user()->id);
|
||||
|
||||
$cu->first()->account->companies->each(function ($company) use($cu){
|
||||
|
||||
if($company->tokens()->where('is_system', true)->count() == 0)
|
||||
{
|
||||
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
|
||||
}
|
||||
});
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
|
||||
//check the user doesn't already exist in some form
|
||||
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
|
||||
{
|
||||
Auth::login($existing_login_user, true);
|
||||
$existing_login_user->setCompany($existing_login_user->account->default_company);
|
||||
|
||||
$this->setLoginCache($existing_login_user);
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
]);
|
||||
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', auth()->user()->id);
|
||||
|
||||
$cu->first()->account->companies->each(function ($company) use($cu){
|
||||
|
||||
if($company->tokens()->where('is_system', true)->count() == 0)
|
||||
{
|
||||
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
|
||||
}
|
||||
});
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
}
|
||||
|
||||
|
||||
//user not found anywhere - lets sign them up.
|
||||
$name = OAuth::splitName($google->harvestName($user));
|
||||
|
||||
$new_account = [
|
||||
@ -403,21 +449,14 @@ class LoginController extends BaseController
|
||||
|
||||
MultiDB::setDefaultDatabase();
|
||||
|
||||
$account = CreateAccount::dispatchNow($new_account);
|
||||
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
|
||||
|
||||
Auth::login($account->default_company->owner(), true);
|
||||
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
$timeout = auth()->user()->company()->default_password_timeout;
|
||||
|
||||
if($timeout == 0)
|
||||
$timeout = 30*60*1000*1000;
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
$this->setLoginCache(auth()->user());
|
||||
|
||||
$cu = CompanyUser::whereUserId(auth()->user()->id);
|
||||
|
||||
@ -437,4 +476,62 @@ class LoginController extends BaseController
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
public function redirectToProvider(string $provider)
|
||||
{
|
||||
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
|
||||
$scopes = [];
|
||||
|
||||
if($provider == 'google'){
|
||||
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
|
||||
}
|
||||
|
||||
if (request()->has('code')) {
|
||||
return $this->handleProviderCallback($provider);
|
||||
} else {
|
||||
return Socialite::driver($provider)->scopes($scopes)->redirect();
|
||||
}
|
||||
}
|
||||
|
||||
public function handleProviderCallback(string $provider)
|
||||
{
|
||||
$socialite_user = Socialite::driver($provider)
|
||||
->stateless()
|
||||
->user();
|
||||
|
||||
// if($user = OAuth::handleAuth($socialite_user, $provider))
|
||||
// {
|
||||
// Auth::login($user, true);
|
||||
|
||||
// return redirect($this->redirectTo);
|
||||
// }
|
||||
// else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
|
||||
// {
|
||||
// Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations
|
||||
|
||||
// return view('auth.login');
|
||||
// }
|
||||
// else {
|
||||
// //todo
|
||||
// $name = OAuth::splitName($socialite_user->getName());
|
||||
|
||||
// $new_account = [
|
||||
// 'first_name' => $name[0],
|
||||
// 'last_name' => $name[1],
|
||||
// 'password' => '',
|
||||
// 'email' => $socialite_user->getEmail(),
|
||||
// 'oauth_user_id' => $socialite_user->getId(),
|
||||
// 'oauth_provider_id' => $provider
|
||||
// ];
|
||||
|
||||
// $account = CreateAccount::dispatchNow($new_account);
|
||||
|
||||
// Auth::login($account->default_company->owner(), true);
|
||||
|
||||
// $cookie = cookie('db', $account->default_company->db);
|
||||
|
||||
// return redirect($this->redirectTo)->withCookie($cookie);
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ class BaseController extends Controller
|
||||
*/
|
||||
public function notFoundClient()
|
||||
{
|
||||
return abort(404);
|
||||
abort(404, 'Page not found in client portal.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,10 +309,6 @@ class BaseController extends Controller
|
||||
},
|
||||
'company.tax_rates' => function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('tax_rates.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.vendors'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents');
|
||||
@ -323,15 +319,9 @@ class BaseController extends Controller
|
||||
},
|
||||
'company.expense_categories'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('expense_categories.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.task_statuses'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
|
||||
|
||||
},
|
||||
'company.activities'=> function ($query) use($user) {
|
||||
|
||||
|
@ -68,7 +68,7 @@ class DocumentController extends Controller
|
||||
|
||||
$documents->map(function ($document) {
|
||||
if (auth()->user('contact')->client->id != $document->documentable->id) {
|
||||
abort(401);
|
||||
abort(401, 'Permission denied');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,7 +31,7 @@ class EntityViewController extends Controller
|
||||
public function index(string $entity_type, string $invitation_key)
|
||||
{
|
||||
if (! in_array($entity_type, $this->entity_types)) {
|
||||
abort(404);
|
||||
abort(404, 'Entity not found');
|
||||
}
|
||||
|
||||
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type));
|
||||
@ -91,7 +91,7 @@ class EntityViewController extends Controller
|
||||
public function handlePassword(string $entity_type, string $invitation_key)
|
||||
{
|
||||
if (! in_array($entity_type, $this->entity_types)) {
|
||||
abort(404);
|
||||
abort(404, 'Entity not found');
|
||||
}
|
||||
|
||||
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type));
|
||||
|
@ -57,7 +57,7 @@ class InvitationController extends Controller
|
||||
/* Return early if we have the correct client_hash embedded */
|
||||
|
||||
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
|
||||
auth()->guard('contact')->login($invitation->contact, true);
|
||||
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
|
||||
|
||||
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
|
||||
|
||||
@ -66,7 +66,7 @@ class InvitationController extends Controller
|
||||
return redirect()->route('client.login');
|
||||
|
||||
} else {
|
||||
auth()->guard('contact')->login($invitation->contact, true);
|
||||
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -149,6 +149,6 @@ class PaymentMethodController extends Controller
|
||||
return $gateway = auth()->user()->client->getBankTransferGateway();
|
||||
}
|
||||
|
||||
return abort(404);
|
||||
abort(404, 'Gateway not found.');
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
use App\Transformers\CompanyUserTransformer;
|
||||
use App\Transformers\UserTransformer;
|
||||
use App\Utils\Traits\User\LoginCache;
|
||||
use Google_Client;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@ -24,6 +25,7 @@ use Illuminate\Support\Str;
|
||||
|
||||
class ConnectedAccountController extends BaseController
|
||||
{
|
||||
use LoginCache;
|
||||
|
||||
protected $entity_type = User::class;
|
||||
|
||||
@ -113,8 +115,7 @@ class ConnectedAccountController extends BaseController
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
$timeout = auth()->user()->company()->default_password_timeout;
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
$this->setLoginCache(auth()->user());
|
||||
|
||||
return $this->itemResponse(auth()->user());
|
||||
|
||||
|
@ -93,11 +93,16 @@ class LoginController extends BaseController
|
||||
public function redirectToProvider(string $provider)
|
||||
{
|
||||
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
|
||||
//
|
||||
$scopes = [];
|
||||
|
||||
if($provider == 'google'){
|
||||
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
|
||||
}
|
||||
|
||||
if (request()->has('code')) {
|
||||
return $this->handleProviderCallback($provider);
|
||||
} else {
|
||||
return Socialite::driver($provider)->scopes()->redirect();
|
||||
return Socialite::driver($provider)->scopes($scopes)->redirect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,43 +236,5 @@ class LoginController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Received the returning object from the provider
|
||||
* which we will use to resolve the user, we return the response in JSON format
|
||||
*
|
||||
* @return json
|
||||
|
||||
public function handleProviderCallbackApiUser(string $provider)
|
||||
{
|
||||
$socialite_user = Socialite::driver($provider)->stateless()->user();
|
||||
|
||||
if($user = OAuth::handleAuth($socialite_user, $provider))
|
||||
{
|
||||
return $this->itemResponse($user);
|
||||
}
|
||||
else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
|
||||
{
|
||||
|
||||
return $this->errorResponse(['message'=>'User exists in system, but not with this authentication method'], 400);
|
||||
|
||||
}
|
||||
else {
|
||||
//todo
|
||||
$name = OAuth::splitName($socialite_user->getName());
|
||||
|
||||
$new_account = [
|
||||
'first_name' => $name[0],
|
||||
'last_name' => $name[1],
|
||||
'password' => '',
|
||||
'email' => $socialite_user->getEmail(),
|
||||
];
|
||||
|
||||
$account = CreateAccount::dispatchNow($new_account);
|
||||
|
||||
return $this->itemResponse($account->default_company->owner());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
64
app/Http/Controllers/ImportJsonController.php
Normal file
64
app/Http/Controllers/ImportJsonController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?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\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Export\StoreExportRequest;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ImportJsonController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/import_json",
|
||||
* operationId="getImportJson",
|
||||
* tags={"import"},
|
||||
* summary="Import data from the system",
|
||||
* description="Import data from the system",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function index(StoreExportRequest $request)
|
||||
{
|
||||
|
||||
// CompanyExport::dispatch(auth()->user()->getCompany(), auth()->user());
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
|
||||
}
|
||||
}
|
@ -845,13 +845,11 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
|
||||
{
|
||||
|
||||
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
|
||||
|
||||
try {
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
} catch (\Exception $e) {
|
||||
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -631,6 +631,7 @@ class TaskController extends BaseController
|
||||
|
||||
$task_status = TaskStatus::where('id', $this->decodePrimaryKey($task_status_hashed_id))
|
||||
->where('company_id', auth()->user()->company()->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
$task_status->status_order = $key;
|
||||
@ -643,19 +644,14 @@ class TaskController extends BaseController
|
||||
|
||||
$sort_status_id = $this->decodePrimaryKey($key);
|
||||
|
||||
// nlog($task_list);
|
||||
|
||||
foreach ($task_list as $key => $task)
|
||||
{
|
||||
|
||||
// nlog($task);
|
||||
|
||||
$task_record = Task::where('id', $this->decodePrimaryKey($task))
|
||||
->where('company_id', auth()->user()->company()->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
// nlog($task_record->id);
|
||||
|
||||
$task_record->status_order = $key;
|
||||
$task_record->status_id = $sort_status_id;
|
||||
$task_record->save();
|
||||
|
@ -211,11 +211,12 @@ class UserController extends BaseController
|
||||
|
||||
$ct = CreateCompanyToken::dispatchNow($company, $user, $user_agent);
|
||||
|
||||
nlog("in the store method of the usercontroller class");
|
||||
|
||||
event(new UserWasCreated($user, auth()->user(), $company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
return $this->itemResponse($user->fresh());
|
||||
$user->setCompany($company);
|
||||
$user->company_id = $company->id;
|
||||
|
||||
return $this->itemResponse($user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@ use App\Http\Middleware\Authenticate;
|
||||
use App\Http\Middleware\CheckClientExistence;
|
||||
use App\Http\Middleware\CheckForMaintenanceMode;
|
||||
use App\Http\Middleware\ClientPortalEnabled;
|
||||
use App\Http\Middleware\ContactAccount;
|
||||
use App\Http\Middleware\ContactKeyLogin;
|
||||
use App\Http\Middleware\ContactRegister;
|
||||
use App\Http\Middleware\ContactSetDb;
|
||||
@ -141,6 +142,7 @@ class Kernel extends HttpKernel
|
||||
'api_secret_check' => ApiSecretCheck::class,
|
||||
'contact_token_auth' => ContactTokenAuth::class,
|
||||
'contact_db' => ContactSetDb::class,
|
||||
'contact_account' => ContactAccount::class,
|
||||
'domain_db' => SetDomainNameDb::class,
|
||||
'email_db' => SetEmailDb::class,
|
||||
'invite_db' => SetInviteDb::class,
|
||||
@ -182,5 +184,6 @@ class Kernel extends HttpKernel
|
||||
PasswordProtection::class,
|
||||
Locale::class,
|
||||
SubstituteBindings::class,
|
||||
ContactAccount::class,
|
||||
];
|
||||
}
|
||||
|
@ -181,14 +181,16 @@ class BillingPortalPurchase extends Component
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$contact = ClientContact::where('email', $this->email)->first();
|
||||
$contact = ClientContact::where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
if ($contact && $this->steps['existing_user'] === false) {
|
||||
return $this->steps['existing_user'] = true;
|
||||
}
|
||||
|
||||
if ($contact && $this->steps['existing_user']) {
|
||||
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password]);
|
||||
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
|
||||
|
||||
return $attempt
|
||||
? $this->getPaymentMethods($contact)
|
||||
|
41
app/Http/Middleware/ContactAccount.php
Normal file
41
app/Http/Middleware/ContactAccount.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?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\Http\Middleware;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Utils\Ninja;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContactAccount
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
if(!Ninja::isHosted()) {
|
||||
|
||||
$account_id = Account::first()->id;
|
||||
$request->attributes->add(['account_id' => $account_id]);
|
||||
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ use Auth;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ContactKeyLogin
|
||||
{
|
||||
@ -42,6 +43,9 @@ class ContactKeyLogin
|
||||
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
|
||||
|
||||
if($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()){
|
||||
if(empty($client_contact->email))
|
||||
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
|
||||
|
||||
Auth::guard('contact')->login($client_contact, true);
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
@ -49,6 +53,10 @@ class ContactKeyLogin
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if(empty($client_contact->email))
|
||||
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
|
||||
|
||||
auth()->guard('contact')->login($client_contact, true);
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
@ -56,19 +64,36 @@ class ContactKeyLogin
|
||||
if (MultiDB::findAndSetDbByClientHash($request->input('client_hash'))) {
|
||||
|
||||
if($client = Client::where('client_hash', $request->input('client_hash'))->first()){
|
||||
auth()->guard('contact')->login($client->primary_contact()->first(), true);
|
||||
|
||||
$primary_contact = $client->primary_contact()->first();
|
||||
|
||||
if(empty($primary_contact->email))
|
||||
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
|
||||
|
||||
auth()->guard('contact')->login($primary_contact, true);
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
}
|
||||
} elseif ($request->has('client_hash')) {
|
||||
if ($client = Client::where('client_hash', $request->input('client_hash'))->first()) {
|
||||
Auth::guard('contact')->login($client->primary_contact()->first(), true);
|
||||
|
||||
$primary_contact = $client->primary_contact()->first();
|
||||
|
||||
if(empty($primary_contact->email))
|
||||
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
|
||||
|
||||
auth()->guard('contact')->login($primary_contact, true);
|
||||
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
|
||||
$contact_email = Cache::get($request->segment(3));
|
||||
if($client_contact = ClientContact::where('email', $contact_email)->first()){
|
||||
Auth::guard('contact')->login($client_contact, true);
|
||||
|
||||
if(empty($client_contact->email))
|
||||
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
|
||||
|
||||
auth()->guard('contact')->login($client_contact, true);
|
||||
|
||||
if ($request->query('redirect') && !empty($request->query('redirect'))) {
|
||||
return redirect()->to($request->query('redirect'));
|
||||
|
@ -52,6 +52,6 @@ class ContactRegister
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return abort(404);
|
||||
abort(404, 'ContactRegister Middlware');
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,11 @@ class SetDomainNameDb
|
||||
'portal_mode' => 'subdomain',
|
||||
];
|
||||
|
||||
if(!MultiDB::findAndSetDbByDomain($query)){
|
||||
if($company = MultiDB::findAndSetDbByDomain($query)){
|
||||
$request->attributes->add(['account_id' => $company->account_id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($request->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
@ -66,7 +70,11 @@ class SetDomainNameDb
|
||||
'portal_mode' => 'domain',
|
||||
];
|
||||
|
||||
if(!MultiDB::findAndSetDbByDomain($query)){
|
||||
if($company = MultiDB::findAndSetDbByDomain($query)){
|
||||
$request->attributes->add(['account_id' => $company->account_id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($request->json) {
|
||||
return response()->json($error, 403);
|
||||
} else {
|
||||
|
@ -49,7 +49,6 @@ class TokenAuth
|
||||
| us to decouple a $user and their attached companies completely.
|
||||
|
|
||||
*/
|
||||
$user->setCompany($company_token->company);
|
||||
|
||||
app('queue')->createPayloadUsing(function () use ($company_token) {
|
||||
return ['db' => $company_token->company->db];
|
||||
@ -67,6 +66,7 @@ class TokenAuth
|
||||
|
||||
//stateless, don't remember the user.
|
||||
auth()->login($user, false);
|
||||
auth()->user()->setCompany($company_token->company);
|
||||
|
||||
} else {
|
||||
$error = [
|
||||
|
@ -40,13 +40,13 @@ class CreateAccountRequest extends Request
|
||||
'password' => 'required|string|min:6',
|
||||
'email' => 'bail|required|email:rfc,dns',
|
||||
'email' => new NewUniqueUserRule(),
|
||||
'privacy_policy' => 'required',
|
||||
'terms_of_service' => 'required',
|
||||
'privacy_policy' => 'required|boolean',
|
||||
'terms_of_service' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
{nlog($this->all());
|
||||
$input = $this->all();
|
||||
|
||||
$input['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||
|
@ -53,6 +53,6 @@ class RegisterRequest extends FormRequest
|
||||
return $company;
|
||||
}
|
||||
|
||||
abort(404);
|
||||
abort(404, 'Register request not found.');
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +45,6 @@ class UpdateUserRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
// if (isset($input['company_user']) && ! auth()->user()->isAdmin()) {
|
||||
// unset($input['company_user']);
|
||||
// }
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
@ -57,13 +57,13 @@ class InvoiceBalanceSanity implements Rule
|
||||
private function checkIfInvoiceBalanceIsSane() : bool
|
||||
{
|
||||
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$this->invoice = Invoice::on(config('database.default'))->find($this->invoice->id);
|
||||
$this->invoice->line_items = $this->input['line_items'];
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
$temp_invoice = $this->invoice->calc()->getTempEntity();
|
||||
|
||||
DB::rollBack();
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
if($temp_invoice->balance < 0){
|
||||
$this->message = 'Invoice balance cannot go negative';
|
||||
|
@ -20,6 +20,7 @@ use Illuminate\Contracts\Validation\Rule;
|
||||
*/
|
||||
class AttachableUser implements Rule
|
||||
{
|
||||
public $message;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -39,7 +40,7 @@ class AttachableUser implements Rule
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return "Cannot add the same user to the same company";
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,9 +64,16 @@ class AttachableUser implements Rule
|
||||
->where('company_id', auth()->user()->company()->id)
|
||||
->exists();
|
||||
|
||||
if($user_already_attached)
|
||||
//If the user is already attached or isn't link to this account - return false
|
||||
if($user_already_attached) {
|
||||
$this->message = ctrans('texts.user_duplicate_error');
|
||||
return false;
|
||||
}
|
||||
|
||||
if($user->account_id != auth()->user()->account_id){
|
||||
$this->message = ctrans('texts.user_cross_linked_error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -25,11 +25,13 @@ use App\Jobs\Util\VersionCheck;
|
||||
use App\Mail\Admin\AccountCreatedObject;
|
||||
use App\Mail\Admin\VerifyUserObject;
|
||||
use App\Models\Account;
|
||||
use App\Models\Timezone;
|
||||
use App\Notifications\Ninja\NewAccountCreated;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
@ -40,9 +42,12 @@ class CreateAccount
|
||||
|
||||
protected $request;
|
||||
|
||||
public function __construct(array $sp660339)
|
||||
protected $client_ip;
|
||||
|
||||
public function __construct(array $sp660339, $client_ip)
|
||||
{
|
||||
$this->request = $sp660339;
|
||||
$this->client_ip = $client_ip;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -74,6 +79,7 @@ class CreateAccount
|
||||
|
||||
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
|
||||
$sp035a66->load('account');
|
||||
$sp035a66->settings = $this->processSettings($sp035a66->settings);
|
||||
$sp794f3f->default_company_id = $sp035a66->id;
|
||||
$sp794f3f->save();
|
||||
|
||||
@ -107,4 +113,53 @@ class CreateAccount
|
||||
|
||||
return $sp794f3f;
|
||||
}
|
||||
|
||||
private function processSettings($settings)
|
||||
{
|
||||
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();
|
||||
|
||||
if ($currency) {
|
||||
$settings->currency_id = (string)$currency->id;
|
||||
}
|
||||
|
||||
$country = Cache::get('countries')->filter(function ($item) use ($country_code) {
|
||||
return strtolower($item->iso_3166_2) == $country_code || strtolower($item->iso_3166_3) == $country_code;
|
||||
})->first();
|
||||
|
||||
if ($country) {
|
||||
$settings->country_id = (string)$country->id;
|
||||
}
|
||||
|
||||
$language = Cache::get('languages')->filter(function ($item) use ($currency_code) {
|
||||
return strtolower($item->locale) == $currency_code;
|
||||
})->first();
|
||||
|
||||
if ($language) {
|
||||
$settings->language_id = (string)$language->id;
|
||||
}
|
||||
|
||||
$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
|
||||
|
||||
if($timezone) {
|
||||
$settings->timezone_id = (string)$timezone->id;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -101,7 +101,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $activity;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
$this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){
|
||||
|
||||
@ -114,43 +114,56 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $backup;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user){
|
||||
|
||||
$user->account_id = $this->encodePrimaryKey($user->account_id);
|
||||
$user->id = $this->encodePrimaryKey($user->id);
|
||||
// $user->id = $this->encodePrimaryKey($user->id);
|
||||
|
||||
return $user;
|
||||
return $user->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact){
|
||||
|
||||
$client_contact = $this->transformArrayOfKeys($client_contact, ['id', 'company_id', 'user_id',' client_id']);
|
||||
$client_contact = $this->transformArrayOfKeys($client_contact, ['company_id', 'user_id', 'client_id']);
|
||||
|
||||
return $client_contact;
|
||||
return $client_contact->makeVisible([
|
||||
'password',
|
||||
'remember_token',
|
||||
'user_id',
|
||||
'company_id',
|
||||
'client_id',
|
||||
'google_2fa_secret',
|
||||
'id',
|
||||
'oauth_provider_id',
|
||||
'oauth_user_id',
|
||||
'token',
|
||||
'hashed_id',
|
||||
]);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['client_gateway_tokens'] = $this->company->client_gateway_tokens->map(function ($client_gateway_token){
|
||||
|
||||
$client_gateway_token = $this->transformArrayOfKeys($client_gateway_token, ['id', 'company_id', 'client_id']);
|
||||
$client_gateway_token = $this->transformArrayOfKeys($client_gateway_token, ['company_id', 'client_id']);
|
||||
|
||||
return $client_gateway_token;
|
||||
return $client_gateway_token->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['clients'] = $this->company->clients->map(function ($client){
|
||||
|
||||
$client = $this->transformArrayOfKeys($client, ['id', 'company_id', 'user_id',' assigned_user_id', 'group_settings_id']);
|
||||
$client = $this->transformArrayOfKeys($client, ['company_id', 'user_id', 'assigned_user_id', 'group_settings_id']);
|
||||
|
||||
return $client;
|
||||
return $client->makeVisible(['id','private_notes','user_id','company_id','last_login','hashed_id']);
|
||||
|
||||
})->all();
|
||||
|
||||
})->toArray();
|
||||
|
||||
$this->export_data['company'] = $this->company->toArray();
|
||||
|
||||
@ -159,9 +172,9 @@ class CompanyExport implements ShouldQueue
|
||||
$company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']);
|
||||
$company_gateway->config = decrypt($company_gateway->config);
|
||||
|
||||
return $company_gateway;
|
||||
return $company_gateway->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['company_tokens'] = $this->company->tokens->map(function ($token){
|
||||
|
||||
@ -169,7 +182,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $token;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger){
|
||||
|
||||
@ -177,7 +190,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $ledger;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){
|
||||
|
||||
@ -185,43 +198,44 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $company_user;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['credits'] = $this->company->credits->map(function ($credit){
|
||||
|
||||
$credit = $this->transformBasicEntities($credit);
|
||||
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']);
|
||||
|
||||
return $credit;
|
||||
return $credit->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){
|
||||
|
||||
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
|
||||
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'credit_id']);
|
||||
|
||||
return $credit;
|
||||
return $credit->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->toArray();
|
||||
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all();
|
||||
|
||||
$this->export_data['documents'] = $this->company->documents->map(function ($document){
|
||||
$this->export_data['documents'] = $this->company->all_documents->map(function ($document){
|
||||
|
||||
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id']);
|
||||
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id','documentable_id']);
|
||||
$document->hashed_id = $this->encodePrimaryKey($document->id);
|
||||
|
||||
return $document;
|
||||
return $document->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['expense_categories'] = $this->company->expenses->map(function ($expense_category){
|
||||
$this->export_data['expense_categories'] = $this->company->expense_categories->map(function ($expense_category){
|
||||
|
||||
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
|
||||
|
||||
return $expense_category;
|
||||
return $expense_category->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['expenses'] = $this->company->expenses->map(function ($expense){
|
||||
@ -229,17 +243,17 @@ class CompanyExport implements ShouldQueue
|
||||
$expense = $this->transformBasicEntities($expense);
|
||||
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']);
|
||||
|
||||
return $expense;
|
||||
return $expense->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs){
|
||||
|
||||
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']);
|
||||
|
||||
return $gs;
|
||||
return $gs->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['invoices'] = $this->company->invoices->map(function ($invoice){
|
||||
@ -247,18 +261,22 @@ class CompanyExport implements ShouldQueue
|
||||
$invoice = $this->transformBasicEntities($invoice);
|
||||
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
|
||||
return $invoice;
|
||||
return $invoice->makeVisible(['id',
|
||||
'private_notes',
|
||||
'user_id',
|
||||
'client_id',
|
||||
'company_id',]);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){
|
||||
|
||||
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
|
||||
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'invoice_id']);
|
||||
|
||||
return $invoice;
|
||||
return $invoice->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term){
|
||||
|
||||
@ -266,61 +284,65 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $term;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
$this->export_data['paymentables'] = $this->company->payments()->with('paymentables')->cursor()->map(function ($paymentable){
|
||||
|
||||
$paymentable = $this->transformArrayOfKeys($paymentable, ['payment_id','paymentable_id']);
|
||||
|
||||
return $paymentable;
|
||||
|
||||
})->toArray();
|
||||
|
||||
$this->export_data['payments'] = $this->company->payments->map(function ($payment){
|
||||
|
||||
$payment = $this->transformBasicEntities($payment);
|
||||
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
|
||||
|
||||
return $payment;
|
||||
$payment->paymentables = $this->transformPaymentable($payment);
|
||||
|
||||
})->toArray();
|
||||
return $payment->makeVisible(['id']);
|
||||
|
||||
})->all();
|
||||
|
||||
$this->export_data['products'] = $this->company->products->map(function ($product){
|
||||
|
||||
$product = $this->transformBasicEntities($product);
|
||||
$product = $this->transformArrayOfKeys($product, ['vendor_id','project_id']);
|
||||
|
||||
return $product->makeVisible(['id']);
|
||||
|
||||
})->all();
|
||||
|
||||
$this->export_data['projects'] = $this->company->projects->map(function ($project){
|
||||
|
||||
$project = $this->transformBasicEntities($project);
|
||||
$project = $this->transformArrayOfKeys($project, ['client_id']);
|
||||
|
||||
return $project;
|
||||
return $project->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['quotes'] = $this->company->quotes->map(function ($quote){
|
||||
|
||||
$quote = $this->transformBasicEntities($quote);
|
||||
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
|
||||
return $quote;
|
||||
return $quote->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){
|
||||
|
||||
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
|
||||
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'quote_id']);
|
||||
|
||||
return $quote;
|
||||
return $quote->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){
|
||||
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->makeVisible(['id'])->map(function ($ri){
|
||||
|
||||
$ri = $this->transformBasicEntities($ri);
|
||||
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
return $ri;
|
||||
|
||||
})->toArray();
|
||||
return $ri->makeVisible(['id']);
|
||||
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($ri){
|
||||
@ -329,16 +351,22 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $ri;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){
|
||||
|
||||
$subscription = $this->transformBasicEntities($subscription);
|
||||
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id);
|
||||
|
||||
return $subscription;
|
||||
return $subscription->makeVisible([ 'id',
|
||||
'user_id',
|
||||
'assigned_user_id',
|
||||
'company_id',
|
||||
'product_ids',
|
||||
'recurring_product_ids',
|
||||
'group_id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['system_logs'] = $this->company->system_logs->map(function ($log){
|
||||
@ -348,16 +376,16 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $log;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
$this->export_data['tasks'] = $this->company->tasks->map(function ($task){
|
||||
|
||||
$task = $this->transformBasicEntities($task);
|
||||
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
|
||||
|
||||
return $task;
|
||||
return $task->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status){
|
||||
|
||||
@ -367,7 +395,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $status;
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate){
|
||||
|
||||
@ -376,13 +404,13 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $rate;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){
|
||||
|
||||
return $this->transformBasicEntities($vendor);
|
||||
return $this->transformBasicEntities($vendor)->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['vendor_contacts'] = VendorContact::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($vendor){
|
||||
@ -390,9 +418,9 @@ class CompanyExport implements ShouldQueue
|
||||
$vendor = $this->transformBasicEntities($vendor);
|
||||
$vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id);
|
||||
|
||||
return $vendor;
|
||||
return $vendor->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->all();
|
||||
|
||||
$this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook){
|
||||
|
||||
@ -401,7 +429,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $hook;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
//write to tmp and email to owner();
|
||||
|
||||
@ -413,7 +441,7 @@ class CompanyExport implements ShouldQueue
|
||||
private function transformBasicEntities($model)
|
||||
{
|
||||
|
||||
return $this->transformArrayOfKeys($model, ['id', 'user_id', 'assigned_user_id', 'company_id']);
|
||||
return $this->transformArrayOfKeys($model, ['user_id', 'assigned_user_id', 'company_id']);
|
||||
|
||||
}
|
||||
|
||||
@ -428,40 +456,48 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function transformPaymentable($payment)
|
||||
{
|
||||
|
||||
$new_arr = [];
|
||||
|
||||
foreach($payment->paymentables as $paymentable)
|
||||
{
|
||||
|
||||
$paymentable->payment_id = $this->encodePrimaryKey($paymentable->payment_id);
|
||||
$paymentable->paymentable_id = $this->encodePrimaryKey($paymentable->paymentable_id);
|
||||
|
||||
$new_arr[] = $paymentable;
|
||||
}
|
||||
|
||||
return $new_arr;
|
||||
|
||||
}
|
||||
|
||||
private function zipAndSend()
|
||||
{
|
||||
|
||||
$tempStream = fopen('php://memory', 'w+');
|
||||
|
||||
$options = new Archive();
|
||||
$options->setOutputStream($tempStream);
|
||||
|
||||
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
|
||||
|
||||
$zip = new ZipStream($file_name, $options);
|
||||
$zip_path = public_path('storage/backups/'.$file_name);
|
||||
$zip = new \ZipArchive();
|
||||
|
||||
$fp = tmpfile();
|
||||
fwrite($fp, json_encode($this->export_data));
|
||||
rewind($fp);
|
||||
$zip->addFileFromStream('backup.json', $fp);
|
||||
if ($zip->open($zip_path, \ZipArchive::CREATE)!==TRUE) {
|
||||
nlog("cannot open {$zip_path}");
|
||||
}
|
||||
|
||||
$zip->finish();
|
||||
|
||||
$path = 'backups/';
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream);
|
||||
|
||||
fclose($tempStream);
|
||||
$zip->addFromString("backup.json", json_encode($this->export_data));
|
||||
$zip->close();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company);
|
||||
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company);
|
||||
$nmo->to_user = $this->user;
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
|
||||
UnlinkFile::dispatch(config('filesystems.default'), 'backups/'.$file_name)->delay(now()->addHours(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Jobs\Company;
|
||||
|
||||
use App\Exceptions\ImportCompanyFailed;
|
||||
use App\Exceptions\NonExistingMigrationFile;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
@ -19,6 +20,7 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\DownloadBackup;
|
||||
use App\Mail\DownloadInvoices;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
@ -63,22 +65,29 @@ class CompanyImport implements ShouldQueue
|
||||
'users',
|
||||
// 'payment_terms',
|
||||
// 'tax_rates',
|
||||
// 'expense_categories',
|
||||
// 'task_statuses',
|
||||
// 'clients',
|
||||
// 'company_gateways',
|
||||
// 'client_gateway_tokens',
|
||||
// 'client_contacts',
|
||||
// 'products',
|
||||
// 'vendors',
|
||||
// 'projects',
|
||||
// 'products',
|
||||
// 'company_gateways',
|
||||
// 'client_gateway_tokens',
|
||||
// 'group_settings',
|
||||
// 'credits',
|
||||
// 'invoices',
|
||||
// 'recurring_invoices',
|
||||
// 'quotes',
|
||||
// 'payments',
|
||||
// 'expense_categories',
|
||||
// 'task_statuses',
|
||||
// 'subscriptions',
|
||||
// 'expenses',
|
||||
// 'tasks',
|
||||
// 'documents',
|
||||
// 'webhooks',
|
||||
// 'system_logs',
|
||||
// 'company_ledger',
|
||||
// 'backups',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -168,6 +177,9 @@ class CompanyImport implements ShouldQueue
|
||||
foreach ($this->backup_file->users as $user)
|
||||
{
|
||||
|
||||
if(User::where('email', $user->email)->where('account_id', '!=', $this->account->id)->exists())
|
||||
throw new ImportCompanyFailed("{$user->email} is already in the system attached to a different account");
|
||||
|
||||
$new_user = User::firstOrNew(
|
||||
['email' => $user->email],
|
||||
(array)$user,
|
||||
@ -176,10 +188,33 @@ class CompanyImport implements ShouldQueue
|
||||
$new_user->account_id = $this->account->id;
|
||||
$new_user->save(['timestamps' => false]);
|
||||
|
||||
$this->ids['users']["{$user->id}"] = $new_user->id;
|
||||
$this->ids['users']["{$user->hashed_id}"] = $new_user->id;
|
||||
|
||||
}
|
||||
|
||||
Expense::reguard();
|
||||
User::reguard();
|
||||
|
||||
}
|
||||
|
||||
private function importCompanyUsers()
|
||||
{
|
||||
CompanyUser::unguard();
|
||||
|
||||
foreach($this->backup_file->company_users as $cu)
|
||||
{
|
||||
$user_id = $this->transformId($cu->user_id);
|
||||
|
||||
$new_cu = CompanyUser::firstOrNew(
|
||||
['user_id' => $user_id, 'company_id', $this->company->id],
|
||||
(array)$cu,
|
||||
);
|
||||
|
||||
$new_cu->account_id = $this->account->id;
|
||||
$new_cu->save(['timestamps' => false]);
|
||||
|
||||
}
|
||||
|
||||
CompanyUser::reguard();
|
||||
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,9 @@
|
||||
namespace App\Jobs\Company;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Http\Request;
|
||||
@ -60,6 +62,12 @@ class CreateCompany
|
||||
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
|
||||
$company->custom_fields = new \stdClass;
|
||||
$company->default_password_timeout = 1800000;
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
else
|
||||
$company->subdomain = '';
|
||||
|
||||
$company->save();
|
||||
|
||||
return $company;
|
||||
|
101
app/Jobs/Cron/AutoBillCron.php
Normal file
101
app/Jobs/Cron/AutoBillCron.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?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\Jobs\Cron;
|
||||
|
||||
use App\Jobs\RecurringInvoice\SendRecurring;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class AutoBillCron
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() : void
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
/* Get all invoices where the send date is less than NOW + 30 minutes() */
|
||||
nlog("Performing Autobilling ".Carbon::now()->format('Y-m-d h:i:s'));
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
|
||||
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('balance', '>', 0)
|
||||
->with('company')
|
||||
->cursor()->each(function ($invoice){
|
||||
$this->runAutoBiller($invoice);
|
||||
});
|
||||
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('balance', '>', 0)
|
||||
->with('company')
|
||||
->cursor()->each(function ($invoice){
|
||||
$this->runAutoBiller($invoice);
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('balance', '>', 0)
|
||||
->with('company')
|
||||
->cursor()->each(function ($invoice){
|
||||
$this->runAutoBiller($invoice);
|
||||
});
|
||||
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('auto_bill_enabled', true)
|
||||
->where('balance', '>', 0)
|
||||
->with('company')
|
||||
->cursor()->each(function ($invoice){
|
||||
$this->runAutoBiller($invoice);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function runAutoBiller(Invoice $invoice)
|
||||
{
|
||||
nlog("Firing autobill for {$invoice->company_id} - {$invoice->number}");
|
||||
$invoice->service()->autoBill()->save();
|
||||
}
|
||||
}
|
@ -293,6 +293,9 @@ class CSVImport implements ShouldQueue {
|
||||
],
|
||||
];
|
||||
|
||||
/* Make sure we don't apply any payments to invoices with a Zero Amount*/
|
||||
if($invoice->amount > 0)
|
||||
{
|
||||
$payment_repository->save(
|
||||
$payment_data,
|
||||
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
|
||||
@ -300,6 +303,7 @@ class CSVImport implements ShouldQueue {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository );
|
||||
}
|
||||
|
@ -47,9 +47,9 @@ class NinjaMailerJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
|
||||
public $tries = 5; //number of retries
|
||||
public $tries = 3; //number of retries
|
||||
|
||||
public $backoff = 5; //seconds to wait until retry
|
||||
public $backoff = 10; //seconds to wait until retry
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
@ -63,6 +63,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
{
|
||||
|
||||
$this->nmo = $nmo;
|
||||
$this->override = $override;
|
||||
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
//send email
|
||||
try {
|
||||
nlog("trying to send");
|
||||
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
|
||||
|
||||
Mail::to($this->nmo->to_user->email)
|
||||
->send($this->nmo->mailable);
|
||||
|
@ -30,6 +30,7 @@ use App\Factory\TaxRateFactory;
|
||||
use App\Factory\UserFactory;
|
||||
use App\Factory\VendorFactory;
|
||||
use App\Http\Requests\Company\UpdateCompanyRequest;
|
||||
use App\Http\ValidationRules\User\AttachableUser;
|
||||
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
||||
use App\Http\ValidationRules\ValidUserForCompany;
|
||||
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
@ -210,8 +211,8 @@ class Import implements ShouldQueue
|
||||
$this->{$method}($data[$import]);
|
||||
}
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$this->processNinjaTokens($data['ninja_tokens']);
|
||||
// if(Ninja::isHosted() && array_key_exists('ninja_tokens', $data))
|
||||
// $this->processNinjaTokens($data['ninja_tokens']);
|
||||
|
||||
$this->setInitialCompanyLedgerBalances();
|
||||
|
||||
@ -225,6 +226,7 @@ class Import implements ShouldQueue
|
||||
->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
@ -296,6 +298,12 @@ class Import implements ShouldQueue
|
||||
|
||||
$data = $this->transformCompanyData($data);
|
||||
|
||||
if(Ninja::isHosted() && strlen($data['subdomain']) > 1) {
|
||||
|
||||
if(!MultiDB::checkDomainAvailable($data['subdomain']))
|
||||
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
|
||||
}
|
||||
|
||||
$rules = (new UpdateCompanyRequest())->rules();
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
@ -419,13 +427,10 @@ class Import implements ShouldQueue
|
||||
$rules = [
|
||||
'*.first_name' => ['string'],
|
||||
'*.last_name' => ['string'],
|
||||
'*.email' => ['distinct'],
|
||||
//'*.email' => ['distinct'],
|
||||
'*.email' => ['distinct', 'email', new ValidUserForCompany()],
|
||||
];
|
||||
|
||||
// if (config('ninja.db.multi_db_enabled')) {
|
||||
// array_push($rules['*.email'], new ValidUserForCompany());
|
||||
// }
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@ -1647,53 +1652,65 @@ class Import implements ShouldQueue
|
||||
|
||||
private function buildNewUserPlan()
|
||||
{
|
||||
$local_company = Company::find($this->company->id);
|
||||
$owner = $local_company->owner();
|
||||
$current_db = config('database.default');
|
||||
|
||||
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
|
||||
nlog($this->company);
|
||||
|
||||
$local_company = Company::on($current_db)->where('company_key', $this->company->company_key)->first();
|
||||
|
||||
MultiDB::setDb('db-ninja-01');
|
||||
$ninja_company = Company::find(config('ninja.ninja_default_company_id'));
|
||||
|
||||
/* If we already have a record of this user - move along. */
|
||||
if($client_contact = ClientContact::on('db-ninja-01')->where(['email' => $owner->email, 'company_id' => $ninja_company->id])->exists())
|
||||
if($client_contact = ClientContact::where(['email' => $this->user->email, 'company_id' => $ninja_company->id])->first())
|
||||
return $client_contact->client;
|
||||
|
||||
$ninja_client = ClientFactory::create($ninja_company->id, $ninja_company->owner()->id);
|
||||
$ninja_client->setConnection('db-ninja-01');
|
||||
$ninja_client->name = $owner->present()->name();
|
||||
$ninja_client->name = $this->user->present()->name();
|
||||
$ninja_client->address1 = $local_company->settings->address1;
|
||||
$ninja_client->address2 = $local_company->settings->address2;
|
||||
$ninja_client->city = $local_company->settings->city;
|
||||
$ninja_client->postal_code = $local_company->settings->postal_code;
|
||||
$ninja_client->state = $local_company->settings->state;
|
||||
$ninja_client->country_id = $local_company->settings->country_id;
|
||||
$ninja_client->custom_value1 = $local_company->company_key;
|
||||
|
||||
$ninja_client->save();
|
||||
|
||||
$ninja_client_contact = ClientContactFactory::create($ninja_company->id, $ninja_company->owner()->id);
|
||||
$ninja_client_contact->setConnection('db-ninja-01');
|
||||
$ninja_client_contact->first_name = $owner->first_name;
|
||||
$ninja_client_contact->last_name = $owner->last_name;
|
||||
$ninja_client_contact->first_name = $this->user->first_name;
|
||||
$ninja_client_contact->last_name = $this->user->last_name;
|
||||
$ninja_client_contact->client_id = $ninja_client->id;
|
||||
$ninja_client_contact->email = $owner->email;
|
||||
$ninja_client_contact->phone = $owner->phone;
|
||||
$ninja_client_contact->email = $this->user->email;
|
||||
$ninja_client_contact->phone = $this->user->phone;
|
||||
$ninja_client_contact->save();
|
||||
|
||||
|
||||
MultiDB::setDb($current_db);
|
||||
|
||||
return $ninja_client;
|
||||
}
|
||||
|
||||
private function processNinjaTokens(array $data)
|
||||
{
|
||||
if(count($data) == 0)
|
||||
$current_db = config('database.default');
|
||||
$local_company = Company::on($current_db)->where('company_key', $this->company->company_key)->first();
|
||||
|
||||
MultiDB::setDb('db-ninja-01');
|
||||
|
||||
if($existing_client = Client::where('custom_value1', $local_company->company_key)->first())
|
||||
$ninja_client = $existing_client;
|
||||
else
|
||||
$ninja_client = $this->buildNewUserPlan();
|
||||
|
||||
foreach($data as $token)
|
||||
{
|
||||
//get invoiceninja company_id
|
||||
$ninja_company = Company::on('db-ninja-01')->where('id', config('ninja.ninja_default_company_id'))->first();
|
||||
$ninja_company = Company::where('id', config('ninja.ninja_default_company_id'))->first();
|
||||
|
||||
$token['company_id'] = $ninja_client->company_id;
|
||||
$token['client_id'] = $ninja_client->id;
|
||||
$token['user_id'] = $ninja_client->user_id;
|
||||
$token['company_id'] = $ninja_company->id;
|
||||
$token['client_id'] = $ninja_client->id;/////
|
||||
$token['user_id'] = $ninja_company->owner()->id;
|
||||
$token['company_gateway_id'] = config('ninja.ninja_default_company_gateway_id');
|
||||
//todo
|
||||
|
||||
@ -1702,8 +1719,10 @@ class Import implements ShouldQueue
|
||||
ClientGatewayToken::reguard();
|
||||
}
|
||||
|
||||
MultiDB::setDb($current_db);
|
||||
}
|
||||
|
||||
|
||||
/* In V4 we use negative invoices (credits) and add then into the client balance. In V5, these sit off ledger and are applied later.
|
||||
This next section will check for credit balances and reduce the client balance so that the V5 balances are correct
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@ use App\Jobs\Entity\EmailEntity;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesReminders;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@ -25,7 +26,7 @@ use Illuminate\Support\Carbon;
|
||||
|
||||
class ReminderJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -39,40 +40,41 @@ class ReminderJob implements ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
|
||||
//always make sure you have set the company as this command is being
|
||||
//run from the console so we have no awareness of the DB.
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->processReminders();
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->processReminders($db);
|
||||
$this->processReminders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processReminders($db = null)
|
||||
private function processReminders()
|
||||
{
|
||||
Invoice::where('next_send_date', Carbon::today()->format('Y-m-d'))->with('invitations')->cursor()->each(function ($invoice) {
|
||||
|
||||
if ($invoice->isPayable()) {
|
||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||
$invoice->service()->touchReminder($reminder_template)->save();
|
||||
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
||||
nlog("Firing email for invoice {$invoice->number}");
|
||||
nlog("Firing reminder email for invoice {$invoice->number}");
|
||||
});
|
||||
|
||||
if ($invoice->invitations->count() > 0) {
|
||||
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
|
||||
}
|
||||
|
||||
$invoice->service()->setReminder()->save();
|
||||
|
||||
} else {
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\MigrationFailed;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@ -139,7 +140,11 @@ class StartMigration implements ShouldQueue
|
||||
$this->company->update_products = $update_product_flag;
|
||||
$this->company->save();
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $e->getMessage()));
|
||||
|
||||
if(Ninja::isHosted())
|
||||
app('sentry')->captureException($e);
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $this->company, $e->getMessage()));
|
||||
|
||||
if (app()->environment() !== 'production') {
|
||||
info($e->getMessage());
|
||||
|
@ -52,37 +52,38 @@ class MultiDB
|
||||
|
||||
public static function checkDomainAvailable($subdomain) : bool
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return Company::whereSubdomain($subdomain)->get()->count() == 0;
|
||||
}
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
//multi-db active
|
||||
foreach (self::$dbs as $db) {
|
||||
if (Company::on($db)->whereSubdomain($subdomain)->get()->count() >= 1) {
|
||||
self::setDb($current_db);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//self::setDefaultDatabase();
|
||||
self::setDb($current_db);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function checkUserEmailExists($email) : bool
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return User::where(['email' => $email])->get()->count() >= 1 ?? false; // true >= 1 emails found / false -> == emails found
|
||||
}
|
||||
|
||||
//multi-db active
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail
|
||||
self::setDb($current_db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDb($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -102,19 +103,21 @@ class MultiDB
|
||||
*/
|
||||
public static function checkUserAndCompanyCoExist($email, $company_key) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail
|
||||
if (Company::on($db)->where(['company_key' => $company_key])->get()->count() >= 1) {
|
||||
if (User::on($db)->where(['email' => $email])->exists()) {
|
||||
if (Company::on($db)->where(['company_key' => $company_key])->exists()) {
|
||||
self::setDb($current_db);
|
||||
return true;
|
||||
} else {
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDb($current_db);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDb($current_db);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -125,20 +128,21 @@ class MultiDB
|
||||
*/
|
||||
public static function hasUser(array $data) : ?User
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return User::where($data)->withTrashed()->first();
|
||||
}
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
|
||||
self::setDB($db);
|
||||
|
||||
if ($user = User::where($data)->withTrashed()->first())
|
||||
if ($user = User::where($data)->withTrashed()->first()) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDb($current_db);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -149,125 +153,139 @@ class MultiDB
|
||||
*/
|
||||
public static function hasContact(string $email) : ?ClientContact
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return ClientContact::where('email', $email)->withTrashed()->first();
|
||||
}
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
|
||||
$user = ClientContact::on($db)->where('email', $email)->withTrashed()->first();
|
||||
|
||||
if ($user) {
|
||||
self::setDB($db);
|
||||
self::setDb($db);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDB($current_db);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function contactFindAndSetDb($token) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($ct = ClientContact::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) {
|
||||
self::setDb($ct->company->db);
|
||||
|
||||
self::setDb($db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function userFindAndSetDb($email) : bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
//multi-db active
|
||||
foreach (self::$dbs as $db) {
|
||||
|
||||
if (User::on($db)->where('email', $email)->count() >= 1){
|
||||
nlog("setting db {$db}");
|
||||
self::setDb($db);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDB($current_db);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDb($token) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($ct = CompanyToken::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) {
|
||||
self::setDb($ct->company->db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByCompanyKey($company_key) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($company = Company::on($db)->where('company_key', $company_key)->first()) {
|
||||
self::setDb($company->db);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByContactKey($contact_key) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($client_contact = ClientContact::on($db)->where('contact_key', $contact_key)->first()) {
|
||||
self::setDb($client_contact->company->db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByClientHash($client_hash) :bool
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($client = Client::on($db)->where('client_hash', $client_hash)->first()) {
|
||||
self::setDb($client->company->db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self::setDefaultDatabase();
|
||||
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByDomain($query_array) :bool
|
||||
public static function findAndSetDbByDomain($query_array)
|
||||
{
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return (Company::where($query_array)->exists() === true);
|
||||
return (Company::where($query_array)->first());
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($company = Company::on($db)->where($query_array)->first()) {
|
||||
self::setDb($company->db);
|
||||
return true;
|
||||
return $company;
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -275,20 +293,48 @@ class MultiDB
|
||||
public static function findAndSetDbByInvitation($entity, $invitation_key)
|
||||
{
|
||||
$class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
$current_db = config('database.default');
|
||||
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->first()) {
|
||||
self::setDb($db);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
self::setDefaultDatabase();
|
||||
self::setDB($current_db);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function randomSubdomainGenerator()
|
||||
{
|
||||
$current_db = config('database.default');
|
||||
|
||||
do {
|
||||
$length = 8;
|
||||
$string = '';
|
||||
$vowels = array("a","e","i","o","u");
|
||||
$consonants = array(
|
||||
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm',
|
||||
'n', 'p', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
|
||||
);
|
||||
|
||||
$max = $length / 2;
|
||||
for ($i = 1; $i <= $max; $i++)
|
||||
{
|
||||
$string .= $consonants[rand(0,19)];
|
||||
$string .= $vowels[rand(0,4)];
|
||||
}
|
||||
}
|
||||
while(!self::checkDomainAvailable($string));
|
||||
|
||||
self::setDb($current_db);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $database
|
||||
*/
|
||||
|
@ -8,21 +8,23 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MigrationFailed extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
public $exception;
|
||||
public $content;
|
||||
|
||||
public $settings;
|
||||
public $company;
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param $content
|
||||
* @param $exception
|
||||
*/
|
||||
public function __construct($exception, $content = null)
|
||||
public function __construct($exception, $company, $content = null)
|
||||
{
|
||||
$this->exception = $exception;
|
||||
$this->content = $content;
|
||||
$this->settings = $company->settings;
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,6 +35,6 @@ class MigrationFailed extends Mailable
|
||||
public function build()
|
||||
{
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->view('email.migration.failed');
|
||||
->view('email.migration.failed', ['settings' => $this->settings, 'company' => $this->company]);
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ class SupportMessageSent extends Mailable
|
||||
|
||||
$subject = "Customer MSG {$user->present()->name} - [{$plan} - DB:{$company->db}]";
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name')) //todo this needs to be fixed to handle the hosted version
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->replyTo($user->email, $user->present()->name())
|
||||
->subject($subject)
|
||||
->markdown('email.support.message', [
|
||||
'message' => $this->message,
|
||||
|
@ -103,6 +103,15 @@ class Activity extends StaticModel
|
||||
'deleted_at' => 'timestamp',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'hashed_id',
|
||||
];
|
||||
|
||||
public function getHashedIdAttribute()
|
||||
{
|
||||
return $this->encodePrimaryKey($this->id);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -36,10 +36,6 @@ class BaseModel extends Model
|
||||
use UserSessionAttributes;
|
||||
use HasFactory;
|
||||
|
||||
//todo customise names of archived_at / updated_at columns
|
||||
///const CREATED_AT = 'creation_date';
|
||||
//const UPDATED_AT = 'last_update';
|
||||
|
||||
protected $appends = [
|
||||
'hashed_id',
|
||||
];
|
||||
|
@ -40,7 +40,6 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
'private_notes',
|
||||
'user_id',
|
||||
'company_id',
|
||||
// 'settings',
|
||||
'last_login',
|
||||
];
|
||||
|
||||
|
@ -131,6 +131,11 @@ class Company extends BaseModel
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
public function all_documents()
|
||||
{
|
||||
return $this->HasMany(Document::class);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -71,10 +71,6 @@ class Quote extends BaseModel
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
// 'custom_surcharge_tax1',
|
||||
// 'custom_surcharge_tax2',
|
||||
// 'custom_surcharge_tax3',
|
||||
// 'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
|
@ -159,8 +159,6 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
*/
|
||||
public function setCompany($company)
|
||||
{
|
||||
// config(['ninja.company_id' => $company->id]);
|
||||
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
@ -170,16 +168,17 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
public function getCompany()
|
||||
{
|
||||
|
||||
if (request()->header('X-API-TOKEN')) {
|
||||
$company_token = CompanyToken::with(['company'])->whereRaw('BINARY `token`= ?', [request()->header('X-API-TOKEN')])->first();
|
||||
|
||||
return $company_token->company;
|
||||
}
|
||||
elseif ($this->company){
|
||||
if ($this->company){
|
||||
|
||||
return $this->company;
|
||||
|
||||
}
|
||||
elseif (request()->header('X-API-TOKEN')) {
|
||||
$company_token = CompanyToken::with(['company'])->whereRaw('BINARY `token`= ?', [request()->header('X-API-TOKEN')])->first();
|
||||
|
||||
return $company_token->company;
|
||||
}
|
||||
|
||||
|
||||
// return false;
|
||||
throw new \Exception('No Company Found');
|
||||
@ -408,7 +407,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
$nmo->settings = $this->account->default_company->settings;
|
||||
$nmo->company = $this->account->default_company;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
//$this->notify(new ResetPasswordNotification($token));
|
||||
}
|
||||
|
@ -39,10 +39,6 @@ class CompanyObserver
|
||||
|
||||
if(Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain'))
|
||||
{
|
||||
nlog('company observer - updated');
|
||||
nlog($company->portal_domain);
|
||||
nlog($company->getOriginal('portal_domain'));
|
||||
|
||||
//fire event to build new custom portal domain
|
||||
\Modules\Admin\Jobs\Domain\CustomDomain::dispatch($company->getOriginal('portal_domain'), $company)->onQueue('domain');
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ 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');
|
||||
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
$this->initializeOmnipayGateway();
|
||||
|
||||
$response = $this->omnipay_gateway
|
||||
->completePurchase(['amount' => $this->payment_hash->data->amount])
|
||||
->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
|
||||
->send();
|
||||
|
||||
if ($response->isCancelled()) {
|
||||
@ -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->invoice_number);
|
||||
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->number);
|
||||
})->toArray()),
|
||||
'transactionId' => $this->payment_hash->hash . '-' . time(),
|
||||
'ButtonSource' => 'InvoiceNinja_SP',
|
||||
|
@ -193,7 +193,8 @@ class ACH
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->stripe->client
|
||||
$this->stripe->client,
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
|
||||
|
@ -82,7 +82,7 @@ class ImportCustomers
|
||||
}
|
||||
|
||||
nlog("inserting a customer");
|
||||
nlog($customer);
|
||||
//nlog($customer);
|
||||
|
||||
$client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id);
|
||||
|
||||
|
@ -21,6 +21,6 @@ trait Utilities
|
||||
|
||||
public function convertToStripeAmount($amount, $precision)
|
||||
{
|
||||
return $amount * pow(10, $precision);
|
||||
return (int)($amount * pow(10, $precision));
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
if ($this->company_gateway->require_billing_address) {
|
||||
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'sometimes'];
|
||||
// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable'];
|
||||
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||
@ -197,7 +197,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
if ($this->company_gateway->require_shipping_address) {
|
||||
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes'];
|
||||
// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes'];
|
||||
$fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
@ -390,6 +390,13 @@ class StripePaymentDriver extends BaseDriver
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
if ($request->type == 'charge.succeeded') {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
// charge.failed, charge.refunded
|
||||
|
||||
return response([], 200);
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Middleware\SetDomainNameDb;
|
||||
use App\Models\Account;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
@ -24,10 +24,10 @@ use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\Proposal;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Observers\AccountObserver;
|
||||
use App\Observers\SubscriptionObserver;
|
||||
use App\Observers\ClientObserver;
|
||||
use App\Observers\CompanyGatewayObserver;
|
||||
use App\Observers\CompanyObserver;
|
||||
@ -39,8 +39,10 @@ use App\Observers\PaymentObserver;
|
||||
use App\Observers\ProductObserver;
|
||||
use App\Observers\ProposalObserver;
|
||||
use App\Observers\QuoteObserver;
|
||||
use App\Observers\SubscriptionObserver;
|
||||
use App\Observers\TaskObserver;
|
||||
use App\Observers\UserObserver;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Queue\Events\JobProcessing;
|
||||
@ -49,6 +51,7 @@ use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -93,6 +96,15 @@ class AppServiceProvider extends ServiceProvider
|
||||
Task::observe(TaskObserver::class);
|
||||
User::observe(UserObserver::class);
|
||||
|
||||
|
||||
/* Handles setting the correct database with livewire classes */
|
||||
if(Ninja::isHosted())
|
||||
{
|
||||
Livewire::addPersistentMiddleware([
|
||||
SetDomainNameDb::class,
|
||||
]);
|
||||
}
|
||||
|
||||
// Queue::before(function (JobProcessing $event) {
|
||||
// // \Log::info('Event Job '.$event->connectionName);
|
||||
// \Log::error('Event Job '.$event->job->getJobId);
|
||||
|
@ -169,10 +169,14 @@ class BaseRepository
|
||||
*/
|
||||
protected function alternativeSave($data, $model)
|
||||
{
|
||||
|
||||
if (array_key_exists('client_id', $data)) //forces the client_id if it doesn't exist
|
||||
//forces the client_id if it doesn't exist
|
||||
if(array_key_exists('client_id', $data))
|
||||
$model->client_id = $data['client_id'];
|
||||
|
||||
//pickup changes here to recalculate reminders
|
||||
if($model instanceof Invoice && ($model->isDirty('date') || $model->isDirty('due_date')))
|
||||
$model->service()->setReminder()->save();
|
||||
|
||||
$client = Client::where('id', $model->client_id)->withTrashed()->first();
|
||||
|
||||
$state = [];
|
||||
@ -189,7 +193,7 @@ class BaseRepository
|
||||
$data = array_merge($company_defaults, $data);
|
||||
}
|
||||
|
||||
$tmp_data = $data; //preserves the $data arrayss
|
||||
$tmp_data = $data; //preserves the $data array
|
||||
|
||||
/* We need to unset some variable as we sometimes unguard the model */
|
||||
if (isset($tmp_data['invitations']))
|
||||
@ -302,6 +306,10 @@ class BaseRepository
|
||||
/* Perform model specific tasks */
|
||||
if ($model instanceof Invoice) {
|
||||
|
||||
nlog("Finished amount = " . $state['finished_amount']);
|
||||
nlog("Starting amount = " . $state['starting_amount']);
|
||||
nlog("Diff = " . ($state['finished_amount'] - $state['starting_amount']));
|
||||
|
||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||
|
||||
$model->service()->updateStatus()->save();
|
||||
|
@ -114,7 +114,7 @@ class UserRepository extends BaseRepository
|
||||
}
|
||||
$user->restore();
|
||||
|
||||
return $user;
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
public function destroy(array $data, User $user)
|
||||
|
@ -40,6 +40,8 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
public function run()
|
||||
{
|
||||
$is_partial = false;
|
||||
|
||||
/* Is the invoice payable? */
|
||||
if (! $this->invoice->isPayable())
|
||||
return $this->invoice;
|
||||
@ -57,6 +59,8 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
/* Determine $amount */
|
||||
if ($this->invoice->partial > 0) {
|
||||
$is_partial = true;
|
||||
$invoice_total = $this->invoice->amount;
|
||||
$amount = $this->invoice->partial;
|
||||
} elseif ($this->invoice->balance > 0) {
|
||||
$amount = $this->invoice->balance;
|
||||
@ -77,6 +81,9 @@ class AutoBillInvoice extends AbstractService
|
||||
//$fee = $gateway_token->gateway->calcGatewayFee($amount, $gateway_token->gateway_type_id, $this->invoice->uses_inclusive_taxes);
|
||||
$this->invoice = $this->invoice->service()->addGatewayFee($gateway_token->gateway, $gateway_token->gateway_type_id, $amount)->save();
|
||||
|
||||
if($is_partial)
|
||||
$fee = $this->invoice->amount - $invoice_total;
|
||||
else
|
||||
$fee = $this->invoice->amount - $amount;
|
||||
|
||||
/* Build payment hash */
|
||||
@ -340,68 +347,4 @@ class AutoBillInvoice extends AbstractService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any existing unpaid gateway fees
|
||||
* due to previous payment failure.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
// private function purgeStaleGatewayFees()
|
||||
// {
|
||||
// $starting_amount = $this->invoice->amount;
|
||||
|
||||
// $line_items = $this->invoice->line_items;
|
||||
|
||||
// $new_items = [];
|
||||
|
||||
// foreach($line_items as $item)
|
||||
// {
|
||||
|
||||
// if($item->type_id != 3)
|
||||
// $new_items[] = $item;
|
||||
|
||||
// }
|
||||
|
||||
// $this->invoice->line_items = $new_items;
|
||||
// $this->invoice->save();
|
||||
|
||||
// $this->invoice = $this->invoice->calc()->getInvoice();
|
||||
|
||||
// if($starting_amount != $this->invoice->amount && $this->invoice->status_id != Invoice::STATUS_DRAFT){
|
||||
// $this->invoice->client->service()->updateBalance($this->invoice->amount - $starting_amount)->save();
|
||||
// $this->invoice->ledger()->updateInvoiceBalance($this->invoice->amount - $starting_amount, 'Invoice balance updated after stale gateway fee removed')->save();
|
||||
// }
|
||||
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Checks whether a given gateway token is able
|
||||
// * to process the payment after passing through the
|
||||
// * fees and limits check.
|
||||
// *
|
||||
// * @param CompanyGateway $cg The CompanyGateway instance
|
||||
// * @param float $amount The amount to be paid
|
||||
// * @return bool
|
||||
// */
|
||||
// public function validGatewayLimits($cg, $amount) : bool
|
||||
// {
|
||||
// if (isset($cg->fees_and_limits)) {
|
||||
// $fees_and_limits = $cg->fees_and_limits->{'1'};
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $amount < $fees_and_limits->min_limit) {
|
||||
// info("amount {$amount} less than ".$fees_and_limits->min_limit);
|
||||
// $passes = false;
|
||||
// } elseif ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $amount > $fees_and_limits->max_limit) {
|
||||
// info("amount {$amount} greater than ".$fees_and_limits->max_limit);
|
||||
// $passes = false;
|
||||
// } else {
|
||||
// $passes = true;
|
||||
// }
|
||||
|
||||
// return $passes;
|
||||
// }
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateInvitations extends AbstractService
|
||||
{
|
||||
|
@ -21,6 +21,8 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Task;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Services\Invoice\UpdateReminder;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -244,6 +246,13 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReminder($settings = null)
|
||||
{
|
||||
$this->invoice = (new UpdateReminder($this->invoice, $settings))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->invoice->status_id = $status;
|
||||
@ -301,6 +310,10 @@ class InvoiceService
|
||||
//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');
|
||||
|
||||
if(Ninja::isHosted()) {
|
||||
Storage::disk('public')->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,6 @@ class MarkSent extends AbstractService
|
||||
|
||||
$this->invoice->markInvitationsSent();
|
||||
|
||||
$this->invoice->setReminder();
|
||||
|
||||
$this->invoice
|
||||
->service()
|
||||
->setStatus(Invoice::STATUS_SENT)
|
||||
@ -48,6 +46,7 @@ class MarkSent extends AbstractService
|
||||
->setDueDate()
|
||||
->updateBalance($this->invoice->amount)
|
||||
->deletePdf()
|
||||
->setReminder()
|
||||
->save();
|
||||
|
||||
$this->client->service()->updateBalance($this->invoice->balance)->save();
|
||||
|
131
app/Services/Invoice/UpdateReminder.php
Normal file
131
app/Services/Invoice/UpdateReminder.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?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\Services\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class UpdateReminder extends AbstractService
|
||||
{
|
||||
public $invoice;
|
||||
|
||||
public $settings;
|
||||
|
||||
public function __construct(Invoice $invoice, $settings = null)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
if (! $this->settings) {
|
||||
$this->settings = $this->invoice->client->getMergedSettings();
|
||||
}
|
||||
|
||||
if (! $this->invoice->isPayable()) {
|
||||
$this->invoice->next_send_date = null;
|
||||
$this->invoice->save();
|
||||
|
||||
return $this->invoice; //exit early
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
$this->invoice->next_send_date = $date_collection->sort()->first();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ class GetInvoicePdf extends AbstractService
|
||||
|
||||
$file_path = $path.$this->entity->hashed_id.'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
$disk = 'public';
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
@ -49,12 +49,6 @@ class GetInvoicePdf extends AbstractService
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
|
||||
/* Copy from remote disk to local when using cloud file storage. */
|
||||
if(config('filesystems.default') == 's3')
|
||||
return TempFile::path(Storage::disk($disk)->url($file_path));
|
||||
|
||||
// return Storage::disk($disk)->url($file_path);
|
||||
return Storage::disk($disk)->path($file_path);
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class CreditTransformer extends EntityTransformer
|
||||
'po_number' => $credit->po_number ?: '',
|
||||
'date' => $credit->date ?: '',
|
||||
'last_sent_date' => $credit->last_sent_date ?: '',
|
||||
'next_send_date' => $credit->date ?: '',
|
||||
'next_send_date' => $credit->next_send_date ?: '',
|
||||
'reminder1_sent' => $credit->reminder1_sent ?: '',
|
||||
'reminder2_sent' => $credit->reminder2_sent ?: '',
|
||||
'reminder3_sent' => $credit->reminder3_sent ?: '',
|
||||
|
@ -114,7 +114,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||
'po_number' => $invoice->po_number ?: '',
|
||||
'date' => $invoice->date ?: '',
|
||||
'last_sent_date' => $invoice->last_sent_date ?: '',
|
||||
'next_send_date' => $invoice->date ?: '',
|
||||
'next_send_date' => $invoice->next_send_date ?: '',
|
||||
'due_date' => $invoice->due_date ?: '',
|
||||
'terms' => $invoice->terms ?: '',
|
||||
'public_notes' => $invoice->public_notes ?: '',
|
||||
|
@ -108,7 +108,7 @@ class QuoteTransformer extends EntityTransformer
|
||||
'po_number' => $quote->po_number ?: '',
|
||||
'date' => $quote->date ?: '',
|
||||
'last_sent_date' => $quote->last_sent_date ?: '',
|
||||
'next_send_date' => $quote->date ?: '',
|
||||
'next_send_date' => $quote->next_send_date ?: '',
|
||||
'reminder1_sent' => $quote->reminder1_sent ?: '',
|
||||
'reminder2_sent' => $quote->reminder2_sent ?: '',
|
||||
'reminder3_sent' => $quote->reminder3_sent ?: '',
|
||||
|
@ -118,6 +118,8 @@ class Phantom
|
||||
|
||||
$finfo = new \finfo(FILEINFO_MIME);
|
||||
|
||||
nlog($pdf);
|
||||
|
||||
if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
|
||||
{
|
||||
SystemLogger::dispatch(
|
||||
|
@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Queue;
|
||||
class SystemHealth
|
||||
{
|
||||
private static $extensions = [
|
||||
'mysqli',
|
||||
// 'mysqli',
|
||||
'gd',
|
||||
'curl',
|
||||
'zip',
|
||||
@ -34,7 +34,7 @@ class SystemHealth
|
||||
'mbstring',
|
||||
'xml',
|
||||
'bcmath',
|
||||
'mysqlnd',
|
||||
// 'mysqlnd',
|
||||
//'intl', //todo double check whether we need this for email dns validation
|
||||
];
|
||||
|
||||
|
@ -19,97 +19,6 @@ use Illuminate\Support\Carbon;
|
||||
*/
|
||||
trait MakesReminders
|
||||
{
|
||||
public function setReminder($settings = null)
|
||||
{
|
||||
if (! $settings) {
|
||||
$settings = $this->client->getMergedSettings();
|
||||
}
|
||||
|
||||
if (! $this->isPayable()) {
|
||||
$this->next_send_date = null;
|
||||
$this->save();
|
||||
|
||||
return; //exit early
|
||||
}
|
||||
|
||||
$date_collection = collect();
|
||||
|
||||
if ($settings->schedule_reminder1 == 'after_invoice_date' &&
|
||||
$settings->num_days_reminder1 > 0) {
|
||||
$reminder_date = Carbon::parse($this->date)->addDays($settings->num_days_reminder1);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder1 == 'before_due_date' &&
|
||||
$settings->num_days_reminder1 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->subDays($settings->num_days_reminder1);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder1 == 'after_due_date' &&
|
||||
$settings->num_days_reminder1 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->addDays($settings->num_days_reminder1);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder2 == 'after_invoice_date' &&
|
||||
$settings->num_days_reminder2 > 0) {
|
||||
$reminder_date = Carbon::parse($this->date)->addDays($settings->num_days_reminder2);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder2 == 'before_due_date' &&
|
||||
$settings->num_days_reminder2 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->subDays($settings->num_days_reminder2);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder2 == 'after_due_date' &&
|
||||
$settings->num_days_reminder2 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->addDays($settings->num_days_reminder2);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder3 == 'after_invoice_date' &&
|
||||
$settings->num_days_reminder3 > 0) {
|
||||
$reminder_date = Carbon::parse($this->date)->addDays($settings->num_days_reminder3);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder3 == 'before_due_date' &&
|
||||
$settings->num_days_reminder3 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->subDays($settings->num_days_reminder3);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
if ($settings->schedule_reminder3 == 'after_due_date' &&
|
||||
$settings->num_days_reminder3 > 0) {
|
||||
$reminder_date = Carbon::parse($this->due_date)->addDays($settings->num_days_reminder3);
|
||||
|
||||
if ($reminder_date->gt(Carbon::parse($this->next_send_date)));
|
||||
$date_collection->push($reminder_date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
$this->next_send_date = $date_collection->sort()->first();
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function inReminderWindow($schedule_reminder, $num_days_reminder)
|
||||
{
|
||||
@ -177,10 +86,8 @@ trait MakesReminders
|
||||
|
||||
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
|
||||
{
|
||||
if (!$date) {
|
||||
if (!$date)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
switch ($endless_reminder_frequency_id) {
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
|
35
app/Utils/Traits/User/LoginCache.php
Normal file
35
app/Utils/Traits/User/LoginCache.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\Utils\Traits\User;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait LoginCache
|
||||
{
|
||||
|
||||
public function setLoginCache($user)
|
||||
{
|
||||
|
||||
$timeout = $user->company()->default_password_timeout;
|
||||
|
||||
if($timeout == 0)
|
||||
$timeout = 30*60*1000*1000;
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
Cache::put($user->hashed_id.'_'.$user->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
}
|
||||
|
||||
}
|
113
config/livewire.php
Normal file
113
config/livewire.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Namespace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the root namespace for Livewire component classes in
|
||||
| your application. This value affects component auto-discovery and
|
||||
| any Livewire file helper commands, like `artisan make:livewire`.
|
||||
|
|
||||
| After changing this item, run: `php artisan livewire:discover`.
|
||||
|
|
||||
*/
|
||||
|
||||
'class_namespace' => 'App\\Http\\Livewire',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| View Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path for Livewire component views. This affects
|
||||
| file manipulation helper commands like `artisan make:livewire`.
|
||||
|
|
||||
*/
|
||||
|
||||
'view_path' => resource_path('views/livewire'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Layout
|
||||
|--------------------------------------------------------------------------
|
||||
| The default layout view that will be used when rendering a component via
|
||||
| Route::get('/some-endpoint', SomeComponent::class);. In this case the
|
||||
| the view returned by SomeComponent will be wrapped in "layouts.app"
|
||||
|
|
||||
*/
|
||||
|
||||
'layout' => 'layouts.app',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Assets URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path to Livewire JavaScript assets, for cases where
|
||||
| your app's domain root is not the correct path. By default, Livewire
|
||||
| will load its JavaScript assets from the app's "relative root".
|
||||
|
|
||||
| Examples: "/assets", "myurl.com/app".
|
||||
|
|
||||
*/
|
||||
|
||||
'asset_url' => env('ASSETS_URL', config('app.url')),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Endpoint Middleware Group
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the middleware group that will be applied to the main
|
||||
| Livewire "message" endpoint (the endpoint that gets hit everytime
|
||||
| a Livewire component updates). It is set to "web" by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware_group' => 'web',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Temporary File Uploads Endpoint Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||
| before the file is validated and stored permanently. All file uploads
|
||||
| are directed to a global endpoint for temporary storage. The config
|
||||
| items below are used for customizing the way the endpoint works.
|
||||
|
|
||||
*/
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => null, // Example: 'local', 's3' Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => null, // Example: 'tmp' Default 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs.
|
||||
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||
],
|
||||
'max_upload_time' => 5, // Max duration (in minutes) before an upload gets invalidated.
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Manifest File Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path to the Livewire manifest file.
|
||||
| The default should work for most cases (which is
|
||||
| "<app_root>/bootstrap/cache/livewire-components.php)", but for specific
|
||||
| cases like when hosting on Laravel Vapor, it could be set to a different value.
|
||||
|
|
||||
| Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php".
|
||||
|
|
||||
*/
|
||||
|
||||
'manifest_path' => null,
|
||||
|
||||
];
|
@ -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.65',
|
||||
'app_tag' => '5.1.65-release',
|
||||
'app_version' => '5.1.66',
|
||||
'app_tag' => '5.1.66-release',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
38
database/factories/TaxRateFactory.php
Normal file
38
database/factories/TaxRateFactory.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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 Database\Factories;
|
||||
|
||||
use App\Models\TaxRate;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TaxRateFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = TaxRate::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->word(3),
|
||||
'rate' => rand(1,20)
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Document;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MakeDocumentsAssignedUserNullable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table){
|
||||
$table->unsignedInteger('assigned_user_id')->nullable()->change();
|
||||
});
|
||||
|
||||
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
|
||||
|
||||
if(config('ninja.db.multi_db_enabled')){
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
Document::on($db)->where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
|
||||
}
|
||||
}
|
||||
else{
|
||||
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
1
public/css/admin.css
vendored
Normal file
1
public/css/admin.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -3,9 +3,9 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"version.json": "ea1781094b87723b953889a712b1feba",
|
||||
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"main.dart.js": "f46c892fb39ce4151ac09e5794745701",
|
||||
"main.dart.js": "55df523d1b81bba3d88e7b77511c7a87",
|
||||
"/": "23224b5e03519aaa87594403d54412cf",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",
|
||||
|
1
public/js/admin.js
vendored
Normal file
1
public/js/admin.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
(()=>{var r,e={847:()=>{},113:()=>{}},o={};function n(r){var t=o[r];if(void 0!==t)return t.exports;var a=o[r]={exports:{}};return e[r](a,a.exports,n),a.exports}n.m=e,r=[],n.O=(e,o,t,a)=>{if(!o){var v=1/0;for(p=0;p<r.length;p++){for(var[o,t,a]=r[p],l=!0,i=0;i<o.length;i++)(!1&a||v>=a)&&Object.keys(n.O).every((r=>n.O[r](o[i])))?o.splice(i--,1):(l=!1,a<v&&(v=a));l&&(r.splice(p--,1),e=t())}return e}a=a||0;for(var p=r.length;p>0&&r[p-1][2]>a;p--)r[p]=r[p-1];r[p]=[o,t,a]},n.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={467:0,703:0};n.O.j=e=>0===r[e];var e=(e,o)=>{var t,a,[v,l,i]=o,p=0;for(t in l)n.o(l,t)&&(n.m[t]=l[t]);if(i)var f=i(n);for(e&&e(o);p<v.length;p++)a=v[p],n.o(r,a)&&r[a]&&r[a][0](),r[v[p]]=0;return n.O(f)},o=self.webpackChunk=self.webpackChunk||[];o.forEach(e.bind(null,0)),o.push=e.bind(null,o.push.bind(o))})(),n.O(void 0,[703],(()=>n(847)));var t=n.O(void 0,[703],(()=>n(113)));t=n.O(t)})();
|
214752
public/main.dart.js
vendored
214752
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
216566
public/main.foss.dart.js
vendored
216566
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
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=987a5ab343fc0d5c6cba",
|
||||
"/css/app.css": "/css/app.css?id=14a824656f32eec8c2b1",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
|
4
public/vendor/livewire/livewire.js
vendored
4
public/vendor/livewire/livewire.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/livewire.js.map
vendored
2
public/vendor/livewire/livewire.js.map
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
2
public/vendor/livewire/manifest.json
vendored
@ -1 +1 @@
|
||||
{"/livewire.js":"/livewire.js?id=d9e06c155e467adb5de2"}
|
||||
{"/livewire.js":"/livewire.js?id=54d078b2ce39327a1702"}
|
@ -1 +1 @@
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.48","build_number":"48"}
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.51","build_number":"51"}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user