1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 00:11:35 +02:00

Merge branch 'develop' of github.com:hillelcoren/invoice-ninja into develop

Conflicts:
	resources/lang/en/texts.php
	resources/views/accounts/invoice_design.blade.php
	resources/views/invoices/edit.blade.php
This commit is contained in:
Hillel Coren 2016-04-19 22:48:03 +03:00
commit 0dcee794b5
68 changed files with 1349 additions and 381 deletions

View File

@ -36,7 +36,7 @@ class SendReminders extends Command
$this->info(count($accounts).' accounts found');
foreach ($accounts as $account) {
if (!$account->isPro()) {
if (!$account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
continue;
}

View File

@ -5,7 +5,7 @@ use DateTime;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Models\Account;
use App\Models\Company;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\AccountRepository;
@ -30,24 +30,32 @@ class SendRenewalInvoices extends Command
$today = new DateTime();
$sentTo = [];
// get all accounts with pro plans expiring in 10 days
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')
// get all accounts with plans expiring in 10 days
$companies = Company::whereRaw('datediff(plan_expires, curdate()) = 10')
->orderBy('id')
->get();
$this->info(count($accounts).' accounts found');
$this->info(count($companies).' companies found');
foreach ($accounts as $account) {
// don't send multiple invoices to multi-company users
if ($userAccountId = $this->accountRepo->getUserAccountId($account)) {
if (isset($sentTo[$userAccountId])) {
foreach ($companies as $company) {
if (!count($company->accounts)) {
continue;
} else {
$sentTo[$userAccountId] = true;
}
$account = $company->accounts->sortBy('id')->first();
$plan = $company->plan;
$term = $company->plan_term;
if ($company->pending_plan) {
$plan = $company->pending_plan;
$term = $company->pending_term;
}
if ($plan == PLAN_FREE || !$plan || !$term ){
continue;
}
$client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account, $plan, $term);
// set the due date to 10 days from now
$invoice = $invitation->invoice;

View File

@ -31,6 +31,7 @@ use App\Ninja\Mailers\ContactMailer;
use App\Events\UserSignedUp;
use App\Events\UserSettingsChanged;
use App\Services\AuthService;
use App\Services\PaymentService;
use App\Http\Requests\UpdateAccountRequest;
@ -40,8 +41,9 @@ class AccountController extends BaseController
protected $userMailer;
protected $contactMailer;
protected $referralRepository;
protected $paymentService;
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository)
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository, PaymentService $paymentService)
{
//parent::__construct();
@ -49,6 +51,7 @@ class AccountController extends BaseController
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
$this->referralRepository = $referralRepository;
$this->paymentService = $paymentService;
}
public function demo()
@ -111,11 +114,136 @@ class AccountController extends BaseController
public function enableProPlan()
{
$invitation = $this->accountRepo->enableProPlan();
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
return false;
}
$invitation = $this->accountRepo->enablePlan();
return $invitation->invitation_key;
}
public function changePlan() {
$user = Auth::user();
$account = $user->account;
$plan = Input::get('plan');
$term = Input::get('plan_term');
$planDetails = $account->getPlanDetails(false, false);
$credit = 0;
if ($planDetails) {
if ($planDetails['plan'] == PLAN_PRO && $plan == PLAN_ENTERPRISE) {
// Upgrade from pro to enterprise
if($planDetails['term'] == PLAN_TERM_YEARLY && $term == PLAN_TERM_MONTHLY) {
// Upgrade to yearly for now; switch to monthly in a year
$pending_monthly = true;
$term = PLAN_TERM_YEARLY;
}
$new_plan = array(
'plan' => PLAN_ENTERPRISE,
'term' => $term,
);
} elseif ($planDetails['plan'] == $plan) {
// Term switch
if ($planDetails['term'] == PLAN_TERM_YEARLY && $term == PLAN_TERM_MONTHLY) {
$pending_change = array(
'plan' => $plan,
'term' => $term
);
} elseif ($planDetails['term'] == PLAN_TERM_MONTHLY && $term == PLAN_TERM_YEARLY) {
$new_plan = array(
'plan' => $plan,
'term' => $term,
);
} else {
// Cancel the pending change
$account->company->pending_plan = null;
$account->company->pending_term = null;
$account->company->save();
Session::flash('message', trans('texts.updated_plan'));
}
} else {
// Downgrade
$refund_deadline = clone $planDetails['started'];
$refund_deadline->modify('+30 days');
if ($plan == PLAN_FREE && $refund_deadline >= date_create()) {
// Refund
$account->company->plan = null;
$account->company->plan_term = null;
$account->company->plan_started = null;
$account->company->plan_expires = null;
$account->company->plan_paid = null;
$account->company->pending_plan = null;
$account->company->pending_term = null;
if ($account->company->payment) {
$payment = $account->company->payment;
$gateway = $this->paymentService->createGateway($payment->account_gateway);
$refund = $gateway->refund(array(
'transactionReference' => $payment->transaction_reference,
'amount' => $payment->amount * 100
));
$refund->send();
$payment->delete();
Session::flash('message', trans('texts.plan_refunded'));
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
} else {
Session::flash('message', trans('texts.updated_plan'));
}
$account->company->save();
} else {
$pending_change = array(
'plan' => $plan,
'term' => $plan == PLAN_FREE ? null : $term,
);
}
}
if (!empty($new_plan)) {
$time_used = $planDetails['paid']->diff(date_create());
$days_used = $time_used->days;
if ($time_used->invert) {
// They paid in advance
$days_used *= -1;
}
$days_total = $planDetails['paid']->diff($planDetails['expires'])->days;
$percent_used = $days_used / $days_total;
$old_plan_price = Account::$plan_prices[$planDetails['plan']][$planDetails['term']];
$credit = $old_plan_price * (1 - $percent_used);
}
} else {
$new_plan = array(
'plan' => $plan,
'term' => $term,
);
}
if (!empty($pending_change) && empty($new_plan)) {
$account->company->pending_plan = $pending_change['plan'];
$account->company->pending_term = $pending_change['term'];
$account->company->save();
Session::flash('message', trans('texts.updated_plan'));
}
if (!empty($new_plan)) {
$invitation = $this->accountRepo->enablePlan($new_plan['plan'], $new_plan['term'], $credit, !empty($pending_monthly));
return Redirect::to('payment/'.$invitation->invitation_key);
}
return Redirect::to('/settings/'.ACCOUNT_MANAGEMENT, 301);
}
public function setTrashVisible($entityType, $visible)
{
Session::put("show_trash:{$entityType}", $visible == 'true');
@ -149,6 +277,8 @@ class AccountController extends BaseController
return self::showInvoiceSettings();
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_MANAGEMENT) {
return self::showAccountManagement();
} elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) {
return self::showInvoiceDesign($section);
} elseif ($section == ACCOUNT_CLIENT_PORTAL) {
@ -232,6 +362,18 @@ class AccountController extends BaseController
return View::make('accounts.details', $data);
}
private function showAccountManagement()
{
$account = Auth::user()->account;
$data = [
'account' => $account,
'planDetails' => $account->getPlanDetails(true),
'title' => trans('texts.account_management'),
];
return View::make('accounts.management', $data);
}
public function showUserDetails()
{
$oauthLoginUrls = [];
@ -379,7 +521,7 @@ class AccountController extends BaseController
$invoice->client = $client;
$invoice->invoice_items = [$invoiceItem];
//$invoice->documents = $account->isPro() ? [$document] : [];
//$invoice->documents = $account->hasFeature(FEATURE_DOCUMENTS) ? [$document] : [];
$invoice->documents = [];
$data['account'] = $account;
@ -553,7 +695,7 @@ class AccountController extends BaseController
private function saveCustomizeDesign()
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) {
$account = Auth::user()->account;
$account->custom_design = Input::get('custom_design');
$account->invoice_design_id = CUSTOM_DESIGN;
@ -568,7 +710,7 @@ class AccountController extends BaseController
private function saveClientPortal()
{
// Only allowed for pro Invoice Ninja users or white labeled self-hosted users
if ((Utils::isNinja() && Auth::user()->account->isPro()) || Auth::user()->account->isWhiteLabel()) {
if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
$input_css = Input::get('client_view_css');
if (Utils::isNinja()) {
// Allow referencing the body element
@ -619,7 +761,7 @@ class AccountController extends BaseController
private function saveEmailTemplates()
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
$account = Auth::user()->account;
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) {
@ -681,7 +823,7 @@ class AccountController extends BaseController
private function saveEmailSettings()
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_CUSTOM_EMAILS)) {
$rules = [];
$user = Auth::user();
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH));
@ -724,7 +866,7 @@ class AccountController extends BaseController
private function saveInvoiceSettings()
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_INVOICE_SETTINGS)) {
$rules = [
'invoice_number_pattern' => 'has_counter',
'quote_number_pattern' => 'has_counter',
@ -805,7 +947,7 @@ class AccountController extends BaseController
private function saveInvoiceDesign()
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) {
$account = Auth::user()->account;
$account->hide_quantity = Input::get('hide_quantity') ? true : false;
$account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false;
@ -1054,7 +1196,7 @@ class AccountController extends BaseController
$user->registered = true;
$user->save();
$user->account->startTrial();
$user->account->startTrial(PLAN_PRO);
if (Input::get('go_pro') == 'true') {
Session::set(REQUESTED_PRO_PLAN, true);
@ -1110,6 +1252,9 @@ class AccountController extends BaseController
\Log::info("Canceled Account: {$account->name} - {$user->email}");
$this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete();
Auth::logout();
@ -1126,12 +1271,12 @@ class AccountController extends BaseController
return Redirect::to('/settings/'.ACCOUNT_USER_DETAILS)->with('message', trans('texts.confirmation_resent'));
}
public function startTrial()
public function startTrial($plan)
{
$user = Auth::user();
if ($user->isEligibleForTrial()) {
$user->account->startTrial();
if ($user->isEligibleForTrial($plan)) {
$user->account->startTrial($plan);
}
return Redirect::back()->with('message', trans('texts.trial_success'));

View File

@ -133,6 +133,9 @@ class AuthController extends Controller {
if (Auth::check() && !Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete();
}

View File

@ -33,7 +33,7 @@ class AuthController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}

View File

@ -50,7 +50,7 @@ class PasswordController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}
@ -117,7 +117,7 @@ class PasswordController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}

View File

@ -114,7 +114,7 @@ class ClientController extends BaseController
if(Task::canCreate()){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
}
if (Utils::isPro() && Invoice::canCreate()) {
if (Utils::hasFeature(FEATURE_QUOTES) && Invoice::canCreate()) {
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
}
@ -201,7 +201,7 @@ class ClientController extends BaseController
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
$data['planDetails'] = $account->getPlanDetails(false, false);
}
}

View File

@ -114,7 +114,7 @@ class DocumentController extends BaseController
public function postUpload()
{
if (!Utils::isPro()) {
if (!Utils::hasFeature(FEATURE_DOCUMENTS)) {
return;
}

View File

@ -132,7 +132,11 @@ class InvoiceController extends BaseController
$invoice->start_date = Utils::fromSqlDate($invoice->start_date);
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date);
$invoice->is_pro = Auth::user()->isPro();
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$actions = [
['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")],
@ -573,7 +577,11 @@ class InvoiceController extends BaseController
$invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro();
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->is_quote = intval($invoice->is_quote);
$activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
@ -591,7 +599,11 @@ class InvoiceController extends BaseController
$backup = json_decode($activity->json_backup);
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
$backup->due_date = Utils::fromSqlDate($backup->due_date);
$backup->is_pro = Auth::user()->isPro();
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote);
$backup->account = $invoice->account->toArray();

View File

@ -191,7 +191,7 @@ class PaymentController extends BaseController
'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),

View File

@ -72,7 +72,11 @@ class PublicClientController extends BaseController
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $account->isPro();
$invoice->features = [
'customize_invoice_design' => $account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => $account->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => $account->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->invoice_fonts = $account->getFontsData();
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
@ -122,10 +126,10 @@ class PublicClientController extends BaseController
'account' => $account,
'showApprove' => $showApprove,
'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(),
@ -140,7 +144,7 @@ class PublicClientController extends BaseController
'phantomjs' => Input::has('phantomjs'),
);
if($account->isPro() && $this->canCreateZip()){
if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
if(count($zipDocs) > 1){
@ -220,8 +224,8 @@ class PublicClientController extends BaseController
'color' => $color,
'account' => $account,
'client' => $client,
'hideLogo' => $account->isWhiteLabel(),
'showDocuments' => $account->isPro(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
];
@ -273,9 +277,9 @@ class PublicClientController extends BaseController
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'),
@ -310,9 +314,9 @@ class PublicClientController extends BaseController
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT,
@ -354,9 +358,9 @@ class PublicClientController extends BaseController
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'),
@ -392,9 +396,9 @@ class PublicClientController extends BaseController
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->isPro(),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.documents'),

View File

@ -47,7 +47,7 @@ class QuoteController extends BaseController
public function index()
{
if (!Utils::isPro()) {
if (!Utils::hasFeature(FEATURE_QUOTES)) {
return Redirect::to('/invoices/create');
}
@ -84,7 +84,7 @@ class QuoteController extends BaseController
return $response;
}
if (!Utils::isPro()) {
if (!Utils::hasFeature(FEATURE_QUOTES)) {
return Redirect::to('/invoices/create');
}

View File

@ -21,7 +21,7 @@ class ReportController extends BaseController
$message = '';
$fileName = storage_path().'/dataviz_sample.txt';
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
$account = Account::where('id', '=', Auth::user()->account->id)
->with(['clients.invoices.invoice_items', 'clients.contacts'])
->first();
@ -99,7 +99,7 @@ class ReportController extends BaseController
'title' => trans('texts.charts_and_reports'),
];
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
if ($enableReport) {
$isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));

View File

@ -93,7 +93,7 @@ class TokenController extends BaseController
*/
public function save($tokenPublicId = false)
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_API)) {
$rules = [
'name' => 'required',
];

View File

@ -164,7 +164,7 @@ class UserController extends BaseController
*/
public function save($userPublicId = false)
{
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
if (Auth::user()->hasFeature(FEATURE_USERS)) {
$rules = [
'first_name' => 'required',
'last_name' => 'required',
@ -190,8 +190,10 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
} else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
@ -202,13 +204,15 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH);
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
$user->public_id = $lastUser->public_id + 1;
if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
}
$user->save();
@ -286,6 +290,9 @@ class UserController extends BaseController
if (!Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete();
}
}

View File

@ -175,8 +175,8 @@ class VendorController extends BaseController
$data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($vendor->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
if ($account = Account::whereId($client->public_id)->first()) {
$data['planDetails'] = $account->getPlanDetails(false, false);
}
}

View File

@ -47,7 +47,7 @@ class ApiCheck {
return $next($request);
}
if (!Utils::isPro() && !$loggingIn) {
if (!Utils::hasFeature(FEATURE_API) && !$loggingIn) {
return Response::json('API requires pro plan', 403, $headers);
} else {
$key = Auth::check() ? Auth::user()->account->id : $request->getClientIp();

View File

@ -42,7 +42,7 @@ class Authenticate {
// Does this account require portal passwords?
$account = Account::whereId($account_id)->first();
if($account && (!$account->enable_portal_password || !$account->isPro())){
if($account && (!$account->enable_portal_password || !$account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD))){
$authenticated = true;
}

View File

@ -141,9 +141,10 @@ class StartupCheck
}
} elseif ($productId == PRODUCT_WHITE_LABEL) {
if ($data == 'valid') {
$account = Auth::user()->account;
$account->pro_plan_paid = date_create()->format('Y-m-d');
$account->save();
$company = Auth::user()->account->company;
$company->plan_paid = date_create()->format('Y-m-d');
$company->plan = PLAN_WHITE_LABEL;
$company->save();
Session::flash('message', trans('texts.bought_white_label'));
}

View File

@ -188,7 +188,8 @@ Route::group([
Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('start_trial', 'AccountController@startTrial');
Route::get('start_trial/{plan}', 'AccountController@startTrial')
->where(['plan'=>'pro']);
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
@ -212,6 +213,7 @@ Route::group([
Route::get('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/change_plan', 'AccountController@changePlan');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::post('settings/company_details', 'AccountController@updateDetails');
Route::get('settings/{section?}', 'AccountController@showSection');
@ -354,6 +356,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_MANAGEMENT', 'account_management');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
@ -552,7 +555,6 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.5.1.3');
define('NINJA_DATE', '2000-01-01');
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');
define('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja');
@ -582,6 +584,10 @@ if (!defined('CONTACT_EMAIL')) {
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50);
define('PLAN_PRICE_PRO_MONTHLY', 5);
define('PLAN_PRICE_PRO_YEARLY', 50);
define('PLAN_PRICE_ENTERPRISE_MONTHLY', 10);
define('PLAN_PRICE_ENTERPRISE_YEARLY', 100);
define('WHITE_LABEL_PRICE', 20);
define('INVOICE_DESIGNS_PRICE', 10);
@ -645,6 +651,45 @@ if (!defined('CONTACT_EMAIL')) {
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_WHITE_LABEL', 'white_label');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
// Pro
define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design');
define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by');
define('FEATURE_DIFFERENT_DESIGNS', 'different_designs');
define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders');
define('FEATURE_INVOICE_SETTINGS', 'invoice_settings');
define('FEATURE_CUSTOM_EMAILS', 'custom_emails');
define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment');
define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs');
define('FEATURE_QUOTES', 'quotes');
define('FEATURE_REPORTS', 'reports');
define('FEATURE_API', 'api');
define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password');
define('FEATURE_CUSTOM_URL', 'custom_url');
define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed
// Whitelabel
define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css');
define('FEATURE_WHITE_LABEL', 'feature_white_label');
// Enterprise
define('FEATURE_DOCUMENTS', 'documents');
// No Trial allowed
define('FEATURE_USERS', 'users');// Grandfathered for old Pro users
define('FEATURE_USER_PERMISSIONS', 'user_permissions');
// Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],

View File

@ -118,6 +118,11 @@ class Utils
return Auth::check() && Auth::user()->isPro();
}
public static function hasFeature($feature)
{
return Auth::check() && Auth::user()->hasFeature($feature);
}
public static function isAdmin()
{
return Auth::check() && Auth::user()->is_admin;
@ -440,7 +445,12 @@ class Utils
return false;
}
if ($date instanceof DateTime) {
$dateTime = $date;
} else {
$dateTime = new DateTime($date);
}
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
@ -961,38 +971,6 @@ class Utils
return $entity1;
}
public static function withinPastYear($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
$interval = $today->diff($datePaid);
return $interval->y == 0;
}
public static function getInterval($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
return $today->diff($datePaid);
}
public static function withinPastTwoWeeks($date)
{
$interval = Utils::getInterval($date);
return $interval && $interval->d <= 14;
}
public static function addHttp($url)
{
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {

View File

@ -14,10 +14,11 @@ class InvoiceListener
{
public function createdInvoice(InvoiceWasCreated $event)
{
if (Utils::isPro()) {
if (Utils::hasFeature(FEATURE_DIFFERENT_DESIGNS)) {
return;
}
// Make sure the account has the same design set as the invoice does
if (Auth::check()) {
$invoice = $event->invoice;
$account = Auth::user()->account;

View File

@ -19,6 +19,17 @@ class Account extends Eloquent
use PresentableTrait;
use SoftDeletes;
public static $plan_prices = array(
PLAN_PRO => array(
PLAN_TERM_MONTHLY => PLAN_PRICE_PRO_MONTHLY,
PLAN_TERM_YEARLY => PLAN_PRICE_PRO_YEARLY,
),
PLAN_ENTERPRISE => array(
PLAN_TERM_MONTHLY => PLAN_PRICE_ENTERPRISE_MONTHLY,
PLAN_TERM_YEARLY => PLAN_PRICE_ENTERPRISE_YEARLY,
),
);
protected $presenter = 'App\Ninja\Presenters\AccountPresenter';
protected $dates = ['deleted_at'];
protected $hidden = ['ip'];
@ -57,6 +68,7 @@ class Account extends Eloquent
ACCOUNT_PRODUCTS,
ACCOUNT_NOTIFICATIONS,
ACCOUNT_IMPORT_EXPORT,
ACCOUNT_MANAGEMENT,
];
public static $advancedSettings = [
@ -176,6 +188,11 @@ class Account extends Eloquent
return $this->hasMany('App\Models\Payment','account_id','id')->withTrashed();
}
public function company()
{
return $this->belongsTo('App\Models\Company');
}
public function setIndustryIdAttribute($value)
{
$this->attributes['industry_id'] = $value ?: null;
@ -516,7 +533,7 @@ class Account extends Eloquent
public function getNumberPrefix($isQuote)
{
if ( ! $this->isPro()) {
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return '';
}
@ -525,7 +542,7 @@ class Account extends Eloquent
public function hasNumberPattern($isQuote)
{
if ( ! $this->isPro()) {
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return false;
}
@ -645,7 +662,7 @@ class Account extends Eloquent
$default = $this->invoice_number_counter;
$actual = Utils::parseInt($invoice->invoice_number);
if ( ! $this->isPro() && $default != $actual) {
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) {
$this->invoice_number_counter = $actual + 1;
} else {
$this->invoice_number_counter += 1;
@ -766,17 +783,80 @@ class Account extends Eloquent
return $this->account_key === NINJA_ACCOUNT_KEY;
}
public function startTrial()
public function startTrial($plan)
{
if ( ! Utils::isNinja()) {
return;
}
$this->pro_plan_trial = date_create()->format('Y-m-d');
$this->save();
$this->company->trial_plan = $plan;
$this->company->trial_started = date_create()->format('Y-m-d');
$this->company->save();
}
public function isPro()
public function hasFeature($feature)
{
$planDetails = $this->getPlanDetails();
$selfHost = !Utils::isNinjaProd();
if (!$selfHost && function_exists('ninja_account_features')) {
$result = ninja_account_features($this, $feature);
if ($result != null) {
return $result;
}
}
switch ($feature) {
// Pro
case FEATURE_CUSTOMIZE_INVOICE_DESIGN:
case FEATURE_REMOVE_CREATED_BY:
case FEATURE_DIFFERENT_DESIGNS:
case FEATURE_EMAIL_TEMPLATES_REMINDERS:
case FEATURE_INVOICE_SETTINGS:
case FEATURE_CUSTOM_EMAILS:
case FEATURE_PDF_ATTACHMENT:
case FEATURE_MORE_INVOICE_DESIGNS:
case FEATURE_QUOTES:
case FEATURE_REPORTS:
case FEATURE_API:
case FEATURE_CLIENT_PORTAL_PASSWORD:
case FEATURE_CUSTOM_URL:
return $selfHost || !empty($planDetails);
// Pro; No trial allowed, unless they're trialing enterprise with an active pro plan
case FEATURE_MORE_CLIENTS:
return $selfHost || !empty($planDetails) && (!$planDetails['trial'] || !empty($this->getPlanDetails(false, false)));
// White Label
case FEATURE_WHITE_LABEL:
if ($this->isNinjaAccount() || (!$selfHost && $planDetails && !$plan_details['expires'])) {
return false;
}
// Fallthrough
case FEATURE_CLIENT_PORTAL_CSS:
return !empty($planDetails);// A plan is required even for self-hosted users
// Enterprise; No Trial allowed; grandfathered for old pro users
case FEATURE_USERS:// Grandfathered for old Pro users
if($planDetails && $planDetails['trial']) {
// Do they have a non-trial plan?
$planDetails = $this->getPlanDetails(false, false);
}
return $selfHost || !empty($planDetails) && ($planDetails['plan'] == PLAN_ENTERPRISE || $planDetails['started'] <= date_create(PRO_USERS_GRANDFATHER_DEADLINE));
// Enterprise; No Trial allowed
case FEATURE_DOCUMENTS:
case FEATURE_USER_PERMISSIONS:
return $selfHost || !empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE && !$planDetails['trial'];
default:
return false;
}
}
public function isPro(&$plan_details = null)
{
if (!Utils::isNinjaProd()) {
return true;
@ -786,14 +866,113 @@ class Account extends Eloquent
return true;
}
$datePaid = $this->pro_plan_paid;
$trialStart = $this->pro_plan_trial;
$plan_details = $this->getPlanDetails();
if ($datePaid == NINJA_DATE) {
return !empty($plan_details);
}
public function isEnterprise(&$plan_details = null)
{
if (!Utils::isNinjaProd()) {
return true;
}
return Utils::withinPastTwoWeeks($trialStart) || Utils::withinPastYear($datePaid);
if ($this->isNinjaAccount()) {
return true;
}
$plan_details = $this->getPlanDetails();
return $plan_details && $plan_details['plan'] == PLAN_ENTERPRISE;
}
public function getPlanDetails($include_inactive = false, $include_trial = true)
{
if (!$this->company) {
return null;
}
$plan = $this->company->plan;
$trial_plan = $this->company->trial_plan;
if(!$plan && (!$trial_plan || !$include_trial)) {
return null;
}
$trial_active = false;
if ($trial_plan && $include_trial) {
$trial_started = DateTime::createFromFormat('Y-m-d', $this->company->trial_started);
$trial_expires = clone $trial_started;
$trial_expires->modify('+2 weeks');
if ($trial_expires >= date_create()) {
$trial_active = true;
}
}
$plan_active = false;
if ($plan) {
if ($this->company->plan_expires == null) {
$plan_active = true;
$plan_expires = false;
} else {
$plan_expires = DateTime::createFromFormat('Y-m-d', $this->company->plan_expires);
if ($plan_expires >= date_create()) {
$plan_active = true;
}
}
}
if (!$include_inactive && !$plan_active && !$trial_active) {
return null;
}
// Should we show plan details or trial details?
if (($plan && !$trial_plan) || !$include_trial) {
$use_plan = true;
} elseif (!$plan && $trial_plan) {
$use_plan = false;
} else {
// There is both a plan and a trial
if (!empty($plan_active) && empty($trial_active)) {
$use_plan = true;
} elseif (empty($plan_active) && !empty($trial_active)) {
$use_plan = false;
} elseif (!empty($plan_active) && !empty($trial_active)) {
// Both are active; use whichever is a better plan
if ($plan == PLAN_ENTERPRISE) {
$use_plan = true;
} elseif ($trial_plan == PLAN_ENTERPRISE) {
$use_plan = false;
} else {
// They're both the same; show the plan
$use_plan = true;
}
} else {
// Neither are active; use whichever expired most recently
$use_plan = $plan_expires >= $trial_expires;
}
}
if ($use_plan) {
return array(
'trial' => false,
'plan' => $plan,
'started' => DateTime::createFromFormat('Y-m-d', $this->company->plan_started),
'expires' => $plan_expires,
'paid' => DateTime::createFromFormat('Y-m-d', $this->company->plan_paid),
'term' => $this->company->plan_term,
'active' => $plan_active,
);
} else {
return array(
'trial' => true,
'plan' => $trial_plan,
'started' => $trial_started,
'expires' => $trial_expires,
'active' => $trial_active,
);
}
}
public function isTrial()
@ -802,34 +981,53 @@ class Account extends Eloquent
return false;
}
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
return false;
$plan_details = $this->getPlanDetails();
return $plan_details && $plan_details['trial'];
}
return Utils::withinPastTwoWeeks($this->pro_plan_trial);
}
public function isEligibleForTrial()
public function isEligibleForTrial($plan = null)
{
return ! $this->pro_plan_trial || $this->pro_plan_trial == '0000-00-00';
if (!$this->company->trial_plan) {
if ($plan) {
return $plan == PLAN_PRO || $plan == PLAN_ENTERPRISE;
} else {
return array(PLAN_PRO, PLAN_ENTERPRISE);
}
}
if ($this->company->trial_plan == PLAN_PRO) {
if ($plan) {
return $plan != PLAN_PRO;
} else {
return array(PLAN_ENTERPRISE);
}
}
return false;
}
public function getCountTrialDaysLeft()
{
$interval = Utils::getInterval($this->pro_plan_trial);
$planDetails = $this->getPlanDetails(true);
return $interval ? 14 - $interval->d : 0;
if(!$planDetails || !$planDetails['trial']) {
return 0;
}
$today = new DateTime('now');
$interval = $today->diff($planDetails['expires']);
return $interval ? $interval->d : 0;
}
public function getRenewalDate()
{
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
$date = DateTime::createFromFormat('Y-m-d', $this->pro_plan_paid);
$date->modify('+1 year');
$planDetails = $this->getPlanDetails();
if ($planDetails) {
$date = $planDetails['expires'];
$date = max($date, date_create());
} elseif ($this->isTrial()) {
$date = date_create();
$date->modify('+'.$this->getCountTrialDaysLeft().' day');
} else {
$date = date_create();
}
@ -837,23 +1035,6 @@ class Account extends Eloquent
return $date->format('Y-m-d');
}
public function isWhiteLabel()
{
if ($this->isNinjaAccount()) {
return false;
}
if (Utils::isNinjaProd()) {
return self::isPro() && $this->pro_plan_paid != NINJA_DATE;
} else {
if ($this->pro_plan_paid == NINJA_DATE) {
return true;
}
return Utils::withinPastYear($this->pro_plan_paid);
}
}
public function getLogoSize()
{
if(!$this->hasLogo()){
@ -930,7 +1111,7 @@ class Account extends Eloquent
public function getEmailSubject($entityType)
{
if ($this->isPro()) {
if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) {
$field = "email_subject_{$entityType}";
$value = $this->$field;
@ -950,7 +1131,7 @@ class Account extends Eloquent
$template = "<div>\$client,</div><br>";
if ($this->isPro() && $this->email_design_id != EMAIL_DESIGN_PLAIN) {
if ($this->hasFeature(FEATURE_CUSTOM_EMAILS) && $this->email_design_id != EMAIL_DESIGN_PLAIN) {
$template .= "<div>" . trans("texts.{$entityType}_message_button", ['amount' => '$amount']) . "</div><br>" .
"<div style=\"text-align: center;\">\$viewButton</div><br>";
} else {
@ -969,7 +1150,7 @@ class Account extends Eloquent
{
$template = false;
if ($this->isPro()) {
if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) {
$field = "email_template_{$entityType}";
$template = $this->$field;
}
@ -1065,7 +1246,7 @@ class Account extends Eloquent
public function showCustomField($field, $entity = false)
{
if ($this->isPro()) {
if ($this->hasFeature(FEATURE_INVOICE_SETTINGS)) {
return $this->$field ? true : false;
}
@ -1081,18 +1262,18 @@ class Account extends Eloquent
public function attatchPDF()
{
return $this->isPro() && $this->pdf_email_attachment;
return $this->hasFeaure(FEATURE_PDF_ATTACHMENT) && $this->pdf_email_attachment;
}
public function getEmailDesignId()
{
return $this->isPro() ? $this->email_design_id : EMAIL_DESIGN_PLAIN;
return $this->hasFeature(FEATURE_CUSTOM_EMAILS) ? $this->email_design_id : EMAIL_DESIGN_PLAIN;
}
public function clientViewCSS(){
$css = null;
$css = '';
if ($this->isPro()) {
if ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) {
$bodyFont = $this->getBodyFontCss();
$headerFont = $this->getHeaderFontCss();
@ -1100,12 +1281,11 @@ class Account extends Eloquent
if ($headerFont != $bodyFont) {
$css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}';
}
if ((Utils::isNinja() && $this->isPro()) || $this->isWhiteLabel()) {
}
if ($this->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
// For self-hosted users, a white-label license is required for custom CSS
$css .= $this->client_view_css;
}
}
return $css;
}
@ -1126,11 +1306,11 @@ class Account extends Eloquent
}
public function getHeaderFontId() {
return ($this->isPro() && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT;
return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT;
}
public function getBodyFontId() {
return ($this->isPro() && $this->body_font_id) ? $this->body_font_id : DEFAULT_BODY_FONT;
return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->body_font_id) ? $this->body_font_id : DEFAULT_BODY_FONT;
}
public function getHeaderFontName(){

View File

@ -155,7 +155,7 @@ class Client extends EntityModel
$contact->send_invoice = true;
}
if (!Utils::isPro() || $this->account->enable_portal_password){
if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->account->enable_portal_password){
if(!empty($data['password']) && $data['password']!='-%unchanged%-'){
$contact->password = bcrypt($data['password']);
} else if(empty($data['password'])){

21
app/Models/Company.php Normal file
View File

@ -0,0 +1,21 @@
<?php namespace App\Models;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class Company extends Eloquent
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function accounts()
{
return $this->hasMany('App\Models\Account');
}
public function payment()
{
return $this->belongsTo('App\Models\Payment');
}
}

View File

@ -40,7 +40,7 @@ class Invitation extends EntityModel
$url = SITE_URL;
$iframe_url = $this->account->iframe_url;
if ($this->account->isPro()) {
if ($this->account->hasFeature(FEATURE_CUSTOM_URL)) {
if ($iframe_url && !$forceOnsite) {
return "{$iframe_url}?{$this->invitation_key}";
} elseif ($this->account->subdomain) {

View File

@ -409,7 +409,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'invoice_design',
'invoice_design_id',
'invoice_fonts',
'is_pro',
'features',
'is_quote',
'custom_value1',
'custom_value2',

View File

@ -112,9 +112,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isPro();
}
public function hasFeature($feature)
{
return $this->account->hasFeature($feature);
}
public function isPaidPro()
{
return $this->isPro() && ! $this->isTrial();
return $this->isPro($accountDetails) && !$accountDetails['trial'];
}
public function isTrial()
@ -122,14 +127,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isTrial();
}
public function isEligibleForTrial()
public function isEligibleForTrial($plan = null)
{
return $this->account->isEligibleForTrial();
return $this->account->isEligibleForTrial($plan);
}
public function maxInvoiceDesignId()
{
return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
return $this->hasFeature(FEATURE_MORE_INVOICE_DESIGNS) ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
}
public function getDisplayName()
@ -173,7 +178,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getMaxNumClients()
{
if ($this->isPro() && ! $this->isTrial()) {
if ($this->hasFeature(FEATURE_MORE_CLIENTS)) {
return MAX_NUM_CLIENTS_PRO;
}
@ -186,7 +191,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getMaxNumVendors()
{
if ($this->isPro() && ! $this->isTrial()) {
if ($this->hasFeature(FEATURE_MORE_CLIENTS)) {
return MAX_NUM_VENDORS_PRO;
}

View File

@ -136,7 +136,7 @@ class ContactMailer extends Mailer
'amount' => $invoice->getRequestedAmount()
];
if (empty($invitation->contact->password) && $account->isPro() && $account->enable_portal_password && $account->send_portal_password) {
if (empty($invitation->contact->password) && $account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password && $account->send_portal_password) {
// The contact needs a password
$variables['password'] = $password = $this->generatePassword();
$invitation->contact->password = bcrypt($password);
@ -291,7 +291,7 @@ class ContactMailer extends Mailer
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$documentsHTML = '';
if($account->isPro() && $invoice->hasDocuments()){
if($account->hasFeature(FEATURE_DOCUMENTS) && $invoice->hasDocuments()){
$documentsHTML .= trans('texts.email_documents_header').'<ul>';
foreach($invoice->documents as $document){
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';

View File

@ -17,6 +17,7 @@ use App\Models\Client;
use App\Models\Language;
use App\Models\Contact;
use App\Models\Account;
use App\Models\Company;
use App\Models\User;
use App\Models\UserAccount;
use App\Models\AccountToken;
@ -25,9 +26,13 @@ class AccountRepository
{
public function create($firstName = '', $lastName = '', $email = '', $password = '')
{
$company = new Company();
$company->save();
$account = new Account();
$account->ip = Request::getClientIp();
$account->account_key = str_random(RANDOM_KEY_LENGTH);
$account->company_id = $company->id;
// Track referal code
if ($referralCode = Session::get(SESSION_REFERRAL_CODE)) {
@ -205,21 +210,23 @@ class AccountRepository
return $data;
}
public function enableProPlan()
public function enablePlan($plan = PLAN_PRO, $term = PLAN_TERM_MONTHLY, $credit = 0, $pending_monthly = false)
{
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
return false;
}
$account = Auth::user()->account;
$client = $this->getNinjaClient($account);
$invitation = $this->createNinjaInvoice($client, $account);
$invitation = $this->createNinjaInvoice($client, $account, $plan, $term, $credit, $pending_monthly);
return $invitation;
}
public function createNinjaInvoice($client, $clientAccount)
public function createNinjaInvoice($client, $clientAccount, $plan = PLAN_PRO, $term = PLAN_TERM_MONTHLY, $credit = 0, $pending_monthly = false)
{
if ($credit < 0) {
$credit = 0;
}
$plan_cost = Account::$plan_prices[$plan][$term];
$account = $this->getNinjaAccount();
$lastInvoice = Invoice::withTrashed()->whereAccountId($account->id)->orderBy('public_id', 'DESC')->first();
$publicId = $lastInvoice ? ($lastInvoice->public_id + 1) : 1;
@ -230,20 +237,40 @@ class AccountRepository
$invoice->client_id = $client->id;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->invoice_date = $clientAccount->getRenewalDate();
$invoice->amount = PRO_PLAN_PRICE;
$invoice->balance = PRO_PLAN_PRICE;
$invoice->amount = $invoice->balance = $plan_cost - $credit;
$invoice->save();
$item = new InvoiceItem();
$item->account_id = $account->id;
$item->user_id = $account->users()->first()->id;
$item->public_id = $publicId;
if ($credit) {
$credit_item = InvoiceItem::createNew($invoice);
$credit_item->qty = 1;
$credit_item->cost = -$credit;
$credit_item->notes = trans('texts.plan_credit_description');
$credit_item->product_key = trans('texts.plan_credit_product');
$invoice->invoice_items()->save($credit_item);
}
$item = InvoiceItem::createNew($invoice);
$item->qty = 1;
$item->cost = PRO_PLAN_PRICE;
$item->notes = trans('texts.pro_plan_description');
$item->product_key = trans('texts.pro_plan_product');
$item->cost = $plan_cost;
$item->notes = trans("texts.{$plan}_plan_{$term}_description");
// Don't change this without updating the regex in PaymentService->createPayment()
$item->product_key = 'Plan - '.ucfirst($plan).' ('.ucfirst($term).')';
$invoice->invoice_items()->save($item);
if ($pending_monthly) {
$term_end = $term == PLAN_MONTHLY ? date_create('+1 month') : date_create('+1 year');
$pending_monthly_item = InvoiceItem::createNew($invoice);
$item->qty = 1;
$pending_monthly_item->cost = 0;
$pending_monthly_item->notes = trans("texts.plan_pending_monthly", array('date', Utils::dateToString($term_end)));
// Don't change this without updating the text in PaymentService->createPayment()
$pending_monthly_item->product_key = 'Pending Monthly';
$invoice->invoice_items()->save($pending_monthly_item);
}
$invitation = new Invitation();
$invitation->account_id = $account->id;
$invitation->user_id = $account->users()->first()->id;
@ -355,7 +382,7 @@ class AccountRepository
$user->last_name = $lastName;
$user->registered = true;
$user->account->startTrial();
$user->account->startTrial(PLAN_PRO);
}
$user->oauth_provider_id = $providerId;
@ -471,10 +498,10 @@ class AccountRepository
$item = new stdClass();
$item->id = $record->id;
$item->user_id = $user->id;
$item->public_id = $user->public_id;
$item->user_name = $user->getDisplayName();
$item->account_id = $user->account->id;
$item->account_name = $user->account->getDisplayName();
$item->pro_plan_paid = $user->account->pro_plan_paid;
$item->logo_url = $user->account->hasLogo() ? $user->account->getLogoUrl() : null;
$data[] = $item;
}
@ -487,43 +514,6 @@ class AccountRepository
return self::prepareUsersData($record);
}
public function syncAccounts($userId, $proPlanPaid) {
$users = self::loadAccounts($userId);
self::syncUserAccounts($users, $proPlanPaid);
}
public function syncUserAccounts($users, $proPlanPaid = false) {
if (!$users) {
return;
}
if (!$proPlanPaid) {
foreach ($users as $user) {
if ($user->pro_plan_paid && $user->pro_plan_paid != '0000-00-00') {
$proPlanPaid = $user->pro_plan_paid;
break;
}
}
}
if (!$proPlanPaid) {
return;
}
$accountIds = [];
foreach ($users as $user) {
if ($user->pro_plan_paid != $proPlanPaid) {
$accountIds[] = $user->account_id;
}
}
if (count($accountIds)) {
DB::table('accounts')
->whereIn('id', $accountIds)
->update(['pro_plan_paid' => $proPlanPaid]);
}
}
public function associateAccounts($userId1, $userId2) {
$record = self::findUserAccounts($userId1, $userId2);
@ -542,8 +532,59 @@ class AccountRepository
$record->save();
$users = self::prepareUsersData($record);
self::syncUserAccounts($users);
$users = $this->getUserAccounts($record);
// Pick the primary user
foreach ($users as $user) {
if (!$user->public_id) {
$useAsPrimary = false;
if(empty($primaryUser)) {
$useAsPrimary = true;
}
$planDetails = $user->account->getPlanDetails(false, false);
$planLevel = 0;
if ($planDetails) {
$planLevel = 1;
if ($planDetails['plan'] == PLAN_ENTERPRISE) {
$planLevel = 2;
}
if (!$useAsPrimary && (
$planLevel > $primaryUserPlanLevel
|| ($planLevel == $primaryUserPlanLevel && $planDetails['expires'] > $primaryUserPlanExpires)
)) {
$useAsPrimary = true;
}
}
if ($useAsPrimary) {
$primaryUser = $user;
$primaryUserPlanLevel = $planLevel;
if ($planDetails) {
$primaryUserPlanExpires = $planDetails['expires'];
}
}
}
}
// Merge other companies into the primary user's company
if (!empty($primaryUser)) {
foreach ($users as $user) {
if ($user == $primaryUser || $user->public_id) {
continue;
}
if ($user->account->company_id != $primaryUser->account->company_id) {
foreach ($user->account->company->accounts as $account) {
$account->company_id = $primaryUser->account->company_id;
$account->save();
}
$user->account->company->forceDelete();
}
}
}
return $users;
}
@ -563,6 +604,15 @@ class AccountRepository
$userAccount->removeUserId($userId);
$userAccount->save();
}
$user = User::whereId($userId)->first();
if (!$user->public_id && $user->account->company->accounts->count() > 1) {
$company = Company::create();
$company->save();
$user->account->company_id = $company->id;
$user->account->save();
}
}
public function findWithReminders()

View File

@ -4,7 +4,7 @@ use App\Models\Account;
class NinjaRepository
{
public function updateProPlanPaid($clientPublicId, $proPlanPaid)
public function updatePlanDetails($clientPublicId, $data)
{
$account = Account::whereId($clientPublicId)->first();
@ -12,7 +12,13 @@ class NinjaRepository
return;
}
$account->pro_plan_paid = $proPlanPaid;
$account->save();
$company = $account->company;
$company->plan = !empty($data['plan']) && $data['plan'] != PLAN_FREE?$data['plan']:null;
$company->plan_term = !empty($data['plan_term'])?$data['plan_term']:null;
$company->plan_paid = !empty($data['plan_paid'])?$data['plan_paid']:null;
$company->plan_started = !empty($data['plan_started'])?$data['plan_started']:null;
$company->plan_expires = !empty($data['plan_expires'])?$data['plan_expires']:null;
$company->save();
}
}

View File

@ -1,31 +1,32 @@
<?php namespace App\Ninja\Repositories;
use DB;
use App\Models\Account;
use Utils;
class ReferralRepository
{
public function getCounts($userId)
{
$accounts = DB::table('accounts')
->where('referral_user_id', $userId)
->get(['id', 'pro_plan_paid']);
$accounts = Account::where('referral_user_id', $userId);
$counts = [
'free' => 0,
'pro' => 0
'pro' => 0,
'enterprise' => 0
];
foreach ($accounts as $account) {
$counts['free']++;
if (Utils::withinPastYear($account->pro_plan_paid)) {
$plan = $account->getPlanDetails(false, false);
if ($plan) {
$counts['pro']++;
if ($plan['plan'] == PLAN_ENTERPRISE) {
$counts['enterprise']++;
}
}
}
return $counts;
}
}

View File

@ -68,7 +68,7 @@ class AppServiceProvider extends ServiceProvider {
if(!empty($items))$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</a></li>';
if(Invoice::canCreate())$items[] = '<li><a href="'.URL::to('recurring_invoices/create').'">'.trans("texts.new_recurring_invoice").'</a></li>';
if (Auth::user()->isPro()) {
if (Auth::user()->hasFeature(FEATURE_QUOTES)) {
$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>';
if(Invoice::canCreate())$items[] = '<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';

View File

@ -32,8 +32,8 @@ class ClientService extends BaseService
public function save($data)
{
if (Auth::user()->account->isNinjaAccount() && isset($data['pro_plan_paid'])) {
$this->ninjaRepo->updateProPlanPaid($data['public_id'], $data['pro_plan_paid']);
if (Auth::user()->account->isNinjaAccount() && isset($data['plan'])) {
$this->ninjaRepo->updatePlanDetails($data['public_id'], $data);
}
return $this->clientRepo->save($data);
@ -134,7 +134,7 @@ class ClientService extends BaseService
return URL::to("quotes/create/{$model->public_id}");
},
function ($model) {
return Auth::user()->isPro() && Invoice::canCreate();
return Auth::user()->hasFeature(FEATURE_QUOTES) && Invoice::canCreate();
}
],
[

View File

@ -100,7 +100,7 @@ class InvoiceService extends BaseService
return null;
}
if ($account->auto_convert_quote || ! $account->isPro()) {
if ($account->auto_convert_quote || ! $account->hasFeature(FEATURE_QUOTES)) {
$invoice = $this->convertQuote($quote, $invitation);
event(new QuoteInvitationWasApproved($quote, $invoice, $invitation));

View File

@ -216,17 +216,6 @@ class PaymentService extends BaseService
{
$invoice = $invitation->invoice;
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY && $invoice->amount == PRO_PLAN_PRICE) {
$account = Account::with('users')->find($invoice->client->public_id);
$account->pro_plan_paid = $account->getRenewalDate();
$account->save();
// sync pro accounts
$user = $account->users()->first();
$this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid);
}
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
@ -243,6 +232,59 @@ class PaymentService extends BaseService
$payment->save();
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
foreach ($invoice->invoice_items as $invoice_item) {
// Hacky, but invoices don't have meta fields to allow us to store this easily
if (1 == preg_match('/^Plan - (.+) \((.+)\)$/', $invoice_item->product_key, $matches)) {
$plan = strtolower($matches[1]);
$term = strtolower($matches[2]);
} elseif ($invoice_item->product_key == 'Pending Monthly') {
$pending_monthly = true;
}
}
if (!empty($plan)) {
$account = Account::with('users')->find($invoice->client->public_id);
if(
$account->company->plan != $plan
|| DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create('-7 days')
) {
// Either this is a different plan, or the subscription expired more than a week ago
// Reset any grandfathering
$account->company->plan_started = date_create()->format('Y-m-d');
}
if (
$account->company->plan == $plan
&& $account->company->plan_term == $term
&& DateTime::createFromFormat('Y-m-d', $account->company->plan_expires) >= date_create()
) {
// This is a renewal; mark it paid as of when this term expires
$account->company->plan_paid = $account->company->plan_expires;
} else {
$account->company->plan_paid = date_create()->format('Y-m-d');
}
$account->company->payment_id = $payment->id;
$account->company->plan = $plan;
$account->company->plan_term = $term;
$account->company->plan_expires = DateTime::createFromFormat('Y-m-d', $account->company->plan_paid)
->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d');
if (!empty($pending_monthly)) {
$account->company->pending_plan = $plan;
$account->company->pending_term = PLAN_TERM_MONTHLY;
} else {
$account->company->pending_plan = null;
$account->company->pending_term = null;
}
$account->company->save();
}
}
return $payment;
}

View File

@ -28,8 +28,8 @@ class VendorService extends BaseService
public function save($data)
{
if (Auth::user()->account->isNinjaAccount() && isset($data['pro_plan_paid'])) {
$this->ninjaRepo->updateProPlanPaid($data['public_id'], $data['pro_plan_paid']);
if (Auth::user()->account->isNinjaAccount() && isset($data['plan'])) {
$this->ninjaRepo->updatePlanDetails($data['public_id'], $data);
}
return $this->vendorRepo->save($data);

View File

@ -63,6 +63,7 @@ class AddDocuments extends Migration {
$table->dropColumn('logo_height');
$table->dropColumn('logo_size');
$table->dropColumn('invoice_embed_documents');
$table->dropColumn('document_email_attachment');
});
Schema::dropIfExists('documents');

View File

@ -0,0 +1,156 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Models\Company;
use App\Models\Account;
class EnterprisePlan extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up() {
Schema::create('companies', function($table)
{
$table->increments('id');
$table->enum('plan', array('pro', 'enterprise', 'white_label'))->nullable();
$table->enum('plan_term', array('month', 'year'))->nullable();
$table->date('plan_started')->nullable();
$table->date('plan_paid')->nullable();
$table->date('plan_expires')->nullable();
$table->unsignedInteger('payment_id')->nullable();
$table->foreign('payment_id')->references('id')->on('payments');
$table->date('trial_started')->nullable();
$table->enum('trial_plan', array('pro', 'enterprise'))->nullable();
$table->enum('pending_plan', array('pro', 'enterprise', 'free'))->nullable();
$table->enum('pending_term', array('month', 'year'))->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::table('accounts', function($table)
{
$table->unsignedInteger('company_id')->nullable();
$table->foreign('company_id')->references('id')->on('companies');
});
$single_account_ids = \DB::table('users')
->leftJoin('user_accounts', function ($join) {
$join->on('user_accounts.user_id1', '=', 'users.id');
$join->orOn('user_accounts.user_id2', '=', 'users.id');
$join->orOn('user_accounts.user_id3', '=', 'users.id');
$join->orOn('user_accounts.user_id4', '=', 'users.id');
$join->orOn('user_accounts.user_id5', '=', 'users.id');
})
->whereNull('user_accounts.id')
->where(function ($query) {
$query->whereNull('users.public_id');
$query->orWhere('users.public_id', '=', 0);
})
->lists('users.account_id');
$group_accounts = \DB::select(
'SELECT u1.account_id as account1, u2.account_id as account2, u3.account_id as account3, u4.account_id as account4, u5.account_id as account5 FROM `user_accounts`
LEFT JOIN users u1 ON (u1.public_id IS NULL OR u1.public_id = 0) AND user_accounts.user_id1 = u1.id
LEFT JOIN users u2 ON (u2.public_id IS NULL OR u2.public_id = 0) AND user_accounts.user_id2 = u2.id
LEFT JOIN users u3 ON (u3.public_id IS NULL OR u3.public_id = 0) AND user_accounts.user_id3 = u3.id
LEFT JOIN users u4 ON (u4.public_id IS NULL OR u4.public_id = 0) AND user_accounts.user_id4 = u4.id
LEFT JOIN users u5 ON (u5.public_id IS NULL OR u5.public_id = 0) AND user_accounts.user_id5 = u5.id');
foreach (Account::find($single_account_ids) as $account) {
$this->upAccounts($account);
}
foreach ($group_accounts as $group_account) {
$this->upAccounts(null, Account::find(get_object_vars($group_account)));
}
Schema::table('accounts', function($table)
{
$table->dropColumn('pro_plan_paid');
$table->dropColumn('pro_plan_trial');
});
}
private function upAccounts($primaryAccount, $otherAccounts = array()) {
if(!$primaryAccount) {
$primaryAccount = $otherAccounts->first();
}
$company = Company::create();
if ($primaryAccount->pro_plan_paid && $primaryAccount->pro_plan_paid != '0000-00-00') {
$company->plan = 'pro';
$company->plan_term = 'year';
$company->plan_started = $primaryAccount->pro_plan_paid;
$company->plan_paid = $primaryAccount->pro_plan_paid;
if (!Utils::isNinjaProd()) {
$company->plan = 'white_label';
$company->plan_term = null;
} elseif ($company->plan_paid != '2000-01-01'/* NINJA_DATE*/) {
$expires = DateTime::createFromFormat('Y-m-d', $primaryAccount->pro_plan_paid);
$expires->modify('+1 year');
$company->plan_expires = $expires->format('Y-m-d');
}
}
if ($primaryAccount->pro_plan_trial && $primaryAccount->pro_plan_trial != '0000-00-00') {
$company->trial_started = $primaryAccount->pro_plan_trial;
$company->trial_plan = 'pro';
}
$company->save();
$primaryAccount->company_id = $company->id;
$primaryAccount->save();
if (!empty($otherAccounts)) {
foreach ($otherAccounts as $account) {
if ($account && $account->id != $primaryAccount->id) {
$account->company_id = $company->id;
$account->save();
}
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->date('pro_plan_paid')->nullable();
$table->date('pro_plan_trial')->nullable();
});
foreach (Company::all() as $company) {
foreach ($company->accounts as $account) {
$account->pro_plan_paid = $company->plan_paid;
$account->pro_plan_trial = $company->trial_started;
$account->save();
}
}
Schema::table('accounts', function($table)
{
$table->dropForeign('accounts_company_id_foreign');
$table->dropColumn('company_id');
});
Schema::dropIfExists('companies');
}
}

View File

@ -13,10 +13,13 @@ class UserTableSeeder extends Seeder
Eloquent::unguard();
$company = Company::create();
$account = Account::create([
//'name' => 'Test Account',
'account_key' => str_random(RANDOM_KEY_LENGTH),
'timezone_id' => 1,
'company_id' => $company,
]);
User::create([

File diff suppressed because one or more lines are too long

View File

@ -55,7 +55,7 @@ function GetPdfMake(invoice, javascript, callback) {
}
// determine whether or not to show the header/footer
if (invoice.is_pro) {
if (invoice.features.customize_invoice_design) {
if (key === 'header') {
return function(page, pages) {
return page === 1 || invoice.account.all_pages_header == '1' ? val : '';
@ -86,7 +86,7 @@ function GetPdfMake(invoice, javascript, callback) {
// Add ninja logo to the footer
var dd = JSON.parse(javascript, jsonCallBack);
var designId = invoice.invoice_design_id;
if (!invoice.is_pro) {
if (!invoice.features.remove_created_by) {
if (designId == NINJA.TEMPLATES.CLEAN || designId == NINJA.TEMPLATES.NORMAL) {
dd.footer.columns.push({image: logoImages.imageLogo1, alignment: 'right', width: 130, margin: [0, 0, 0, 0]})
} else if (designId == NINJA.TEMPLATES.BOLD) {
@ -270,10 +270,10 @@ NINJA.invoiceColumns = function(invoice)
columns.push("*")
if (invoice.is_pro && account.custom_invoice_item_label1) {
if (invoice.features.invoice_settings && account.custom_invoice_item_label1) {
columns.push("10%");
}
if (invoice.is_pro && account.custom_invoice_item_label2) {
if (invoice.features.invoice_settings && account.custom_invoice_item_label2) {
columns.push("10%");
}
@ -293,7 +293,7 @@ NINJA.invoiceColumns = function(invoice)
NINJA.invoiceFooter = function(invoice)
{
if (!invoice.is_pro && invoice.invoice_design_id == 3) {
if (!invoice.features.invoice_settings && invoice.invoice_design_id == 3) {
return invoice.invoice_footer ? invoice.invoice_footer.substring(0, 200) : ' ';
} else {
return invoice.invoice_footer || ' ';
@ -325,10 +325,10 @@ NINJA.invoiceLines = function(invoice) {
grid[0].push({text: invoiceLabels.description, style: ['tableHeader', 'descriptionTableHeader']});
if (invoice.is_pro && account.custom_invoice_item_label1) {
if (invoice.features.invoice_settings && account.custom_invoice_item_label1) {
grid[0].push({text: account.custom_invoice_item_label1, style: ['tableHeader', 'custom1TableHeader']});
}
if (invoice.is_pro && account.custom_invoice_item_label2) {
if (invoice.features.invoice_ettings && account.custom_invoice_item_label2) {
grid[0].push({text: account.custom_invoice_item_label2, style: ['tableHeader', 'custom2TableHeader']});
}
@ -385,10 +385,10 @@ NINJA.invoiceLines = function(invoice) {
row.push({style:["productKey", rowStyle], text:productKey || ' '}); // product key can be blank when selecting from a datalist
}
row.push({style:["notes", rowStyle], stack:[{text:notes || ' '}]});
if (invoice.is_pro && account.custom_invoice_item_label1) {
if (invoice.features.invoice_settings && account.custom_invoice_item_label1) {
row.push({style:["customValue1", rowStyle], text:item.custom_value1 || ' '});
}
if (invoice.is_pro && account.custom_invoice_item_label2) {
if (invoice.features.invoice_settings && account.custom_invoice_item_label2) {
row.push({style:["customValue2", rowStyle], text:item.custom_value2 || ' '});
}
row.push({style:["cost", rowStyle], text:cost});
@ -555,7 +555,7 @@ NINJA.accountAddress = function(invoice) {
{text: account.country ? account.country.name : ''},
];
if (invoice.is_pro) {
if (invoice.features.invoice_settings) {
data.push({text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false});
data.push({text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false});
}

View File

@ -81,7 +81,7 @@ $LANG = array(
'company_details' => 'Company Details',
'online_payments' => 'Online Payments',
'notifications' => 'Email Notifications',
'import_export' => 'Import | Export | Cancel',
'import_export' => 'Import | Export',
'done' => 'Done',
'save' => 'Save',
'create' => 'Create',
@ -268,7 +268,6 @@ $LANG = array(
'erase_data' => 'This will permanently erase your data.',
'password' => 'Password',
'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!<p/>&nbsp;<br/>
<b>Next Steps</b><p/>A payable invoice has been sent to the email
address associated with your account. To unlock all of the awesome
@ -368,7 +367,7 @@ $LANG = array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account' => 'Delete Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
@ -1127,6 +1126,52 @@ $LANG = array(
'enable_client_portal_dashboard' => 'Dashboard',
'enable_client_portal_dashboard_help' => 'Show/hide the dashboard page in the client portal.',
// Plans
'account_management' => 'Account Management',
'plan_status' => 'Plan Status',
'plan_upgrade' => 'Upgrade',
'plan_change' => 'Change Plan',
'pending_change_to' => 'Changes To',
'plan_changes_to' => ':plan on :date',
'plan_term_changes_to' => ':plan (:term) on :date',
'cancel_plan_change' => 'Cancel Change',
'plan' => 'Plan',
'expires' => 'Expires',
'renews' => 'Renews',
'plan_expired' => ':plan Plan Expired',
'trial_expired' => ':plan Plan Trial Ended',
'never' => 'Never',
'plan_free' => 'Free',
'plan_pro' => 'Pro',
'plan_enterprise' => 'Enterprise',
'plan_white_label' => 'Self Hosted (White labeled)',
'plan_free_self_hosted' => 'Self Hosted (Free)',
'plan_trial' => 'Trial',
'plan_term' => 'Term',
'plan_term_monthly' => 'Monthly',
'plan_term_yearly' => 'Yearly',
'plan_term_month' => 'Month',
'plan_term_year' => 'Year',
'plan_price_monthly' => '$:price/Month',
'plan_price_yearly' => '$:price/Year',
'updated_plan' => 'Updated plan settings',
'plan_paid' => 'Term Started',
'plan_started' => 'Plan Started',
'plan_expires' => 'Plan Expires',
'white_label_button' => 'White Label',
'pro_plan_year_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_month_description' => 'One month enrollment in the Invoice Ninja Pro Plan.',
'enterprise_plan_product' => 'Enterprise Plan',
'enterprise_plan_year_description' => 'One year enrollment in the Invoice Ninja Enterprise Plan.',
'enterprise_plan_month_description' => 'One month enrollment in the Invoice Ninja Enterprise Plan.',
'plan_credit_product' => 'Credit',
'plan_credit_description' => 'Credit for unused time',
'plan_pending_monthly' => 'Will switch to monthly on :date',
'plan_refunded' => 'A refund has been issued.',
'live_preview' => 'Live Preview',
'page_size' => 'Page Size',
'live_preview_disabled' => 'Live preview has been disabled to support selected font',

View File

@ -9,7 +9,7 @@
@if (Utils::isNinja())
{!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!}
@endif
@if (Utils::isPro())
@if (Utils::hasFeature(FEATURE_API))
{!! Button::primary(trans('texts.add_token'))->asLinkTo(URL::to('/tokens/create'))->appendIcon(Icon::create('plus-sign')) !!}
@endif
</div>

View File

@ -18,7 +18,7 @@
{!! Former::populateField('enable_portal_password', intval($enable_portal_password)) !!}
{!! Former::populateField('send_portal_password', intval($send_portal_password)) !!}
@if (!Utils::isNinja() && !Auth::user()->account->isWhiteLabel())
@if (!Utils::isNinja() && !Auth::user()->account->hasFeature(FEATURE_WHITE_LABEL))
<div class="alert alert-warning" style="font-size:larger;">
<center>
{!! trans('texts.white_label_custom_css', ['link'=>'<a href="#" onclick="$(\'#whiteLabelModal\').modal(\'show\');">'.trans('texts.white_label_purchase_link').'</a>']) !!}
@ -59,6 +59,7 @@
</div>
</div>
</div>
@if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_CSS))
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.custom_css') !!}</h3>
@ -74,6 +75,7 @@
->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!}
</div>
</div>
@endif
</div>
</div>
</div>

View File

@ -45,7 +45,11 @@
var customDesign = origCustomDesign = {!! $customDesign ?: 'JSON.parse(invoiceDesigns[0].javascript);' !!};
function getPDFString(cb, force) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
invoice.features = {
customize_invoice_design:{{ Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'true' : 'false' }},
remove_created_by:{{ Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY) ? 'true' : 'false' }},
invoice_settings:{{ Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS) ? 'true' : 'false' }}
};
invoice.account.hide_quantity = {!! Auth::user()->account->hide_quantity ? 'true' : 'false' !!};
invoice.account.hide_paid_to_date = {!! Auth::user()->account->hide_paid_to_date ? 'true' : 'false' !!};
invoice.invoice_design_id = {!! Auth::user()->account->invoice_design_id !!};
@ -194,7 +198,7 @@
</div>
<script>
@if (!Auth::user()->isPro())
@if (!Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN))
$(function() {
$('form.warn-on-exit input, .save-button').prop('disabled', true);
});

View File

@ -86,7 +86,7 @@
</div>
</div>
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_CUSTOM_EMAILS))
<center>
{!! Button::success(trans('texts.save'))->large()->submit()->appendIcon(Icon::create('floppy-disk')) !!}
</center>

View File

@ -73,51 +73,7 @@
{!! Former::close() !!}
{!! Former::open('settings/cancel_account')->addClass('cancel-account') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.cancel_account') !!}</h3>
</div>
<div class="panel-body">
{!! Former::actions( Button::danger(trans('texts.cancel_account'))->large()->withAttributes(['onclick' => 'showConfirm()'])->appendIcon(Icon::create('trash'))) !!}
</div>
</div>
<div class="modal fade" id="confirmCancelModal" tabindex="-1" role="dialog" aria-labelledby="confirmCancelModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="confirmCancelModalLabel">{!! trans('texts.cancel_account') !!}</h4>
</div>
<div style="background-color: #fff; padding-left: 16px; padding-right: 16px">
&nbsp;<p>{{ trans('texts.cancel_account_message') }}</p>&nbsp;
&nbsp;<p>{!! Former::textarea('reason')->placeholder(trans('texts.reason_for_canceling'))->raw() !!}</p>&nbsp;
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.go_back') }}</button>
<button type="button" class="btn btn-danger" onclick="confirmCancel()">{{ trans('texts.cancel_account') }}</button>
</div>
</div>
</div>
</div>
{!! Former::close() !!}
<script type="text/javascript">
function showConfirm() {
$('#confirmCancelModal').modal('show');
}
function confirmCancel() {
$('form.cancel-account').submit();
}
function setEntityTypesVisible() {
var selector = '.entity-types input[type=checkbox]';
if ($('#format').val() === 'JSON') {

View File

@ -46,7 +46,11 @@
}
function getPDFString(cb) {
invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!};
invoice.features = {
customize_invoice_design:{{ Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'true' : 'false' }},
remove_created_by:{{ Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY) ? 'true' : 'false' }},
invoice_settings:{{ Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS) ? 'true' : 'false' }}
};
invoice.account.hide_quantity = $('#hide_quantity').is(":checked");
invoice.account.invoice_embed_documents = $('#invoice_embed_documents').is(":checked");
invoice.account.hide_paid_to_date = $('#hide_paid_to_date').is(":checked");
@ -87,7 +91,7 @@
$(function() {
var options = {
preferredFormat: 'hex',
disabled: {!! Auth::user()->isPro() ? 'false' : 'true' !!},
disabled: {!! Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'false' : 'true' !!},
showInitial: false,
showInput: true,
allowEmpty: true,
@ -150,7 +154,7 @@
<div class="row">
<div class="col-md-6">
@if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
@if (!Utils::hasFeature(FEATURE_MORE_INVOICE_DESIGNS) || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
{!! Former::select('invoice_design_id')
->fromQuery($invoiceDesigns, 'name', 'id')
->addOption(trans('texts.more_designs') . '...', '-1') !!}
@ -261,7 +265,7 @@
) !!}
<br/>
@if (!Auth::user()->isPro())
@if (!Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN))
<script>
$(function() {
$('form.warn-on-exit input, .save-button').prop('disabled', true);

View File

@ -265,7 +265,7 @@
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS))
<center>
{!! Button::success(trans('texts.save'))->large()->submit()->appendIcon(Icon::create('floppy-disk')) !!}
</center>

View File

@ -0,0 +1,225 @@
@extends('header')
@section('content')
@parent
@include('accounts.nav', ['selected' => ACCOUNT_MANAGEMENT])
<div class="row">
<div class="col-md-12">
{!! Former::open('settings/change_plan')->addClass('change-plan') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.plan_status') !!}</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label class="col-sm-4 control-label">{{ trans('texts.plan') }}</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($planDetails && $planDetails['active'])
{{ trans('texts.plan_'.$planDetails['plan']) }}
@if ($planDetails['trial'])
({{ trans('texts.plan_trial') }})
@elseif ($planDetails['expires'])
({{ trans('texts.plan_term_'.$planDetails['term'].'ly') }})
@endif
@elseif(Utils::isNinjaProd())
{{ trans('texts.plan_free') }}
@else
{{ trans('texts.plan_free_self_hosted') }}
@endif
</p>
</div>
</div>
@if ($planDetails && $planDetails['active'])
<div class="form-group">
<label class="col-sm-4 control-label">
@if((!$account->company->pending_plan || $account->company->pending_plan == $planDetails['plan']) && $planDetails['expires'] && !$planDetails['trial'])
{{ trans('texts.renews') }}
@else
{{ trans('texts.expires') }}
@endif
</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($planDetails['expires'] === false)
{{ trans('texts.never') }}
@else
{{ Utils::dateToString($planDetails['expires']) }}
@endif
</p>
</div>
</div>
@if ($account->company->pending_plan)
<div class="form-group">
<label class="col-sm-4 control-label">{{ trans('texts.pending_change_to') }}</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($account->company->pending_plan == PLAN_FREE)
{{ trans('texts.plan_changes_to', [
'plan'=>trans('texts.plan_free'),
'date'=>Utils::dateToString($planDetails['expires'])
]) }}
@else
{{ trans('texts.plan_term_changes_to', [
'plan'=>trans('texts.plan_'.$account->company->pending_plan),
'term'=>trans('texts.plan_term_'.$account->company->pending_term.'ly'),
'date'=>Utils::dateToString($planDetails['expires'])
]) }}
@endif<br>
<a href="#" onclick="cancelPendingChange()">{{ trans('texts.cancel_plan_change') }}</a>
</p>
</div>
</div>
@endif
@if (Utils::isNinjaProd())
{!! Former::actions( Button::info(trans('texts.plan_change'))->large()->withAttributes(['onclick' => 'showChangePlan()'])->appendIcon(Icon::create('edit'))) !!}
@endif
@else
@if ($planDetails)
<div class="form-group">
<label class="col-sm-4 control-label">
@if ($planDetails['trial'])
{{ trans('texts.trial_expired', ['plan'=>trans('texts.plan_'.$planDetails['plan'])]) }}
@else
{{ trans('texts.plan_expired', ['plan'=>trans('texts.plan_'.$planDetails['plan'])]) }}
@endif
</label>
<div class="col-sm-8">
<p class="form-control-static">
{{ Utils::dateToString($planDetails['expires']) }}
</p>
</div>
</div>
@endif
@if (Utils::isNinjaProd())
{!! Former::actions( Button::success(trans('texts.plan_upgrade'))->large()->withAttributes(['onclick' => 'showChangePlan()'])->appendIcon(Icon::create('plus-sign'))) !!}
@else
{!! Former::actions( Button::success(trans('texts.white_label_button'))->large()->withAttributes(['onclick' => 'loadImages("#whiteLabelModal");$("#whiteLabelModal").modal("show");'])->appendIcon(Icon::create('plus-sign'))) !!}
@endif
@endif
</div>
</div>
@if (Utils::isNinjaProd())
<div class="modal fade" id="changePlanModel" tabindex="-1" role="dialog" aria-labelledby="changePlanModelLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="changePlanModelLabel">
@if ($planDetails && $planDetails['active'])
{!! trans('texts.plan_change') !!}
@else
{!! trans('texts.plan_upgrade') !!}
@endif
</h4>
</div>
<div class="modal-body">
@if ($planDetails && $planDetails['active'])
{!! Former::select('plan')
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)
->addOption(trans('texts.plan_free'), PLAN_FREE)!!}
@else
{!! Former::select('plan')
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)!!}
@endif
{!! Former::select('plan_term')
->addOption(trans('texts.plan_term_yearly'), PLAN_TERM_YEARLY)
->addOption(trans('texts.plan_term_monthly'), PLAN_TERM_MONTHLY)!!}
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.go_back') }}</button>
@if ($planDetails && $planDetails['active'])
<button type="button" class="btn btn-primary" onclick="confirmChangePlan()">{{ trans('texts.plan_change') }}</button>
@else
<button type="button" class="btn btn-success" onclick="confirmChangePlan()">{{ trans('texts.plan_upgrade') }}</button>
@endif
</div>
</div>
</div>
</div>
@endif
{!! Former::close() !!}
{!! Former::open('settings/cancel_account')->addClass('cancel-account') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.cancel_account') !!}</h3>
</div>
<div class="panel-body">
{!! Former::actions( Button::danger(trans('texts.cancel_account'))->large()->withAttributes(['onclick' => 'showConfirm()'])->appendIcon(Icon::create('trash'))) !!}
</div>
</div>
<div class="modal fade" id="confirmCancelModal" tabindex="-1" role="dialog" aria-labelledby="confirmCancelModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="confirmCancelModalLabel">{!! trans('texts.cancel_account') !!}</h4>
</div>
<div style="background-color: #fff; padding-left: 16px; padding-right: 16px">
&nbsp;<p>{{ trans('texts.cancel_account_message') }}</p>&nbsp;
&nbsp;<p>{!! Former::textarea('reason')->placeholder(trans('texts.reason_for_canceling'))->raw() !!}</p>&nbsp;
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.go_back') }}</button>
<button type="button" class="btn btn-danger" onclick="confirmCancel()">{{ trans('texts.cancel_account') }}</button>
</div>
</div>
</div>
</div>
{!! Former::close() !!}
</div>
</div>
<script type="text/javascript">
function showChangePlan() {
$('#changePlanModel').modal('show');
}
function confirmChangePlan() {
$('form.change-plan').submit();
}
function showConfirm() {
$('#confirmCancelModal').modal('show');
}
function confirmCancel() {
$('form.cancel-account').submit();
}
@if ($account->company->pending_plan)
function cancelPendingChange(){
$('#plan').val('{{ $planDetails['plan'] }}')
$('#plan_term').val('{{ $planDetails['term'] }}')
confirmChangePlan();
return false;
}
@endif
jQuery(document).ready(function($){
function updatePlanModal() {
var plan = $('#plan').val();
$('#plan_term').closest('.form-group').toggle(plan!='free');
if(plan=='{{PLAN_PRO}}'){
$('#plan_term option[value=month]').text({!! json_encode(trans('texts.plan_price_monthly', ['price'=>PLAN_PRICE_PRO_MONTHLY])) !!});
$('#plan_term option[value=year]').text({!! json_encode(trans('texts.plan_price_yearly', ['price'=>PLAN_PRICE_PRO_YEARLY])) !!});
} else if(plan=='{{PLAN_ENTERPRISE}}') {
$('#plan_term option[value=month]').text({!! json_encode(trans('texts.plan_price_monthly', ['price'=>PLAN_PRICE_ENTERPRISE_MONTHLY])) !!});
$('#plan_term option[value=year]').text({!! json_encode(trans('texts.plan_price_yearly', ['price'=>PLAN_PRICE_ENTERPRISE_YEARLY])) !!});
}
}
$('#plan_term, #plan').change(updatePlanModal);
updatePlanModal();
});
</script>
@stop

View File

@ -125,7 +125,7 @@
</div>
</div>
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS))
<center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
</center>

View File

@ -23,7 +23,7 @@
</div>
</div>
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_API))
{!! Former::actions(
Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/api_tokens'))->appendIcon(Icon::create('remove-circle'))->large(),
Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))

View File

@ -6,7 +6,7 @@
<div class="pull-right">
@if (Utils::isPro() && ! Utils::isTrial())
@if (Utils::hasFeature(FEATURE_USERS))
{!! Button::primary(trans('texts.add_user'))->asLinkTo(URL::to('/users/create'))->appendIcon(Icon::create('plus-sign')) !!}
@endif
</div>

View File

@ -32,7 +32,7 @@
{!! Former::text('last_name')->data_bind("value: last_name, valueUpdate: 'afterkeydown'") !!}
{!! Former::text('email')->data_bind("value: email, valueUpdate: 'afterkeydown'") !!}
{!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown'") !!}
@if ($account->isPro() && $account->enable_portal_password)
@if ($account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password)
{!! Former::password('password')->data_bind("value: password()?'-%unchanged%-':'', valueUpdate: 'afterkeydown'") !!}
@endif

View File

@ -43,7 +43,7 @@
{!! Former::text('website') !!}
{!! Former::text('work_phone') !!}
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS))
@if ($customLabel1)
{!! Former::text('custom_value1')->label($customLabel1) !!}
@endif
@ -93,7 +93,7 @@
attr: {name: 'contacts[' + \$index() + '][email]', id:'email'+\$index()}") !!}
{!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown',
attr: {name: 'contacts[' + \$index() + '][phone]'}") !!}
@if ($account->isPro() && $account->enable_portal_password)
@if ($account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password)
{!! Former::password('password')->data_bind("value: password()?'-%unchanged%-':'', valueUpdate: 'afterkeydown',
attr: {name: 'contacts[' + \$index() + '][password]'}") !!}
@endif
@ -134,15 +134,43 @@
{!! Former::textarea('private_notes') !!}
@if (isset($proPlanPaid))
{!! Former::populateField('pro_plan_paid', $proPlanPaid) !!}
{!! Former::text('pro_plan_paid')
@if (Auth::user()->account->isNinjaAccount())
@if (isset($planDetails))
{!! Former::populateField('plan', $planDetails['plan']) !!}
{!! Former::populateField('plan_term', $planDetails['term']) !!}
@if (!empty($planDetails['paid']))
{!! Former::populateField('plan_paid', $planDetails['paid']->format('Y-m-d')) !!}
@endif
@if (!empty($planDetails['expires']))
{!! Former::populateField('plan_expires', $planDetails['expires']->format('Y-m-d')) !!}
@endif
@if (!empty($planDetails['started']))
{!! Former::populateField('plan_started', $planDetails['started']->format('Y-m-d')) !!}
@endif
@endif
{!! Former::select('plan')
->addOption(trans('texts.plan_free'), PLAN_FREE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)!!}
{!! Former::select('plan_term')
->addOption()
->addOption(trans('texts.plan_term_yearly'), PLAN_TERM_YEARLY)
->addOption(trans('texts.plan_term_monthly'), PLAN_TERM_MONTHLY)!!}
{!! Former::text('plan_started')
->data_date_format('yyyy-mm-dd')
->addGroupClass('pro_plan_paid_date')
->addGroupClass('plan_start_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('plan_paid')
->data_date_format('yyyy-mm-dd')
->addGroupClass('plan_paid_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('plan_expires')
->data_date_format('yyyy-mm-dd')
->addGroupClass('plan_expire_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
<script type="text/javascript">
$(function() {
$('#pro_plan_paid').datepicker();
$('#plan_started, #plan_paid, #plan_expires').datepicker();
});
</script>
@endif

View File

@ -230,7 +230,7 @@
@endif
@if (Utils::isPro() && $hasQuotes)
@if (Utils::hasFeature(FEATURE_QUOTES) && $hasQuotes)
<div class="tab-pane" id="quotes">
{!! Datatable::table()

View File

@ -362,7 +362,7 @@
}
}
@if (Auth::user()->account->isPro())
@if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS))
function handleDocumentAdded(file){
if(file.mock)return;
file.index = model.documents().length;

View File

@ -431,7 +431,7 @@
<div class="btn-group user-dropdown">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
<div id="myAccountButton" class="ellipsis nav-account-name" style="max-width:{{ Utils::isPro() && ! Utils::isTrial() ? '130' : '100' }}px;">
<div id="myAccountButton" class="ellipsis nav-account-name" style="max-width:{{ Utils::hasFeature(FEATURE_USERS) ? '130' : '100' }}px;">
@if (session(SESSION_USER_ACCOUNTS) && count(session(SESSION_USER_ACCOUNTS)))
{{ Auth::user()->account->getDisplayName() }}
@else
@ -726,8 +726,8 @@
<center>
<h2>{{ trans('texts.pro_plan_title') }}</h2>
<img class="img-responsive price" alt="Only $50 Per Year" src="{{ asset('images/pro_plan/price.png') }}"/>
@if (Auth::user()->isEligibleForTrial())
<a class="button" href="{{ URL::to('start_trial') }}">{{ trans('texts.trial_call_to_action') }}</a>
@if (Auth::user()->isEligibleForTrial(PLAN_PRO))
<a class="button" href="{{ URL::to('start_trial/'.PLAN_PRO) }}">{{ trans('texts.trial_call_to_action') }}</a>
@else
<a class="button" href="#" onclick="submitProPlan()">{{ trans('texts.pro_plan_call_to_action') }}</a>
@endif
@ -769,7 +769,7 @@
{{-- Per our license, please do not remove or modify this section. --}}
{!! link_to('https://www.invoiceninja.com/?utm_source=powered_by', 'InvoiceNinja.com', ['target' => '_blank', 'title' => 'invoiceninja.com']) !!} -
{!! link_to(RELEASES_URL, 'v' . NINJA_VERSION, ['target' => '_blank', 'title' => trans('texts.trello_roadmap')]) !!} |
@if (Auth::user()->account->isWhiteLabel())
@if (Auth::user()->account->hasFeature(FEATURE_WHITE_LABEL))
{{ trans('texts.white_labeled') }}
@else
<a href="#" onclick="loadImages('#whiteLabelModal');$('#whiteLabelModal').modal('show');">{{ trans('texts.white_label_link') }}</a>

View File

@ -296,7 +296,7 @@
<li role="presentation" class="active"><a href="#notes" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.note_to_client') }}</a></li>
<li role="presentation"><a href="#terms" aria-controls="terms" role="tab" data-toggle="tab">{{ trans("texts.terms") }}</a></li>
<li role="presentation"><a href="#footer" aria-controls="footer" role="tab" data-toggle="tab">{{ trans("texts.footer") }}</a></li>
@if ($account->isPro())
@if ($account->hasFeature(FEATURE_DOCUMENTS))
<li role="presentation"><a href="#attached-documents" aria-controls="attached-documents" role="tab" data-toggle="tab">{{ trans("texts.invoice_documents") }}</a></li>
@endif
</ul>
@ -330,7 +330,7 @@
</div>
</div>') !!}
</div>
@if ($account->isPro())
@if ($account->hasFeature(FEATURE_DOCUMENTS))
<div role="tabpanel" class="tab-pane" id="attached-documents" style="position:relative;z-index:9">
<div id="document-upload">
<div class="dropzone">
@ -493,7 +493,7 @@
{!! Former::text('pdfupload') !!}
</div>
@if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
@if (!Utils::hasFeature(FEATURE_MORE_INVOICE_DESIGNS) || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST)
{!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id")->addOption(trans('texts.more_designs') . '...', '-1') !!}
@else
{!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!}
@ -565,7 +565,7 @@
</span>
@if (Auth::user()->isPro())
@if (Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS))
@if ($account->custom_client_label1)
{!! Former::text('client[custom_value1]')
->label($account->custom_client_label1)
@ -620,7 +620,7 @@
->addClass('client-email') !!}
{!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown',
attr: {name: 'client[contacts][' + \$index() + '][phone]'}") !!}
@if ($account->isPro() && $account->enable_portal_password)
@if ($account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password)
{!! Former::password('password')->data_bind("value: (typeof password=='function'?password():null)?'-%unchanged%-':'', valueUpdate: 'afterkeydown',
attr: {name: 'client[contacts][' + \$index() + '][password]'}") !!}
@endif
@ -960,7 +960,7 @@
applyComboboxListeners();
@if (Auth::user()->account->isPro())
@if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS))
$('.main-form').submit(function(){
if($('#document-upload .dropzone .fallback input').val())$(this).attr('enctype', 'multipart/form-data')
else $(this).removeAttr('enctype')
@ -1056,7 +1056,11 @@
var model = ko.toJS(window.model);
if(!model)return;
var invoice = model.invoice;
invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }};
invoice.features = {
customize_invoice_design:{{ Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'true' : 'false' }},
remove_created_by:{{ Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY) ? 'true' : 'false' }},
invoice_settings:{{ Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS) ? 'true' : 'false' }}
};
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
@ -1369,7 +1373,7 @@
model.invoice().invoice_number(number);
}
@if ($account->isPro())
@if ($account->hasFeature(FEATURE_DOCUMENTS))
function handleDocumentAdded(file){
if(file.mock)return;
file.index = model.invoice().documents().length;
@ -1393,7 +1397,7 @@
@endif
</script>
@if ($account->isPro() && $account->invoice_embed_documents)
@if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents)
@foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>

View File

@ -57,7 +57,7 @@
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
@if (Utils::isPro() && $invoice->account->invoice_embed_documents)
@if (Utils::hasFeature(FEATURE_DOCUMENTS) && $invoice->account->invoice_embed_documents)
@foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>

View File

@ -84,7 +84,7 @@
@endif
var NINJA = NINJA || {};
@if ($account->isPro())
@if ($account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN))
NINJA.primaryColor = "{{ $account->primary_color }}";
NINJA.secondaryColor = "{{ $account->secondary_color }}";
NINJA.fontSize = {{ $account->font_size }};

View File

@ -65,7 +65,7 @@
</div>
@endif
@if ($account->isPro() && $account->invoice_embed_documents)
@if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents)
@foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" async></script>
@ -82,7 +82,11 @@
<script type="text/javascript">
window.invoice = {!! $invoice->toJson() !!};
invoice.is_pro = {{ $invoice->client->account->isPro() ? 'true' : 'false' }};
invoice.features = {
customize_invoice_design:{{ $invoice->client->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'true' : 'false' }},
remove_created_by:{{ $invoice->client->account)->hasFeature(FEATURE_REMOVE_CREATED_BY) ? 'true' : 'false' }},
invoice_settings:{{ $invoice->client->account->hasFeature(FEATURE_INVOICE_SETTINGS) ? 'true' : 'false' }}
};
invoice.is_quote = {{ $invoice->is_quote ? 'true' : 'false' }};
invoice.contact = {!! $contact->toJson() !!};

View File

@ -31,7 +31,7 @@
<div id="top_right_buttons" class="pull-right">
<input id="tableFilter" type="text" style="width:140px;margin-right:17px;background-color: white !important"
class="form-control pull-left" placeholder="{{ trans('texts.filter') }}" value="{{ Input::get('filter') }}"/>
@if (Auth::user()->isPro() && $entityType == ENTITY_INVOICE)
@if (Auth::user()->hasFeature(FEATURE_QUOTES) && $entityType == ENTITY_INVOICE)
{!! Button::normal(trans('texts.quotes'))->asLinkTo(URL::to('/quotes'))->appendIcon(Icon::create('list')) !!}
{!! Button::normal(trans('texts.recurring'))->asLinkTo(URL::to('/recurring_invoices'))->appendIcon(Icon::create('list')) !!}
@elseif ($entityType == ENTITY_EXPENSE)

View File

@ -45,7 +45,7 @@
Button::success(trans('texts.run'))->withAttributes(array('id' => 'submitButton'))->submit()->appendIcon(Icon::create('play'))
) !!}
@if (!Auth::user()->isPro())
@if (!Auth::user()->hasFeature(FEATURE_REPORTS))
<script>
$(function() {
$('form.warn-on-exit').find('input, button').prop('disabled', true);

View File

@ -268,7 +268,7 @@
return -1;
}
var dayInSeconds = 1000*60*60*24;
@if (Auth::user()->account->isPro())
@if (Auth::user()->account->hasFeature(FEATURE_REPORTS))
var date = convertToJsDate(invoice.created_at);
@else
var date = new Date().getTime() - (dayInSeconds * Math.random() * 100);

View File

@ -27,6 +27,7 @@
</div>
</div>
@if (Utils::hasFeature(FEATURE_USER_PERMISSIONS))
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.permissions') !!}</h3>
@ -59,6 +60,7 @@
</div>
</div>
@endif
{!! Former::actions(
Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/user_management'))->appendIcon(Icon::create('remove-circle'))->large(),

View File

@ -114,15 +114,43 @@
{!! Former::textarea('private_notes')->rows(6) !!}
@if (isset($proPlanPaid))
{!! Former::populateField('pro_plan_paid', $proPlanPaid) !!}
{!! Former::text('pro_plan_paid')
@if (Auth::user()->account->isNinjaAccount())
@if (isset($planDetails))
{!! Former::populateField('plan', $planDetails['plan']) !!}
{!! Former::populateField('plan_term', $planDetails['term']) !!}
@if (!empty($planDetails['paid']))
{!! Former::populateField('plan_paid', $planDetails['paid']->format('Y-m-d')) !!}
@endif
@if (!empty($planDetails['expires']))
{!! Former::populateField('plan_expires', $planDetails['expires']->format('Y-m-d')) !!}
@endif
@if (!empty($planDetails['started']))
{!! Former::populateField('plan_started', $planDetails['started']->format('Y-m-d')) !!}
@endif
@endif
{!! Former::select('plan')
->addOption(trans('texts.plan_free'), PLAN_FREE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)!!}
{!! Former::select('plan_term')
->addOption()
->addOption(trans('texts.plan_term_yearly'), PLAN_TERM_YEARLY)
->addOption(trans('texts.plan_term_monthly'), PLAN_TERM_MONTHLY)!!}
{!! Former::text('plan_started')
->data_date_format('yyyy-mm-dd')
->addGroupClass('pro_plan_paid_date')
->addGroupClass('plan_start_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('plan_paid')
->data_date_format('yyyy-mm-dd')
->addGroupClass('plan_paid_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('plan_expires')
->data_date_format('yyyy-mm-dd')
->addGroupClass('plan_expire_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
<script type="text/javascript">
$(function() {
$('#pro_plan_paid').datepicker();
$('#plan_started, #plan_paid, #plan_expires').datepicker();
});
</script>
@endif