Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
David Bomba 2016-05-07 08:44:39 +10:00
commit 1bef3b0791
38 changed files with 345 additions and 167 deletions

View File

@ -1301,4 +1301,37 @@ class AccountController extends BaseController
return Redirect::to("/settings/$section/", 301);
}
public function previewEmail(\App\Services\TemplateService $templateService)
{
$template = Input::get('template');
$invoice = Invoice::scope()
->invoices()
->withTrashed()
->first();
if ( ! $invoice) {
return trans('texts.create_invoice_for_sample');
}
$account = Auth::user()->account;
// replace the variables with sample data
$data = [
'account' => $account,
'invoice' => $invoice,
'invitation' => $invoice->invitations->first(),
'client' => $invoice->client,
'amount' => $invoice->amount
];
// create the email view
$view = 'emails.' . $account->getTemplateView(ENTITY_INVOICE) . '_html';
$data = array_merge($data, [
'body' => $templateService->processVariables($template, $data),
'entityType' => ENTITY_INVOICE,
]);
return Response::view($view, $data);
}
}

View File

@ -266,18 +266,7 @@ class AppController extends BaseController
Cache::flush();
Session::flush();
Artisan::call('migrate', array('--force' => true));
foreach ([
'PaymentLibraries',
'Fonts',
'Banks',
'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
}
Artisan::call('db:seed', array('--force' => true, '--class' => "UpdateSeeder"));
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) {

View File

@ -9,7 +9,7 @@ class ClientRequest extends EntityRequest {
$client = parent::entity();
// eager load the contacts
if ($client && ! count($client->contacts)) {
if ($client && ! $client->relationLoaded('contacts')) {
$client->load('contacts');
}

View File

@ -8,11 +8,11 @@ class ExpenseRequest extends EntityRequest {
{
$expense = parent::entity();
// eager load the contacts
if ($expense && ! count($expense->documents)) {
// eager load the documents
if ($expense && ! $expense->relationLoaded('documents')) {
$expense->load('documents');
}
return $expense;
}
}

View File

@ -8,8 +8,8 @@ class InvoiceRequest extends EntityRequest {
{
$invoice = parent::entity();
// eager load the contacts
if ($invoice && ! count($invoice->invoice_items)) {
// eager load the invoice items
if ($invoice && ! $invoice->relationLoaded('invoice_items')) {
$invoice->load('invoice_items');
}

View File

@ -9,7 +9,7 @@ class VendorRequest extends EntityRequest {
$vendor = parent::entity();
// eager load the contacts
if ($vendor && ! count($vendor->vendor_contacts)) {
if ($vendor && ! $vendor->relationLoaded('vendor_contacts')) {
$vendor->load('vendor_contacts');
}

View File

@ -208,6 +208,7 @@ Route::group([
Route::resource('tax_rates', 'TaxRateController');
Route::post('tax_rates/bulk', 'TaxRateController@bulk');
Route::get('settings/email_preview', 'AccountController@previewEmail');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/charts_and_reports', 'ReportController@showReports');

View File

@ -341,6 +341,7 @@ class Utils
$currency = self::getFromCache($currencyId, 'currencies');
$thousand = $currency->thousand_separator;
$decimal = $currency->decimal_separator;
$precision = $currency->precision;
$code = $currency->code;
$swapSymbol = false;
@ -355,7 +356,7 @@ class Utils
}
}
$value = number_format($value, $currency->precision, $decimal, $thousand);
$value = number_format($value, $precision, $decimal, $thousand);
$symbol = $currency->symbol;
if ($showCode || !$symbol) {

View File

@ -212,7 +212,9 @@ class Account extends Eloquent
public function isGatewayConfigured($gatewayId = 0)
{
$this->load('account_gateways');
if ( ! $this->relationLoaded('account_gateways')) {
$this->load('account_gateways');
}
if ($gatewayId) {
return $this->getGatewayConfig($gatewayId) != false;
@ -241,7 +243,7 @@ class Account extends Eloquent
return $this->name;
}
$this->load('users');
//$this->load('users');
$user = $this->users()->first();
return $user->getDisplayName();
@ -1167,6 +1169,11 @@ class Account extends Eloquent
return str_replace('/>', ' />', $template);
}
public function getTemplateView($view = '')
{
return $this->getEmailDesignId() == EMAIL_DESIGN_PLAIN ? $view : 'design' . $this->getEmailDesignId();
}
public function getEmailFooter()
{
if ($this->email_footer) {

View File

@ -256,13 +256,17 @@ class Client extends EntityModel
public function getGatewayToken()
{
$this->account->load('account_gateways');
$account = $this->account;
if ( ! $account->relationLoaded('account_gateways')) {
$account->load('account_gateways');
}
if (!count($this->account->account_gateways)) {
if (!count($account->account_gateways)) {
return false;
}
$accountGateway = $this->account->getGatewayConfig(GATEWAY_STRIPE);
$accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE);
if (!$accountGateway) {
return false;

View File

@ -228,6 +228,12 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\Expense','invoice_id','id')->withTrashed();
}
public function scopeInvoices($query)
{
return $query->where('is_quote', '=', false)
->where('is_recurring', '=', false);
}
public function markInvitationsSent($notify = false)
{
foreach ($this->invitations as $invitation) {

View File

@ -1,17 +1,13 @@
<?php namespace App\Ninja\Mailers;
use Form;
use HTML;
use Utils;
use Event;
use URL;
use Auth;
use App\Services\TemplateService;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Activity;
use App\Models\Gateway;
use App\Events\InvoiceWasEmailed;
use App\Events\QuoteWasEmailed;
@ -36,6 +32,11 @@ class ContactMailer extends Mailer
'paymentButton',
];
public function __construct(TemplateService $templateService)
{
$this->templateService = $templateService;
}
public function sendInvoice(Invoice $invoice, $reminder = false, $pdfString = false)
{
$invoice->load('invitations', 'client.language', 'account');
@ -144,7 +145,7 @@ class ContactMailer extends Mailer
}
$data = [
'body' => $this->processVariables($body, $variables),
'body' => $this->templateService->processVariables($body, $variables),
'link' => $invitation->getLink(),
'entityType' => $invoice->getEntityType(),
'invoiceId' => $invoice->id,
@ -160,14 +161,9 @@ class ContactMailer extends Mailer
$data['pdfFileName'] = $invoice->getFileName();
}
$subject = $this->processVariables($subject, $variables);
$subject = $this->templateService->processVariables($subject, $variables);
$fromEmail = $user->email;
if ($account->getEmailDesignId() == EMAIL_DESIGN_PLAIN) {
$view = ENTITY_INVOICE;
} else {
$view = 'design' . ($account->getEmailDesignId() - 1);
}
$view = $account->getTemplateView(ENTITY_INVOICE);
$response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data);
@ -230,7 +226,7 @@ class ContactMailer extends Mailer
];
$data = [
'body' => $this->processVariables($emailTemplate, $variables),
'body' => $this->templateService->processVariables($emailTemplate, $variables),
'link' => $invitation->getLink(),
'invoice' => $invoice,
'client' => $client,
@ -244,14 +240,10 @@ class ContactMailer extends Mailer
$data['pdfFileName'] = $invoice->getFileName();
}
$subject = $this->processVariables($emailSubject, $variables);
$subject = $this->templateService->processVariables($emailSubject, $variables);
$data['invoice_id'] = $payment->invoice->id;
if ($account->getEmailDesignId() == EMAIL_DESIGN_PLAIN) {
$view = 'payment_confirmation';
} else {
$view = 'design' . ($account->getEmailDesignId() - 1);
}
$view = $account->getTemplateView('payment_confirmation');
if ($user->email && $contact->email) {
$this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data);
@ -281,75 +273,4 @@ class ContactMailer extends Mailer
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
private function processVariables($template, $data)
{
$account = $data['account'];
$client = $data['client'];
$invitation = $data['invitation'];
$invoice = $invitation->invoice;
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$documentsHTML = '';
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>';
}
foreach($invoice->expenses as $expense){
foreach($expense->documents as $document){
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
}
}
$documentsHTML .= '</ul>';
}
$variables = [
'$footer' => $account->getEmailFooter(),
'$client' => $client->getDisplayName(),
'$account' => $account->getDisplayName(),
'$dueDate' => $account->formatDate($invoice->due_date),
'$invoiceDate' => $account->formatDate($invoice->invoice_date),
'$contact' => $invitation->contact->getDisplayName(),
'$firstName' => $invitation->contact->first_name,
'$amount' => $account->formatMoney($data['amount'], $client),
'$invoice' => $invoice->invoice_number,
'$quote' => $invoice->invoice_number,
'$link' => $invitation->getLink(),
'$password' => $passwordHTML,
'$viewLink' => $invitation->getLink().'$password',
'$viewButton' => Form::emailViewButton($invitation->getLink(), $invoice->getEntityType()).'$password',
'$paymentLink' => $invitation->getLink('payment').'$password',
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
'$customClient1' => $account->custom_client_label1,
'$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1,
'$customInvoice2' => $account->custom_invoice_text_label2,
'$documents' => $documentsHTML,
];
// Add variables for available payment types
foreach (Gateway::$paymentTypes as $type) {
$camelType = Gateway::getPaymentTypeName($type);
$type = Utils::toSnakeCase($camelType);
$variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}";
$variables["\${$camelType}Button"] = Form::emailPaymentButton($invitation->getLink('payment') . "/{$type}");
}
$includesPasswordPlaceholder = strpos($template, '$password') !== false;
$str = str_replace(array_keys($variables), array_values($variables), $template);
if(!$includesPasswordPlaceholder && $passwordHTML){
$pos = strrpos($str, '$password');
if($pos !== false)
{
$str = substr_replace($str, $passwordHTML, $pos, 9/* length of "$password" */);
}
}
$str = str_replace('$password', '', $str);
$str = autolink($str, 100);
return $str;
}
}

View File

@ -18,7 +18,7 @@ class InvoicePresenter extends Presenter {
public function balanceDueLabel()
{
if ($this->entity->partial) {
if ($this->entity->partial > 0) {
return 'partial_due';
} elseif ($this->entity->is_quote) {
return 'total';

View File

@ -167,6 +167,7 @@ class AccountRepository
ENTITY_QUOTE,
ENTITY_TASK,
ENTITY_EXPENSE,
ENTITY_VENDOR,
ENTITY_RECURRING_INVOICE,
ENTITY_PAYMENT,
ENTITY_CREDIT
@ -183,15 +184,22 @@ class AccountRepository
];
}
$features[] = ['dashboard', '/dashboard'];
$features[] = ['customize_design', '/settings/customize_design'];
$features[] = ['new_tax_rate', '/tax_rates/create'];
$features[] = ['new_product', '/products/create'];
$features[] = ['new_user', '/users/create'];
$features[] = ['custom_fields', '/settings/invoice_settings'];
$features = array_merge($features, [
['dashboard', '/dashboard'],
['customize_design', '/settings/customize_design'],
['new_tax_rate', '/tax_rates/create'],
['new_product', '/products/create'],
['new_user', '/users/create'],
['custom_fields', '/settings/invoice_settings'],
['invoice_number', '/settings/invoice_settings'],
]);
$settings = array_merge(Account::$basicSettings, Account::$advancedSettings);
if ( ! Utils::isNinjaProd()) {
$settings[] = ACCOUNT_SYSTEM_SETTINGS;
}
foreach ($settings as $setting) {
$features[] = [
$setting,
@ -332,7 +340,7 @@ class AccountRepository
$client->public_id = $account->id;
$client->user_id = $ninjaAccount->users()->first()->id;
$client->currency_id = 1;
foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone', 'language_id'] as $field) {
foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone', 'language_id', 'vat_number'] as $field) {
$client->$field = $account->$field;
}
$ninjaAccount->clients()->save($client);

View File

@ -105,9 +105,9 @@ class ClientRepository extends BaseRepository
// If the primary is set ensure it's listed first
usort($contacts, function ($left, $right) {
return (isset($right['is_primary']) ? $right['is_primary'] : 0) - (isset($left['is_primary']) ? $left['is_primary'] : 0);
return (isset($right['is_primary']) ? $right['is_primary'] : 1) - (isset($left['is_primary']) ? $left['is_primary'] : 0);
});
foreach ($contacts as $contact) {
$contact = $client->addContact($contact, $first);
$contactIds[] = $contact->public_id;

View File

@ -0,0 +1,80 @@
<?php namespace App\Services;
use Form;
use HTML;
use Utils;
use App\Models\Gateway;
class TemplateService
{
public function processVariables($template, $data)
{
$account = $data['account'];
$client = $data['client'];
$invitation = $data['invitation'];
$invoice = $invitation->invoice;
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$documentsHTML = '';
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>';
}
foreach($invoice->expenses as $expense){
foreach($expense->documents as $document){
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
}
}
$documentsHTML .= '</ul>';
}
$variables = [
'$footer' => $account->getEmailFooter(),
'$client' => $client->getDisplayName(),
'$account' => $account->getDisplayName(),
'$dueDate' => $account->formatDate($invoice->due_date),
'$invoiceDate' => $account->formatDate($invoice->invoice_date),
'$contact' => $invitation->contact->getDisplayName(),
'$firstName' => $invitation->contact->first_name,
'$amount' => $account->formatMoney($data['amount'], $client),
'$invoice' => $invoice->invoice_number,
'$quote' => $invoice->invoice_number,
'$link' => $invitation->getLink(),
'$password' => $passwordHTML,
'$viewLink' => $invitation->getLink().'$password',
'$viewButton' => Form::emailViewButton($invitation->getLink(), $invoice->getEntityType()).'$password',
'$paymentLink' => $invitation->getLink('payment').'$password',
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
'$customClient1' => $account->custom_client_label1,
'$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1,
'$customInvoice2' => $account->custom_invoice_text_label2,
'$documents' => $documentsHTML,
];
// Add variables for available payment types
foreach (Gateway::$paymentTypes as $type) {
$camelType = Gateway::getPaymentTypeName($type);
$type = Utils::toSnakeCase($camelType);
$variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}";
$variables["\${$camelType}Button"] = Form::emailPaymentButton($invitation->getLink('payment') . "/{$type}");
}
$includesPasswordPlaceholder = strpos($template, '$password') !== false;
$str = str_replace(array_keys($variables), array_values($variables), $template);
if (!$includesPasswordPlaceholder && $passwordHTML) {
$pos = strrpos($str, '$password');
if ($pos !== false)
{
$str = substr_replace($str, $passwordHTML, $pos, 9/* length of "$password" */);
}
}
$str = str_replace('$password', '', $str);
$str = autolink($str, 100);
return $str;
}
}

View File

@ -8,6 +8,7 @@ class CurrenciesSeeder extends Seeder
{
Eloquent::unguard();
// http://www.localeplanet.com/icu/currency.html
$currencies = [
['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
@ -56,6 +57,7 @@ class CurrenciesSeeder extends Seeder
['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Costa Rican Colón', 'code' => 'CRC', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pakistani Rupee', 'code' => 'PKR', 'symbol' => 'Rs ', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {

View File

@ -23,5 +23,6 @@ class DatabaseSeeder extends Seeder
$this->call('DateFormatsSeeder');
$this->call('InvoiceDesignsSeeder');
$this->call('PaymentTermsSeeder');
$this->call('LanguageSeeder');
}
}

View File

@ -0,0 +1,41 @@
<?php
use App\Models\Language;
class LanguageSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
$languages = [
['name' => 'English', 'locale' => 'en'],
['name' => 'Italian', 'locale' => 'it'],
['name' => 'German', 'locale' => 'de'],
['name' => 'French', 'locale' => 'fr'],
['name' => 'Brazilian Portuguese', 'locale' => 'pt_BR'],
['name' => 'Dutch', 'locale' => 'nl'],
['name' => 'Spanish', 'locale' => 'es'],
['name' => 'Norwegian', 'locale' => 'nb_NO'],
['name' => 'Danish', 'locale' => 'da'],
['name' => 'Japanese', 'locale' => 'ja'],
['name' => 'Swedish', 'locale' => 'sv'],
['name' => 'Spanish - Spain', 'locale' => 'es_ES'],
['name' => 'French - Canada', 'locale' => 'fr_CA'],
['name' => 'Lithuanian', 'locale' => 'lt'],
['name' => 'Polish', 'locale' => 'pl'],
];
foreach ($languages as $language) {
$record = Language::whereLocale($language['locale'])->first();
if ($record) {
$record->name = $language['name'];
$record->save();
} else {
Language::create($language);
}
}
Eloquent::reguard();
}
}

View File

@ -19,5 +19,6 @@ class UpdateSeeder extends Seeder
$this->call('DateFormatsSeeder');
$this->call('InvoiceDesignsSeeder');
$this->call('PaymentTermsSeeder');
$this->call('LanguageSeeder');
}
}

View File

@ -1,9 +1,13 @@
<?php
use App\Models\User;
use App\Models\Font;
use App\Models\Account;
use App\Models\Company;
use App\Models\Affiliate;
use App\Models\Country;
use App\Models\InvoiceDesign;
use Faker\Factory;
class UserTableSeeder extends Seeder
{
@ -14,11 +18,25 @@ class UserTableSeeder extends Seeder
Eloquent::unguard();
$faker = Faker\Factory::create();
$company = Company::create();
$account = Account::create([
//'name' => 'Test Account',
'name' => $faker->name,
'address1' => $faker->streetAddress,
'address2' => $faker->secondaryAddress,
'city' => $faker->city,
'state' => $faker->state,
'postal_code' => $faker->postcode,
'country_id' => Country::all()->random()->id,
'account_key' => str_random(RANDOM_KEY_LENGTH),
'invoice_terms' => $faker->text($faker->numberBetween(50, 300)),
'work_phone' => $faker->phoneNumber,
'work_email' => $faker->safeEmail,
'invoice_design_id' => min(InvoiceDesign::all()->random()->id, 10),
'header_font_id' => min(Font::all()->random()->id, 17),
'body_font_id' => min(Font::all()->random()->id, 17),
'primary_color' => $faker->hexcolor,
'timezone_id' => 1,
'company_id' => $company->id,
]);
@ -30,6 +48,8 @@ class UserTableSeeder extends Seeder
'password' => Hash::make(TEST_PASSWORD),
'registered' => true,
'confirmed' => true,
'notify_sent' => false,
'notify_paid' => false,
]);
Affiliate::create([

View File

@ -31196,7 +31196,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
field = toSnakeCase(field);
var value = getDescendantProp(invoice, field);
if (match.indexOf('?') < 0 || value) {
if (invoice.partial && field == 'balance_due') {
if (invoice.partial > 0 && field == 'balance_due') {
field = 'partial_due';
} else if (invoice.is_quote) {
field = field.replace('invoice', 'quote');
@ -31584,7 +31584,7 @@ NINJA.invoiceDetails = function(invoice) {
],
[
{text: (invoice.is_quote ? invoiceLabels.valid_until : invoiceLabels.due_date)},
{text: invoice.due_date}
{text: invoice.is_recurring ? false : invoice.due_date}
]
];

View File

@ -2539,6 +2539,10 @@ ul.dropdown-menu,
box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
}
.twitter-typeahead .tt-menu {
overflow-x: hidden;
}
.panel-default,
canvas {
border: 1px solid;

View File

@ -410,6 +410,10 @@ ul.dropdown-menu,
box-shadow: 0 0 10px 2px rgba(0,0,0,.05);
}
.twitter-typeahead .tt-menu {
overflow-x: hidden;
}
.panel-default,
canvas {
border: 1px solid;

View File

@ -192,7 +192,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
field = toSnakeCase(field);
var value = getDescendantProp(invoice, field);
if (match.indexOf('?') < 0 || value) {
if (invoice.partial && field == 'balance_due') {
if (invoice.partial > 0 && field == 'balance_due') {
field = 'partial_due';
} else if (invoice.is_quote) {
field = field.replace('invoice', 'quote');
@ -580,7 +580,7 @@ NINJA.invoiceDetails = function(invoice) {
],
[
{text: (invoice.is_quote ? invoiceLabels.valid_until : invoiceLabels.due_date)},
{text: invoice.due_date}
{text: invoice.is_recurring ? false : invoice.due_date}
]
];

View File

@ -1176,6 +1176,8 @@ $LANG = array(
'page_size' => 'Page Size',
'live_preview_disabled' => 'Live preview has been disabled to support selected font',
'invoice_number_padding' => 'Padding',
'preview' => 'Preview',
'list_vendors' => 'List Vendors',
);

View File

@ -94,9 +94,9 @@
</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'))) !!}
{!! Former::actions( Button::success(trans('texts.plan_upgrade'))->large()->withAttributes(['onclick' => 'showChangePlan()'])->appendIcon(Icon::create('plus-sign'))) !!}
@elseif (!$account->hasFeature(FEATURE_WHITE_LABEL))
{!! 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>

View File

@ -62,11 +62,14 @@
<div id="{{ $field }}_template_preview"></div>
</div>
</div>
<p>&nbsp;<p/>
<div class="row">
<div class="col-md-12">
<p>&nbsp;<p/>
<div class="col-md-10">
@include('partials/quill_toolbar', ['name' => $field])
</div>
<div class="col-md-2" style="padding-top:10px">
{!! Button::primary(trans('texts.preview'))->withAttributes(['onclick' => 'serverPreview("'.$field.'")'])->small() !!}
</div>
</div>
</div>
</div>

View File

@ -25,11 +25,16 @@
{!! Former::vertical_open()->addClass('warn-on-exit') !!}
{!! Former::populate($account) !!}
@foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type)
@foreach (['subject', 'template'] as $field)
{!! Former::populateField("email_{$field}_{$type}", $templates[$type][$field]) !!}
{{ Former::populateField("email_{$field}_{$type}", $templates[$type][$field]) }}
@endforeach
@endforeach
@foreach ([REMINDER1, REMINDER2, REMINDER3] as $type)
@foreach (['enable', 'num_days', 'direction', 'field'] as $field)
{{ Former::populateField("{$field}_{$type}", $account->{"{$field}_{$type}"}) }}
@endforeach
@endforeach
@ -80,6 +85,26 @@
</div>
<div class="modal fade" id="templatePreviewModal" tabindex="-1" role="dialog" aria-labelledby="templatePreviewModalLabel" aria-hidden="true">
<div class="modal-dialog" style="width:800px">
<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="templatePreviewModalLabel">{{ trans('texts.preview') }}</h4>
</div>
<div class="modal-body">
<iframe id="server-preview" frameborder="1" width="100%" height="500px"/></iframe>
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ trans('texts.close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="templateHelpModal" tabindex="-1" role="dialog" aria-labelledby="templateHelpModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
@ -158,6 +183,22 @@
}
}
function serverPreview(field) {
console.log(field);
$('#templatePreviewModal').modal('show');
var template = $('#email_template_' + field).val();
var url = '{{ URL::to('settings/email_preview') }}?template=' + template;
$('#server-preview').attr('src', url).load(function() {
// disable links in the preview
$('iframe').contents().find('a').each(function(index) {
$(this).on('click', function(event) {
event.preventDefault();
event.stopPropagation();
});
});
});
}
$(function() {
for (var i=0; i<entityTypes.length; i++) {
var entityType = entityTypes[i];

View File

@ -12,30 +12,30 @@
</tr>
<tr>
<td style="border-collapse: collapse;">
<table cellpadding="10" cellspacing="0" border="0" bgcolor="{{ $account->primary_color ?: '#2E2B2B' }}" width="580" align="center" class="header"
style="border-bottom-width: 6px; border-bottom-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-bottom-style: solid;">
<table cellpadding="10" cellspacing="0" border="0" bgcolor="#F4F5F5" width="600" align="center"
class="header" style="border-top-width: 6px; border-top-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-top-style: solid;">
<tr>
<td class="logo" width="205" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
<td class="logo" width="208" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
@include('emails.partials.account_logo')
</td>
<td width="183" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
<p class="left" style="line-height: 22px; margin: 3px 0 0; padding: 0;">
<td width="183" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
<p class="left" style="line-height: 22px; margin: 0; padding: 2px 0 0;">
@if ($invoice->due_date)
<span style="font-size: 11px; color: #8f8d8e;">
{{ strtoupper(trans('texts.due_by', ['date' => $account->formatDate($invoice->due_date)])) }}
</span><br />
@endif
<span style="font-size: 19px; color: #FFFFFF;">
<span style="font-size: 18px;">
{{ trans("texts.{$entityType}") }} {{ $invoice->invoice_number }}
</span>
</p>
</td>
<td style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
<p style="margin: 0; padding: 0;">
<span style="font-size: 12px; color: #8f8d8e;">
{{ strtoupper(trans('texts.' . $invoice->present()->balanceDueLabel)) }}:
<td style="border-collapse: collapse; vertical-align: middle;" valign="middle">
<p class="right" style="line-height: 14px; margin: 0; padding: 0;">
<span style="font-size: 15px; color: #231F20;">
{{ trans('texts.' . $invoice->present()->balanceDueLabel) }}:
</span><br />
<span class="total" style="font-size: 27px; color: #FFFFFF; margin-top: 5px;display: block;">
<span class="total" style="font-size: 26px; display: block;margin-top: 5px;">
{{ $account->formatMoney($invoice->getRequestedAmount(), $client) }}
</span>
</p>

View File

@ -12,30 +12,30 @@
</tr>
<tr>
<td style="border-collapse: collapse;">
<table cellpadding="10" cellspacing="0" border="0" bgcolor="#F4F5F5" width="580" align="center"
class="header" style="border-top-width: 6px; border-top-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-top-style: solid;">
<table cellpadding="10" cellspacing="0" border="0" bgcolor="{{ $account->primary_color ?: '#2E2B2B' }}" width="600" align="center" class="header"
style="border-bottom-width: 6px; border-bottom-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-bottom-style: solid;">
<tr>
<td class="logo" width="208" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
<td class="logo" width="205" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
@include('emails.partials.account_logo')
</td>
<td width="183" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
<p class="left" style="line-height: 22px; margin: 0; padding: 2px 0 0;">
<td width="183" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
<p class="left" style="line-height: 22px; margin: 3px 0 0; padding: 0;">
@if ($invoice->due_date)
<span style="font-size: 11px; color: #8f8d8e;">
{{ strtoupper(trans('texts.due_by', ['date' => $account->formatDate($invoice->due_date)])) }}
</span><br />
@endif
<span style="font-size: 18px;">
<span style="font-size: 19px; color: #FFFFFF;">
{{ trans("texts.{$entityType}") }} {{ $invoice->invoice_number }}
</span>
</p>
</td>
<td style="border-collapse: collapse; vertical-align: middle;" valign="middle">
<p class="right" style="line-height: 14px; margin: 0; padding: 0;">
<span style="font-size: 15px; color: #231F20;">
{{ trans('texts.' . $invoice->present()->balanceDueLabel) }}:
<td style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
<p style="margin: 0; padding: 0;">
<span style="font-size: 12px; color: #8f8d8e;">
{{ strtoupper(trans('texts.' . $invoice->present()->balanceDueLabel)) }}:
</span><br />
<span class="total" style="font-size: 26px; display: block;margin-top: 5px;">
<span class="total" style="font-size: 27px; color: #FFFFFF; margin-top: 5px;display: block;">
{{ $account->formatMoney($invoice->getRequestedAmount(), $client) }}
</span>
</p>

View File

@ -3,7 +3,7 @@
<a href="{{ $account->website }}" style="color: #19BB40; text-decoration: underline;">
@endif
<img src="{{ $message->embed($account->getLogoURL()) }}" style="max-height:50px; max-width:140px; margin-left: 33px;" />
<img src="{{ isset($message) ? $message->embed($account->getLogoURL()) : $account->getLogoURL() }}" style="max-height:50px; max-width:140px; margin-left: 33px;" />
@if ($account->website)
</a>

View File

@ -93,14 +93,14 @@
<div class="container main-container">
<div class="row">
<div class="col-md-3 logo">
<div class="col-md-6 logo">
@if ($account->hasLogo())
{!! HTML::image($account->getLogoURL()) !!}
@else
<h2>{{ $account->name}}</h2>
@endif
</div>
<div class="col-md-3 col-md-offset-3 address-details">
<div class="col-md-3 address-details">
@if ($account->address1)
{{ $account->address1 }}<br/>
@endif

View File

@ -73,7 +73,12 @@
<div class="form-group">
<label for="client" class="control-label col-lg-4 col-sm-4">{{ trans('texts.client') }}</label>
<div class="col-lg-8 col-sm-8">
<h4><div data-bind="text: getClientDisplayName(ko.toJS(client()))"></div></h4>
<h4>
<span data-bind="text: getClientDisplayName(ko.toJS(client()))"></span>
@if ($invoice->client->is_deleted)
&nbsp;&nbsp;<div class="label label-danger">{{ trans('texts.deleted') }}</div>
@endif
</h4>
@can('view', $invoice->client)
@can('edit', $invoice->client)
@ -109,6 +114,7 @@
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check', name: 'client[contacts][' + $index() + '][send_invoice]'}">
<span data-bind="html: email.display"></span>
</label>
@if ( ! $invoice->is_deleted && ! $invoice->client->is_deleted)
<span data-bind="visible: !$root.invoice().is_recurring()">
<span data-bind="html: $data.view_as_recipient"></span>&nbsp;&nbsp;
@if (Utils::isConfirmed())
@ -119,6 +125,7 @@
style: {color: $data.info_color}"></span>
@endif
</span>
@endif
</div>
</div>
</div>

View File

@ -66,6 +66,7 @@
}
var currency = currencyMap[currencyId];
var precision = currency.precision;
var thousand = currency.thousand_separator;
var decimal = currency.decimal_separator;
var code = currency.code;
@ -82,7 +83,7 @@
}
}
value = accounting.formatMoney(value, '', 2, thousand, decimal);
value = accounting.formatMoney(value, '', precision, thousand, decimal);
var symbol = currency.symbol;
if (hideSymbol) {

View File

@ -96,7 +96,7 @@ class OnlinePaymentCest
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
$I->checkOption('#auto_bill');
$I->executeJS('preparePdfData(\'email\')');
$I->wait(2);
$I->wait(3);
$I->see("$0.00");
}

View File

@ -84,7 +84,8 @@ class TaxRatesCest
// check total is right after saving
$I->see("\${$total}");
$I->amOnPage('/invoices');
$I->wait(2);
// check total is right in list view
$I->see("\${$total}");
}