1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-23 01:41:34 +02:00

Merge branch 'develop' of https://github.com/turbo124/invoiceninja into develop

iiiiiespecially if it merges an updated upstream into a topic branch.
This commit is contained in:
David Bomba 2016-04-21 12:32:06 +10:00
commit b6c862aa46
77 changed files with 1781 additions and 679 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])) {
continue;
} else {
$sentTo[$userAccountId] = true;
}
foreach ($companies as $company) {
if (!count($company->accounts)) {
continue;
}
$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

@ -71,11 +71,10 @@ class Handler extends ExceptionHandler {
}
}
return parent::render($request, $e);
/*
// In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {
if (Utils::isNinjaProd()
&& !Utils::isDownForMaintenance()
&& !($e instanceof HttpResponseException)) {
$data = [
'error' => get_class($e),
'hideHeader' => true,
@ -85,6 +84,5 @@ class Handler extends ExceptionHandler {
} else {
return parent::render($request, $e);
}
*/
}
}

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,10 +114,135 @@ 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)
{
@ -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;
@ -389,6 +531,58 @@ class AccountController extends BaseController
$data['invoiceDesigns'] = InvoiceDesign::getDesigns();
$data['invoiceFonts'] = Cache::get('fonts');
$data['section'] = $section;
$pageSizes = [
'A0',
'A1',
'A2',
'A3',
'A4',
'A5',
'A6',
'A7',
'A8',
'A9',
'A10',
'B0',
'B1',
'B2',
'B3',
'B4',
'B5',
'B6',
'B7',
'B8',
'B9',
'B10',
'C0',
'C1',
'C2',
'C3',
'C4',
'C5',
'C6',
'C7',
'C8',
'C9',
'C10',
'RA0',
'RA1',
'RA2',
'RA3',
'RA4',
'SRA0',
'SRA1',
'SRA2',
'SRA3',
'SRA4',
'Executive',
'Folio',
'Legal',
'Letter',
'Tabloid',
];
$data['pageSizes'] = array_combine($pageSizes, $pageSizes);
$design = false;
foreach ($data['invoiceDesigns'] as $item) {
@ -501,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;
@ -516,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
@ -567,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) {
@ -629,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));
@ -672,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',
@ -701,6 +895,7 @@ class AccountController extends BaseController
$account->custom_invoice_item_label1 = trim(Input::get('custom_invoice_item_label1'));
$account->custom_invoice_item_label2 = trim(Input::get('custom_invoice_item_label2'));
$account->invoice_number_padding = Input::get('invoice_number_padding');
$account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix');
$account->share_counter = Input::get('share_counter') ? true : false;
@ -752,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;
@ -764,9 +959,20 @@ class AccountController extends BaseController
$account->primary_color = Input::get('primary_color');
$account->secondary_color = Input::get('secondary_color');
$account->invoice_design_id = Input::get('invoice_design_id');
$account->font_size = intval(Input::get('font_size'));
$account->page_size = Input::get('page_size');
$account->live_preview = Input::get('live_preview') ? true : false;
if (Input::has('font_size')) {
$account->font_size = intval(Input::get('font_size'));
// Automatically disable live preview when using a large font
$fonts = Cache::get('fonts')->filter(function($font) use ($account) {
if ($font->google_font) {
return false;
}
return $font->id == $account->header_font_id || $font->id == $account->body_font_id;
});
if ($account->live_preview && count($fonts)) {
$account->live_preview = false;
Session::flash('warning', trans('texts.live_preview_disabled'));
}
$labels = [];
@ -990,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);
@ -1046,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();
@ -1062,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

@ -178,9 +178,12 @@ class AppController extends BaseController
$config = '';
foreach ($_ENV as $key => $val) {
if (preg_match('/\s/',$val)) {
$val = "'{$val}'";
}
if (is_array($val)) {
continue;
}
if (preg_match('/\s/', $val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n";
}

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'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
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,12 +204,14 @@ 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;
$user->permissions = Input::get('permissions');
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);
@ -644,7 +650,46 @@ 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;
}
$dateTime = new DateTime($date);
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

@ -18,6 +18,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'];
@ -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;
}
@ -551,7 +568,7 @@ class Account extends Eloquent
$replace = [date('Y')];
$search[] = '{$counter}';
$replace[] = str_pad($this->getCounter($invoice->is_quote), 4, '0', STR_PAD_LEFT);
$replace[] = str_pad($this->getCounter($invoice->is_quote), $this->invoice_number_padding, '0', STR_PAD_LEFT);
if (strstr($pattern, '{$userId}')) {
$search[] = '{$userId}';
@ -617,7 +634,7 @@ class Account extends Eloquent
// confirm the invoice number isn't already taken
do {
$number = $prefix . str_pad($counter, 4, '0', STR_PAD_LEFT);
$number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
@ -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 && !$planDetails['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();
return !empty($plan_details);
}
if ($datePaid == NINJA_DATE) {
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()
@ -801,35 +980,54 @@ class Account extends Eloquent
if (!Utils::isNinjaProd()) {
return false;
}
$plan_details = $this->getPlanDetails();
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
return false;
}
return Utils::withinPastTwoWeeks($this->pro_plan_trial);
return $plan_details && $plan_details['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->hasFeature(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,27 +1281,15 @@ 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()) {
// For self-hosted users, a white-label license is required for custom CSS
$css .= $this->client_view_css;
}
}
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;
}
public function hasLargeFont()
{
foreach (['chinese', 'japanese'] as $language) {
if (stripos($this->getBodyFontName(), $language) || stripos($this->getHeaderFontName(), $language)) {
return true;
}
}
return false;
}
public function getFontsUrl($protocol = ''){
$bodyFont = $this->getHeaderFontId();
$headerFont = $this->getBodyFontId();
@ -1137,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',
@ -474,7 +474,8 @@ class Invoice extends EntityModel implements BalanceAffecting
'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
'invoice_embed_documents'
'invoice_embed_documents',
'page_size',
]);
foreach ($this->invoice_items as $invoiceItem) {

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,19 +237,39 @@ 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;
@ -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

@ -284,7 +284,14 @@ class InvoiceRepository extends BaseRepository
$invoice->end_date = null;
}
$invoice->terms = (isset($data['terms']) && trim($data['terms'])) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : '');
if (isset($data['terms']) && trim($data['terms'])) {
$invoice->terms = trim($data['terms']);
} elseif ($isNew && $account->{"{$entityType}_terms"}) {
$invoice->terms = $account->{"{$entityType}_terms"};
} else {
$invoice->terms = '';
}
$invoice->invoice_footer = (isset($data['invoice_footer']) && trim($data['invoice_footer'])) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : '');
$invoice->public_notes = isset($data['public_notes']) ? trim($data['public_notes']) : null;

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

@ -39,18 +39,7 @@ class PaymentService extends BaseService
public function createGateway($accountGateway)
{
$gateway = Omnipay::create($accountGateway->gateway->provider);
$config = $accountGateway->getConfig();
foreach ($config as $key => $val) {
if (!$val) {
continue;
}
$function = "set".ucfirst($key);
if (method_exists($gateway, $function)) {
$gateway->$function($val);
}
}
$gateway->initialize((array) $accountGateway->getConfig());
if ($accountGateway->isGateway(GATEWAY_DWOLLA)) {
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
@ -216,17 +205,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;
@ -236,13 +214,66 @@ class PaymentService extends BaseService
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
$payment->payment_date = date_create()->format('Y-m-d');
if ($payerId) {
$payment->payer_id = $payerId;
}
$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

@ -26,7 +26,7 @@
"anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0",
"chumper/datatable": "dev-develop#04ef2bf",
"omnipay/omnipay": "~2.3.0",
"omnipay/omnipay": "~2.3",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
"barryvdh/laravel-ide-helper": "dev-master",

8
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "2ab7ab9013e31d8a2f0dcf43b31beefa",
"content-hash": "188fba7fcc31b702098d5417bc0e63e2",
"hash": "cf82d2ddb25cb1a7d6b4867bcc8692b8",
"content-hash": "481a95753b873249aebceb99e7426421",
"packages": [
{
"name": "agmscode/omnipay-agms",
@ -127,7 +127,7 @@
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96",
"url": "https://api.github.com/repos/formers/former/zipball/78ae8c65b1f8134e2db1c9491c251c03638823ca",
"reference": "d97f907741323b390f43954a90a227921ecc6b96",
"shasum": ""
},
@ -4229,7 +4229,7 @@
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/cf813f1d5436a1d2f942d3df6666695d1e2b5280",
"url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/9cadfb7955bd361d1a00ac8f0570aee4c05c6bb4",
"reference": "cf813f1d5436a1d2f942d3df6666695d1e2b5280",
"shasum": ""
},

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

@ -0,0 +1,72 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPageSize extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function ($table) {
$table->string('page_size')->default('A4');
$table->boolean('live_preview')->default(true);
$table->smallInteger('invoice_number_padding')->default(4);
});
Schema::table('fonts', function ($table) {
$table->dropColumn('is_early_access');
});
Schema::create('expense_categories', function($table)
{
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('account_id')->index();
$table->timestamps();
$table->softDeletes();
$table->string('name')->nullable();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique( array('account_id','public_id') );
});
Schema::table('expenses', function ($table) {
$table->unsignedInteger('expense_category_id')->nullable()->index();
$table->foreign('expense_category_id')->references('id')->on('expense_categories')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function ($table) {
$table->dropColumn('page_size');
$table->dropColumn('live_preview');
$table->dropColumn('invoice_number_padding');
});
Schema::table('fonts', function ($table) {
$table->boolean('is_early_access');
});
Schema::dropIfExists('expense_categories');
Schema::table('expenses', function ($table) {
$table->dropColumn('expense_category_id');
});
}
}

View File

@ -246,7 +246,6 @@ class FontsSeeder extends Seeder
foreach ($fonts as $font) {
if (!DB::table('fonts')->where('name', '=', $font['name'])->get()) {
$font['is_early_access'] = false;
Font::create($font);
}
}

View File

@ -2,6 +2,7 @@
use App\Models\User;
use App\Models\Account;
use App\Models\Company;
use App\Models\Affiliate;
class UserTableSeeder extends Seeder
@ -13,10 +14,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->id,
]);
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) {
@ -96,7 +96,8 @@ function GetPdfMake(invoice, javascript, callback) {
}
}
// set page size
dd.pageSize = invoice.account.page_size;
pdfMake.fonts = {}
fonts = window.invoiceFonts || invoice.invoice_fonts;
@ -269,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%");
}
@ -292,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 || ' ';
@ -324,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']});
}
@ -384,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});
@ -554,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',
@ -976,9 +975,9 @@ $LANG = array(
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.',
'trello_roadmap' => 'Trello Roadmap',
'header_footer' => 'Header/Footer',
'first_page' => 'first page',
'all_pages' => 'all pages',
'last_page' => 'last page',
'first_page' => 'First page',
'all_pages' => 'All pages',
'last_page' => 'Last page',
'all_pages_header' => 'Show header on',
'all_pages_footer' => 'Show footer on',
'invoice_currency' => 'Invoice Currency',
@ -1126,6 +1125,57 @@ $LANG = array(
'enable_client_portal_help' => 'Show/hide the client portal.',
'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',
'invoice_number_padding' => 'Padding',
);

View File

@ -929,267 +929,264 @@ return array(
'client_portal' => 'Portal do Cliente',
'admin' => 'Admin',
'disabled' => 'Disabilitado',
'disabled' => 'Desabilitado',
'show_archived_users' => 'Mostrar usuários arquivados',
'notes' => 'Observações',
'invoice_will_create' => 'cliente será criado',
'invoices_will_create' => 'faturas serão criadas',
'failed_to_import' => 'A importação dos seguintes registros falhou',
'publishable_key' => 'Publishable Key',
'secret_key' => 'Secret Key',
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'publishable_key' => 'Chave Publicável',
'secret_key' => 'Chave Secreta',
'missing_publishable_key' => 'Defina o sua chave publicável do Stripe para um processo de pagamento melhorado',
'email_design' => 'Email Design',
'due_by' => 'Due by :date',
'enable_email_markup' => 'Enable Markup',
'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.',
'template_help_title' => 'Templates Help',
'template_help_1' => 'Available variables:',
'email_design_id' => 'Email Style',
'email_design_help' => 'Make your emails look more professional with HTML layouts',
'plain' => 'Plain',
'light' => 'Light',
'dark' => 'Dark',
'email_design' => 'Template de E-mail',
'due_by' => 'Vencido em :date',
'enable_email_markup' => 'Habilitar Marcação',
'enable_email_markup_help' => 'Tornar mais fácil para os seus clientes efetuarem seus pagamentos, acrescentando marcação schema.org a seus e-mails.',
'template_help_title' => 'Ajuda de Templates',
'template_help_1' => 'Variáveis disponíveis:',
'email_design_id' => 'Estilo de e-mails',
'email_design_help' => 'Deixe seus e-mails mais profissionais com layouts HTML',
'plain' => 'Plano',
'light' => 'Claro',
'dark' => 'Escuro',
'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.',
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.',
'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.',
'custom_client_fields_helps' => 'Add a text input to the client create/edit page and display the label and value on the PDF.',
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.',
'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.',
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.',
'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.',
'industry_help' => 'Usado para fornecer comparações contra as médias das empresas de tamanho e indústria similar.',
'subdomain_help' => 'Personalizar o link da fatura ou exibir a fatura em seu próprio site.',
'invoice_number_help' => 'Especifique um prefixo ou usar um padrão personalizado para definir dinamicamente o número da fatura.',
'quote_number_help' => 'Especifique um prefixo ou usar um padrão personalizado para definir dinamicamente o número do orçamento.',
'custom_client_fields_helps' => 'Adicionar uma entrada de texto na página Criar/Editar Cliente e exibir no PDF.',
'custom_account_fields_helps' => 'Adicionar um rótulo e um valor para a seção detalhes da empresa do PDF.',
'custom_invoice_fields_helps' => 'Adicionar uma entrada de texto na página Criar/Editar Fatura e exibir no PDF.',
'custom_invoice_charges_helps' => 'Adicionar uma entrada de texto na página Criar/Editar Fatura e incluir nos subtotais da fatura.',
'color_help' => 'Nota: A cor primária também é utilizada nos projetos do portal do cliente e-mail personalizado.',
'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.',
'confirm' => 'Confirm',
'email_preferences' => 'Email Preferences',
'created_invoices' => 'Successfully created :count invoice(s)',
'next_invoice_number' => 'The next invoice number is :number.',
'next_quote_number' => 'The next quote number is :number.',
'token_expired' => 'Token de acesso expirado. Tente novamente!',
'invoice_link' => 'Link da Fatura',
'button_confirmation_message' => 'Clique para confirmar seu endereço de e-mail.',
'confirm' => 'Confirmar',
'email_preferences' => 'Preferências de E-mails',
'created_invoices' => ':count fatura(s) criadas com sucesso',
'next_invoice_number' => 'O número da próxima fatura será :number.',
'next_quote_number' => 'O número do próximo orçamento será :number.',
'days_before' => 'days before',
'days_after' => 'days after',
'field_due_date' => 'due date',
'field_invoice_date' => 'invoice date',
'schedule' => 'Schedule',
'email_designs' => 'Email Designs',
'assigned_when_sent' => 'Assigned when sent',
'days_before' => 'dias antes',
'days_after' => 'dias depois',
'field_due_date' => 'data de vencimento',
'field_invoice_date' => 'data da fatura',
'schedule' => 'Agendamento',
'email_designs' => 'Design de E-mails',
'assigned_when_sent' => 'Assinar quando enviar',
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.',
'white_label_purchase_link' => 'Purchase a white label license',
'white_label_custom_css' => ':link apenas $'.WHITE_LABEL_PRICE.' para permitir um estilo personalizado e apoiar o nosso projecto.',
'white_label_purchase_link' => 'Adquira uma licença white label',
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors',
'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net',
'vendor' => 'Vendor',
'edit_vendor' => 'Edit Vendor',
'archive_vendor' => 'Archive Vendor',
'delete_vendor' => 'Delete Vendor',
'view_vendor' => 'View Vendor',
'deleted_expense' => 'Successfully deleted expense',
'archived_expense' => 'Successfully archived expense',
'deleted_expenses' => 'Successfully deleted expenses',
'archived_expenses' => 'Successfully archived expenses',
'expense' => 'Despesa',
'expenses' => 'Despesas',
'new_expense' => 'Adicionar Despesa',
'enter_expense' => 'Incluir Despesa',
'vendors' => 'Fornecedor',
'new_vendor' => 'Novo Fornecedor',
'payment_terms_net' => 'Rede',
'vendor' => 'Fornecedor',
'edit_vendor' => 'Editar Fornecedor',
'archive_vendor' => 'Arquivar Fornecedor',
'delete_vendor' => 'Deletar Fornecedor',
'view_vendor' => 'Visualizar Fornecedor',
'deleted_expense' => 'Despesa excluída com sucesso',
'archived_expense' => 'Despesa arquivada com sucesso',
'deleted_expenses' => 'Despesas excluídas com sucesso',
'archived_expenses' => 'Despesas arquivada com sucesso',
// Expenses
'expense_amount' => 'Expense Amount',
'expense_balance' => 'Expense Balance',
'expense_date' => 'Expense Date',
'expense_should_be_invoiced' => 'Should this expense be invoiced?',
'public_notes' => 'Public Notes',
'invoice_amount' => 'Invoice Amount',
'exchange_rate' => 'Exchange Rate',
'yes' => 'Yes',
'no' => 'No',
'should_be_invoiced' => 'Should be invoiced',
'view_expense' => 'View expense # :expense',
'edit_expense' => 'Edit Expense',
'archive_expense' => 'Archive Expense',
'delete_expense' => 'Delete Expense',
'view_expense_num' => 'Expense # :expense',
'updated_expense' => 'Successfully updated expense',
'created_expense' => 'Successfully created expense',
'enter_expense' => 'Enter Expense',
'view' => 'View',
'restore_expense' => 'Restore Expense',
'invoice_expense' => 'Invoice Expense',
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense has already been invoiced',
'convert_currency' => 'Convert currency',
'expense_amount' => 'Total de Despesas',
'expense_balance' => 'Saldo de Despesas',
'expense_date' => 'Data da Despesa',
'expense_should_be_invoiced' => 'Esta despesa deve ser faturada?',
'public_notes' => 'Notas Públicas',
'invoice_amount' => 'Total da Fatura',
'exchange_rate' => 'Taxa de Câmbio',
'yes' => 'Sim',
'no' => 'Não',
'should_be_invoiced' => 'Deve ser Faturada',
'view_expense' => 'Visualizar despesa # :expense',
'edit_expense' => 'Editar Despesa',
'archive_expense' => 'Arquivar Despesa',
'delete_expense' => 'Deletar Despesa',
'view_expense_num' => 'Despesa # :expense',
'updated_expense' => 'Despesa atualizada com sucesso',
'created_expense' => 'Despesa criada com sucesso',
'enter_expense' => 'Incluir Despesa',
'view' => 'Visualizar',
'restore_expense' => 'Restaurar Despesa',
'invoice_expense' => 'Faturar Despesa',
'expense_error_multiple_clients' => 'Despesas não podem pertencer a clientes diferentes',
'expense_error_invoiced' => 'Despeja já faturada',
'convert_currency' => 'Converter moeda',
// Payment terms
'num_days' => 'Number of days',
'create_payment_term' => 'Create Payment Term',
'edit_payment_terms' => 'Edit Payment Term',
'edit_payment_term' => 'Edit Payment Term',
'archive_payment_term' => 'Archive Payment Term',
'num_days' => 'Número de dias',
'create_payment_term' => 'Criar Termo de Pagamento',
'edit_payment_terms' => 'Editar Termos de Pagamento',
'edit_payment_term' => 'Editar Termo de Pagamento',
'archive_payment_term' => 'Arquivar Termo de Pagamento',
// recurring due dates
'recurring_due_dates' => 'Recurring Invoice Due Dates',
'recurring_due_date_help' => '<p>Automatically sets a due date for the invoice.</p>
<p>Invoices on a monthly or yearly cycle set to be due on or before the day they are created will be due the next month. Invoices set to be due on the 29th or 30th in months that don\'t have that day will be due the last day of the month.</p>
<p>Invoices on a weekly cycle set to be due on the day of the week they are created will be due the next week.</p>
<p>For example:</p>
'recurring_due_dates' => 'Data de Vencimento das Faturas Recorrentes',
'recurring_due_date_help' => '<p>Definir automaticamente a data de vencimento da fatura.</p>
<p>Faturas em um ciclo mensal ou anual com vencimento anterior ou na data em que são criadas serão faturadas para o próximo mês. Faturas com vencimento no dia 29 ou 30 nos meses que não tem esse dia será faturada no último dia do mês..</p>
<p>Faturas em um clclo mensal com vencimento no dia da semana em que foi criada serão faturadas para a próxima semana.</p>
<p>Exemplo:</p>
<ul>
<li>Today is the 15th, due date is 1st of the month. The due date should likely be the 1st of the next month.</li>
<li>Today is the 15th, due date is the last day of the month. The due date will be the last day of the this month.
</li>
<li>Today is the 15th, due date is the 15th day of the month. The due date will be the 15th day of <strong>next</strong> month.
</li>
<li>Today is the Friday, due date is the 1st Friday after. The due date will be next Friday, not today.
</li>
<li>Hoje é dia 15, vencimento no primeiro dia do mês. O Vencimento será no primeiro dia do próximo mês.</li>
<li>Hoje é dia 15, vencimento no último dia do mês. O Vencimento será no último dia do mês corrente</li>
<li>Hoje é dia 15, vencimento no dia 15. O venciemnto será no dia 15 do <strong>próximo</strong> mês.</li>
<li>Hoje é Sexta-Feira, vencimento na primeira sexta-feira. O venciemnto será na próxima sexta-feira, não hoje.</li>
</ul>',
'due' => 'Due',
'next_due_on' => 'Due Next: :date',
'use_client_terms' => 'Use client terms',
'day_of_month' => ':ordinal day of month',
'last_day_of_month' => 'Last day of month',
'day_of_week_after' => ':ordinal :day after',
'sunday' => 'Sunday',
'monday' => 'Monday',
'tuesday' => 'Tuesday',
'wednesday' => 'Wednesday',
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
'due' => 'Vencimento',
'next_due_on' => 'Próximo Vencimento: :date',
'use_client_terms' => 'Usar condições do cliente',
'day_of_month' => ':ordinal dia do mês ',
'last_day_of_month' => 'Último dia do mês',
'day_of_week_after' => ':ordinal :day depois',
'sunday' => 'Domingo',
'monday' => 'Segunda-Feira',
'tuesday' => 'Terça-Feira',
'wednesday' => 'Quarta-Feira',
'thursday' => 'Quinta-Feira',
'friday' => 'Sexta-Feira',
'saturday' => 'Sábado',
// Fonts
'header_font_id' => 'Header Font',
'body_font_id' => 'Body Font',
'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.',
'header_font_id' => 'Fonte do Cabeçalho',
'body_font_id' => 'Fonte dos Textos',
'color_font_help' => 'Nota: A cor primária também é utilizada nos projetos do portal do cliente e-mail personalizado.',
'live_preview' => 'Live Preview',
'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.',
'live_preview' => 'Preview',
'invalid_mail_config' => 'Falha ao enviar e-mail, verifique as configurações.',
'invoice_message_button' => 'To view your invoice for :amount, click the button below.',
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'add_bank_account' => 'Add Bank Account',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
'bank_id' => 'bank',
'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account',
'edit_bank_account' => 'Edit Bank Account',
'archive_bank_account' => 'Archive Bank Account',
'archived_bank_account' => 'Successfully archived bank account',
'created_bank_account' => 'Successfully created bank account',
'validate_bank_account' => 'Validate Bank Account',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>',
'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.',
'username' => 'Username',
'account_number' => 'Account Number',
'account_name' => 'Account Name',
'bank_account_error' => 'Failed to retreive account details, please check your credentials.',
'status_approved' => 'Approved',
'quote_settings' => 'Quote Settings',
'auto_convert_quote' => 'Auto convert quote',
'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.',
'validate' => 'Validate',
'invoice_message_button' => 'Para visualizar sua fatura de :amount, clique no botão abaixo.',
'quote_message_button' => 'Para visualizar seu orçamento de :amount, clique no botão abaixo.',
'payment_message_button' => 'Obrigado pelo seu pagamento de :amount.',
'payment_type_direct_debit' => 'Débito',
'bank_accounts' => 'Contas Bancárias',
'add_bank_account' => 'Adicionar Conta Bancária',
'setup_account' => 'Configurar Conta',
'import_expenses' => 'Importar Despesas',
'bank_id' => 'banco',
'integration_type' => 'Tipo de Integração',
'updated_bank_account' => 'Conta bancária atualizada com sucesso',
'edit_bank_account' => 'Editar Conta Bancária',
'archive_bank_account' => 'Arquivar Conta Bancária',
'archived_bank_account' => 'Conta bancária arquivada com sucesso',
'created_bank_account' => 'Conta bancária criada com sucesso',
'validate_bank_account' => 'Validar Conta Bancária',
'bank_accounts_help' => 'Conecte sua conta bancária para importar suas despesas e criar fornecedores. Suporte ao American Express e <a href="'.OFX_HOME_URL.'" target="_blank">400+ bancos americanos.</a>',
'bank_password_help' => 'Nota: sua senha é transferida de forma segura e não será armazenada em nossos servidores.',
'bank_password_warning' => 'Atenção: sua senha será transferida de forma não segura, considere habilitar HTTPS.',
'username' => 'Usuário',
'account_number' => 'Conta',
'account_name' => 'Nome da Conta',
'bank_account_error' => 'Falha ao receber os detalhes da sua conta, verifique seus dados de acesso.',
'status_approved' => 'Aprovado',
'quote_settings' => 'Configuração de Orçamentos',
'auto_convert_quote' => 'Auto converter orçamento',
'auto_convert_quote_help' => 'Converter automaticamente um orçamento quando for aprovado pelo cliente.',
'validate' => 'Validado',
'info' => 'Info',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)',
'imported_expenses' => ':count_vendors fornecedor(s) e :count_expenses despesa(s) importadas com sucesso',
'iframe_url_help3' => 'Note: if you plan on accepting credit cards details we strongly recommend enabling HTTPS on your site.',
'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.',
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.',
'iframe_url_help3' => 'Nota: se o seu plano aceita detalhes do cartão de crédito recomendamos que seja habilitado o HTTPS em seu site.',
'expense_error_multiple_currencies' => 'As despesas não podem ter diferentes moedas.',
'expense_error_mismatch_currencies' => 'As configurações de moeda do cliente não coincide com a moeda nesta despesa.',
'trello_roadmap' => 'Trello Roadmap',
'header_footer' => 'Header/Footer',
'first_page' => 'first page',
'all_pages' => 'all pages',
'last_page' => 'last page',
'all_pages_header' => 'Show header on',
'all_pages_footer' => 'Show footer on',
'invoice_currency' => 'Invoice Currency',
'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.',
'quote_issued_to' => 'Quote issued to',
'show_currency_code' => 'Currency Code',
'trial_message' => 'Your account will receive a free two week trial of our pro plan.',
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.',
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.',
'trial_call_to_action' => 'Start Free Trial',
'trial_success' => 'Successfully enabled two week free pro plan trial',
'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'header_footer' => 'Cabeçalho/Rodapé',
'first_page' => 'primeira página',
'all_pages' => 'todas as páginas',
'last_page' => 'última página',
'all_pages_header' => 'Mostrar cabeçalho on',
'all_pages_footer' => 'Mostrar rodapé on',
'invoice_currency' => 'Moeda da Fatura',
'enable_https' => 'Recomendamos a utilização de HTTPS para receber os detalhes do cartão de crédito online.',
'quote_issued_to' => 'Orçamento emitido para',
'show_currency_code' => 'Código da Moeda',
'trial_message' => 'Sua conta receberá duas semanas receberá duas semanas gratuitamente para testar nosso plano pro.',
'trial_footer' => 'Seu período de teste expira em :count dias, :link para adquirir o plano pro.',
'trial_footer_last_day' => 'Seu período de testes encerra hoje, :link para adiquirir o plano pro.',
'trial_call_to_action' => 'Iniciar período de testes',
'trial_success' => 'Duas semanas de testes foi habilitado com sucesso',
'overdue' => 'Vencido',
'white_label_text' => 'Adquira UM ano de licença white label por $'.WHITE_LABEL_PRICE.' para remover a marca Invoice Ninja do portal do cliente e ajudar nosso projeto.',
'navigation' => 'Navigation',
'list_invoices' => 'List Invoices',
'list_clients' => 'List Clients',
'list_quotes' => 'List Quotes',
'list_tasks' => 'List Tasks',
'list_expenses' => 'List Expenses',
'list_recurring_invoices' => 'List Recurring Invoices',
'list_payments' => 'List Payments',
'list_credits' => 'List Credits',
'tax_name' => 'Tax Name',
'report_settings' => 'Report Settings',
'search_hotkey' => 'shortcut is /',
'navigation' => 'Navegação',
'list_invoices' => 'Listar Faturas',
'list_clients' => 'Listar Clientes',
'list_quotes' => 'Listar Orçamentos',
'list_tasks' => 'Listar Tarefas',
'list_expenses' => 'Listar Despesas',
'list_recurring_invoices' => 'Listar Faturas Recorrentes',
'list_payments' => 'Listar Pagamentos',
'list_credits' => 'Listar Créditos',
'tax_name' => 'Nome da Taxa',
'report_settings' => 'Configuração de Relatórios',
'search_hotkey' => 'atalho /',
'new_user' => 'New User',
'new_product' => 'New Product',
'new_tax_rate' => 'New Tax Rate',
'invoiced_amount' => 'Invoiced Amount',
'invoice_item_fields' => 'Invoice Item Fields',
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
'recurring_invoice_number' => 'Recurring Invoice Number',
'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.',
'enable_client_portal' => 'Dashboard',
'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.',
'new_user' => 'Novo Usuário',
'new_product' => 'Novo Produto',
'new_tax_rate' => 'Nova Taxa de Juro',
'invoiced_amount' => 'Total Faturado',
'invoice_item_fields' => 'Campos de Ítens da Fatura',
'custom_invoice_item_fields_help' => 'Adicionar um campo ao adicionar um ítem na fatura e exibir no PDF.',
'recurring_invoice_number' => 'Número da Fatura Recorrente',
'recurring_invoice_number_prefix_help' => 'Informe um prefixo para a numeração das faturas recorrentes. O valor padrão é \'R\'.',
'enable_client_portal' => 'Painel',
'enable_client_portal_help' => 'Mostrar/Ocultar o painel no portal do cliente.',
// Client Passwords
'enable_portal_password'=>'Password protect invoices',
'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.',
'send_portal_password'=>'Generate password automatically',
'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.',
'enable_portal_password'=>'Faturas protegidas por senha',
'enable_portal_password_help'=>'Permite definir uma senha para cada contato. Se uma senha for definida, o contato deverá informar sua senha antes de visualizar a fatura.',
'send_portal_password'=>'Gerar senha automaticamente',
'send_portal_password_help'=>'Se uma senha não for definida, uma senha será gerada e enviada juntamente com a primeira fatura.',
'expired' => 'Expired',
'invalid_card_number' => 'The credit card number is not valid.',
'invalid_expiry' => 'The expiration date is not valid.',
'invalid_cvv' => 'The CVV is not valid.',
'cost' => 'Cost',
'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.',
'expired' => 'Expireda',
'invalid_card_number' => 'Cartão de Crédito inválido.',
'invalid_expiry' => 'Data para expirar não é valida.',
'invalid_cvv' => 'O código CVV não é válido.',
'cost' => 'Custo',
'create_invoice_for_sample' => 'Nota: cria sua primeira fatura para visualizar aqui.',
// User Permissions
'owner' => 'Owner',
'administrator' => 'Administrator',
'administrator_help' => 'Allow user to manage users, change settings and modify all records',
'user_create_all' => 'Create clients, invoices, etc.',
'user_view_all' => 'View all clients, invoices, etc.',
'user_edit_all' => 'Edit all clients, invoices, etc.',
'gateway_help_20' => ':link to sign up for Sage Pay.',
'gateway_help_21' => ':link to sign up for Sage Pay.',
'partial_due' => 'Partial Due',
'restore_vendor' => 'Restore Vendor',
'restored_vendor' => 'Successfully restored vendor',
'restored_expense' => 'Successfully restored expense',
'permissions' => 'Permissions',
'create_all_help' => 'Allow user to create and modify records',
'view_all_help' => 'Allow user to view records they didn\'t create',
'edit_all_help' => 'Allow user to modify records they didn\'t create',
'view_payment' => 'View Payment',
'owner' => 'Proprietário',
'administrator' => 'Administrador',
'administrator_help' => 'Permite usuário gerenciar usuários, configurações e alterar todos os cadastros',
'user_create_all' => 'Criar clientes, faturas, etc.',
'user_view_all' => 'Visualizar todos os clientes, faturas, etc.',
'user_edit_all' => 'Editar todos os clientes, faturas, etc.',
'gateway_help_20' => ':link para habilitar Sage Pay.',
'gateway_help_21' => ':link para habilitar Sage Pay.',
'partial_due' => 'Vencimento Parcial',
'restore_vendor' => 'Restaurar Fornecedor',
'restored_vendor' => 'Fornecedor restarurado com sucesso',
'restored_expense' => 'Despesa restaurada com sucesso',
'permissions' => 'Permissões',
'create_all_help' => 'Permite o usuário criar e alterar todos os regitros',
'view_all_help' => 'Permite usuario visualizar regitros que ele não criou',
'edit_all_help' => 'Permite usuario editar regitros que ele não criou',
'view_payment' => 'Visualizar ',
'january' => 'January',
'february' => 'February',
'march' => 'March',
'april' => 'April',
'may' => 'May',
'june' => 'June',
'july' => 'July',
'august' => 'August',
'september' => 'September',
'october' => 'October',
'november' => 'November',
'december' => 'December',
'january' => 'Janeiro',
'february' => 'Fevereiro',
'march' => 'Março',
'april' => 'Abril',
'may' => 'Maio',
'june' => 'Junho',
'july' => 'Julho',
'august' => 'Agosto',
'september' => 'Setembro',
'october' => 'Outubro',
'november' => 'Novembro',
'december' => 'Dezembro',
);
);

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,21 +46,23 @@
}
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");
invoice.invoice_design_id = $('#invoice_design_id').val();
invoice.account.page_size = $('#page_size option:selected').text();
NINJA.primaryColor = $('#primary_color').val();
NINJA.secondaryColor = $('#secondary_color').val();
NINJA.fontSize = parseInt($('#font_size').val());
@if (Auth::user()->isPro())
NINJA.headerFont = $('#header_font_id option:selected').text();
NINJA.bodyFont = $('#body_font_id option:selected').text();
@else
NINJA.headerFont = NINJA.bodyFont = 'Roboto';
@endif
NINJA.headerFont = $('#header_font_id option:selected').text();
NINJA.bodyFont = $('#body_font_id option:selected').text();
var fields = [
'item',
'description',
@ -89,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,
@ -112,7 +114,16 @@
<div class="col-md-12">
{!! Former::open()->addClass('warn-on-exit')->onchange('if(!window.loadingFonts)refreshPDF()') !!}
{!! Former::populate($account) !!}
{!! Former::populateField('invoice_design_id', $account->invoice_design_id) !!}
{!! Former::populateField('body_font_id', $account->body_font_id) !!}
{!! Former::populateField('header_font_id', $account->header_font_id) !!}
{!! Former::populateField('live_preview', intval($account->live_preview)) !!}
{!! Former::populateField('font_size', $account->font_size) !!}
{!! Former::populateField('page_size', $account->page_size) !!}
{!! Former::populateField('invoice_embed_documents', intval($account->invoice_embed_documents)) !!}
{!! Former::populateField('primary_color', $account->primary_color) !!}
{!! Former::populateField('secondary_color', $account->secondary_color) !!}
{!! Former::populateField('hide_quantity', intval($account->hide_quantity)) !!}
{!! Former::populateField('hide_paid_to_date', intval($account->hide_paid_to_date)) !!}
{!! Former::populateField('all_pages_header', intval($account->all_pages_header)) !!}
@ -143,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') !!}
@ -156,13 +167,17 @@
{!! Former::select('header_font_id')
->fromQuery($invoiceFonts, 'name', 'id') !!}
{!! Former::checkbox('live_preview')->text(trans('texts.enable')) !!}
</div>
<div class="col-md-6">
{{ Former::setOption('TwitterBootstrap3.labelWidths.large', 6) }}
{{ Former::setOption('TwitterBootstrap3.labelWidths.small', 6) }}
{!! Former::select('page_size')
->options($pageSizes) !!}
{!! Former::text('font_size')
->type('number')
->min('0')
@ -171,6 +186,7 @@
{!! Former::text('primary_color') !!}
{!! Former::text('secondary_color') !!}
{{ Former::setOption('TwitterBootstrap3.labelWidths.large', 4) }}
{{ Former::setOption('TwitterBootstrap3.labelWidths.small', 4) }}
@ -249,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

@ -59,12 +59,13 @@
{!! Former::text('invoice_number_prefix')
->addGroupClass('invoice-prefix')
->label(' ') !!}
->label(trans('texts.prefix')) !!}
{!! Former::text('invoice_number_pattern')
->appendIcon('question-sign')
->addGroupClass('invoice-pattern')
->label(' ')
->label(trans('texts.pattern'))
->addGroupClass('number-pattern') !!}
{!! Former::text('invoice_number_padding') !!}
{!! Former::text('invoice_number_counter')
->label(trans('texts.counter'))
->help(trans('texts.invoice_number_help') . ' ' .
@ -84,12 +85,12 @@
{!! Former::text('quote_number_prefix')
->addGroupClass('quote-prefix')
->label(' ') !!}
->label(trans('texts.prefix')) !!}
{!! Former::text('quote_number_pattern')
->appendIcon('question-sign')
->addGroupClass('quote-pattern')
->addGroupClass('number-pattern')
->label(' ') !!}
->label(trans('texts.pattern')) !!}
{!! Former::text('quote_number_counter')
->label(trans('texts.counter'))
->addGroupClass('pad-checkbox')
@ -264,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

@ -6,7 +6,7 @@
<link href="{{ isset($account) ? $account->getFontsUrl('http') : '' }}" rel="stylesheet" type="text/css" />
<!--<![endif]-->
</head>
<body style="min-height: 700px; color: #000000;{!! isset($account) ? $account->getBodyFontCss() : '' !!}font-size: 12px; -webkit-text-size-adjust: none; -ms-text-size-adjust: none; background: #F4F5F5; margin: 0; padding: 0;"
<body style="color: #000000;{!! isset($account) ? $account->getBodyFontCss() : '' !!}font-size: 12px; -webkit-text-size-adjust: none; -ms-text-size-adjust: none; background: #F4F5F5; margin: 0; padding: 0;"
alink="#FF0000" link="#FF0000" bgcolor="#F4F5F5" text="#000000" yahoo="fix">
@yield('markup')
@ -50,8 +50,8 @@
}
</style>
<div id="body_style" style="min-height: 700px;{!! isset($account) ? $account->getBodyFontCss() : '' !!};color: #2E2B2B; font-size: 16px;
background: #F4F5F5; padding: 0px 15px;">
<div id="body_style" style="{!! isset($account) ? $account->getBodyFontCss() : '' !!};color: #2E2B2B; font-size: 16px;
background: #F4F5F5; padding: 0px 0px 15px;">
<table cellpadding="0" cellspacing="0" border="0" bgcolor="#FFFFFF" width="580" align="center">

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,13 +493,7 @@
{!! Former::text('pdfupload') !!}
</div>
@if ($account->hasLargeFont())
<label for="livePreview" class="control-label" style="padding-right:10px">
<input id="livePreview" type="checkbox"/> {{ trans('texts.live_preview') }}
</label>
@endif
@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") !!}
@ -571,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)
@ -626,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
@ -966,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')
@ -1062,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});
@ -1097,8 +1095,8 @@
window.generatedPDF = false;
function getPDFString(cb, force) {
@if ($account->hasLargeFont())
if (!$('#livePreview').is(':checked') && window.generatedPDF) {
@if (!$account->live_preview)
if (window.generatedPDF) {
return;
}
@endif
@ -1375,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;
@ -1399,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