1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Added support for additional invoice designs

This commit is contained in:
Hillel Coren 2014-10-24 13:48:59 +03:00
parent ea429368a8
commit cc6c6194c6
35 changed files with 614 additions and 674 deletions

View File

@ -31,8 +31,7 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.js',
'public/js/bootstrap-combobox.js',
'public/vendor/jspdf/dist/jspdf.min.js',
//'public/js/jspdf.source.js',
//'public/js/jspdf.plugin.split_text_to_size.js',
'public/vendor/lightbox2/js/lightbox.min.js',
'public/js/script.js',
],
dest: 'public/built.js',
@ -58,6 +57,7 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',
'public/vendor/lightbox2/css/lightbox.css',
'public/css/style.css',
],
dest: 'public/built.css',

View File

@ -272,7 +272,7 @@ class AccountController extends \BaseController {
$invoice = new stdClass();
$client = new stdClass();
$invoiceItem = new stdClass();
$client->name = 'Sample Client';
$client->address1 = '';
$client->city = '';
@ -280,10 +280,10 @@ class AccountController extends \BaseController {
$client->postal_code = '';
$client->work_phone = '';
$client->work_email = '';
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->account = Auth::user()->account;
$invoice->account = json_decode(Auth::user()->account->toJson());
$invoice->amount = $invoice->balance = 100;
$invoiceItem->cost = 100;
@ -295,7 +295,7 @@ class AccountController extends \BaseController {
$invoice->invoice_items = [$invoiceItem];
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get();
$data['invoiceDesigns'] = InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache')->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
}
return View::make("accounts.{$subSection}", $data);

View File

@ -243,7 +243,7 @@ class InvoiceController extends \BaseController {
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache')->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',

View File

@ -19,16 +19,16 @@ class PaymentController extends \BaseController
$this->contactMailer = $contactMailer;
}
public function index()
{
public function index()
{
return View::make('list', array(
'entityType'=>ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns'=>Utils::trans(['checkbox', 'invoice', 'client', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'action'])
));
}
}
public function getDatatable($clientPublicId = null)
public function getDatatable($clientPublicId = null)
{
$payments = $this->paymentRepo->find($clientPublicId, Input::get('sSearch'));
$table = Datatable::query($payments);
@ -47,7 +47,7 @@ class PaymentController extends \BaseController
->addColumn('payment_type', function($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); });
return $table->addColumn('amount', function($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function($model) { return Utils::dateToString($model->payment_date); })
->addColumn('payment_date', function($model) { return Utils::dateToString($model->payment_date); })
->addColumn('dropdown', function($model)
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
@ -60,7 +60,7 @@ class PaymentController extends \BaseController
</ul>
</div>';
})
->make();
->make();
}
@ -132,13 +132,13 @@ class PaymentController extends \BaseController
return $gateway;
}
private function getLicensePaymentDetails($input)
private function getLicensePaymentDetails($input, $affiliate)
{
$data = self::convertInputForOmnipay($input);
$card = new CreditCard($data);
return [
'amount' => LICENSE_PRICE,
'amount' => $affiliate->price,
'card' => $card,
'currency' => 'USD',
'returnUrl' => URL::to('license_complete'),
@ -172,7 +172,7 @@ class PaymentController extends \BaseController
private function getPaymentDetails($invoice, $input = null)
{
$key = $invoice->invoice_number . '_details';
$gateway = $invoice->client->account->account_gateways[0]->gateway;
$gateway = $invoice->client->account->account_gateways[0]->gateway;
$paymentLibrary = $gateway->paymentlibrary;
if ($input && $paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY)
@ -181,9 +181,9 @@ class PaymentController extends \BaseController
Session::put($key, $data);
}
else if ($input && $paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS)
else if ($input && $paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS)
{
$input = Input::all();
$input = Input::all();
$data = [
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
@ -200,29 +200,29 @@ class PaymentController extends \BaseController
'ship_to_city' => $input['city'],
'ship_to_state' => $input['state'],
'ship_to_postal_code' => $input['postal_code'],
'currency_code' => $invoice->client->currency->code,
'currency_code' => $invoice->client->currency->code,
];
switch($gateway->id)
{
case GATEWAY_BEANSTREAM:
$data['phone'] = $input['phone'];
$data['email'] = $input['email'];
$data['country'] = $input['country'];
$data['ship_to_country'] = $input['country'];
break;
case GATEWAY_BRAINTREE:
$data['ship_to_state'] = 'Ohio'; //$input['state'];
break;
}
if(strlen($data['cc_exp']) == 5)
{
$data['cc_exp'] = '0'.$data['cc_exp'];
}
switch($gateway->id)
{
case GATEWAY_BEANSTREAM:
$data['phone'] = $input['phone'];
$data['email'] = $input['email'];
$data['country'] = $input['country'];
$data['ship_to_country'] = $input['country'];
break;
case GATEWAY_BRAINTREE:
$data['ship_to_state'] = 'Ohio'; //$input['state'];
break;
}
if(strlen($data['cc_exp']) == 5)
{
$data['cc_exp'] = '0'.$data['cc_exp'];
}
Session::put($key, $data);
return $data;
return $data;
}
else if (Session::get($key))
{
@ -233,22 +233,22 @@ class PaymentController extends \BaseController
$data = [];
}
if($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY)
{
$card = new CreditCard($data);
return [
'amount' => $invoice->amount,
'card' => $card,
'currency' => $invoice->client->currency->code,
'returnUrl' => URL::to('complete'),
'cancelUrl' => URL::to('/')
];
}
else
{
return $data;
}
if($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY)
{
$card = new CreditCard($data);
return [
'amount' => $invoice->amount,
'card' => $card,
'currency' => $invoice->client->currency->code,
'returnUrl' => URL::to('complete'),
'cancelUrl' => URL::to('/')
];
}
else
{
return $data;
}
}
public function show_payment($invitationKey)
@ -306,16 +306,18 @@ class PaymentController extends \BaseController
{
if ($affiliate = Affiliate::where('affiliate_key', '=', Input::get('affiliate_key'))->first())
{
Session::set('affiliate_id', $affiliate->id);
Session::set('affiliate_id', $affiliate->id);
}
}
Session::set('product_id', Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL));
if (!Session::get('affiliate_id'))
{
return Utils::fatalError();
}
if (Input::has('test_mode'))
if (Utils::isNinjaDev() && Input::has('test_mode'))
{
Session::set('test_mode', Input::get('test_mode'));
}
@ -334,7 +336,7 @@ class PaymentController extends \BaseController
'showBreadcrumbs' => false,
'hideHeader' => true,
'url' => 'license',
'amount' => LICENSE_PRICE,
'amount' => $affiliate->price,
'client' => false,
'contact' => false,
'paymentLibrary' => $paymentLibrary,
@ -380,6 +382,8 @@ class PaymentController extends \BaseController
try
{
$affiliate = Affiliate::find(Session::get('affiliate_id'));
if ($testMode)
{
$ref = 'TEST_MODE';
@ -387,7 +391,7 @@ class PaymentController extends \BaseController
else
{
$gateway = self::createGateway($accountGateway);
$details = self::getLicensePaymentDetails(Input::all());
$details = self::getLicensePaymentDetails(Input::all(), $affiliate);
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
@ -415,10 +419,9 @@ class PaymentController extends \BaseController
$license->transaction_reference = $ref;
$license->license_key = $licenseKey;
$license->affiliate_id = Session::get('affiliate_id');
$license->product_id = Session::get('product_id');
$license->save();
$affiliate = Affiliate::find(Session::get('affiliate_id'));
$data = [
'message' => $affiliate->payment_subtitle,
'license' => $licenseKey,
@ -426,11 +429,16 @@ class PaymentController extends \BaseController
];
$name = "{$license->first_name} {$license->last_name}";
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, LICENSE_PRICE, $license->license_key);
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
return View::make('public.license', $data);
//return Redirect::away(Session::get('return_url') . "?license_key={$license->license_key}");
if (Session::has('return_url'))
{
return Redirect::away(Session::get('return_url') . "?license_key={$license->license_key}&product_id=" . Session::get('product_id'));
}
else
{
return View::make('public.license', $data);
}
}
catch (\Exception $e)
{
@ -443,8 +451,13 @@ class PaymentController extends \BaseController
public function claim_license()
{
$license = License::where('license_key', '=', Input::get('license_key'))
->where('is_claimed', '=', false)->first();
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '=', false)
->where('product_id', '=', $productId)
->first();
if ($license)
{
@ -454,7 +467,7 @@ class PaymentController extends \BaseController
$license->save();
}
return 'valid';
return $productId == PRODUCT_ONE_CLICK_INSTALL ? 'valid' : $_ENV['INVOICE_DESIGNS'];
}
else
{
@ -503,84 +516,84 @@ class PaymentController extends \BaseController
$client->postal_code = trim(Input::get('postal_code'));
$client->save();
}
try
{
if($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY)
{
$gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invoice, Input::all());
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
if (!$ref)
{
Session::flash('error', $response->getMessage());
return Redirect::to('payment/' . $invitationKey)
->withInput();
}
if($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY)
{
$gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invoice, Input::all());
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
if (!$ref)
{
Session::flash('error', $response->getMessage());
return Redirect::to('payment/' . $invitationKey)
->withInput();
}
if ($response->isSuccessful())
{
$payment = self::createPayment($invitation, $ref);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/' . $payment->invitation->invitation_key);
}
else if ($response->isRedirect())
{
$invitation->transaction_reference = $ref;
$invitation->save();
$response->redirect();
}
else
{
Session::flash('error', $response->getMessage());
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage());
}
if ($response->isSuccessful())
{
$payment = self::createPayment($invitation, $ref);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/' . $payment->invitation->invitation_key);
}
else if ($response->isRedirect())
{
$invitation->transaction_reference = $ref;
$invitation->save();
$response->redirect();
}
else
{
Session::flash('error', $response->getMessage());
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage());
}
}
else if ($paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS)
{
$gateway = $accountGateway->gateway;
$provider = $gateway->provider;
$p = new PHP_Payments(array('mode' => 'test'));
$config = Payment_Utility::load('config', 'drivers/'.$provider);
switch($gateway->id)
{
case GATEWAY_BEANSTREAM:
$config['delay_charge'] = FALSE;
$config['bill_outstanding'] = TRUE;
break;
case GATEWAY_AMAZON:
$config['return_url'] = URL::to('complete');
$config['abandon_url'] = URL::to('/');
$config['immediate_return'] = 0;
$config['process_immediate'] = 1;
$config['ipn_url'] = URL::to('ipn');
$config['collect_shipping_address'] = false;
break;
}
$details = self::getPaymentDetails($invoice, Input::all());
$response = $p->oneoff_payment($provider, $details, $config);
else if ($paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS)
{
$gateway = $accountGateway->gateway;
$provider = $gateway->provider;
$p = new PHP_Payments(array('mode' => 'test'));
$config = Payment_Utility::load('config', 'drivers/'.$provider);
switch($gateway->id)
{
case GATEWAY_BEANSTREAM:
$config['delay_charge'] = FALSE;
$config['bill_outstanding'] = TRUE;
break;
case GATEWAY_AMAZON:
$config['return_url'] = URL::to('complete');
$config['abandon_url'] = URL::to('/');
$config['immediate_return'] = 0;
$config['process_immediate'] = 1;
$config['ipn_url'] = URL::to('ipn');
$config['collect_shipping_address'] = false;
break;
}
$details = self::getPaymentDetails($invoice, Input::all());
$response = $p->oneoff_payment($provider, $details, $config);
if (strtolower($response->status) == 'success')
{
$payment = self::createPayment($invitation, $response->response_message);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/' . $payment->invitation->invitation_key);
}
else
{
Session::flash('error', $response->response_message);
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->response_message);
}
}
if (strtolower($response->status) == 'success')
{
$payment = self::createPayment($invitation, $response->response_message);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/' . $payment->invitation->invitation_key);
}
else
{
Session::flash('error', $response->response_message);
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->response_message);
}
}
}
catch (\Exception $e)
{
@ -715,4 +728,4 @@ class PaymentController extends \BaseController
return Redirect::to('payments');
}
}
}

View File

@ -95,7 +95,7 @@ class QuoteController extends \BaseController {
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache')->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
];
}

View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddAffiliatePrice extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('affiliates', function($table)
{
$table->decimal('price', 7, 2)->nullable();
});
Schema::table('licenses', function($table)
{
$table->unsignedInteger('product_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('affiliates', function($table)
{
$table->dropColumn('price');
});
Schema::table('licenses', function($table)
{
$table->dropColumn('product_id');
});
}
}

View File

@ -77,6 +77,34 @@ App::before(function($request)
$locale = Session::get(SESSION_LOCALE, DEFAULT_LOCALE);
App::setLocale($locale);
}
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id'))
{
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id');
if ($productId == PRODUCT_INVOICE_DESIGNS)
{
//$data = file_get_contents("http://ninja.dev/claim_license?license_key={$licenseKey}&product_id={$productId}");
$data = file_get_contents(NINJA_URL . "/claim_license?license_key={$licenseKey}&product_id={$productId}");
if ($data = json_decode($data))
{
foreach ($data as $item)
{
$design = new InvoiceDesign();
$design->id = $item->id;
$design->name = $item->name;
$design->javascript = $item->javascript;
$design->save();
}
Cache::forget('invoice_designs_cache');
Session::flash('message', trans('texts.bought_designs'));
}
}
}
});

View File

@ -449,4 +449,14 @@ return array(
'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.',
'gateway_help_27' => ':link to sign up for TwoCheckout.',
'more_designs' => 'More designs',
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
);

View File

@ -85,6 +85,11 @@ class User extends ConfideUser implements UserInterface, RemindableInterface
return $this->account->id == Utils::getDemoAccountId();
}
public function maxInvoiceDesignId()
{
return $this->isPro() ? 10 : COUNT_FREE_DESIGNS;
}
public function getDisplayName()
{
if ($this->getFullName())

View File

@ -37,7 +37,7 @@ class ContactMailer extends Mailer {
'contactName' => $invitation->contact->getDisplayName(),
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->currency_id),
'emailFooter' => $invoice->account->email_footer,
'showNinjaFooter' => !$invoice->account->isPro() || !Utils::isNinjaProd()
'showNinjaFooter' => !$invoice->account->isPro()
];
$fromEmail = $invitation->user->email;
@ -67,25 +67,31 @@ class ContactMailer extends Mailer {
'clientName' => $payment->client->getDisplayName(),
'emailFooter' => $payment->account->email_footer,
'paymentAmount' => Utils::formatMoney($payment->amount, $payment->client->currency_id),
'showNinjaFooter' => !$payment->account->isPro() || !Utils::isNinjaProd()
'showNinjaFooter' => !$payment->account->isPro()
];
$user = $payment->invitation->user;
$this->sendTo($payment->contact->email, $user->email, $user->getDisplayName(), $subject, $view, $data);
}
public function sendLicensePaymentConfirmation($name, $email, $amount, $license)
public function sendLicensePaymentConfirmation($name, $email, $amount, $license, $productId)
{
$view = 'payment_confirmation';
$subject = trans('texts.payment_subject');
if ($productId == PRODUCT_ONE_CLICK_INSTALL) {
$message = "Softaculous install license: $license";
} else if ($productId == PRODUCT_INVOICE_DESIGNS) {
$message = "Invoice designs license: $license";
}
$data = [
'accountName' => trans('texts.email_from'),
'clientName' => $name,
'emailFooter' => false,
'paymentAmount' => Utils::formatMoney($amount, 1),
'showNinjaFooter' => false,
'emailMessage' => "Your license: $license",
'emailMessage' => $message,
];
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);

View File

@ -13,7 +13,7 @@
*/
//apc_clear_cache();
//Cache::flush();
Cache::flush();
//dd(DB::getQueryLog());
//dd(Client::getPrivateId(1));
@ -248,8 +248,11 @@ define('NINJA_URL', 'https://www.invoiceninja.com');
define('NINJA_VERSION', '1.4.0');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('COUNT_FREE_DESIGNS', 4);
define('PRO_PLAN_PRICE', 50);
define('LICENSE_PRICE', 30.00);
define('PRODUCT_ONE_CLICK_INSTALL', 1);
define('PRODUCT_INVOICE_DESIGNS', 2);
define('DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');

View File

@ -17,7 +17,13 @@
function getDesignJavascript() {
var id = $('#invoice_design_id').val();
return invoiceDesigns[id-1].javascript;
if (id == '-1') {
showMoreDesigns();
$('#invoice_design_id').val(1);
return invoiceDesigns[0].javascript;
} else {
return invoiceDesigns[id-1].javascript;
}
}
function getPDFString() {
@ -64,8 +70,15 @@
{{ Former::populateField('hide_paid_to_date', intval($account->hide_paid_to_date)) }}
{{ Former::legend('invoice_design') }}
{{ Former::select('invoice_design_id')->style('display:inline;width:120px')
->fromQuery($invoiceDesigns, 'name', 'id') }}
@if (InvoiceDesign::count() == COUNT_FREE_DESIGNS)
{{ Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id')->addOption(trans('texts.more_designs') . '...', '-1') }}
@else
{{ Former::select('invoice_design_id')->style('display:inline;width:120px')->fromQuery($invoiceDesigns, 'name', 'id') }}
@endif
{{ Former::text('primary_color') }}
{{ Former::text('secondary_color') }}

View File

@ -265,11 +265,12 @@
</div>
@if (InvoiceDesign::count() == COUNT_FREE_DESIGNS)
{{ Former::select('invoice_design_id')->style('display:inline;width:150px')->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')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") }}
@endif
{{ Former::select('invoice_design_id')->style('display:inline;width:120px')->raw()
->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") }}
{{ Button::primary(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()'))->append_with_icon('download-alt'); }}
@if (!$invoice || (!$invoice->trashed() && !$invoice->client->trashed()))
@ -646,6 +647,7 @@
function getPDFString() {
var invoice = createInvoiceModel();
var design = getDesignJavascript();
if (!design) return;
var doc = generatePDF(invoice, design);
if (!doc) return;
return doc.output('datauristring');
@ -653,13 +655,21 @@
function getDesignJavascript() {
var id = $('#invoice_design_id').val();
return invoiceDesigns[id-1].javascript;
if (id == '-1') {
showMoreDesigns();
$('#invoice_design_id').val(1);
return invoiceDesigns[0].javascript;
} else {
return invoiceDesigns[id-1].javascript;
}
}
function onDownloadClick() {
trackUrl('/download_pdf');
var invoice = createInvoiceModel();
var doc = generatePDF(invoice, true);
var design = getDesignJavascript();
if (!design) return;
var doc = generatePDF(invoice, design, true);
doc.save('Invoice-' + $('#invoice_number').val() + '.pdf');
}

View File

@ -1,6 +1,54 @@
<iframe id="theFrame" style="display:none" frameborder="1" width="100%" height="{{ isset($pdfHeight) ? $pdfHeight : 1180 }}px"></iframe>
<canvas id="theCanvas" style="display:none;width:100%;border:solid 1px #CCCCCC;"></canvas>
<div class="modal fade" id="moreDesignsModal" tabindex="-1" role="dialog" aria-labelledby="moreDesignsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<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">{{ trans('texts.more_designs_title') }}</h4>
</div>
<div class="container">
@if (Utils::isNinja())
<h3>{{ trans('texts.more_designs_cloud_header') }}</h3>
<p>{{ trans('texts.more_designs_cloud_text') }}</p>
@else
<h3>{{ trans('texts.more_designs_self_host_header') }}</h3>
<p>{{ trans('texts.more_designs_self_host_text') }}</p>
@endif
</div>
<center id="designThumbs">
<p>&nbsp;</p>
<a href="/images/designs/business.png" data-lightbox="more-designs" data-title="Business"><img src="/images/designs/business_thumb.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/images/designs/creative.png" data-lightbox="more-designs" data-title="Creative"><img src="/images/designs/creative_thumb.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/images/designs/elegant.png" data-lightbox="more-designs" data-title="Elegant"><img src="/images/designs/elegant_thumb.png"/></a>
<p>&nbsp;</p>
<a href="/images/designs/hipster.png" data-lightbox="more-designs" data-title="Hipster"><img src="/images/designs/hipster_thumb.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/images/designs/playful.png" data-lightbox="more-designs" data-title="Playful"><img src="/images/designs/playful_thumb.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/images/designs/photo.png" data-lightbox="more-designs" data-title="Photo"><img src="/images/designs/photo_thumb.png"/></a>
<p>&nbsp;</p>
</center>
<div class="modal-footer" id="signUpFooter">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
&nbsp;&nbsp;&nbsp;Coming soon
<!--
@if (Utils::isNinja())
<button type="button" class="btn btn-primary" onclick="submitProPlan()">{{ trans('texts.go_pro') }}</button>
@else
<button type="button" class="btn btn-primary" onclick="buyDesigns()">{{ trans('texts.buy') }}</button>
@endif
-->
</div>
</div>
</div>
</div>
<script type="text/javascript">
window.logoImages = {};
@ -70,4 +118,12 @@
}
}
function showMoreDesigns() {
$('#moreDesignsModal').modal('show');
}
function buyDesigns() {
window.open('{{ NINJA_URL }}/license?return_url=' + window.location + '&affiliate_key={{ DESIGNS_AFFILIATE_KEY }}&product_id={{ PRODUCT_INVOICE_DESIGNS }}');
}
</script>

View File

@ -17,7 +17,8 @@
"typeahead.js": "~0.9.3",
"accounting": "~0.*",
"spectrum": "~1.3.4",
"d3": "~3.4.11"
"d3": "~3.4.11",
"lightbox2": "~2.7.1"
},
"resolutions": {
"jquery": "~1.11"

View File

@ -1773,6 +1773,217 @@ See http://bgrins.github.io/spectrum/themes/ for instructions.
border-radius: 6px;
line-height: 1.33;
}
/* Preload images */
body:after {
content: url(images/lightbox/close.png) url(images/lightbox/loading.gif) url(images/lightbox/prev.png) url(images/lightbox/next.png);
display: none;
}
.lightboxOverlay {
position: absolute;
top: 0;
left: 0;
z-index: 9999;
background-color: black;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
opacity: 0.8;
display: none;
}
.lightbox {
position: absolute;
left: 0;
width: 100%;
z-index: 10000;
text-align: center;
line-height: 0;
font-weight: normal;
}
.lightbox .lb-image {
display: block;
height: auto;
max-width: inherit;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
}
.lightbox a img {
border: none;
}
.lb-outerContainer {
position: relative;
background-color: white;
*zoom: 1;
width: 250px;
height: 250px;
margin: 0 auto;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
}
.lb-outerContainer:after {
content: "";
display: table;
clear: both;
}
.lb-container {
padding: 4px;
}
.lb-loader {
position: absolute;
top: 43%;
left: 0;
height: 25%;
width: 100%;
text-align: center;
line-height: 0;
}
.lb-cancel {
display: block;
width: 32px;
height: 32px;
margin: 0 auto;
background: url(images/lightbox/loading.gif) no-repeat;
}
.lb-nav {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 10;
}
.lb-container > .nav {
left: 0;
}
.lb-nav a {
outline: none;
background-image: url('data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');
}
.lb-prev, .lb-next {
height: 100%;
cursor: pointer;
display: block;
}
.lb-nav a.lb-prev {
width: 34%;
left: 0;
float: left;
background: url(images/lightbox/prev.png) left 48% no-repeat;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
-o-transition: opacity 0.6s;
transition: opacity 0.6s;
}
.lb-nav a.lb-prev:hover {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
.lb-nav a.lb-next {
width: 64%;
right: 0;
float: right;
background: url(images/lightbox/next.png) right 48% no-repeat;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
opacity: 0;
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
-o-transition: opacity 0.6s;
transition: opacity 0.6s;
}
.lb-nav a.lb-next:hover {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
.lb-dataContainer {
margin: 0 auto;
padding-top: 5px;
*zoom: 1;
width: 100%;
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.lb-dataContainer:after {
content: "";
display: table;
clear: both;
}
.lb-data {
padding: 0 4px;
color: #ccc;
}
.lb-data .lb-details {
width: 85%;
float: left;
text-align: left;
line-height: 1.1em;
}
.lb-data .lb-caption {
font-size: 13px;
font-weight: bold;
line-height: 1em;
}
.lb-data .lb-number {
display: block;
clear: left;
padding-bottom: 1em;
font-size: 12px;
color: #999999;
}
.lb-data .lb-close {
display: block;
float: right;
width: 30px;
height: 30px;
background: url(images/lightbox/close.png) top right no-repeat;
text-align: right;
outline: none;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
opacity: 0.7;
-webkit-transition: opacity 0.2s;
-moz-transition: opacity 0.2s;
-o-transition: opacity 0.2s;
transition: opacity 0.2s;
}
.lb-data .lb-close:hover {
cursor: pointer;
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
}
body { background: #f8f8f8 !important;
font-family: 'Roboto', sans-serif;
}
@ -2482,6 +2693,10 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2);
.plans-table a .cta h2 span {background: #1e84a5;}
#designThumbs img {
border: 1px solid #CCCCCC;
}
@media screen and (min-width: 992px) {
.hide-desktop {display: none;}
}

File diff suppressed because one or more lines are too long

View File

@ -707,6 +707,10 @@ box-shadow: 0px 0px 15px 0px rgba(0, 5, 5, 0.2);
.plans-table a .cta h2 span {background: #1e84a5;}
#designThumbs img {
border: 1px solid #CCCCCC;
}
@media screen and (min-width: 992px) {
.hide-desktop {display: none;}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -958,7 +958,6 @@ function getInvoiceTaxRate(invoice) {
return tax;
}
/*
function displayInvoiceHeader(doc, invoice, layout) {
var costX = layout.unitCostRight - (doc.getStringUnitWidth(invoiceLabels.unit_cost) * doc.internal.getFontSize());
@ -966,6 +965,15 @@ function displayInvoiceHeader(doc, invoice, layout) {
var taxX = layout.taxRight - (doc.getStringUnitWidth(invoiceLabels.tax) * doc.internal.getFontSize());
var totalX = layout.lineTotalRight - (doc.getStringUnitWidth(invoiceLabels.line_total) * doc.internal.getFontSize());
if (invoice.invoice_design_id == 6 || invoice.invoice_design_id == 8 || invoice.invoice_design_id == 10) {
invoiceLabels.item = invoiceLabels.item.toUpperCase();
invoiceLabels.description = invoiceLabels.description.toUpperCase();
invoiceLabels.unit_cost = invoiceLabels.unit_cost.toUpperCase();
invoiceLabels.quantity = invoiceLabels.quantity.toUpperCase();
invoiceLabels.line_total = invoiceLabels.total.toUpperCase();
invoiceLabels.tax = invoiceLabels.tax.toUpperCase();
}
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item);
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description);
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost);
@ -978,258 +986,7 @@ function displayInvoiceHeader(doc, invoice, layout) {
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax);
}
}
function displayInvoiceItems(doc, invoice, layout) {
doc.setFontType("normal");
var line = 1;
var total = 0;
var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
var tableTop = layout.tableTop;
var hideQuantity = invoice.account.hide_quantity == '1';
doc.setFontSize(8);
for (var i=0; i<invoice.invoice_items.length; i++) {
var item = invoice.invoice_items[i];
var numLines = doc.splitTextToSize(item.notes, 200).length + 2;
//console.log('num lines %s', numLines);
var y = tableTop + (line * layout.tableRowHeight) + (2 * layout.tablePadding);
var top = y - layout.tablePadding;
var newTop = top + (numLines * layout.tableRowHeight);
if (newTop > 770) {
line = 0;
tableTop = layout.accountTop + layout.tablePadding;
y = tableTop;
top = y - layout.tablePadding;
newTop = top + (numLines * layout.tableRowHeight);
doc.addPage();
}
var left = layout.marginLeft - layout.tablePadding;
var width = layout.marginRight + layout.tablePadding;
var cost = formatMoney(item.cost, currencyId, true);
var qty = NINJA.parseFloat(item.qty) ? NINJA.parseFloat(item.qty) + '' : '';
var notes = item.notes;
var productKey = item.product_key;
var tax = 0;
if (item.tax && parseFloat(item.tax.rate)) {
tax = parseFloat(item.tax.rate);
} else if (item.tax_rate && parseFloat(item.tax_rate)) {
tax = parseFloat(item.tax_rate);
}
// show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// process date variables
notes = processVariables(notes);
productKey = processVariables(productKey);
var lineTotal = NINJA.parseFloat(item.cost) * NINJA.parseFloat(item.qty);
if (tax) {
lineTotal += lineTotal * tax / 100;
}
if (lineTotal) {
total += lineTotal;
}
lineTotal = formatMoney(lineTotal, currencyId);
var costX = layout.unitCostRight - (doc.getStringUnitWidth(cost) * doc.internal.getFontSize());
var qtyX = layout.qtyRight - (doc.getStringUnitWidth(qty) * doc.internal.getFontSize());
var taxX = layout.taxRight - (doc.getStringUnitWidth(tax+'%') * doc.internal.getFontSize());
var totalX = layout.lineTotalRight - (doc.getStringUnitWidth(lineTotal) * doc.internal.getFontSize());
line += numLines;
if (invoice.invoice_design_id == 1) {
if (i%2 == 0) {
doc.setDrawColor(255,255,255);
doc.setFillColor(246,246,246);
doc.rect(left, top, width-left, newTop-top, 'FD');
doc.setLineWidth(0.3);
doc.setDrawColor(200,200,200);
doc.line(left, top, width, top);
doc.line(left, newTop, width, newTop);
}
} else if (invoice.invoice_design_id == 2) {
if (i%2 == 0) {
left = 0;
width = 1000;
doc.setDrawColor(255,255,255);
doc.setFillColor(235,235,235);
doc.rect(left, top, width-left, newTop-top, 'FD');
}
} else if (invoice.invoice_design_id == 5) {
if (i%2 == 0) {
doc.setDrawColor(255,255,255);
doc.setFillColor(247,247,247);
doc.rect(left, top, width-left+17, newTop-top, 'FD');
doc.setLineWidth(0.3);
doc.setDrawColor(255,255,255);
} else {
doc.setDrawColor(255,255,255);
doc.setFillColor(232,232,232);
doc.rect(left, top, width-left+17, newTop-top, 'FD');
doc.setLineWidth(0.3);
doc.setDrawColor(255,255,255);
}
} else {
doc.setLineWidth(0.3);
doc.setDrawColor(150,150,150);
doc.line(left, newTop, width, newTop);
}
y += 4;
if (invoice.invoice_design_id == 1) {
SetPdfColor('LightBlue', doc, 'primary');
} else if (invoice.invoice_design_id == 2) {
SetPdfColor('SomeGreen', doc, 'primary');
} else if (invoice.invoice_design_id == 3) {
doc.setFontType('bold');
} else if (invoice.invoice_design_id == 4) {
SetPdfColor('Black', doc);
} else if (invoice.invoice_design_id == 5) {
SetPdfColor('Black', doc);
}
var splitTitle = doc.splitTextToSize(productKey, 60);
doc.text(layout.marginLeft, y+2, splitTitle);
if (invoice.invoice_design_id == 5) {
doc.setDrawColor(255, 255, 255);
doc.setLineWidth(1);
doc.line(layout.descriptionLeft-8, y-16,layout.descriptionLeft-8, y+55);
doc.setDrawColor(255, 255, 255);
doc.setLineWidth(1);
doc.line(costX-30, y-16,costX-30, y+55);
doc.setDrawColor(255, 255, 255);
doc.setLineWidth(1);
doc.line(qtyX-45, y-16,qtyX-45, y+55);
if (invoice.has_taxes) {
doc.setDrawColor(255, 255, 255);
doc.setLineWidth(1);
doc.line(taxX-15, y-16,taxX-15, y+55);
}
doc.setDrawColor(255, 255, 255);
doc.setLineWidth(1);
doc.line(totalX-27, y-16,totalX-27, y+55);
}
SetPdfColor('Black', doc);
doc.setFontType('normal');
doc.text(layout.descriptionLeft, y+2, notes);
doc.text(costX, y+2, cost);
if (!hideQuantity) {
doc.text(qtyX, y+2, qty);
}
doc.text(totalX, y+2, lineTotal);
if (tax) {
doc.text(taxX, y+2, tax+'%');
}
}
y = tableTop + (line * layout.tableRowHeight) + (3 * layout.tablePadding);
var cutoff = 700;
if (invoice.terms) {
cutoff -= 50;
}
if (invoice.public_notes) {
cutoff -= 50;
}
if (y > cutoff) {
doc.addPage();
return layout.marginLeft;
}
return y;
}
*/
function displayInvoiceHeader(doc, invoice, layout) {
var costX = layout.unitCostRight - (doc.getStringUnitWidth(invoiceLabels.unit_cost) * doc.internal.getFontSize());
var qtyX = layout.qtyRight - (doc.getStringUnitWidth(invoiceLabels.quantity) * doc.internal.getFontSize());
var taxX = layout.taxRight - (doc.getStringUnitWidth(invoiceLabels.tax) * doc.internal.getFontSize());
var totalX = layout.lineTotalRight - (doc.getStringUnitWidth(invoiceLabels.line_total) * doc.internal.getFontSize());
if(invoice.invoice_design_id == 6) {
doc.setFontType('normal');
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item.toUpperCase());
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description.toUpperCase());
doc.text(costX, layout.tableTop, ' UNIT');
if (invoice.account.hide_quantity != '1') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity.toUpperCase());
}
doc.text(totalX, layout.tableTop, ' TOTAL');
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax.toUpperCase());
}
} else if(invoice.invoice_design_id == 8) {
doc.setFontType('bold');
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item.toUpperCase());
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description.toUpperCase());
doc.text(costX, layout.tableTop, ' UNIT');
if (invoice.account.hide_quantity != '1') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity.toUpperCase());
}
doc.text(totalX, layout.tableTop, ' TOTAL');
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax.toUpperCase());
}
} else if(invoice.invoice_design_id == 10) {
doc.setFontType('bold');
doc.setTextColor(63,60,60);
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item.toUpperCase());
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description.toUpperCase());
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost.toUpperCase());
if (invoice.account.hide_quantity != '1') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity.toUpperCase());
}
doc.text(totalX, layout.tableTop, invoiceLabels.line_total.toUpperCase());
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax.toUpperCase());
}
} else {
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item);
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description);
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost);
if (invoice.account.hide_quantity != '1') {
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity);
}
doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, invoiceLabels.tax);
}
}
}
function displayInvoiceItems(doc, invoice, layout) {
@ -1410,9 +1167,11 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.line(costX-30, y-16,costX-30, y+55);
doc.line(qtyX-45, y-16,qtyX-45, y+55);
doc.line(taxX-15, y-16,taxX-15, y+55);
if (invoice.has_taxes) {
doc.line(taxX-15, y-16,taxX-15, y+55);
}
doc.line(totalX-27, y-16,totalX-27, y+55);
}
@ -1427,9 +1186,11 @@ function displayInvoiceItems(doc, invoice, layout) {
doc.line(costX-30, y-60,costX-30, y+20);
doc.line(qtyX-45, y-60,qtyX-45, y+20);
doc.line(taxX-15, y-60,taxX-15, y+20);
if (invoice.has_taxes) {
doc.line(taxX-10, y-60,taxX-10, y+20);
}
doc.line(totalX-27, y-60,totalX-27, y+120);
doc.line(totalX+35, y-60,totalX+35, y+120);