mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 15:14:33 -04:00
commit
b244555fec
@ -1 +1 @@
|
||||
5.1.65
|
||||
5.1.67
|
@ -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++) {
|
||||
|
@ -12,12 +12,12 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\Ninja\SendReminders;
|
||||
use App\Jobs\Util\WebHookHandler;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
|
||||
class SendRemindersCron extends Command
|
||||
{
|
||||
@ -54,8 +54,8 @@ class SendRemindersCron extends Command
|
||||
{
|
||||
SendReminders::dispatchNow();
|
||||
|
||||
$this->webHookOverdueInvoices();
|
||||
$this->webHookExpiredQuotes();
|
||||
$this->webHookOverdueInvoices();
|
||||
$this->webHookExpiredQuotes();
|
||||
}
|
||||
|
||||
private function webHookOverdueInvoices()
|
||||
@ -90,6 +90,7 @@ class SendRemindersCron extends Command
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
|
||||
|
||||
});
|
||||
|
||||
$quotes = Quote::where('is_deleted', 0)
|
||||
|
@ -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
|
||||
@ -245,8 +245,8 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $hide_paid_to_date = false; //@TODO where?
|
||||
public $embed_documents = false; //@TODO where?
|
||||
public $all_pages_header = false; //@implemented
|
||||
public $all_pages_footer = false; //@implemented
|
||||
public $all_pages_header = false; //@deprecated 31-05-2021
|
||||
public $all_pages_footer = false; //@deprecated 31-05-2021
|
||||
public $pdf_variables = ''; //@implemented
|
||||
|
||||
public $portal_custom_head = ''; //@TODO @BEN
|
||||
@ -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) [];
|
||||
$company->subdomain = '';
|
||||
|
||||
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) {
|
||||
|
||||
|
@ -585,4 +585,61 @@ class ClientController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UploadClientRequest $request
|
||||
* @param Client $client
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/clients/{id}/adjust_ledger",
|
||||
* operationId="adjustLedger",
|
||||
* tags={"clients"},
|
||||
* summary="Adjust the client ledger to rebalance",
|
||||
* description="Adjust the client ledger to rebalance",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Client Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the client object",
|
||||
* @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\JsonContent(ref="#/components/schemas/Client"),
|
||||
* ),
|
||||
* @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 adjustLedger(Request $request, Client $client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,9 +115,8 @@ 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
96
app/Http/Controllers/ImportJsonController.php
Normal file
96
app/Http/Controllers/ImportJsonController.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?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\Import\ImportJsonRequest;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use App\Jobs\Company\CompanyImport;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
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(ImportJsonRequest $request)
|
||||
{
|
||||
|
||||
$import_file = $request->file('files');
|
||||
|
||||
$contents = $this->unzipFile($import_file->getPathname());
|
||||
|
||||
$hash = Str::random(32);
|
||||
|
||||
Cache::put( $hash, base64_encode( $contents ), 3600 );
|
||||
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->all());
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
|
||||
}
|
||||
|
||||
private function unzipFile($file_contents)
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($file_contents);
|
||||
|
||||
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
|
||||
$zip->extractTo(public_path("storage/backups/{$filename}"));
|
||||
$zip->close();
|
||||
$file_location = public_path("storage/backups/$filename/backup.json");
|
||||
|
||||
if (! file_exists($file_location))
|
||||
throw new NonExistingMigrationFile('Backup file does not exist, or it is corrupted.');
|
||||
|
||||
$data = json_decode(file_get_contents($file_location));
|
||||
|
||||
unlink($file_contents);
|
||||
unlink($file_location);
|
||||
|
||||
return $data
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,18 +644,13 @@ 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;
|
||||
@ -663,6 +659,6 @@ class TaskController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Ok'],200);
|
||||
return response()->json(['message' => 'Ok'], 200);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
55
app/Http/Requests/Client/AdjustClientLedgerRequest.php
Normal file
55
app/Http/Requests/Client/AdjustClientLedgerRequest.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\Requests\Client;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AdjustClientLedgerRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->client);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
|
||||
$rules = [];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -53,6 +53,6 @@ class RegisterRequest extends FormRequest
|
||||
return $company;
|
||||
}
|
||||
|
||||
abort(404);
|
||||
abort(404, 'Register request not found.');
|
||||
}
|
||||
}
|
||||
|
39
app/Http/Requests/Import/ImportJsonRequest.php
Normal file
39
app/Http/Requests/Import/ImportJsonRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?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\Requests\Import;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ImportJsonRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// 'import_type' => 'required',
|
||||
// 'files' => 'required_without:hash|array|min:1|max:6',
|
||||
// 'hash' => 'nullable|string',
|
||||
// 'column_map' => 'required_with:hash|array',
|
||||
// 'skip_header' => 'required_with:hash|boolean',
|
||||
// 'files.*' => 'file|mimes:csv,txt',
|
||||
];
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
||||
$this->invoice->line_items = $this->input['line_items'];
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
DB::beginTransaction();
|
||||
$this->invoice = Invoice::on(config('database.default'))->find($this->invoice->id);
|
||||
$this->invoice->line_items = $this->input['line_items'];
|
||||
$temp_invoice = $this->invoice->calc()->getTempEntity();
|
||||
|
||||
$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';
|
||||
@ -71,7 +71,7 @@ class InvoiceBalanceSanity implements Rule
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,15 +58,22 @@ class AttachableUser implements Rule
|
||||
if(!$user)
|
||||
return true;
|
||||
|
||||
$user_already_attached = CompanyUser::query()
|
||||
$user_already_attached = CompanyUser::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('account_id',$user->account_id)
|
||||
->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;
|
||||
|
||||
return $ri->makeVisible(['id']);
|
||||
|
||||
})->toArray();
|
||||
})->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,10 +429,10 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
return $hook;
|
||||
|
||||
})->makeHidden(['id'])->toArray();
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
//write to tmp and email to owner();
|
||||
|
||||
|
||||
$this->zipAndSend();
|
||||
|
||||
return true;
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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,10 +293,14 @@ class CSVImport implements ShouldQueue {
|
||||
],
|
||||
];
|
||||
|
||||
$payment_repository->save(
|
||||
$payment_data,
|
||||
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
|
||||
);
|
||||
/* 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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -35,9 +35,9 @@ class WebhookHandler implements ShouldQueue
|
||||
|
||||
private $company;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
//multi-db active
|
||||
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
|
||||
*/
|
||||
|
@ -45,7 +45,7 @@ class VendorUpdatedActivity implements ShouldQueue
|
||||
|
||||
$fields = new stdClass;
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->vendor->user_id;
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->vendor->user_id;
|
||||
|
||||
$fields->vendor_id = $vendor->id;
|
||||
$fields->user_id = $user_id;
|
||||
|
@ -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,12 +59,13 @@ 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
|
||||
->subject($subject)
|
||||
->markdown('email.support.message', [
|
||||
'message' => $this->message,
|
||||
'system_info' => $system_info,
|
||||
'laravel_log' => $log_lines,
|
||||
]);
|
||||
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,
|
||||
'system_info' => $system_info,
|
||||
'laravel_log' => $log_lines,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
@ -255,7 +255,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
public function createPaymentIntent($data): ?PaymentIntent
|
||||
{
|
||||
$this->init();
|
||||
|
||||
|
||||
$meta = $this->stripe_connect_auth;
|
||||
|
||||
return PaymentIntent::create($data, $meta);
|
||||
@ -299,7 +299,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
$customer = null;
|
||||
|
||||
$this->init();
|
||||
|
||||
|
||||
$client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first();
|
||||
|
||||
if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) {
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -420,12 +427,12 @@ class StripePaymentDriver extends BaseDriver
|
||||
nlog($e->getMessage());
|
||||
|
||||
SystemLogger::dispatch([
|
||||
'server_response' => $e->getMessage(),
|
||||
'server_response' => $e->getMessage(),
|
||||
'data' => request()->all(),
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->client, $this->client->company);
|
||||
|
||||
}
|
||||
@ -444,7 +451,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
$this->init();
|
||||
|
||||
try{
|
||||
|
||||
|
||||
$pm = $this->getStripePaymentMethod($token->token);
|
||||
$pm->detach([], $this->stripe_connect_auth);
|
||||
|
||||
@ -453,12 +460,12 @@ class StripePaymentDriver extends BaseDriver
|
||||
nlog($e->getMessage());
|
||||
|
||||
SystemLogger::dispatch([
|
||||
'server_response' => $e->getMessage(),
|
||||
'server_response' => $e->getMessage(),
|
||||
'data' => request()->all(),
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->client, $this->client->company);
|
||||
|
||||
}
|
||||
@ -499,7 +506,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
/**
|
||||
* Pull all client payment methods and update
|
||||
* the respective tokens in the system.
|
||||
*
|
||||
*
|
||||
*/
|
||||
// public function updateAllPaymentMethods()
|
||||
// {
|
||||
@ -508,9 +515,9 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
/**
|
||||
* Imports stripe customers and their payment methods
|
||||
* Matches users in the system based on the $match_on_record
|
||||
* Matches users in the system based on the $match_on_record
|
||||
* ie. email
|
||||
*
|
||||
*
|
||||
* Phone
|
||||
* Email
|
||||
*/
|
||||
|
@ -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']))
|
||||
@ -301,6 +305,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)) {
|
||||
|
||||
|
@ -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,7 +81,10 @@ 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();
|
||||
|
||||
$fee = $this->invoice->amount - $amount;
|
||||
if($is_partial)
|
||||
$fee = $this->invoice->amount - $invoice_total;
|
||||
else
|
||||
$fee = $this->invoice->amount - $amount;
|
||||
|
||||
/* Build payment hash */
|
||||
$payment_hash = PaymentHash::create([
|
||||
@ -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;
|
||||
@ -243,6 +245,13 @@ class InvoiceService
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReminder($settings = null)
|
||||
{
|
||||
$this->invoice = (new UpdateReminder($this->invoice, $settings))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStatus($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;
|
||||
}
|
||||
|
||||
@ -419,7 +432,7 @@ class InvoiceService
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the invoice.
|
||||
* @return Invoice object
|
||||
|
@ -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,98 +19,7 @@ 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)
|
||||
{
|
||||
switch ($schedule_reminder) {
|
||||
@ -177,11 +86,9 @@ 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:
|
||||
return Carbon::parse($date)->addWeek()->startOfDay();
|
||||
|
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.67',
|
||||
'app_tag' => '5.1.67-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
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user