1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 16:13:20 +01:00

Merge pull request #5862 from turbo124/v5-stable

v5.1.67
This commit is contained in:
David Bomba 2021-05-31 17:19:33 +10:00 committed by GitHub
commit b244555fec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
160 changed files with 265583 additions and 226064 deletions

View File

@ -1 +1 @@
5.1.65 5.1.67

View File

@ -65,7 +65,7 @@ class CheckData extends Command
/** /**
* @var string * @var string
*/ */
protected $name = 'ninja:check-data'; protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=}';
/** /**
* @var string * @var string

View File

@ -34,6 +34,7 @@ use App\Models\Project;
use App\Models\Quote; use App\Models\Quote;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\Task; use App\Models\Task;
use App\Models\TaxRate;
use App\Models\User; use App\Models\User;
use App\Models\Vendor; use App\Models\Vendor;
use App\Models\VendorContact; use App\Models\VendorContact;
@ -109,6 +110,14 @@ class CreateSingleAccount extends Command
'portal_domain' => 'http://ninja.test:8000', '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->default_company_id = $company->id;
$account->save(); $account->save();
@ -146,6 +155,29 @@ class CreateSingleAccount extends Command
'company_id' => $company->id, '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'); $this->info('Creating '.$this->count.' clients');
for ($x = 0; $x < $this->count; $x++) { for ($x = 0; $x < $this->count; $x++) {

View File

@ -12,12 +12,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\Ninja\SendReminders; use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebHookHandler;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Quote; use App\Models\Quote;
use App\Models\Webhook; use App\Models\Webhook;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Jobs\Util\WebhookHandler;
class SendRemindersCron extends Command class SendRemindersCron extends Command
{ {
@ -90,6 +90,7 @@ class SendRemindersCron extends Command
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company); WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
}); });
$quotes = Quote::where('is_deleted', 0) $quotes = Quote::where('is_deleted', 0)

View File

@ -11,8 +11,9 @@
namespace App\Console; namespace App\Console;
use App\Jobs\Cron\SubscriptionCron; use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\CompanySizeCheck; use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Util\ReminderJob; use App\Jobs\Util\ReminderJob;
@ -46,7 +47,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new VersionCheck)->daily(); $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(); $schedule->job(new ReminderJob)->daily()->withoutOverlapping();
@ -58,6 +59,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
$schedule->job(new SchedulerCheck)->everyFiveMinutes(); $schedule->job(new SchedulerCheck)->everyFiveMinutes();
/* Run hosted specific jobs */ /* Run hosted specific jobs */
@ -65,6 +68,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping(); $schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping(); $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
} }

View File

@ -202,7 +202,7 @@ class CompanySettings extends BaseSettings
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented 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 $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_amount1 = 0; //@implemented
public $late_fee_amount2 = 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 $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where? public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@implemented public $all_pages_header = false; //@deprecated 31-05-2021
public $all_pages_footer = false; //@implemented public $all_pages_footer = false; //@deprecated 31-05-2021
public $pdf_variables = ''; //@implemented public $pdf_variables = ''; //@implemented
public $portal_custom_head = ''; //@TODO @BEN public $portal_custom_head = ''; //@TODO @BEN
@ -667,8 +667,9 @@ class CompanySettings extends BaseSettings
'$custom_surcharge4', '$custom_surcharge4',
'$total_taxes', '$total_taxes',
'$line_taxes', '$line_taxes',
'$paid_to_date',
'$total', '$total',
'$paid_to_date',
'$outstanding',
], ],
]; ];

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ImportCompanyFailed extends Exception
{
// ..
}

View File

@ -12,7 +12,9 @@
namespace App\Factory; namespace App\Factory;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company; use App\Models\Company;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
class CompanyFactory class CompanyFactory
@ -33,7 +35,12 @@ class CompanyFactory
$company->db = config('database.default'); $company->db = config('database.default');
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3']; //$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
$company->custom_fields = (object) []; $company->custom_fields = (object) [];
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else
$company->subdomain = ''; $company->subdomain = '';
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095 $company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
$company->default_password_timeout = 1800000; $company->default_password_timeout = 1800000;

View File

@ -142,7 +142,7 @@ class AccountController extends BaseController
*/ */
public function store(CreateAccountRequest $request) public function store(CreateAccountRequest $request)
{ {
$account = CreateAccount::dispatchNow($request->all()); $account = CreateAccount::dispatchNow($request->all(), $request->getClientIp());
if (! ($account instanceof Account)) { if (! ($account instanceof Account)) {
return $account; return $account;

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Account;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -50,11 +51,15 @@ class ContactForgotPasswordController extends Controller
* *
* @return Factory|View * @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', [ return $this->render('auth.passwords.request', [
'title' => 'Client Password Reset', 'title' => 'Client Password Reset',
'passwordEmailRoute' => 'client.password.email', 'passwordEmailRoute' => 'client.password.email',
'account' => $account
]); ]);
} }

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Events\Contact\ContactLoggedIn; use App\Events\Contact\ContactLoggedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Utils\Ninja; use App\Utils\Ninja;
use Auth; use Auth;
@ -31,9 +32,13 @@ class ContactLoginController extends Controller
$this->middleware('guest:contact', ['except' => ['logout']]); $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) public function login(Request $request)

View File

@ -24,7 +24,7 @@ class ContactRegisterController extends Controller
$company = Company::where('company_key', $key)->firstOrFail(); $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) public function register(RegisterRequest $request)

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -62,8 +63,11 @@ class ContactResetPasswordController extends Controller
*/ */
public function showResetForm(Request $request, $token = null) 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( return $this->render('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email] ['token' => $token, 'email' => $request->email, 'account' => $account]
); );
} }

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Account;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
@ -104,9 +105,8 @@ class ForgotPasswordController extends Controller
*/ */
public function sendResetLinkEmail(Request $request) public function sendResetLinkEmail(Request $request)
{ {
// MultiDB::userFindAndSetDb($request->input('email')); MultiDB::userFindAndSetDb($request->input('email'));
$user = MultiDB::hasUser(['email' => $request->input('email')]);
// $user = MultiDB::hasUser(['email' => $request->input('email')]);
$this->validateEmail($request); $this->validateEmail($request);

View File

@ -31,6 +31,7 @@ use App\Models\User;
use App\Transformers\CompanyUserTransformer; use App\Transformers\CompanyUserTransformer;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\UserSessionAttributes; use App\Utils\Traits\UserSessionAttributes;
use App\Utils\Traits\User\LoginCache;
use Google_Client; use Google_Client;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -55,6 +56,7 @@ class LoginController extends BaseController
use AuthenticatesUsers; use AuthenticatesUsers;
use UserSessionAttributes; use UserSessionAttributes;
use LoginCache;
protected $entity_type = CompanyUser::class; 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))); 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')) if($user->google_2fa_secret && $request->has('one_time_password'))
{ {
$google2fa = new Google2FA(); $google2fa = new Google2FA();
@ -203,14 +204,7 @@ class LoginController extends BaseController
$user->setCompany($user->account->default_company); $user->setCompany($user->account->default_company);
$timeout = $user->company()->default_password_timeout; $this->setLoginCache($user);
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);
$cu = CompanyUser::query() $cu = CompanyUser::query()
->where('user_id', auth()->user()->id); ->where('user_id', auth()->user()->id);
@ -228,7 +222,7 @@ class LoginController extends BaseController
}); });
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
// return $this->listResponse($cu);
} else { } else {
@ -351,6 +345,7 @@ class LoginController extends BaseController
if (is_array($user)) { if (is_array($user)) {
//
$query = [ $query = [
'oauth_user_id' => $google->harvestSubField($user), 'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google', 'oauth_provider_id'=> 'google',
@ -361,14 +356,7 @@ class LoginController extends BaseController
Auth::login($existing_user, true); Auth::login($existing_user, true);
$existing_user->setCompany($existing_user->account->default_company); $existing_user->setCompany($existing_user->account->default_company);
$timeout = $existing_user->company()->default_password_timeout; $this->setLoginCache($existing_user);
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);
$cu = CompanyUser::query() $cu = CompanyUser::query()
->where('user_id', auth()->user()->id); ->where('user_id', auth()->user()->id);
@ -384,10 +372,68 @@ class LoginController extends BaseController
return $this->timeConstrainedResponse($cu); 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) { 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)); $name = OAuth::splitName($google->harvestName($user));
$new_account = [ $new_account = [
@ -403,21 +449,14 @@ class LoginController extends BaseController
MultiDB::setDefaultDatabase(); MultiDB::setDefaultDatabase();
$account = CreateAccount::dispatchNow($new_account); $account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
Auth::login($account->default_company->owner(), true); Auth::login($account->default_company->owner(), true);
auth()->user()->email_verified_at = now(); auth()->user()->email_verified_at = now();
auth()->user()->save(); auth()->user()->save();
$timeout = auth()->user()->company()->default_password_timeout; $this->setLoginCache(auth()->user());
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);
$cu = CompanyUser::whereUserId(auth()->user()->id); $cu = CompanyUser::whereUserId(auth()->user()->id);
@ -437,4 +476,62 @@ class LoginController extends BaseController
->header('X-App-Version', config('ninja.app_version')) ->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_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);
// }
}
} }

View File

@ -164,7 +164,7 @@ class BaseController extends Controller
*/ */
public function notFoundClient() 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) { 'company.tax_rates' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at); $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) { 'company.vendors'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); $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) { 'company.expense_categories'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at); $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) { 'company.task_statuses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at); $query->where('updated_at', '>=', $updated_at);
}, },
'company.activities'=> function ($query) use($user) { 'company.activities'=> function ($query) use($user) {

View File

@ -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)
{
}
} }

View File

@ -68,7 +68,7 @@ class DocumentController extends Controller
$documents->map(function ($document) { $documents->map(function ($document) {
if (auth()->user('contact')->client->id != $document->documentable->id) { if (auth()->user('contact')->client->id != $document->documentable->id) {
abort(401); abort(401, 'Permission denied');
} }
}); });

View File

@ -31,7 +31,7 @@ class EntityViewController extends Controller
public function index(string $entity_type, string $invitation_key) public function index(string $entity_type, string $invitation_key)
{ {
if (! in_array($entity_type, $this->entity_types)) { if (! in_array($entity_type, $this->entity_types)) {
abort(404); abort(404, 'Entity not found');
} }
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type)); $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) public function handlePassword(string $entity_type, string $invitation_key)
{ {
if (! in_array($entity_type, $this->entity_types)) { if (! in_array($entity_type, $this->entity_types)) {
abort(404); abort(404, 'Entity not found');
} }
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type)); $invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type));

View File

@ -57,7 +57,7 @@ class InvitationController extends Controller
/* Return early if we have the correct client_hash embedded */ /* Return early if we have the correct client_hash embedded */
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) { 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) { } elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
@ -66,7 +66,7 @@ class InvitationController extends Controller
return redirect()->route('client.login'); return redirect()->route('client.login');
} else { } else {
auth()->guard('contact')->login($invitation->contact, true); auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
} }

View File

@ -149,6 +149,6 @@ class PaymentMethodController extends Controller
return $gateway = auth()->user()->client->getBankTransferGateway(); return $gateway = auth()->user()->client->getBankTransferGateway();
} }
return abort(404); abort(404, 'Gateway not found.');
} }
} }

View File

@ -17,6 +17,7 @@ use App\Models\CompanyUser;
use App\Models\User; use App\Models\User;
use App\Transformers\CompanyUserTransformer; use App\Transformers\CompanyUserTransformer;
use App\Transformers\UserTransformer; use App\Transformers\UserTransformer;
use App\Utils\Traits\User\LoginCache;
use Google_Client; use Google_Client;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -24,6 +25,7 @@ use Illuminate\Support\Str;
class ConnectedAccountController extends BaseController class ConnectedAccountController extends BaseController
{ {
use LoginCache;
protected $entity_type = User::class; protected $entity_type = User::class;
@ -113,8 +115,7 @@ class ConnectedAccountController extends BaseController
auth()->user()->email_verified_at = now(); auth()->user()->email_verified_at = now();
auth()->user()->save(); auth()->user()->save();
$timeout = auth()->user()->company()->default_password_timeout; $this->setLoginCache(auth()->user());
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $this->itemResponse(auth()->user()); return $this->itemResponse(auth()->user());

View File

@ -93,11 +93,16 @@ class LoginController extends BaseController
public function redirectToProvider(string $provider) public function redirectToProvider(string $provider)
{ {
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid' //'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')) { if (request()->has('code')) {
return $this->handleProviderCallback($provider); return $this->handleProviderCallback($provider);
} else { } 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());
}
}
*/
} }

View 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
}
}

View File

@ -845,13 +845,11 @@ class InvoiceController extends BaseController
*/ */
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice) public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
{ {
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact); $file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
try {
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); 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);
}
} }
/** /**

View File

@ -631,6 +631,7 @@ class TaskController extends BaseController
$task_status = TaskStatus::where('id', $this->decodePrimaryKey($task_status_hashed_id)) $task_status = TaskStatus::where('id', $this->decodePrimaryKey($task_status_hashed_id))
->where('company_id', auth()->user()->company()->id) ->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first(); ->first();
$task_status->status_order = $key; $task_status->status_order = $key;
@ -643,19 +644,14 @@ class TaskController extends BaseController
$sort_status_id = $this->decodePrimaryKey($key); $sort_status_id = $this->decodePrimaryKey($key);
// nlog($task_list);
foreach ($task_list as $key => $task) foreach ($task_list as $key => $task)
{ {
// nlog($task);
$task_record = Task::where('id', $this->decodePrimaryKey($task)) $task_record = Task::where('id', $this->decodePrimaryKey($task))
->where('company_id', auth()->user()->company()->id) ->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first(); ->first();
// nlog($task_record->id);
$task_record->status_order = $key; $task_record->status_order = $key;
$task_record->status_id = $sort_status_id; $task_record->status_id = $sort_status_id;
$task_record->save(); $task_record->save();
@ -663,6 +659,6 @@ class TaskController extends BaseController
} }
return response()->json(['message' => 'Ok'],200); return response()->json(['message' => 'Ok'], 200);
} }
} }

View File

@ -211,11 +211,12 @@ class UserController extends BaseController
$ct = CreateCompanyToken::dispatchNow($company, $user, $user_agent); $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))); 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);
} }
/** /**

View File

@ -16,6 +16,7 @@ use App\Http\Middleware\Authenticate;
use App\Http\Middleware\CheckClientExistence; use App\Http\Middleware\CheckClientExistence;
use App\Http\Middleware\CheckForMaintenanceMode; use App\Http\Middleware\CheckForMaintenanceMode;
use App\Http\Middleware\ClientPortalEnabled; use App\Http\Middleware\ClientPortalEnabled;
use App\Http\Middleware\ContactAccount;
use App\Http\Middleware\ContactKeyLogin; use App\Http\Middleware\ContactKeyLogin;
use App\Http\Middleware\ContactRegister; use App\Http\Middleware\ContactRegister;
use App\Http\Middleware\ContactSetDb; use App\Http\Middleware\ContactSetDb;
@ -141,6 +142,7 @@ class Kernel extends HttpKernel
'api_secret_check' => ApiSecretCheck::class, 'api_secret_check' => ApiSecretCheck::class,
'contact_token_auth' => ContactTokenAuth::class, 'contact_token_auth' => ContactTokenAuth::class,
'contact_db' => ContactSetDb::class, 'contact_db' => ContactSetDb::class,
'contact_account' => ContactAccount::class,
'domain_db' => SetDomainNameDb::class, 'domain_db' => SetDomainNameDb::class,
'email_db' => SetEmailDb::class, 'email_db' => SetEmailDb::class,
'invite_db' => SetInviteDb::class, 'invite_db' => SetInviteDb::class,
@ -182,5 +184,6 @@ class Kernel extends HttpKernel
PasswordProtection::class, PasswordProtection::class,
Locale::class, Locale::class,
SubstituteBindings::class, SubstituteBindings::class,
ContactAccount::class,
]; ];
} }

View File

@ -181,14 +181,16 @@ class BillingPortalPurchase extends Component
{ {
$this->validate(); $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) { if ($contact && $this->steps['existing_user'] === false) {
return $this->steps['existing_user'] = true; return $this->steps['existing_user'] = true;
} }
if ($contact && $this->steps['existing_user']) { 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 return $attempt
? $this->getPaymentMethods($contact) ? $this->getPaymentMethods($contact)

View 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);
}
}

View File

@ -18,6 +18,7 @@ use Auth;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ContactKeyLogin class ContactKeyLogin
{ {
@ -42,6 +43,9 @@ class ContactKeyLogin
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) { if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
if($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()){ 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); Auth::guard('contact')->login($client_contact, true);
return redirect()->to('client/dashboard'); return redirect()->to('client/dashboard');
} }
@ -49,6 +53,10 @@ class ContactKeyLogin
} }
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) { } elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) { 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); auth()->guard('contact')->login($client_contact, true);
return redirect()->to('client/dashboard'); return redirect()->to('client/dashboard');
} }
@ -56,19 +64,36 @@ class ContactKeyLogin
if (MultiDB::findAndSetDbByClientHash($request->input('client_hash'))) { if (MultiDB::findAndSetDbByClientHash($request->input('client_hash'))) {
if($client = Client::where('client_hash', $request->input('client_hash'))->first()){ 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'); return redirect()->to('client/dashboard');
} }
} }
} elseif ($request->has('client_hash')) { } elseif ($request->has('client_hash')) {
if ($client = Client::where('client_hash', $request->input('client_hash'))->first()) { 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'); return redirect()->to('client/dashboard');
} }
} elseif ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { } elseif ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
$contact_email = Cache::get($request->segment(3)); $contact_email = Cache::get($request->segment(3));
if($client_contact = ClientContact::where('email', $contact_email)->first()){ 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'))) { if ($request->query('redirect') && !empty($request->query('redirect'))) {
return redirect()->to($request->query('redirect')); return redirect()->to($request->query('redirect'));

View File

@ -52,6 +52,6 @@ class ContactRegister
return $next($request); return $next($request);
} }
return abort(404); abort(404, 'ContactRegister Middlware');
} }
} }

View File

@ -48,7 +48,11 @@ class SetDomainNameDb
'portal_mode' => 'subdomain', 'portal_mode' => 'subdomain',
]; ];
if(!MultiDB::findAndSetDbByDomain($query)){ if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]);
}
else
{
if ($request->json) { if ($request->json) {
return response()->json($error, 403); return response()->json($error, 403);
} else { } else {
@ -66,7 +70,11 @@ class SetDomainNameDb
'portal_mode' => 'domain', 'portal_mode' => 'domain',
]; ];
if(!MultiDB::findAndSetDbByDomain($query)){ if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]);
}
else
{
if ($request->json) { if ($request->json) {
return response()->json($error, 403); return response()->json($error, 403);
} else { } else {

View File

@ -49,7 +49,6 @@ class TokenAuth
| us to decouple a $user and their attached companies completely. | us to decouple a $user and their attached companies completely.
| |
*/ */
$user->setCompany($company_token->company);
app('queue')->createPayloadUsing(function () use ($company_token) { app('queue')->createPayloadUsing(function () use ($company_token) {
return ['db' => $company_token->company->db]; return ['db' => $company_token->company->db];
@ -67,6 +66,7 @@ class TokenAuth
//stateless, don't remember the user. //stateless, don't remember the user.
auth()->login($user, false); auth()->login($user, false);
auth()->user()->setCompany($company_token->company);
} else { } else {
$error = [ $error = [

View File

@ -40,13 +40,13 @@ class CreateAccountRequest extends Request
'password' => 'required|string|min:6', 'password' => 'required|string|min:6',
'email' => 'bail|required|email:rfc,dns', 'email' => 'bail|required|email:rfc,dns',
'email' => new NewUniqueUserRule(), 'email' => new NewUniqueUserRule(),
'privacy_policy' => 'required', 'privacy_policy' => 'required|boolean',
'terms_of_service' => 'required', 'terms_of_service' => 'required|boolean',
]; ];
} }
protected function prepareForValidation() protected function prepareForValidation()
{ {nlog($this->all());
$input = $this->all(); $input = $this->all();
$input['user_agent'] = request()->server('HTTP_USER_AGENT'); $input['user_agent'] = request()->server('HTTP_USER_AGENT');

View 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);
}
}

View File

@ -53,6 +53,6 @@ class RegisterRequest extends FormRequest
return $company; return $company;
} }
abort(404); abort(404, 'Register request not found.');
} }
} }

View 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',
];
}
}

View File

@ -45,9 +45,6 @@ class UpdateUserRequest extends Request
{ {
$input = $this->all(); $input = $this->all();
// if (isset($input['company_user']) && ! auth()->user()->isAdmin()) {
// unset($input['company_user']);
// }
$this->replace($input); $this->replace($input);
} }

View File

@ -57,13 +57,13 @@ class InvoiceBalanceSanity implements Rule
private function checkIfInvoiceBalanceIsSane() : bool private function checkIfInvoiceBalanceIsSane() : bool
{ {
DB::connection(config('database.default'))->beginTransaction();
$this->invoice = Invoice::on(config('database.default'))->find($this->invoice->id);
$this->invoice->line_items = $this->input['line_items']; $this->invoice->line_items = $this->input['line_items'];
DB::beginTransaction();
$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){ if($temp_invoice->balance < 0){
$this->message = 'Invoice balance cannot go negative'; $this->message = 'Invoice balance cannot go negative';

View File

@ -20,6 +20,7 @@ use Illuminate\Contracts\Validation\Rule;
*/ */
class AttachableUser implements Rule class AttachableUser implements Rule
{ {
public $message;
public function __construct() public function __construct()
{ {
@ -39,7 +40,7 @@ class AttachableUser implements Rule
*/ */
public function message() public function message()
{ {
return "Cannot add the same user to the same company"; return $this->message;
} }
/** /**
@ -63,9 +64,16 @@ class AttachableUser implements Rule
->where('company_id', auth()->user()->company()->id) ->where('company_id', auth()->user()->company()->id)
->exists(); ->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; return false;
}
if($user->account_id != auth()->user()->account_id){
$this->message = ctrans('texts.user_cross_linked_error');
return false;
}
return true; return true;
} }

View File

@ -25,11 +25,13 @@ use App\Jobs\Util\VersionCheck;
use App\Mail\Admin\AccountCreatedObject; use App\Mail\Admin\AccountCreatedObject;
use App\Mail\Admin\VerifyUserObject; use App\Mail\Admin\VerifyUserObject;
use App\Models\Account; use App\Models\Account;
use App\Models\Timezone;
use App\Notifications\Ninja\NewAccountCreated; use App\Notifications\Ninja\NewAccountCreated;
use App\Utils\Ninja; use App\Utils\Ninja;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Turbo124\Beacon\Facades\LightLogs; use Turbo124\Beacon\Facades\LightLogs;
@ -40,9 +42,12 @@ class CreateAccount
protected $request; protected $request;
public function __construct(array $sp660339) protected $client_ip;
public function __construct(array $sp660339, $client_ip)
{ {
$this->request = $sp660339; $this->request = $sp660339;
$this->client_ip = $client_ip;
} }
public function handle() public function handle()
@ -74,6 +79,7 @@ class CreateAccount
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f); $sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
$sp035a66->load('account'); $sp035a66->load('account');
$sp035a66->settings = $this->processSettings($sp035a66->settings);
$sp794f3f->default_company_id = $sp035a66->id; $sp794f3f->default_company_id = $sp035a66->id;
$sp794f3f->save(); $sp794f3f->save();
@ -107,4 +113,53 @@ class CreateAccount
return $sp794f3f; 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;
}
} }

View File

@ -101,7 +101,7 @@ class CompanyExport implements ShouldQueue
return $activity; return $activity;
})->makeHidden(['id'])->toArray(); })->makeHidden(['id'])->all();
$this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){ $this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){
@ -114,43 +114,56 @@ class CompanyExport implements ShouldQueue
return $backup; return $backup;
})->toArray(); })->all();
$this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user){ $this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user){
$user->account_id = $this->encodePrimaryKey($user->account_id); $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){ $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){ $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){ $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(); $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 = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']);
$company_gateway->config = decrypt($company_gateway->config); $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){ $this->export_data['company_tokens'] = $this->company->tokens->map(function ($token){
@ -169,7 +182,7 @@ class CompanyExport implements ShouldQueue
return $token; return $token;
})->toArray(); })->all();
$this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger){ $this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger){
@ -177,7 +190,7 @@ class CompanyExport implements ShouldQueue
return $ledger; return $ledger;
})->toArray(); })->all();
$this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){ $this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){
@ -185,43 +198,44 @@ class CompanyExport implements ShouldQueue
return $company_user; return $company_user;
})->toArray(); })->all();
$this->export_data['credits'] = $this->company->credits->map(function ($credit){ $this->export_data['credits'] = $this->company->credits->map(function ($credit){
$credit = $this->transformBasicEntities($credit); $credit = $this->transformBasicEntities($credit);
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']); $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){ $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']); $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){ $this->export_data['expenses'] = $this->company->expenses->map(function ($expense){
@ -229,17 +243,17 @@ class CompanyExport implements ShouldQueue
$expense = $this->transformBasicEntities($expense); $expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']); $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){ $this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs){
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']); $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){ $this->export_data['invoices'] = $this->company->invoices->map(function ($invoice){
@ -247,18 +261,22 @@ class CompanyExport implements ShouldQueue
$invoice = $this->transformBasicEntities($invoice); $invoice = $this->transformBasicEntities($invoice);
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $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){ $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){ $this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term){
@ -266,61 +284,65 @@ class CompanyExport implements ShouldQueue
return $term; 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){ $this->export_data['payments'] = $this->company->payments->map(function ($payment){
$payment = $this->transformBasicEntities($payment); $payment = $this->transformBasicEntities($payment);
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']); $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){ $this->export_data['projects'] = $this->company->projects->map(function ($project){
$project = $this->transformBasicEntities($project); $project = $this->transformBasicEntities($project);
$project = $this->transformArrayOfKeys($project, ['client_id']); $project = $this->transformArrayOfKeys($project, ['client_id']);
return $project; return $project->makeVisible(['id']);
})->toArray(); })->all();
$this->export_data['quotes'] = $this->company->quotes->map(function ($quote){ $this->export_data['quotes'] = $this->company->quotes->map(function ($quote){
$quote = $this->transformBasicEntities($quote); $quote = $this->transformBasicEntities($quote);
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $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){ $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->transformBasicEntities($ri);
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $ri;
})->toArray(); return $ri->makeVisible(['id']);
})->all();
$this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($ri){ $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; return $ri;
})->toArray(); })->all();
$this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){ $this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){
$subscription = $this->transformBasicEntities($subscription); $subscription = $this->transformBasicEntities($subscription);
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id); $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){ $this->export_data['system_logs'] = $this->company->system_logs->map(function ($log){
@ -348,16 +376,16 @@ class CompanyExport implements ShouldQueue
return $log; return $log;
})->makeHidden(['id'])->toArray(); })->makeHidden(['id'])->all();
$this->export_data['tasks'] = $this->company->tasks->map(function ($task){ $this->export_data['tasks'] = $this->company->tasks->map(function ($task){
$task = $this->transformBasicEntities($task); $task = $this->transformBasicEntities($task);
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']); $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){ $this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status){
@ -367,7 +395,7 @@ class CompanyExport implements ShouldQueue
return $status; return $status;
})->toArray(); })->all();
$this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate){ $this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate){
@ -376,13 +404,13 @@ class CompanyExport implements ShouldQueue
return $rate; return $rate;
})->makeHidden(['id'])->toArray(); })->makeHidden(['id'])->all();
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){ $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){ $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 = $this->transformBasicEntities($vendor);
$vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id); $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){ $this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook){
@ -401,7 +429,7 @@ class CompanyExport implements ShouldQueue
return $hook; return $hook;
})->makeHidden(['id'])->toArray(); })->makeHidden(['id'])->all();
//write to tmp and email to owner(); //write to tmp and email to owner();
@ -413,7 +441,7 @@ class CompanyExport implements ShouldQueue
private function transformBasicEntities($model) 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() 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'); $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(); if ($zip->open($zip_path, \ZipArchive::CREATE)!==TRUE) {
fwrite($fp, json_encode($this->export_data)); nlog("cannot open {$zip_path}");
rewind($fp); }
$zip->addFileFromStream('backup.json', $fp);
$zip->finish(); $zip->addFromString("backup.json", json_encode($this->export_data));
$zip->close();
$path = 'backups/';
Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream);
fclose($tempStream);
$nmo = new NinjaMailerObject; $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->to_user = $this->user;
$nmo->company = $this->company; $nmo->company = $this->company;
$nmo->settings = $this->company->settings; $nmo->settings = $this->company->settings;
NinjaMailerJob::dispatch($nmo); 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

View File

@ -12,7 +12,9 @@
namespace App\Jobs\Company; namespace App\Jobs\Company;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company; use App\Models\Company;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -60,6 +62,12 @@ class CreateCompany
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : ''; $company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
$company->custom_fields = new \stdClass; $company->custom_fields = new \stdClass;
$company->default_password_timeout = 1800000; $company->default_password_timeout = 1800000;
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else
$company->subdomain = '';
$company->save(); $company->save();
return $company; return $company;

View 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();
}
}

View File

@ -293,6 +293,9 @@ class CSVImport implements ShouldQueue {
], ],
]; ];
/* Make sure we don't apply any payments to invoices with a Zero Amount*/
if($invoice->amount > 0)
{
$payment_repository->save( $payment_repository->save(
$payment_data, $payment_data,
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id ) PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
@ -300,6 +303,7 @@ class CSVImport implements ShouldQueue {
} }
} }
} }
}
$this->actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository ); $this->actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository );
} }

View File

@ -47,9 +47,9 @@ class NinjaMailerJob implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash; 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; public $deleteWhenMissingModels = true;
@ -63,6 +63,7 @@ class NinjaMailerJob implements ShouldQueue
{ {
$this->nmo = $nmo; $this->nmo = $nmo;
$this->override = $override;
} }
@ -105,7 +106,7 @@ class NinjaMailerJob implements ShouldQueue
//send email //send email
try { try {
nlog("trying to send"); nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
Mail::to($this->nmo->to_user->email) Mail::to($this->nmo->to_user->email)
->send($this->nmo->mailable); ->send($this->nmo->mailable);

View File

@ -30,6 +30,7 @@ use App\Factory\TaxRateFactory;
use App\Factory\UserFactory; use App\Factory\UserFactory;
use App\Factory\VendorFactory; use App\Factory\VendorFactory;
use App\Http\Requests\Company\UpdateCompanyRequest; use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\ValidationRules\User\AttachableUser;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule; use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Http\ValidationRules\ValidUserForCompany; use App\Http\ValidationRules\ValidUserForCompany;
use App\Jobs\Company\CreateCompanyTaskStatuses; use App\Jobs\Company\CreateCompanyTaskStatuses;
@ -210,8 +211,8 @@ class Import implements ShouldQueue
$this->{$method}($data[$import]); $this->{$method}($data[$import]);
} }
if(Ninja::isHosted()) // if(Ninja::isHosted() && array_key_exists('ninja_tokens', $data))
$this->processNinjaTokens($data['ninja_tokens']); // $this->processNinjaTokens($data['ninja_tokens']);
$this->setInitialCompanyLedgerBalances(); $this->setInitialCompanyLedgerBalances();
@ -225,6 +226,7 @@ class Import implements ShouldQueue
->send(new MigrationCompleted($this->company, implode("<br>",$check_data))); ->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
} }
catch(\Exception $e) { catch(\Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());
} }
@ -296,6 +298,12 @@ class Import implements ShouldQueue
$data = $this->transformCompanyData($data); $data = $this->transformCompanyData($data);
if(Ninja::isHosted() && strlen($data['subdomain']) > 1) {
if(!MultiDB::checkDomainAvailable($data['subdomain']))
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
}
$rules = (new UpdateCompanyRequest())->rules(); $rules = (new UpdateCompanyRequest())->rules();
$validator = Validator::make($data, $rules); $validator = Validator::make($data, $rules);
@ -419,13 +427,10 @@ class Import implements ShouldQueue
$rules = [ $rules = [
'*.first_name' => ['string'], '*.first_name' => ['string'],
'*.last_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); $validator = Validator::make($data, $rules);
if ($validator->fails()) { if ($validator->fails()) {
@ -1647,53 +1652,65 @@ class Import implements ShouldQueue
private function buildNewUserPlan() private function buildNewUserPlan()
{ {
$local_company = Company::find($this->company->id); $current_db = config('database.default');
$owner = $local_company->owner();
$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 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; return $client_contact->client;
$ninja_client = ClientFactory::create($ninja_company->id, $ninja_company->owner()->id); $ninja_client = ClientFactory::create($ninja_company->id, $ninja_company->owner()->id);
$ninja_client->setConnection('db-ninja-01'); $ninja_client->name = $this->user->present()->name();
$ninja_client->name = $owner->present()->name();
$ninja_client->address1 = $local_company->settings->address1; $ninja_client->address1 = $local_company->settings->address1;
$ninja_client->address2 = $local_company->settings->address2; $ninja_client->address2 = $local_company->settings->address2;
$ninja_client->city = $local_company->settings->city; $ninja_client->city = $local_company->settings->city;
$ninja_client->postal_code = $local_company->settings->postal_code; $ninja_client->postal_code = $local_company->settings->postal_code;
$ninja_client->state = $local_company->settings->state; $ninja_client->state = $local_company->settings->state;
$ninja_client->country_id = $local_company->settings->country_id; $ninja_client->country_id = $local_company->settings->country_id;
$ninja_client->custom_value1 = $local_company->company_key;
$ninja_client->save(); $ninja_client->save();
$ninja_client_contact = ClientContactFactory::create($ninja_company->id, $ninja_company->owner()->id); $ninja_client_contact = ClientContactFactory::create($ninja_company->id, $ninja_company->owner()->id);
$ninja_client_contact->setConnection('db-ninja-01'); $ninja_client_contact->first_name = $this->user->first_name;
$ninja_client_contact->first_name = $owner->first_name; $ninja_client_contact->last_name = $this->user->last_name;
$ninja_client_contact->last_name = $owner->last_name;
$ninja_client_contact->client_id = $ninja_client->id; $ninja_client_contact->client_id = $ninja_client->id;
$ninja_client_contact->email = $owner->email; $ninja_client_contact->email = $this->user->email;
$ninja_client_contact->phone = $owner->phone; $ninja_client_contact->phone = $this->user->phone;
$ninja_client_contact->save(); $ninja_client_contact->save();
MultiDB::setDb($current_db);
return $ninja_client; return $ninja_client;
} }
private function processNinjaTokens(array $data) 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(); $ninja_client = $this->buildNewUserPlan();
foreach($data as $token) foreach($data as $token)
{ {
//get invoiceninja company_id //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['company_id'] = $ninja_company->id;
$token['client_id'] = $ninja_client->id; $token['client_id'] = $ninja_client->id;/////
$token['user_id'] = $ninja_client->user_id; $token['user_id'] = $ninja_company->owner()->id;
$token['company_gateway_id'] = config('ninja.ninja_default_company_gateway_id'); $token['company_gateway_id'] = config('ninja.ninja_default_company_gateway_id');
//todo //todo
@ -1702,8 +1719,10 @@ class Import implements ShouldQueue
ClientGatewayToken::reguard(); 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. /* 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 This next section will check for credit balances and reduce the client balance so that the V5 balances are correct
*/ */

View File

@ -16,6 +16,7 @@ use App\Jobs\Entity\EmailEntity;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Invoice; use App\Models\Invoice;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesReminders;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -25,7 +26,7 @@ use Illuminate\Support\Carbon;
class ReminderJob implements ShouldQueue class ReminderJob implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders;
public function __construct() public function __construct()
{ {
@ -39,40 +40,41 @@ class ReminderJob implements ShouldQueue
public function handle() 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')) { if (! config('ninja.db.multi_db_enabled')) {
$this->processReminders(); $this->processReminders();
} else { } else {
//multiDB environment, need to //multiDB environment, need to
foreach (MultiDB::$dbs as $db) { foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db); MultiDB::setDB($db);
$this->processReminders();
$this->processReminders($db);
} }
} }
} }
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) { Invoice::where('next_send_date', Carbon::today()->format('Y-m-d'))->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) { if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice'); $reminder_template = $invoice->calculateTemplate('invoice');
$invoice->service()->touchReminder($reminder_template)->save(); $invoice->service()->touchReminder($reminder_template)->save();
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $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) { if ($invoice->invitations->count() > 0) {
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template)); event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
} }
$invoice->service()->setReminder()->save();
} else { } else {
$invoice->next_send_date = null; $invoice->next_send_date = null;
$invoice->save(); $invoice->save();
} }
}); });
} }
} }

View File

@ -20,6 +20,7 @@ use App\Libraries\MultiDB;
use App\Mail\MigrationFailed; use App\Mail\MigrationFailed;
use App\Models\Company; use App\Models\Company;
use App\Models\User; use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -139,7 +140,11 @@ class StartMigration implements ShouldQueue
$this->company->update_products = $update_product_flag; $this->company->update_products = $update_product_flag;
$this->company->save(); $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') { if (app()->environment() !== 'production') {
info($e->getMessage()); info($e->getMessage());

View File

@ -35,9 +35,9 @@ class WebhookHandler implements ShouldQueue
private $company; 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; public $deleteWhenMissingModels = true;

View File

@ -52,37 +52,38 @@ class MultiDB
public static function checkDomainAvailable($subdomain) : bool 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; return Company::whereSubdomain($subdomain)->get()->count() == 0;
}
$current_db = config('database.default');
//multi-db active
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if (Company::on($db)->whereSubdomain($subdomain)->get()->count() >= 1) { if (Company::on($db)->whereSubdomain($subdomain)->get()->count() >= 1) {
self::setDb($current_db);
return false; return false;
} }
} }
//self::setDefaultDatabase(); self::setDb($current_db);
return true; return true;
} }
public static function checkUserEmailExists($email) : bool 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 return User::where(['email' => $email])->get()->count() >= 1 ?? false; // true >= 1 emails found / false -> == emails found
}
//multi-db active $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail
self::setDb($current_db);
return true; return true;
} }
} }
self::setDefaultDatabase(); self::setDb($current_db);
return false; return false;
} }
@ -102,19 +103,21 @@ class MultiDB
*/ */
public static function checkUserAndCompanyCoExist($email, $company_key) :bool public static function checkUserAndCompanyCoExist($email, $company_key) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail if (User::on($db)->where(['email' => $email])->exists()) {
if (Company::on($db)->where(['company_key' => $company_key])->get()->count() >= 1) { if (Company::on($db)->where(['company_key' => $company_key])->exists()) {
self::setDb($current_db);
return true; return true;
} else { } else {
self::setDefaultDatabase(); self::setDb($current_db);
return false; return false;
} }
} }
} }
self::setDefaultDatabase(); self::setDb($current_db);
return true; return true;
} }
@ -125,20 +128,21 @@ class MultiDB
*/ */
public static function hasUser(array $data) : ?User 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(); return User::where($data)->withTrashed()->first();
}
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
self::setDB($db); self::setDB($db);
if ($user = User::where($data)->withTrashed()->first()) {
if ($user = User::where($data)->withTrashed()->first())
return $user; return $user;
}
} }
self::setDefaultDatabase(); self::setDb($current_db);
return null; return null;
} }
@ -149,125 +153,139 @@ class MultiDB
*/ */
public static function hasContact(string $email) : ?ClientContact 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(); return ClientContact::where('email', $email)->withTrashed()->first();
}
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
$user = ClientContact::on($db)->where('email', $email)->withTrashed()->first(); $user = ClientContact::on($db)->where('email', $email)->withTrashed()->first();
if ($user) { if ($user) {
self::setDB($db); self::setDb($db);
return $user; return $user;
} }
} }
self::setDefaultDatabase(); self::setDB($current_db);
return null; return null;
} }
public static function contactFindAndSetDb($token) :bool public static function contactFindAndSetDb($token) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($ct = ClientContact::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) { if ($ct = ClientContact::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) {
self::setDb($ct->company->db); self::setDb($db);
return true; return true;
} }
} }
self::setDefaultDatabase(); self::setDB($current_db);
return false; return false;
} }
public static function userFindAndSetDb($email) : bool public static function userFindAndSetDb($email) : bool
{ {
$current_db = config('database.default');
//multi-db active //multi-db active
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if (User::on($db)->where('email', $email)->count() >= 1){ if (User::on($db)->where('email', $email)->count() >= 1){
nlog("setting db {$db}");
self::setDb($db); self::setDb($db);
return true; return true;
} }
} }
self::setDefaultDatabase(); self::setDB($current_db);
return false; return false;
} }
public static function findAndSetDb($token) :bool public static function findAndSetDb($token) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($ct = CompanyToken::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) { if ($ct = CompanyToken::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) {
self::setDb($ct->company->db); self::setDb($ct->company->db);
return true; return true;
} }
} }
self::setDefaultDatabase();
self::setDB($current_db);
return false; return false;
} }
public static function findAndSetDbByCompanyKey($company_key) :bool public static function findAndSetDbByCompanyKey($company_key) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($company = Company::on($db)->where('company_key', $company_key)->first()) { if ($company = Company::on($db)->where('company_key', $company_key)->first()) {
self::setDb($company->db); self::setDb($company->db);
return true; return true;
} }
} }
self::setDefaultDatabase();
self::setDB($current_db);
return false; return false;
} }
public static function findAndSetDbByContactKey($contact_key) :bool public static function findAndSetDbByContactKey($contact_key) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($client_contact = ClientContact::on($db)->where('contact_key', $contact_key)->first()) { if ($client_contact = ClientContact::on($db)->where('contact_key', $contact_key)->first()) {
self::setDb($client_contact->company->db); self::setDb($client_contact->company->db);
return true; return true;
} }
} }
self::setDefaultDatabase();
self::setDB($current_db);
return false; return false;
} }
public static function findAndSetDbByClientHash($client_hash) :bool public static function findAndSetDbByClientHash($client_hash) :bool
{ {
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($client = Client::on($db)->where('client_hash', $client_hash)->first()) { if ($client = Client::on($db)->where('client_hash', $client_hash)->first()) {
self::setDb($client->company->db); self::setDb($client->company->db);
return true; return true;
} }
} }
self::setDefaultDatabase();
self::setDB($current_db);
return false; return false;
} }
public static function findAndSetDbByDomain($query_array) :bool public static function findAndSetDbByDomain($query_array)
{ {
if (! config('ninja.db.multi_db_enabled')) 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) { foreach (self::$dbs as $db) {
if ($company = Company::on($db)->where($query_array)->first()) { if ($company = Company::on($db)->where($query_array)->first()) {
self::setDb($company->db); self::setDb($company->db);
return true; return $company;
} }
} }
self::setDefaultDatabase(); self::setDB($current_db);
return false; return false;
} }
@ -275,20 +293,48 @@ class MultiDB
public static function findAndSetDbByInvitation($entity, $invitation_key) public static function findAndSetDbByInvitation($entity, $invitation_key)
{ {
$class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; $class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
$current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->first()) { if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->first()) {
self::setDb($db); self::setDb($db);
return true; return true;
} }
} }
self::setDefaultDatabase(); self::setDB($current_db);
return false; 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 * @param $database
*/ */

View File

@ -45,7 +45,7 @@ class VendorUpdatedActivity implements ShouldQueue
$fields = new stdClass; $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->vendor_id = $vendor->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;

View File

@ -8,21 +8,23 @@ use Illuminate\Queue\SerializesModels;
class MigrationFailed extends Mailable class MigrationFailed extends Mailable
{ {
// use Queueable, SerializesModels;
public $exception; public $exception;
public $content; public $content;
public $settings;
public $company;
/** /**
* Create a new message instance. * Create a new message instance.
* *
* @param $content * @param $content
* @param $exception * @param $exception
*/ */
public function __construct($exception, $content = null) public function __construct($exception, $company, $content = null)
{ {
$this->exception = $exception; $this->exception = $exception;
$this->content = $content; $this->content = $content;
$this->settings = $company->settings;
$this->company = $company;
} }
/** /**
@ -33,6 +35,6 @@ class MigrationFailed extends Mailable
public function build() public function build()
{ {
return $this->from(config('mail.from.address'), config('mail.from.name')) 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]);
} }
} }

View File

@ -59,7 +59,8 @@ class SupportMessageSent extends Mailable
$subject = "Customer MSG {$user->present()->name} - [{$plan} - DB:{$company->db}]"; $subject = "Customer MSG {$user->present()->name} - [{$plan} - DB:{$company->db}]";
return $this->from(config('mail.from.address'), config('mail.from.name')) //todo this needs to be fixed to handle the hosted version return $this->from(config('mail.from.address'), config('mail.from.name'))
->replyTo($user->email, $user->present()->name())
->subject($subject) ->subject($subject)
->markdown('email.support.message', [ ->markdown('email.support.message', [
'message' => $this->message, 'message' => $this->message,

View File

@ -103,6 +103,15 @@ class Activity extends StaticModel
'deleted_at' => 'timestamp', 'deleted_at' => 'timestamp',
]; ];
protected $appends = [
'hashed_id',
];
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -36,10 +36,6 @@ class BaseModel extends Model
use UserSessionAttributes; use UserSessionAttributes;
use HasFactory; use HasFactory;
//todo customise names of archived_at / updated_at columns
///const CREATED_AT = 'creation_date';
//const UPDATED_AT = 'last_update';
protected $appends = [ protected $appends = [
'hashed_id', 'hashed_id',
]; ];

View File

@ -40,7 +40,6 @@ class Client extends BaseModel implements HasLocalePreference
'private_notes', 'private_notes',
'user_id', 'user_id',
'company_id', 'company_id',
// 'settings',
'last_login', 'last_login',
]; ];

View File

@ -131,6 +131,11 @@ class Company extends BaseModel
return $this->morphMany(Document::class, 'documentable'); return $this->morphMany(Document::class, 'documentable');
} }
public function all_documents()
{
return $this->HasMany(Document::class);
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -71,10 +71,6 @@ class Quote extends BaseModel
'custom_surcharge2', 'custom_surcharge2',
'custom_surcharge3', 'custom_surcharge3',
'custom_surcharge4', 'custom_surcharge4',
// 'custom_surcharge_tax1',
// 'custom_surcharge_tax2',
// 'custom_surcharge_tax3',
// 'custom_surcharge_tax4',
'design_id', 'design_id',
'assigned_user_id', 'assigned_user_id',
'exchange_rate', 'exchange_rate',

View File

@ -159,8 +159,6 @@ class User extends Authenticatable implements MustVerifyEmail
*/ */
public function setCompany($company) public function setCompany($company)
{ {
// config(['ninja.company_id' => $company->id]);
$this->company = $company; $this->company = $company;
} }
@ -170,16 +168,17 @@ class User extends Authenticatable implements MustVerifyEmail
public function getCompany() public function getCompany()
{ {
if (request()->header('X-API-TOKEN')) { if ($this->company){
$company_token = CompanyToken::with(['company'])->whereRaw('BINARY `token`= ?', [request()->header('X-API-TOKEN')])->first();
return $company_token->company;
}
elseif ($this->company){
return $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; // return false;
throw new \Exception('No Company Found'); throw new \Exception('No Company Found');
@ -408,7 +407,7 @@ class User extends Authenticatable implements MustVerifyEmail
$nmo->settings = $this->account->default_company->settings; $nmo->settings = $this->account->default_company->settings;
$nmo->company = $this->account->default_company; $nmo->company = $this->account->default_company;
NinjaMailerJob::dispatch($nmo); NinjaMailerJob::dispatch($nmo, true);
//$this->notify(new ResetPasswordNotification($token)); //$this->notify(new ResetPasswordNotification($token));
} }

View File

@ -39,10 +39,6 @@ class CompanyObserver
if(Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain')) 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 //fire event to build new custom portal domain
\Modules\Admin\Jobs\Domain\CustomDomain::dispatch($company->getOriginal('portal_domain'), $company)->onQueue('domain'); \Modules\Admin\Jobs\Domain\CustomDomain::dispatch($company->getOriginal('portal_domain'), $company)->onQueue('domain');
} }

View File

@ -52,6 +52,9 @@ class InvoiceObserver
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company); 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'); // UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath() . $invoice->numberFormatter().'.pdf');
} }

View File

@ -118,7 +118,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
$this->initializeOmnipayGateway(); $this->initializeOmnipayGateway();
$response = $this->omnipay_gateway $response = $this->omnipay_gateway
->completePurchase(['amount' => $this->payment_hash->data->amount]) ->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
->send(); ->send();
if ($response->isCancelled()) { if ($response->isCancelled()) {
@ -187,7 +187,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
'cancelUrl' => $this->client->company->domain() . '/client/invoices', 'cancelUrl' => $this->client->company->domain() . '/client/invoices',
'description' => implode(',', collect($this->payment_hash->data->invoices) 'description' => implode(',', collect($this->payment_hash->data->invoices)
->map(function ($invoice) { ->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()), })->toArray()),
'transactionId' => $this->payment_hash->hash . '-' . time(), 'transactionId' => $this->payment_hash->hash . '-' . time(),
'ButtonSource' => 'InvoiceNinja_SP', 'ButtonSource' => 'InvoiceNinja_SP',

View File

@ -193,7 +193,8 @@ class ACH
SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE, 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)]); return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);

View File

@ -82,7 +82,7 @@ class ImportCustomers
} }
nlog("inserting a customer"); nlog("inserting a customer");
nlog($customer); //nlog($customer);
$client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id); $client = ClientFactory::create($this->stripe->company_gateway->company_id, $this->stripe->company_gateway->user_id);

View File

@ -21,6 +21,6 @@ trait Utilities
public function convertToStripeAmount($amount, $precision) public function convertToStripeAmount($amount, $precision)
{ {
return $amount * pow(10, $precision); return (int)($amount * pow(10, $precision));
} }
} }

View File

@ -189,7 +189,7 @@ class StripePaymentDriver extends BaseDriver
if ($this->company_gateway->require_billing_address) { 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_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_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), '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']; $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) { 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_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_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_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']; $fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
@ -390,6 +390,13 @@ class StripePaymentDriver extends BaseDriver
$payment->save(); $payment->save();
} }
if ($request->type == 'charge.succeeded') {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
// charge.failed, charge.refunded
return response([], 200); return response([], 200);
} }

View File

@ -11,8 +11,8 @@
namespace App\Providers; namespace App\Providers;
use App\Http\Middleware\SetDomainNameDb;
use App\Models\Account; use App\Models\Account;
use App\Models\Subscription;
use App\Models\Client; use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
@ -24,10 +24,10 @@ use App\Models\Payment;
use App\Models\Product; use App\Models\Product;
use App\Models\Proposal; use App\Models\Proposal;
use App\Models\Quote; use App\Models\Quote;
use App\Models\Subscription;
use App\Models\Task; use App\Models\Task;
use App\Models\User; use App\Models\User;
use App\Observers\AccountObserver; use App\Observers\AccountObserver;
use App\Observers\SubscriptionObserver;
use App\Observers\ClientObserver; use App\Observers\ClientObserver;
use App\Observers\CompanyGatewayObserver; use App\Observers\CompanyGatewayObserver;
use App\Observers\CompanyObserver; use App\Observers\CompanyObserver;
@ -39,8 +39,10 @@ use App\Observers\PaymentObserver;
use App\Observers\ProductObserver; use App\Observers\ProductObserver;
use App\Observers\ProposalObserver; use App\Observers\ProposalObserver;
use App\Observers\QuoteObserver; use App\Observers\QuoteObserver;
use App\Observers\SubscriptionObserver;
use App\Observers\TaskObserver; use App\Observers\TaskObserver;
use App\Observers\UserObserver; use App\Observers\UserObserver;
use App\Utils\Ninja;
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Events\JobProcessing;
@ -49,6 +51,7 @@ use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Livewire\Livewire;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -93,6 +96,15 @@ class AppServiceProvider extends ServiceProvider
Task::observe(TaskObserver::class); Task::observe(TaskObserver::class);
User::observe(UserObserver::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) { // Queue::before(function (JobProcessing $event) {
// // \Log::info('Event Job '.$event->connectionName); // // \Log::info('Event Job '.$event->connectionName);
// \Log::error('Event Job '.$event->job->getJobId); // \Log::error('Event Job '.$event->job->getJobId);

View File

@ -169,10 +169,14 @@ class BaseRepository
*/ */
protected function alternativeSave($data, $model) protected function alternativeSave($data, $model)
{ {
//forces the client_id if it doesn't exist
if (array_key_exists('client_id', $data)) //forces the client_id if it doesn't exist if(array_key_exists('client_id', $data))
$model->client_id = $data['client_id']; $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(); $client = Client::where('id', $model->client_id)->withTrashed()->first();
$state = []; $state = [];
@ -189,7 +193,7 @@ class BaseRepository
$data = array_merge($company_defaults, $data); $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 */ /* We need to unset some variable as we sometimes unguard the model */
if (isset($tmp_data['invitations'])) if (isset($tmp_data['invitations']))
@ -302,6 +306,10 @@ class BaseRepository
/* Perform model specific tasks */ /* Perform model specific tasks */
if ($model instanceof Invoice) { 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)) { if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
$model->service()->updateStatus()->save(); $model->service()->updateStatus()->save();

View File

@ -114,7 +114,7 @@ class UserRepository extends BaseRepository
} }
$user->restore(); $user->restore();
return $user; return $user->fresh();
} }
public function destroy(array $data, User $user) public function destroy(array $data, User $user)

View File

@ -40,6 +40,8 @@ class AutoBillInvoice extends AbstractService
public function run() public function run()
{ {
$is_partial = false;
/* Is the invoice payable? */ /* Is the invoice payable? */
if (! $this->invoice->isPayable()) if (! $this->invoice->isPayable())
return $this->invoice; return $this->invoice;
@ -57,6 +59,8 @@ class AutoBillInvoice extends AbstractService
/* Determine $amount */ /* Determine $amount */
if ($this->invoice->partial > 0) { if ($this->invoice->partial > 0) {
$is_partial = true;
$invoice_total = $this->invoice->amount;
$amount = $this->invoice->partial; $amount = $this->invoice->partial;
} elseif ($this->invoice->balance > 0) { } elseif ($this->invoice->balance > 0) {
$amount = $this->invoice->balance; $amount = $this->invoice->balance;
@ -77,6 +81,9 @@ class AutoBillInvoice extends AbstractService
//$fee = $gateway_token->gateway->calcGatewayFee($amount, $gateway_token->gateway_type_id, $this->invoice->uses_inclusive_taxes); //$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(); $this->invoice = $this->invoice->service()->addGatewayFee($gateway_token->gateway, $gateway_token->gateway_type_id, $amount)->save();
if($is_partial)
$fee = $this->invoice->amount - $invoice_total;
else
$fee = $this->invoice->amount - $amount; $fee = $this->invoice->amount - $amount;
/* Build payment hash */ /* Build payment hash */
@ -340,68 +347,4 @@ class AutoBillInvoice extends AbstractService
return $this; 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;
// }
} }

View File

@ -16,6 +16,7 @@ use App\Factory\InvoiceInvitationFactory;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Services\AbstractService; use App\Services\AbstractService;
use Illuminate\Support\Str;
class CreateInvitations extends AbstractService class CreateInvitations extends AbstractService
{ {

View File

@ -21,6 +21,8 @@ use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Task; use App\Models\Task;
use App\Services\Client\ClientService; use App\Services\Client\ClientService;
use App\Services\Invoice\UpdateReminder;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -244,6 +246,13 @@ class InvoiceService
return $this; return $this;
} }
public function setReminder($settings = null)
{
$this->invoice = (new UpdateReminder($this->invoice, $settings))->run();
return $this;
}
public function setStatus($status) public function setStatus($status)
{ {
$this->invoice->status_id = $status; $this->invoice->status_id = $status;
@ -301,6 +310,10 @@ class InvoiceService
//UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf'); //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'); 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; return $this;
} }

View File

@ -39,8 +39,6 @@ class MarkSent extends AbstractService
$this->invoice->markInvitationsSent(); $this->invoice->markInvitationsSent();
$this->invoice->setReminder();
$this->invoice $this->invoice
->service() ->service()
->setStatus(Invoice::STATUS_SENT) ->setStatus(Invoice::STATUS_SENT)
@ -48,6 +46,7 @@ class MarkSent extends AbstractService
->setDueDate() ->setDueDate()
->updateBalance($this->invoice->amount) ->updateBalance($this->invoice->amount)
->deletePdf() ->deletePdf()
->setReminder()
->save(); ->save();
$this->client->service()->updateBalance($this->invoice->balance)->save(); $this->client->service()->updateBalance($this->invoice->balance)->save();

View 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;
}
}

View File

@ -41,7 +41,7 @@ class GetInvoicePdf extends AbstractService
$file_path = $path.$this->entity->hashed_id.'.pdf'; $file_path = $path.$this->entity->hashed_id.'.pdf';
$disk = config('filesystems.default'); $disk = 'public';
$file = Storage::disk($disk)->exists($file_path); $file = Storage::disk($disk)->exists($file_path);
@ -49,12 +49,6 @@ class GetInvoicePdf extends AbstractService
$file_path = CreateEntityPdf::dispatchNow($invitation); $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); return Storage::disk($disk)->path($file_path);
} }
} }

View File

@ -108,7 +108,7 @@ class CreditTransformer extends EntityTransformer
'po_number' => $credit->po_number ?: '', 'po_number' => $credit->po_number ?: '',
'date' => $credit->date ?: '', 'date' => $credit->date ?: '',
'last_sent_date' => $credit->last_sent_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 ?: '', 'reminder1_sent' => $credit->reminder1_sent ?: '',
'reminder2_sent' => $credit->reminder2_sent ?: '', 'reminder2_sent' => $credit->reminder2_sent ?: '',
'reminder3_sent' => $credit->reminder3_sent ?: '', 'reminder3_sent' => $credit->reminder3_sent ?: '',

View File

@ -114,7 +114,7 @@ class InvoiceTransformer extends EntityTransformer
'po_number' => $invoice->po_number ?: '', 'po_number' => $invoice->po_number ?: '',
'date' => $invoice->date ?: '', 'date' => $invoice->date ?: '',
'last_sent_date' => $invoice->last_sent_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 ?: '', 'due_date' => $invoice->due_date ?: '',
'terms' => $invoice->terms ?: '', 'terms' => $invoice->terms ?: '',
'public_notes' => $invoice->public_notes ?: '', 'public_notes' => $invoice->public_notes ?: '',

View File

@ -108,7 +108,7 @@ class QuoteTransformer extends EntityTransformer
'po_number' => $quote->po_number ?: '', 'po_number' => $quote->po_number ?: '',
'date' => $quote->date ?: '', 'date' => $quote->date ?: '',
'last_sent_date' => $quote->last_sent_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 ?: '', 'reminder1_sent' => $quote->reminder1_sent ?: '',
'reminder2_sent' => $quote->reminder2_sent ?: '', 'reminder2_sent' => $quote->reminder2_sent ?: '',
'reminder3_sent' => $quote->reminder3_sent ?: '', 'reminder3_sent' => $quote->reminder3_sent ?: '',

View File

@ -118,6 +118,8 @@ class Phantom
$finfo = new \finfo(FILEINFO_MIME); $finfo = new \finfo(FILEINFO_MIME);
nlog($pdf);
if($finfo->buffer($pdf) != 'application/pdf; charset=binary') if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
{ {
SystemLogger::dispatch( SystemLogger::dispatch(

View File

@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Queue;
class SystemHealth class SystemHealth
{ {
private static $extensions = [ private static $extensions = [
'mysqli', // 'mysqli',
'gd', 'gd',
'curl', 'curl',
'zip', 'zip',
@ -34,7 +34,7 @@ class SystemHealth
'mbstring', 'mbstring',
'xml', 'xml',
'bcmath', 'bcmath',
'mysqlnd', // 'mysqlnd',
//'intl', //todo double check whether we need this for email dns validation //'intl', //todo double check whether we need this for email dns validation
]; ];

View File

@ -19,97 +19,6 @@ use Illuminate\Support\Carbon;
*/ */
trait MakesReminders 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) public function inReminderWindow($schedule_reminder, $num_days_reminder)
{ {
@ -177,10 +86,8 @@ trait MakesReminders
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{ {
if (!$date) { if (!$date)
return null; return null;
}
switch ($endless_reminder_frequency_id) { switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_WEEKLY: case RecurringInvoice::FREQUENCY_WEEKLY:

View 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
View 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,
];

View File

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

View 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)
];
}
}

View File

@ -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

File diff suppressed because one or more lines are too long

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,9 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache'; const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache'; const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = { const RESOURCES = {
"version.json": "ea1781094b87723b953889a712b1feba", "version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "f46c892fb39ce4151ac09e5794745701", "main.dart.js": "55df523d1b81bba3d88e7b77511c7a87",
"/": "23224b5e03519aaa87594403d54412cf", "/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296", "assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f", "assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

1
public/js/admin.js vendored Normal file
View 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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5", "/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/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/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7", "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",

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