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

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

This commit is contained in:
Hillel Coren 2016-11-15 21:28:34 +02:00
commit 3680985a20
23 changed files with 527 additions and 17 deletions

View File

@ -81,3 +81,7 @@ WEPAY_FEE_PAYER=payee
WEPAY_APP_FEE_MULTIPLIER=0.002
WEPAY_APP_FEE_FIXED=0
WEPAY_THEME='{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}' # See https://www.wepay.com/developer/reference/structures#theme
BLUEVINE_PARTNER_UNIQUE_ID=
BLUEVINE_PARTNER_TOKEN=

View File

@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers;
use Auth;
use Input;
use Redirect;
use URL;
use Session;
class BlueVineController extends BaseController {
public function signup() {
$user = Auth::user();
$data = array(
'personal_user_full_name' => Input::get( 'name' ),
'business_phone_number' => Input::get( 'phone' ),
'email' => Input::get( 'email' ),
'personal_fico_score' => intval( Input::get( 'fico_score' ) ),
'business_annual_revenue' => intval( Input::get( 'annual_revenue' ) ),
'business_monthly_average_bank_balance' => intval( Input::get( 'average_bank_balance' ) ),
'business_inception_date' => date( 'Y-m-d', strtotime( Input::get( 'business_inception' ) ) ),
'partner_internal_business_id' => 'ninja_account_' . $user->account_id,
);
if ( ! empty( Input::get( 'quote_type_factoring' ) ) ) {
$data['invoice_factoring_offer'] = true;
$data['desired_credit_line'] = intval( Input::get( 'desired_credit_limit' )['invoice_factoring'] );
}
if ( ! empty( Input::get( 'quote_type_loc' ) ) ) {
$data['line_of_credit_offer'] = true;
$data['desired_credit_line_for_loc'] = intval( Input::get( 'desired_credit_limit' )['line_of_credit'] );
}
$api_client = new \GuzzleHttp\Client();
try {
$response = $api_client->request( 'POST',
'https://app.bluevine.com/api/v1/user/register_external?' . http_build_query( array(
'external_register_token' => env( 'BLUEVINE_PARTNER_TOKEN' ),
'c' => env( 'BLUEVINE_PARTNER_UNIQUE_ID' ),
'signup_parent_url' => URL::to( '/bluevine/completed' ),
) ), array(
'json' => $data
)
);
} catch ( \GuzzleHttp\Exception\RequestException $ex ) {
if ( $ex->getCode() == 403 ) {
$response_body = $ex->getResponse()->getBody( true );
$response_data = json_decode( $response_body );
return response()->json( [
'error' => true,
'message' => $response_data->reason
] );
} else {
return response()->json( [
'error' => true
] );
}
}
$user->account->bluevine_status = 'signed_up';
$user->account->save();
$quote_data = json_decode( $response->getBody() );
return response()->json( $quote_data );
}
public function hideMessage() {
$user = Auth::user();
if ( $user ) {
$user->account->bluevine_status = 'ignored';
$user->account->save();
}
return 'success';
}
public function handleCompleted() {
Session::flash( 'message', trans( 'texts.bluevine_completed' ) );
return Redirect::to( '/dashboard' );
}
}

View File

@ -42,6 +42,7 @@ class DashboardController extends BaseController
$payments = $dashboardRepo->payments($accountId, $userId, $viewAll);
$expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll);
$tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll);
$showBlueVinePromo = !$account->bluevine_status && env('BLUEVINE_PARTNER_UNIQUE_ID');
// check if the account has quotes
$hasQuotes = false;
@ -75,6 +76,7 @@ class DashboardController extends BaseController
$data = [
'account' => $user->account,
'user' => $user,
'paidToDate' => $paidToDate,
'balances' => $balances,
'averageInvoice' => $averageInvoice,
@ -90,8 +92,29 @@ class DashboardController extends BaseController
'currencies' => $currencies,
'expenses' => $expenses,
'tasks' => $tasks,
'showBlueVinePromo' => $showBlueVinePromo,
];
if($showBlueVinePromo){
$usdLast12Months = 0;
$paidLast12Months = $dashboardRepo->paidLast12Months( $account, $userId, $viewAll );
foreach ( $paidLast12Months as $item ) {
if ( $item->currency_id == null ) {
$currency = $user->account->currency_id ?: DEFAULT_CURRENCY;
} else {
$currency = $item->currency_id;
}
if ( $currency == CURRENCY_DOLLAR ) {
$usdLast12Months += $item->value;
}
}
$data['usdLast12Months'] = $usdLast12Months;
}
return View::make('dashboard', $data);
}

View File

@ -215,6 +215,11 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::put('expense_categories/{expense_categories}', 'ExpenseCategoryController@update');
Route::get('expense_categories/{expense_categories}/edit', 'ExpenseCategoryController@edit');
Route::post('expense_categories/bulk', 'ExpenseCategoryController@bulk');
// BlueVine
Route::post('bluevine/signup', 'BlueVineController@signup');
Route::get('bluevine/hide_message', 'BlueVineController@hideMessage');
Route::get('bluevine/completed', 'BlueVineController@handleCompleted');
});
Route::group([

View File

@ -211,6 +211,34 @@ class DashboardRepository
->get();
}
public function paidLast12Months($account, $userId, $viewAll)
{
$accountId = $account->id;
$select = DB::raw(
'SUM('.DB::getQueryGrammar()->wrap('payments.amount', true).' - '.DB::getQueryGrammar()->wrap('payments.refunded', true).') as value,'
.DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
);
$paidLast12Months = DB::table('payments')
->select($select)
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
->where('payments.account_id', '=', $accountId)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('payments.is_deleted', '=', false)
->whereNotIn('payments.payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
if (!$viewAll){
$paidLast12Months->where('invoices.user_id', '=', $userId);
}
$paidLast12Months->where( 'payments.payment_date', '>=', date( 'Y-m-d', strtotime( '-1 year' ) ) );
return $paidLast12Months->groupBy('payments.account_id')
->groupBy(DB::raw('CASE WHEN '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' IS NULL THEN '.($account->currency_id ?: DEFAULT_CURRENCY).' ELSE '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' END'))
->get();
}
public function averages($account, $userId, $viewAll)
{
$accountId = $account->id;

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddBlueVineFields extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up() {
Schema::table( 'accounts', function ( $table ) {
$table->enum( 'bluevine_status', array( 'ignored', 'signed_up' ) )->nullable();
} );
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down() {
Schema::table( 'accounts', function ( $table ) {
$table->dropColumn( 'bluevine_status' );
} );
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1225,3 +1225,17 @@ div.panel-body div.panel-body {
vertical-align: middle;
margin-left: 16px;
}
/*
* BlueVine Promo
*/
@media (min-width: 900px) {
#bluevineModal .modal-dialog{
width:800px;
}
}
.bluevine-quote .row{
text-align: right;
}

View File

@ -2191,6 +2191,38 @@ $LANG = array(
'authorization' => 'Authorization',
'signed' => 'Signed',
// BlueVine
'bluevine_promo' => 'Get flexible business lines of credit and invoice factoring using BlueVine.',
'bluevine_modal_label' => 'Sign up with BlueVine',
'bluevine_modal_text' => '<h3>Fast funding for your business. No paperwork.</h3>
<ul><li>Flexible business lines of credit and invoice factoring.</li>
<li>Credit lines from $5,000 to $2M. Cash in as fast as 1 day.</li></ul>',
'bluevine_create_account' => 'Create an account',
'quote_types' => 'Get a quote for',
'invoice_factoring' => 'Invoice factoring',
'line_of_credit' => 'Line of credit',
'fico_score' => 'Your FICO score',
'business_inception' => 'Business Inception Date',
'average_bank_balance' => 'Average bank account balance',
'annual_revenue' => 'Annual revenue',
'desired_credit_limit_factoring' => 'Desired invoice factoring limit',
'desired_credit_limit_loc' => 'Desired line of credit limit',
'desired_credit_limit' => 'Desired credit limit',
'bluevine_credit_line_type_required' => 'You must choose at least one',
'bluevine_field_required' => 'This field is required',
'bluevine_unexpected_error' => 'An unexpected error occurred.',
'bluevine_no_conditional_offer' => 'More information is required before getting a quote. Click continue below.',
'bluevine_invoice_factoring' => 'Invoice Factoring',
'bluevine_conditional_offer' => 'Conditional Offer',
'bluevine_credit_line_amount' => 'Credit Line',
'bluevine_advance_rate' => 'Advance Rate',
'bluevine_weekly_discount_rate' => 'Weekly Discount Rate',
'bluevine_minimum_fee_rate' => 'Minimum Fee',
'bluevine_line_of_credit' => 'Line of Credit',
'bluevine_interest_Rate' => 'Interest Rate',
'bluevine_weekly_draw_rate' => 'Weekly Draw Rate',
'bluevine_continue' => 'Continue to BlueVine',
'bluevine_completed' => 'BlueVine signup completed',
);
return $LANG;

View File

@ -194,7 +194,9 @@
@endif
</div>
</div>
@if($showBlueVinePromo)
@include('partials/bluevine_promo')
@endif
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">

View File

@ -0,0 +1,286 @@
<div class="alert alert-info" id="bluevinePromo">
{{ trans('texts.bluevine_promo') }}
<a href="#" onclick="showBlueVineModal()"
class="btn btn-primary btn-sm">{{ trans('texts.learn_more') }}</a>
<a href="#" onclick="hideBlueVineMessage()" class="pull-right">{{ trans('texts.hide') }}</a>
</div>
<div class="modal fade" id="bluevineModal" tabindex="-1" role="dialog" aria-labelledby="bluevineModalLabel"
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"
id="bluevineModalLabel">{{ trans('texts.bluevine_modal_label') }}</h4>
</div>
<div class="modal-body">
{!! Former::open('/bluevine/signup')->id('bluevineSignup') !!}
{!! trans('texts.bluevine_modal_text') !!}
<h3>{!! trans('texts.bluevine_create_account') !!}</h3>
{!! Former::text('name')->id('bluevine_name')->placeholder(trans('texts.name'))->value($user->first_name . ' ' . $user->last_name)->required() !!}
{!! Former::text('email')->id('bluevine_email')->placeholder(trans('texts.email'))->value($user->email)->required() !!}
{!! Former::text('phone')->id('bluevine_phone')->placeholder(trans('texts.phone'))->value(!empty($user->phone) ? $user->phone : '')->required() !!}
{!! Former::number('fico_score')->min(300)->max(850)->placeholder(trans('texts.fico_score'))->required() !!}
{!! Former::text('business_inception')->append('<span class="glyphicon glyphicon-calendar"></span>')->placeholder(trans('texts.business_inception'))->required() !!}
{!! Former::number('annual_revenue')->prepend('$')->append('.00')->placeholder(trans('texts.annual_revenue'))->value(floor($usdLast12Months))->required() !!}
{!! Former::number('average_bank_balance')->prepend('$')->append('.00')->placeholder(trans('texts.average_bank_balance'))->required() !!}
{!! Former::checkboxes('quote_types')
->onchange('bluevineQuoteTypesChanged()')
->required()
->checkboxes([
trans('texts.invoice_factoring') => ['value' => 'invoice_factoring', 'name' => 'quote_type_factoring', 'id'=>'quote_type_factoring'],
trans('texts.line_of_credit') => ['value' => 'line_of_credit', 'name' => 'quote_type_loc', 'id'=>'quote_type_loc'],
]) !!}
{!! Former::number('desired_credit_limit_factoring')
->id('desired_credit_limit_factoring')
->name('desired_credit_limit[invoice_factoring]')
->prepend('$')->append('.00')
->value(5000)
->required()
->placeholder(trans('texts.desired_credit_limit'))
->label(trans('texts.desired_credit_limit_factoring'))!!}
{!! Former::number('desired_credit_limit_loc')
->id('desired_credit_limit_loc')
->name('desired_credit_limit[line_of_credit]')
->prepend('$')->append('.00')
->value(5000)
->required()
->placeholder(trans('texts.desired_credit_limit'))
->label(trans('texts.desired_credit_limit_loc'))!!}
{!! Former::close() !!}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">{{ trans('texts.cancel') }}</button>
<button type="button" class="btn btn-primary"
onclick="bluevineCreateAccount()">{{ trans('texts.sign_up') }}</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
function hideBlueVineMessage() {
jQuery('#bluevinePromo').fadeOut();
$.get('/bluevine/hide_message', function(response) {
console.log('Reponse: %s', response);
});
return false;
}
function showBlueVineModal() {
jQuery('#bluevineModal').modal('show');
return false;
}
function bluevineQuoteTypesChanged() {
if (jQuery('#quote_type_loc').is(':checked')) {
jQuery('#desired_credit_limit_loc').attr('required', 'required').closest('.form-group').show();
} else {
jQuery('#desired_credit_limit_loc').removeAttr('required').closest('.form-group').hide();
}
if (jQuery('#quote_type_factoring').is(':checked')) {
jQuery('#desired_credit_limit_factoring').attr('required', 'required').closest('.form-group').show();
} else {
jQuery('#desired_credit_limit_factoring').removeAttr('required').closest('.form-group').hide();
}
}
bluevineQuoteTypesChanged();
jQuery('#bluevineSignup').on('submit', function (e) {
e.preventDefault();
bluevineCreateAccount();
});
jQuery('#business_inception').datepicker().siblings('.input-group-addon').click(function () {
jQuery('#business_inception').focus();
});
function bluevineCreateAccount() {
var form = $('#bluevineSignup');
$('#bluevineModal').find('.alert').remove();
var fields = [
'bluevine_name',
'bluevine_email',
'bluevine_phone',
'fico_score',
'business_inception',
'annual_revenue',
'average_bank_balance'
];
var hasError = false;
var requestFactoring = jQuery('#quote_type_factoring').is(':checked');
var requestLoc = jQuery('#quote_type_loc').is(':checked');
var quoteTypeFormGroup = jQuery('#quote_type_factoring').closest('.form-group');
if (!requestFactoring && !requestLoc) {
hasError = true;
if (!quoteTypeFormGroup.hasClass('has-error')) {
quoteTypeFormGroup.addClass('has-error');
quoteTypeFormGroup.children('div').append(
jQuery('<div class="help-block error-help-block">').text("{{ trans('texts.bluevine_credit_line_type_required') }}")
);
}
} else {
quoteTypeFormGroup.removeClass('has-error').find('.error-help-block').remove();
}
if (requestFactoring) {
fields.push('desired_credit_limit_factoring')
}
if (requestLoc) {
fields.push('desired_credit_limit_loc')
}
$.each(fields, function (i, fieldId) {
var field = $('#' + fieldId);
var formGroup = field.closest('.form-group');
if (!field.val()) {
if (!formGroup.hasClass('has-error')) {
formGroup.addClass('has-error');
formGroup.children('div').append(
jQuery('<div class="help-block error-help-block">').text("{{ trans('texts.bluevine_field_required') }}")
);
}
hasError = true;
} else {
formGroup.removeClass('has-error').find('.error-help-block').remove();
}
});
if (hasError) {
return;
}
$('#bluevineModal .btn-primary').attr('disabled', 'disabled');
$.post(form.attr('action'), form.serialize(), function (data) {
if (!data.error) {
$('#bluevineSignup').hide();
var factoringOffer, locOffer;
if (data.factoring_offer)factoringOffer = data.factoring_offer;
else if (data.invoice_factoring_offer)factoringOffer = data;
if (data.loc_offer)locOffer = data.loc_offer;
else if (data.line_of_credit_offer)locOffer = data;
var hasOffer, redirectUrl;
if (!hasOffer && factoringOffer) {
hasOffer = factoringOffer.is_conditional_offer;
redirectUrl = factoringOffer.external_register_url;
}
if (!hasOffer && locOffer) {
hasOffer = locOffer.is_conditional_offer;
redirectUrl = locOffer.external_register_url;
}
if (!hasOffer) {
window.location.href = redirectUrl;
} else {
if (factoringOffer) {
var quoteDetails = jQuery('<div class="bluevine-quote">');
if (factoringOffer.is_conditional_offer) {
quoteDetails.append(jQuery('<h4>').text("{{ trans('texts.bluevine_conditional_offer') }}"));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_credit_line_amount') }}"),
jQuery('<div class="col-sm-2">').text(('$' + factoringOffer.credit_line_amount).replace(/(\d)(?=(\d{3})+$)/g, '$1,'))// Add commas to number
));
// Docs claim that advance_rate is a percent from 0 to 100 without fraction,
// but in my testing the number was a percent from 0 to 1.
var advanceRate = factoringOffer.advance_rate > 1 ? factoringOffer.advance_rate : factoringOffer.advance_rate * 100;
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_advance_rate') }}"),
jQuery('<div class="col-sm-2">').text(advanceRate + '%')
));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_weekly_discount_rate') }}"),
jQuery('<div class="col-sm-2">').text(factoringOffer.weekly_discount_rate + '%')
));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_minimum_fee_rate') }}"),
jQuery('<div class="col-sm-2">').text(factoringOffer.minimum_fee_rate + '%')
));
} else {
quoteDetails.append(jQuery('<p>').text("{{trans('texts.bluevine_no_conditional_offer')}}"));
}
$('#bluevineModal .modal-body').append(
jQuery('<h3>').text("{{ trans('texts.bluevine_invoice_factoring') }}"),
quoteDetails
);
}
if (locOffer) {
var quoteDetails = jQuery('<div class="bluevine-quote">');
if (locOffer.is_conditional_offer) {
quoteDetails.append(jQuery('<h4>').text("{{ trans('texts.bluevine_conditional_offer') }}"));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_credit_line_amount') }}"),
jQuery('<div class="col-sm-2">').text(('$' + locOffer.credit_line_amount).replace(/(\d)(?=(\d{3})+$)/g, '$1,'))// Add commas to number
));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_interest_rate') }}"),
jQuery('<div class="col-sm-2">').text(locOffer.interest_rate + '%')
));
quoteDetails.append(jQuery('<div class="row">').append(
jQuery('<strong class="col-sm-3">').text("{{ trans('texts.bluevine_weekly_draw_rate') }}"),
jQuery('<div class="col-sm-2">').text(locOffer.weekly_draw_rate + '%')
));
} else {
quoteDetails.append(jQuery('<p>').text("{{trans('texts.bluevine_no_conditional_offer')}}"));
}
$('#bluevineModal .modal-body').append(
jQuery('<h3>').text("{{ trans('texts.bluevine_line_of_credit') }}"),
quoteDetails
);
}
/*<div class="row"><strong class="col-sm-4">Credit Line Amount</strong> <div class="col-sm-2">$60,000</div>
</div>
<div class="row">
<strong class="col-sm-4">Advance Rate</strong> <div class="col-sm-4">90%</div>
</div>
<div class="row">
<strong class="col-sm-4">Weekly Discount Rate</strong> <div class="col-sm-2">0.8%</div>
</div>
<div class="row">
<strong class="col-sm-4">Minimum Rate</strong> <div class="col-sm-2">1.5%</div>
</div>*/
}
$('#bluevineModal .btn-primary').replaceWith(
jQuery('<a class="btn btn-primary">').attr('href', redirectUrl).text("{{ trans('texts.bluevine_continue') }}")
)
} else {
$('#bluevineModal .modal-body').append(
jQuery('<div class="alert alert-danger">').text(data.message ? data.message : "{{ trans('texts.bluevine_unexpected_error') }}")
);
}
$('#bluevineModal .btn-primary').removeAttr('disabled');
}, 'json').error(
function () {
$('#bluevineModal .modal-body').append(
jQuery('<div class="alert alert-danger">').text("{{ trans('texts.bluevine_unexpected_error') }}")
);
$('#bluevineModal .btn-primary').removeAttr('disabled');
}
);
}
</script>