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

This commit is contained in:
David Bomba 2016-11-03 19:40:25 +11:00
commit 293811d5ca
30 changed files with 262 additions and 205 deletions

View File

@ -10,6 +10,8 @@
## [Hosted](https://www.invoiceninja.com) | [Self-hosted](https://www.invoiceninja.org)
**We're starting work on custom modules, join the discussion [here](https://github.com/invoiceninja/invoiceninja/issues/1131).**
All Pro and Enterprise features from our hosted app are included in both the [self-host zip](https://www.invoiceninja.com/self-host/) and the GitHub repository.
Our [iPhone app](https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1072566815&mt=8) is now available, our Android app is up next...

View File

@ -429,6 +429,7 @@ class AccountController extends BaseController
'currencies' => Cache::get('currencies'),
'title' => trans('texts.localization'),
'weekdays' => Utils::getTranslatedWeekdayNames(),
'months' => Utils::getMonthOptions(),
];
return View::make('accounts.localization', $data);
@ -674,11 +675,9 @@ class AccountController extends BaseController
* @param $section
* @return \Illuminate\Http\RedirectResponse
*/
public function doSection($section = ACCOUNT_COMPANY_DETAILS)
public function doSection($section)
{
if ($section === ACCOUNT_COMPANY_DETAILS) {
return AccountController::saveDetails();
} elseif ($section === ACCOUNT_LOCALIZATION) {
if ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveLocalization();
} elseif ($section == ACCOUNT_PAYMENTS) {
return self::saveOnlinePayments();
@ -1225,6 +1224,7 @@ class AccountController extends BaseController
$account->military_time = Input::get('military_time') ? true : false;
$account->show_currency_code = Input::get('show_currency_code') ? true : false;
$account->start_of_week = Input::get('start_of_week') ? Input::get('start_of_week') : 0;
$account->financial_year_start = Input::get('financial_year_start') ? Input::get('financial_year_start') : null;
$account->save();
event(new UserSettingsChanged());

View File

@ -211,6 +211,8 @@ class ClientPortalController extends BaseController
$client = $contact->client;
$account = $client->account;
$account->loadLocalizationSettings($client);
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$customer = false;
@ -277,6 +279,7 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
@ -304,6 +307,7 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
@ -350,12 +354,14 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'account' => $account,
@ -420,12 +426,14 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'account' => $account,
@ -455,12 +463,14 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'account' => $account,
@ -489,12 +499,14 @@ class ClientPortalController extends BaseController
}
$account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'account' => $account,

View File

@ -23,8 +23,8 @@ class DashboardApiController extends BaseAPIController
$dashboardRepo = $this->dashboardRepo;
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
$paidToDate = $dashboardRepo->paidToDate($accountId, $userId, $viewAll);
$averageInvoice = $dashboardRepo->averages($accountId, $userId, $viewAll);
$paidToDate = $dashboardRepo->paidToDate($account, $userId, $viewAll);
$averageInvoice = $dashboardRepo->averages($account, $userId, $viewAll);
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
$activities = $dashboardRepo->activities($accountId, $userId, $viewAll);
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);

View File

@ -33,9 +33,9 @@ class DashboardController extends BaseController
$dashboardRepo = $this->dashboardRepo;
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
$paidToDate = $dashboardRepo->paidToDate($accountId, $userId, $viewAll);
$averageInvoice = $dashboardRepo->averages($accountId, $userId, $viewAll);
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
$paidToDate = $dashboardRepo->paidToDate($account, $userId, $viewAll);
$averageInvoice = $dashboardRepo->averages($account, $userId, $viewAll);
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
$activities = $dashboardRepo->activities($accountId, $userId, $viewAll);
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
$upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll);

View File

@ -70,11 +70,13 @@ class OnlinePaymentController extends BaseController
]);
}
if ( ! floatval($invitation->invoice->balance)) {
if ( ! $invitation->invoice->canBePaid()) {
return redirect()->to('view/' . $invitation->invitation_key);
}
$invitation = $invitation->load('invoice.client.account.account_gateways.gateway');
$account = $invitation->account;
$account->loadLocalizationSettings($invitation->invoice->client);
if ( ! $gatewayTypeAlias) {
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
@ -84,7 +86,7 @@ class OnlinePaymentController extends BaseController
$gatewayTypeId = $gatewayTypeAlias;
}
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
$paymentDriver = $account->paymentDriver($invitation, $gatewayTypeId);
try {
return $paymentDriver->startPurchase(Input::all(), $sourceId);
@ -103,6 +105,10 @@ class OnlinePaymentController extends BaseController
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
if ( ! $invitation->invoice->canBePaid()) {
return redirect()->to('view/' . $invitation->invitation_key);
}
try {
$paymentDriver->completeOnsitePurchase($request->all());

View File

@ -18,7 +18,6 @@ class Kernel extends HttpKernel {
'App\Http\Middleware\VerifyCsrfToken',
'App\Http\Middleware\DuplicateSubmissionCheck',
'App\Http\Middleware\QueryLogging',
'App\Http\Middleware\SessionDataCheckMiddleware',
'App\Http\Middleware\StartupCheck',
];

View File

@ -1,31 +0,0 @@
<?php namespace App\Http\Middleware;
use Closure;
use Auth;
use Session;
// https://arjunphp.com/laravel5-inactivity-idle-session-logout/
class SessionDataCheckMiddleware {
/**
* Check session data, if role is not valid logout the request
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$bag = Session::getMetadataBag();
$max = env('IDLE_TIMEOUT_MINUTES', 6 * 60) * 60; // minute to second conversion
$elapsed = time() - $bag->getLastUsed();
if ( ! $bag || $elapsed > $max) {
$request->session()->flush();
Auth::logout();
$request->session()->flash('warning', trans('texts.inactive_logout'));
}
return $next($request);
}
}

View File

@ -13,7 +13,7 @@ use Event;
use Schema;
use App\Models\Language;
use App\Models\InvoiceDesign;
use App\Events\UserSettingsChanged;
use App\Events\UserLoggedIn;
use App\Libraries\CurlUtils;
/**
@ -118,7 +118,7 @@ class StartupCheck
// Make sure the account/user localization settings are in the session
if (Auth::check() && !Session::has(SESSION_TIMEZONE)) {
Event::fire(new UserSettingsChanged());
Event::fire(new UserLoggedIn());
}
// Check if the user is claiming a license (ie, additional invoices, white label, etc.)

View File

@ -1,21 +1,21 @@
<?php
class parseCSV {
/*
Class: parseCSV v0.3.2
http://code.google.com/p/parsecsv-for-php/
Fully conforms to the specifications lined out on wikipedia:
- http://en.wikipedia.org/wiki/Comma-separated_values
Based on the concept of Ming Hong Ng's CsvFileParser class:
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
Copyright (c) 2007 Jim Myhrberg (jim@zydev.info).
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -35,9 +35,9 @@ class parseCSV {
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Code Examples
----------------
# general usage
@ -74,7 +74,7 @@ class parseCSV {
$csv = new parseCSV();
$csv->output (true, 'movies.csv', $array);
----------------
*/
@ -83,87 +83,87 @@ class parseCSV {
* Configuration
* - set these options with $object->var_name = 'value';
*/
# use first line/entry as field names
var $heading = true;
# override field names
var $fields = [];
# sort entries by this field
var $sort_by = null;
var $sort_reverse = false;
# delimiter (comma) and enclosure (double quote)
var $delimiter = ',';
var $enclosure = '"';
# basic SQL-like conditions for row matching
var $conditions = null;
# number of rows to ignore from beginning of data
var $offset = null;
# limits the number of returned rows to specified amount
var $limit = null;
# number of rows to analyze when attempting to auto-detect delimiter
var $auto_depth = 15;
# characters to ignore when attempting to auto-detect delimiter
var $auto_non_chars = "a-zA-Z0-9\n\r";
# preferred delimiter characters, only used when all filtering method
# returns multiple possible delimiters (happens very rarely)
var $auto_preferred = ",;\t.:|";
# character encoding options
var $convert_encoding = false;
var $input_encoding = 'ISO-8859-1';
var $output_encoding = 'ISO-8859-1';
# used by unparse(), save(), and output() functions
var $linefeed = "\r\n";
# only used by output() function
var $output_delimiter = ',';
var $output_filename = 'data.csv';
/**
* Internal variables
*/
# current file
var $file;
# loaded file contents
var $file_data;
# array of field values in data parsed
var $titles = [];
# two dimentional array of CSV data
var $data = [];
/**
* Constructor
* @param input CSV file or string
* @return nothing
*/
function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) {
function __construct ($input = null, $offset = null, $limit = null, $conditions = null) {
if ( $offset !== null ) $this->offset = $offset;
if ( $limit !== null ) $this->limit = $limit;
if ( count($conditions) > 0 ) $this->conditions = $conditions;
if ( !empty($input) ) $this->parse($input);
}
// ==============================================
// ----- [ Main Functions ] ---------------------
// ==============================================
/**
* Parse CSV file or string
* @param input CSV file or string
@ -184,7 +184,7 @@ class parseCSV {
}
return true;
}
/**
* Save changes, or new file and/or data
* @param file file to save to
@ -199,7 +199,7 @@ class parseCSV {
$is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ;
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
}
/**
* Generate CSV based string for output
* @param output if true, prints headers and strings to browser
@ -220,7 +220,7 @@ class parseCSV {
}
return $data;
}
/**
* Convert character encoding
* @param input input character encoding, uses default if left blank
@ -232,7 +232,7 @@ class parseCSV {
if ( $input !== null ) $this->input_encoding = $input;
if ( $output !== null ) $this->output_encoding = $output;
}
/**
* Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
* rows to determine most probable delimiter character
@ -244,13 +244,13 @@ class parseCSV {
* @return delimiter character
*/
function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
if ( $file === null ) $file = $this->file;
if ( empty($search_depth) ) $search_depth = $this->auto_depth;
if ( $enclosure === null ) $enclosure = $this->enclosure;
if ( $preferred === null ) $preferred = $this->auto_preferred;
if ( empty($this->file_data) ) {
if ( $this->_check_data($file) ) {
$data = &$this->file_data;
@ -258,24 +258,24 @@ class parseCSV {
} else {
$data = &$this->file_data;
}
$chars = [];
$strlen = strlen($data);
$enclosed = false;
$n = 1;
$to_end = true;
// walk specific depth finding posssible delimiter characters
for ( $i=0; $i < $strlen; $i++ ) {
$ch = $data{$i};
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
// open and closing quotes
if ( $ch == $enclosure && (!$enclosed || $nch != $enclosure) ) {
$enclosed = ( $enclosed ) ? false : true ;
// inline quotes
// inline quotes
} elseif ( $ch == $enclosure && $enclosed ) {
$i++;
@ -287,7 +287,7 @@ class parseCSV {
} else {
$n++;
}
// count character
} elseif (!$enclosed) {
if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) {
@ -299,7 +299,7 @@ class parseCSV {
}
}
}
// filtering
$depth = ( $to_end ) ? $n-1 : $n ;
$filtered = [];
@ -308,24 +308,24 @@ class parseCSV {
$filtered[$match] = $char;
}
}
// capture most probable delimiter
ksort($filtered);
$delimiter = reset($filtered);
$this->delimiter = $delimiter;
// parse data
if ( $parse ) $this->data = $this->parse_string();
return $delimiter;
}
// ==============================================
// ----- [ Core Functions ] ---------------------
// ==============================================
/**
* Read file to string and call parse_string()
* @param file local CSV file
@ -336,7 +336,7 @@ class parseCSV {
if ( empty($this->file_data) ) $this->load_data($file);
return ( !empty($this->file_data) ) ? $this->parse_string() : false ;
}
/**
* Parse CSV strings to arrays
* @param data CSV string
@ -348,7 +348,7 @@ class parseCSV {
$data = &$this->file_data;
} else return false;
}
$rows = [];
$row = [];
$row_count = 0;
@ -358,19 +358,19 @@ class parseCSV {
$enclosed = false;
$was_enclosed = false;
$strlen = strlen($data);
// walk through each character
for ( $i=0; $i < $strlen; $i++ ) {
$ch = $data{$i};
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
// open and closing quotes
if ( $ch == $this->enclosure && (!$enclosed || $nch != $this->enclosure) ) {
$enclosed = ( $enclosed ) ? false : true ;
if ( $enclosed ) $was_enclosed = true;
// inline quotes
// inline quotes
} elseif ( $ch == $this->enclosure && $enclosed ) {
$current .= $ch;
$i++;
@ -382,7 +382,7 @@ class parseCSV {
$row[$key] = $current;
$current = '';
$col++;
// end of row
if ( $ch == "\n" || $ch == "\r" ) {
if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) {
@ -406,7 +406,7 @@ class parseCSV {
$i = $strlen;
}
}
// append character to current field
} else {
$current .= $ch;
@ -421,7 +421,7 @@ class parseCSV {
}
return $rows;
}
/**
* Create CSV data from array
* @param data 2D array with data
@ -436,10 +436,10 @@ class parseCSV {
if ( !is_array($data) || empty($data) ) $data = &$this->data;
if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles;
if ( $delimiter === null ) $delimiter = $this->delimiter;
$string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ;
$entry = [];
// create heading
if ( $this->heading && !$append ) {
foreach( $fields as $key => $value ) {
@ -448,7 +448,7 @@ class parseCSV {
$string .= implode($delimiter, $entry).$this->linefeed;
$entry = [];
}
// create data
foreach( $data as $key => $row ) {
foreach( $row as $field => $value ) {
@ -457,10 +457,10 @@ class parseCSV {
$string .= implode($delimiter, $entry).$this->linefeed;
$entry = [];
}
return $string;
}
/**
* Load local file or string
* @param input local CSV file
@ -488,16 +488,16 @@ class parseCSV {
}
return false;
}
// ==============================================
// ----- [ Internal Functions ] -----------------
// ==============================================
/**
* Validate a row against specified conditions
* @param row array with values from a row
* @param conditions specified conditions that the row must match
* @param conditions specified conditions that the row must match
* @return true of false
*/
function _validate_row_conditions ($row = [], $conditions = null) {
@ -523,11 +523,11 @@ class parseCSV {
}
return false;
}
/**
* Validate a row against a single condition
* @param row array with values from a row
* @param condition specified condition that the row must match
* @param condition specified condition that the row must match
* @return true of false
*/
function _validate_row_condition ($row, $condition) {
@ -583,7 +583,7 @@ class parseCSV {
}
return '1';
}
/**
* Validates if the row is within the offset or not if sorting is disabled
* @param current_row the current row number being processed
@ -593,7 +593,7 @@ class parseCSV {
if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false;
return true;
}
/**
* Enclose values if needed
* - only used by unparse()
@ -611,7 +611,7 @@ class parseCSV {
}
return $value;
}
/**
* Check file data
* @param file local filename
@ -624,8 +624,8 @@ class parseCSV {
}
return true;
}
/**
* Check if passed info might be delimiter
* - only used by find_delimiter()
@ -656,7 +656,7 @@ class parseCSV {
} else return false;
}
}
/**
* Read local file
* @param file local filename
@ -689,7 +689,7 @@ class parseCSV {
}
return false;
}
}
?>
?>

View File

@ -22,6 +22,9 @@ class Utils
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
];
public static $months = [
'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december',
];
public static function isRegistered()
{
@ -92,6 +95,15 @@ class Utils
return Utils::getResllerType() ? true : false;
}
public static function isWhiteLabel()
{
if (Utils::isNinjaProd()) {
return false;
}
return \App\Models\Account::first()->hasFeature(FEATURE_WHITE_LABEL);
}
public static function getResllerType()
{
return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false;
@ -151,6 +163,11 @@ class Utils
return Auth::check() && Auth::user()->isTrial();
}
public static function isPaidPro()
{
return static::isPro() && ! static::isTrial();
}
public static function isEnglish()
{
return App::getLocale() == 'en';
@ -641,11 +658,22 @@ class Utils
}
}
public static function getMonthOptions()
{
$months = [];
for ($i=1; $i<=count(static::$months); $i++) {
$month = static::$months[$i-1];
$number = $i < 10 ? '0' . $i : $i;
$months["2000-{$number}-01"] = trans("texts.{$month}");
}
return $months;
}
private static function getMonth($offset)
{
$months = ['january', 'february', 'march', 'april', 'may', 'june',
'july', 'august', 'september', 'october', 'november', 'december', ];
$months = static::$months;
$month = intval(date('n')) - 1;
$month += $offset;

View File

@ -69,6 +69,7 @@ class Account extends Eloquent
'enable_second_tax_rate',
'include_item_taxes_inline',
'start_of_week',
'financial_year_start',
];
/**

View File

@ -345,7 +345,7 @@ class Client extends EntityModel
$contact = $this->contacts[0];
return $contact->getDisplayName() ?: trans('texts.unnamed_client');
return $contact->getDisplayName();
}
/**

View File

@ -514,6 +514,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf';
}
public function canBePaid()
{
return floatval($this->balance) > 0 && ! $this->is_deleted;
}
/**
* @param $invoice
* @return string

View File

@ -175,29 +175,39 @@ class DashboardRepository
return $metrics->groupBy('accounts.id')->first();
}
public function paidToDate($accountId, $userId, $viewAll)
public function paidToDate($account, $userId, $viewAll)
{
$accountId = $account->id;
$select = DB::raw(
'SUM('.DB::getQueryGrammar()->wrap('clients.paid_to_date', true).') as value,'
'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'
);
$paidToDate = DB::table('accounts')
$paidToDate = DB::table('payments')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', $accountId)
->where('clients.is_deleted', '=', false);
->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)
->whereNotIn('payments.payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
if (!$viewAll){
$paidToDate = $paidToDate->where('clients.user_id', '=', $userId);
$paidToDate->where('invoices.user_id', '=', $userId);
}
return $paidToDate->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' IS NULL THEN CASE WHEN '.DB::getQueryGrammar()->wrap('accounts.currency_id', true).' IS NULL THEN 1 ELSE '.DB::getQueryGrammar()->wrap('accounts.currency_id', true).' END ELSE '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' END'))
if ($account->financial_year_start) {
$yearStart = str_replace('2000', date('Y'), $account->financial_year_start);
$paidToDate->where('payments.payment_date', '>=', $yearStart);
}
return $paidToDate->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($accountId, $userId, $viewAll)
public function averages($account, $userId, $viewAll)
{
$accountId = $account->id;
$select = DB::raw(
'AVG('.DB::getQueryGrammar()->wrap('invoices.amount', true).') as invoice_avg, '
.DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
@ -213,7 +223,12 @@ class DashboardRepository
->where('invoices.is_recurring', '=', false);
if (!$viewAll){
$averageInvoice = $averageInvoice->where('invoices.user_id', '=', $userId);
$averageInvoice->where('invoices.user_id', '=', $userId);
}
if ($account->financial_year_start) {
$yearStart = str_replace('2000', date('Y'), $account->financial_year_start);
$averageInvoice->where('invoices.invoice_date', '>=', $yearStart);
}
return $averageInvoice->groupBy('accounts.id')
@ -252,7 +267,7 @@ class DashboardRepository
}
return $activities->orderBy('activities.created_at', 'desc')
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task', 'expense')
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task', 'expense', 'contact')
->take(50)
->get();
}

View File

@ -86,11 +86,16 @@ class InvoiceService extends BaseService
$sendInvoiceIds = [];
foreach ($client->contacts as $contact) {
if ($contact->send_invoice || count($client->contacts) == 1) {
if ($contact->send_invoice) {
$sendInvoiceIds[] = $contact->id;
}
}
// if no contacts are selected auto-select the first to enusre there's an invitation
if ( ! count($sendInvoiceIds)) {
$sendInvoiceIds[] = $client->contacts[0]->id;
}
foreach ($client->contacts as $contact) {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();

View File

@ -77,7 +77,7 @@
"gatepay/FedACHdir": "dev-master@dev",
"websight/l5-google-cloud-storage": "^1.0",
"wepay/php-sdk": "^0.2",
"collizo4sky/omnipay-wepay": "dev-additional-calls",
"collizo4sky/omnipay-wepay": "dev-address-fix",
"barryvdh/laravel-ide-helper": "~2.2",
"barryvdh/laravel-debugbar": "~2.2",
"fzaninotto/faker": "^1.5",

39
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "f61ec0361b0757ca0cade78837862d39",
"content-hash": "37549f849d74405b68eb7e2df64dfb43",
"hash": "cf642e3384eec7504bcdace749d2bb88",
"content-hash": "c0a5b571bc2305c4b0d9eae18bf5011b",
"packages": [
{
"name": "agmscode/omnipay-agms",
@ -1081,16 +1081,16 @@
},
{
"name": "collizo4sky/omnipay-wepay",
"version": "dev-additional-calls",
"version": "dev-address-fix",
"source": {
"type": "git",
"url": "https://github.com/hillelcoren/omnipay-wepay.git",
"reference": "a341b9997d71803d0f774d86908cb49a8bc4c405"
"reference": "916785146c5433e9216f295d09d1cbcec2fdf33a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hillelcoren/omnipay-wepay/zipball/a341b9997d71803d0f774d86908cb49a8bc4c405",
"reference": "a341b9997d71803d0f774d86908cb49a8bc4c405",
"url": "https://api.github.com/repos/hillelcoren/omnipay-wepay/zipball/916785146c5433e9216f295d09d1cbcec2fdf33a",
"reference": "916785146c5433e9216f295d09d1cbcec2fdf33a",
"shasum": ""
},
"require": {
@ -1120,9 +1120,9 @@
"wepay"
],
"support": {
"source": "https://github.com/sometechie/omnipay-wepay/tree/additional-calls"
"source": "https://github.com/hillelcoren/omnipay-wepay/tree/address-fix"
},
"time": "2016-05-25 19:18:42"
"time": "2016-11-01 10:54:54"
},
{
"name": "container-interop/container-interop",
@ -2184,7 +2184,7 @@
"shasum": null
},
"type": "library",
"time": "2016-06-03 12:00:26"
"time": "2016-10-12 12:00:38"
},
{
"name": "google/apiclient",
@ -3091,6 +3091,7 @@
"purchase",
"wechat"
],
"abandoned": "lokielse/omnipay-wechatpay",
"time": "2016-05-10 08:43:41"
},
{
@ -7211,16 +7212,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v2.8.9",
"version": "v2.8.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "889983a79a043dfda68f38c38b6dba092dd49cd8"
"reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8",
"reference": "889983a79a043dfda68f38c38b6dba092dd49cd8",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934",
"reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934",
"shasum": ""
},
"require": {
@ -7267,7 +7268,7 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2016-07-28 16:56:28"
"time": "2016-10-13 01:43:15"
},
{
"name": "symfony/finder",
@ -7320,16 +7321,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v2.8.9",
"version": "v2.8.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "f20bea598906c990eebe3c70a63ca5ed18cdbc11"
"reference": "a6e6c34d337f3c74c39b29c5f54d33023de8897c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/f20bea598906c990eebe3c70a63ca5ed18cdbc11",
"reference": "f20bea598906c990eebe3c70a63ca5ed18cdbc11",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/a6e6c34d337f3c74c39b29c5f54d33023de8897c",
"reference": "a6e6c34d337f3c74c39b29c5f54d33023de8897c",
"shasum": ""
},
"require": {
@ -7371,7 +7372,7 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
"time": "2016-07-30 07:20:35"
"time": "2016-10-24 15:52:36"
},
{
"name": "symfony/http-kernel",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -534,7 +534,7 @@ NINJA.subtotals = function(invoice, hideBalance)
}
var paid = invoice.amount - invoice.balance;
if (invoice.account.hide_paid_to_date != '1' || paid) {
if (!invoice.is_quote && (invoice.account.hide_paid_to_date != '1' || paid)) {
data.push([{text:invoiceLabels.paid_to_date, style: ['subtotalsLabel', 'paidToDateLabel']}, {text:formatMoneyInvoice(paid, invoice), style: ['subtotals', 'paidToDate']}]);
}
@ -542,7 +542,7 @@ NINJA.subtotals = function(invoice, hideBalance)
if (!hideBalance || isPartial) {
data.push([
{ text: invoiceLabels.balance_due, style: ['subtotalsLabel', isPartial ? '' : 'balanceDueLabel'] },
{ text: invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, style: ['subtotalsLabel', isPartial ? '' : 'balanceDueLabel'] },
{ text: formatMoneyInvoice(invoice.total_amount, invoice), style: ['subtotals', isPartial ? '' : 'balanceDue'] }
]);
}
@ -562,7 +562,7 @@ NINJA.subtotals = function(invoice, hideBalance)
NINJA.subtotalsBalance = function(invoice) {
var isPartial = NINJA.parseFloat(invoice.partial);
return [[
{text: isPartial ? invoiceLabels.partial_due : invoiceLabels.balance_due, style:['subtotalsLabel', 'balanceDueLabel']},
{text: isPartial ? invoiceLabels.partial_due : (invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due), style:['subtotalsLabel', 'balanceDueLabel']},
{text: formatMoneyInvoice(invoice.balance_amount, invoice), style:['subtotals', 'balanceDue']}
]];
}
@ -667,7 +667,7 @@ NINJA.renderInvoiceField = function(invoice, field) {
}
} else if (field == 'invoice.balance_due') {
return [
{text: invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']},
{text: invoice.is_quote ? invoiceLabels.total : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']},
{text: formatMoneyInvoice(invoice.total_amount, invoice), style: ['invoiceDetailBalanceDue']}
];
} else if (field == invoice.partial_due) {

View File

@ -1369,7 +1369,7 @@ $LANG = array(
'failed_remove_payment_method' => 'Failed to remove the payment method',
'gateway_exists' => 'This gateway already exists',
'manual_entry' => 'Manual entry',
'start_of_week' => 'First day of the week',
'start_of_week' => 'First Day of the Week',
// Frequencies
'freq_weekly' => 'Weekly',
@ -2174,6 +2174,7 @@ $LANG = array(
'invalid_white_label_license' => 'The white label license is not valid',
'created_by' => 'Created by :name',
'modules' => 'Modules',
'financial_year_start' => 'First Month of the Year',
);

View File

@ -185,7 +185,6 @@
<script>
var products = {!! $products !!};
console.log(products);
$(function() {
var $productSelect = $('select#product');

View File

@ -42,7 +42,7 @@
{!! Former::select('language_id')->addOption('','')
->fromQuery($languages, 'name', 'id')
->help(trans('texts.translate_app', ['link' => link_to(TRANSIFEX_URL, 'Transifex.com', ['target' => '_blank'])])) !!}
<br/>
<br/>&nbsp;<br/>
{!! Former::select('timezone_id')->addOption('','')
->fromQuery($timezones, 'location', 'id') !!}
@ -50,9 +50,16 @@
->fromQuery($dateFormats) !!}
{!! Former::select('datetime_format_id')->addOption('','')
->fromQuery($datetimeFormats) !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
<br/>&nbsp;<br/>
{!! Former::select('start_of_week')->addOption('','')
->fromQuery($weekdays) !!}
{!! Former::checkbox('military_time')->text(trans('texts.enable')) !!}
{!! Former::select('financial_year_start')
->addOption('','')
->options($months) !!}
</div>
</div>

View File

@ -16,7 +16,7 @@
<div class="panel panel-default">
<div class="panel-heading" style="color:white">
{{ trans("texts.{$type}") }}
@if ($type === ADVANCED_SETTINGS && !Utils::isPro())
@if ($type === ADVANCED_SETTINGS && ! Utils::isPaidPro())
<sup>{{ strtoupper(trans('texts.pro')) }}</sup>
@endif
</div>

View File

@ -17,6 +17,7 @@
}
.modal-header h4 {
margin:0;
color:#fff;
}
.modal-header img {
float: left;
@ -89,10 +90,14 @@
{{ Former::populateField('remember', 'true') }}
<div class="modal-header">
<a href="{{ NINJA_WEB_URL }}" target="_blank">
<img src="{{ asset('images/icon-login.png') }}" />
<h4>Invoice Ninja | {{ trans('texts.account_login') }}</h4>
</a>
@if (Utils::isWhiteLabel())
<h4>{{ trans('texts.account_login') }}</h4>
@else
<a href="{{ NINJA_WEB_URL }}" target="_blank">
<img src="{{ asset('images/icon-login.png') }}" />
<h4>Invoice Ninja | {{ trans('texts.account_login') }}</h4>
</a>
@endif
</div>
<div class="inner">
<p>

View File

@ -76,8 +76,15 @@
{!! Former::open('recover_password')->rules(['email' => 'required|email'])->addClass('form-signin') !!}
<div class="modal-header">
<img src="{{ asset('images/icon-login.png') }}" />
<h4>Invoice Ninja | {{ trans('texts.password_recovery') }}</h4></div>
@if (Utils::isWhiteLabel())
<h4>{{ trans('texts.password_recovery') }}</h4>
@else
<a href="{{ NINJA_WEB_URL }}" target="_blank">
<img src="{{ asset('images/icon-login.png') }}" />
</a>
<h4>Invoice Ninja | {{ trans('texts.password_recovery') }}</h4>
@endif
</div>
<div class="inner">
<p>

View File

@ -62,18 +62,11 @@
<div class="container">
<div class="form-signin">
<div class="modal-header">
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
<a href="{{ NINJA_WEB_URL }}" target="_blank">
<img src="{{ asset('images/icon-login.png') }}" />
<h4>Invoice Ninja | {{ trans('texts.client_session_expired') }}</h4>
</a>
@else
<h4>{{ trans('texts.session_expired') }}</h4>
@endif
<h4>{{ trans('texts.session_expired') }}</h4>
</div>
<div class="inner">
<div class="alert alert-info">{{ trans('texts.client_session_expired_message') }}</div>
</div>
</div>
</div>
@endsection
@endsection

View File

@ -341,11 +341,11 @@
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px">
{!! Former::textarea('public_notes')->data_bind("value: public_notes, valueUpdate: 'afterkeydown'")
->label(null)->style('resize: none; width: 500px;')->rows(4) !!}
->label(null)->style('width: 500px;')->rows(4) !!}
</div>
<div role="tabpanel" class="tab-pane" id="terms">
{!! Former::textarea('terms')->data_bind("value:terms, placeholder: terms_placeholder, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; width: 500px')->rows(4)
->label(false)->style('width: 500px')->rows(4)
->help('<div class="checkbox">
<label>
<input name="set_default_terms" type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>'.trans('texts.save_as_default_terms').'
@ -357,7 +357,7 @@
</div>
<div role="tabpanel" class="tab-pane" id="footer">
{!! Former::textarea('invoice_footer')->data_bind("value:invoice_footer, placeholder: footer_placeholder, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; width: 500px')->rows(4)
->label(false)->style('width: 500px')->rows(4)
->help('<div class="checkbox">
<label>
<input name="set_default_footer" type="checkbox" style="width: 24px" data-bind="checked: set_default_footer"/>'.trans('texts.save_as_default_footer').'
@ -563,7 +563,14 @@
</div>
<p>&nbsp;</p>
@include('invoices.pdf', ['account' => Auth::user()->account])
@if (Auth::user()->account->live_preview))
@include('invoices.pdf', ['account' => Auth::user()->account])
@else
<script type="text/javascript">
var invoiceLabels = {!! json_encode($account->getInvoiceLabels()) !!};
function refreshPDF() {}
</script>
@endif
@if (!Auth::user()->account->isPro())
<div style="font-size:larger">
@ -1118,7 +1125,7 @@
refreshPDF(true);
});
$('textarea').on('keyup focus', function(e) {
$('textarea.word-wrap').on('keyup focus', function(e) {
$(this).height(0).height(this.scrollHeight-18);
});
@ -1167,11 +1174,6 @@
window.generatedPDF = false;
function getPDFString(cb, force) {
@if (!$account->live_preview)
if (window.generatedPDF) {
return;
}
@endif
var invoice = createInvoiceModel();
var design = getDesignJavascript();
if (!design) return;

View File

@ -58,7 +58,7 @@
"type": "rect",
"x": 0,
"y": 0,
"w": 515,
"w": 532,
"h": 26,
"r": 0,
"lineWidth": 1,
@ -260,4 +260,4 @@
}
},
"pageMargins": [40, 120, 40, 50]
}
}