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

This commit is contained in:
David Bomba 2016-09-20 21:20:26 +10:00
commit 58ffb9b4a3
55 changed files with 493 additions and 106149 deletions

View File

@ -1,6 +1,7 @@
<?php namespace App\Console\Commands; <?php namespace App\Console\Commands;
use DB; use DB;
use Mail;
use Carbon; use Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -51,9 +52,12 @@ class CheckData extends Command {
*/ */
protected $description = 'Check/fix data'; protected $description = 'Check/fix data';
protected $log = '';
protected $isValid = true;
public function fire() public function fire()
{ {
$this->info(date('Y-m-d') . ' Running CheckData...'); $this->logMessage(date('Y-m-d') . ' Running CheckData...');
if (!$this->option('client_id')) { if (!$this->option('client_id')) {
$this->checkPaidToDate(); $this->checkPaidToDate();
@ -66,7 +70,21 @@ class CheckData extends Command {
$this->checkAccountData(); $this->checkAccountData();
} }
$this->info('Done'); $this->logMessage('Done');
$errorEmail = env('ERROR_EMAIL');
if ( ! $this->isValid && $errorEmail) {
Mail::raw($this->log, function ($message) use ($errorEmail) {
$message->to($errorEmail)
->from(CONTACT_EMAIL)
->subject('Check-Data');
});
}
}
private function logMessage($str)
{
$this->log .= $str . "\n";
} }
private function checkBlankInvoiceHistory() private function checkBlankInvoiceHistory()
@ -76,7 +94,11 @@ class CheckData extends Command {
->where('json_backup', '=', '') ->where('json_backup', '=', '')
->count(); ->count();
$this->info($count . ' activities with blank invoice backup'); if ($count > 0) {
$this->isValid = false;
}
$this->logMessage($count . ' activities with blank invoice backup');
} }
private function checkAccountData() private function checkAccountData()
@ -131,7 +153,8 @@ class CheckData extends Command {
->get(["{$table}.id", 'clients.account_id', 'clients.user_id']); ->get(["{$table}.id", 'clients.account_id', 'clients.user_id']);
if (count($records)) { if (count($records)) {
$this->info(count($records) . " {$table} records with incorrect {$entityType} account id"); $this->isValid = false;
$this->logMessage(count($records) . " {$table} records with incorrect {$entityType} account id");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
foreach ($records as $record) { foreach ($records as $record) {
@ -161,7 +184,11 @@ class CheckData extends Command {
->groupBy('clients.id') ->groupBy('clients.id')
->havingRaw('clients.paid_to_date != sum(payments.amount - payments.refunded) and clients.paid_to_date != 999999999.9999') ->havingRaw('clients.paid_to_date != sum(payments.amount - payments.refunded) and clients.paid_to_date != 999999999.9999')
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]); ->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
$this->info(count($clients) . ' clients with incorrect paid to date'); $this->logMessage(count($clients) . ' clients with incorrect paid to date');
if (count($clients) > 0) {
$this->isValid = false;
}
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
foreach ($clients as $client) { foreach ($clients as $client) {
@ -178,6 +205,7 @@ class CheckData extends Command {
$clients = DB::table('clients') $clients = DB::table('clients')
->join('invoices', 'invoices.client_id', '=', 'clients.id') ->join('invoices', 'invoices.client_id', '=', 'clients.id')
->join('accounts', 'accounts.id', '=', 'clients.account_id') ->join('accounts', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '!=', 20432)
->where('clients.is_deleted', '=', 0) ->where('clients.is_deleted', '=', 0)
->where('invoices.is_deleted', '=', 0) ->where('invoices.is_deleted', '=', 0)
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
@ -191,10 +219,14 @@ class CheckData extends Command {
$clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at') $clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
->orderBy('accounts.company_id', 'DESC') ->orderBy('accounts.company_id', 'DESC')
->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]); ->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]);
$this->info(count($clients) . ' clients with incorrect balance/activities'); $this->logMessage(count($clients) . ' clients with incorrect balance/activities');
if (count($clients) > 0) {
$this->isValid = false;
}
foreach ($clients as $client) { foreach ($clients as $client) {
$this->info("=== Company: {$client->company_id} Account:{$client->account_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ==="); $this->logMessage("=== Company: {$client->company_id} Account:{$client->account_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ===");
$foundProblem = false; $foundProblem = false;
$lastBalance = 0; $lastBalance = 0;
$lastAdjustment = 0; $lastAdjustment = 0;
@ -204,7 +236,7 @@ class CheckData extends Command {
->where('client_id', '=', $client->id) ->where('client_id', '=', $client->id)
->orderBy('activities.id') ->orderBy('activities.id')
->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']); ->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
//$this->info(var_dump($activities)); //$this->logMessage(var_dump($activities));
foreach ($activities as $activity) { foreach ($activities as $activity) {
@ -251,19 +283,19 @@ class CheckData extends Command {
// **Fix for ninja invoices which didn't have the invoice_type_id value set // **Fix for ninja invoices which didn't have the invoice_type_id value set
if ($noAdjustment && $client->account_id == 20432) { if ($noAdjustment && $client->account_id == 20432) {
$this->info("No adjustment for ninja invoice"); $this->logMessage("No adjustment for ninja invoice");
$foundProblem = true; $foundProblem = true;
$clientFix += $invoice->amount; $clientFix += $invoice->amount;
$activityFix = $invoice->amount; $activityFix = $invoice->amount;
// **Fix for allowing converting a recurring invoice to a normal one without updating the balance** // **Fix for allowing converting a recurring invoice to a normal one without updating the balance**
} elseif ($noAdjustment && $invoice->invoice_type_id == INVOICE_TYPE_STANDARD && !$invoice->is_recurring) { } elseif ($noAdjustment && $invoice->invoice_type_id == INVOICE_TYPE_STANDARD && !$invoice->is_recurring) {
$this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $this->logMessage("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}");
$foundProblem = true; $foundProblem = true;
$clientFix += $invoice->amount; $clientFix += $invoice->amount;
$activityFix = $invoice->amount; $activityFix = $invoice->amount;
// **Fix for updating balance when creating a quote or recurring invoice** // **Fix for updating balance when creating a quote or recurring invoice**
} elseif ($activity->adjustment != 0 && ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE || $invoice->is_recurring)) { } elseif ($activity->adjustment != 0 && ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE || $invoice->is_recurring)) {
$this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $this->logMessage("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
@ -271,7 +303,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
// **Fix for updating balance when deleting a recurring invoice** // **Fix for updating balance when deleting a recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) { if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
if ($activity->balance != $lastBalance) { if ($activity->balance != $lastBalance) {
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
@ -281,7 +313,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
// **Fix for updating balance when archiving an invoice** // **Fix for updating balance when archiving an invoice**
if ($activity->adjustment != 0 && !$invoice->is_recurring) { if ($activity->adjustment != 0 && !$invoice->is_recurring) {
$this->info("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$activityFix = 0; $activityFix = 0;
$clientFix += $activity->adjustment; $clientFix += $activity->adjustment;
@ -289,12 +321,12 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
// **Fix for updating balance when updating recurring invoice** // **Fix for updating balance when updating recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) { if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
} else if ((strtotime($activity->created_at) - strtotime($lastCreatedAt) <= 1) && $activity->adjustment > 0 && $activity->adjustment == $lastAdjustment) { } else if ((strtotime($activity->created_at) - strtotime($lastCreatedAt) <= 1) && $activity->adjustment > 0 && $activity->adjustment == $lastAdjustment) {
$this->info("Duplicate adjustment for updated invoice adjustment:{$activity->adjustment}"); $this->logMessage("Duplicate adjustment for updated invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
@ -302,7 +334,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
// **Fix for updating balance when updating a quote** // **Fix for updating balance when updating a quote**
if ($activity->balance != $lastBalance) { if ($activity->balance != $lastBalance) {
$this->info("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix += $lastBalance - $activity->balance; $clientFix += $lastBalance - $activity->balance;
$activityFix = 0; $activityFix = 0;
@ -310,7 +342,7 @@ class CheckData extends Command {
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) { } else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
// **Fix for deleting payment after deleting invoice** // **Fix for deleting payment after deleting invoice**
if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) { if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) {
$this->info("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$activityFix = 0; $activityFix = 0;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
@ -339,7 +371,7 @@ class CheckData extends Command {
} }
if ($activity->balance + $clientFix != $client->actual_balance) { if ($activity->balance + $clientFix != $client->actual_balance) {
$this->info("** Creating 'recovered update' activity **"); $this->logMessage("** Creating 'recovered update' activity **");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
DB::table('activities')->insert([ DB::table('activities')->insert([
'created_at' => new Carbon, 'created_at' => new Carbon,
@ -353,7 +385,7 @@ class CheckData extends Command {
} }
$data = ['balance' => $client->actual_balance]; $data = ['balance' => $client->actual_balance];
$this->info("Corrected balance:{$client->actual_balance}"); $this->logMessage("Corrected balance:{$client->actual_balance}");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
DB::table('clients') DB::table('clients')
->where('id', $client->id) ->where('id', $client->id)

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Utils;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@ -20,4 +21,20 @@ class BaseController extends Controller
$this->layout = View::make($this->layout); $this->layout = View::make($this->layout);
} }
} }
protected function returnBulk($entityType, $action, $ids)
{
$isDatatable = filter_var(request()->datatable, FILTER_VALIDATE_BOOLEAN);
$entityTypes = Utils::pluralizeEntityType($entityType);
if ($action == 'restore' && count($ids) == 1) {
return redirect("{$entityTypes}/" . $ids[0]);
} elseif ($isDatatable || ($action == 'archive' || $action == 'delete')) {
return redirect("{$entityTypes}");
} elseif (count($ids)) {
return redirect("{$entityTypes}/" . $ids[0]);
} else {
return redirect("{$entityTypes}");
}
}
} }

View File

@ -221,10 +221,6 @@ class ClientController extends BaseController
$message = Utils::pluralize($action.'d_client', $count); $message = Utils::pluralize($action.'d_client', $count);
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { return $this->returnBulk(ENTITY_CLIENT, $action, $ids);
return Redirect::to('clients/'.Utils::getFirst($ids));
} else {
return Redirect::to('clients');
}
} }
} }

View File

@ -42,12 +42,12 @@ class DashboardApiController extends BaseAPIController
$data = [ $data = [
'id' => 1, 'id' => 1,
'paidToDate' => $paidToDate[0]->value ? $paidToDate[0]->value : 0, 'paidToDate' => count($paidToDate) && $paidToDate[0]->value ? $paidToDate[0]->value : 0,
'paidToDateCurrency' => $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0, 'paidToDateCurrency' => count($paidToDate) && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0,
'balances' => $balances[0]->value ? $balances[0]->value : 0, 'balances' => count($balances) && $balances[0]->value ? $balances[0]->value : 0,
'balancesCurrency' => $balances[0]->currency_id ? $balances[0]->currency_id : 0, 'balancesCurrency' => count($balances) && $balances[0]->currency_id ? $balances[0]->currency_id : 0,
'averageInvoice' => (count($averageInvoice) && $averageInvoice[0]->invoice_avg) ? $averageInvoice[0]->invoice_avg : 0, 'averageInvoice' => count($averageInvoice) && $averageInvoice[0]->invoice_avg ? $averageInvoice[0]->invoice_avg : 0,
'averageInvoiceCurrency' => $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0, 'averageInvoiceCurrency' => count($averageInvoice) && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0,
'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY), 'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY),

View File

@ -245,7 +245,7 @@ class ExpenseController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
return Redirect::to('expenses'); return $this->returnBulk($this->entityType, $action, $ids);
} }
private static function getViewModel() private static function getViewModel()

View File

@ -531,11 +531,7 @@ class InvoiceController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
if ($action == 'restore' && $count == 1) { return $this->returnBulk($entityType, $action, $ids);
return Redirect::to("{$entityType}s/".Utils::getFirst($ids));
} else {
return Redirect::to("{$entityType}s");
}
} }
public function convertQuote(InvoiceRequest $request) public function convertQuote(InvoiceRequest $request)
@ -612,8 +608,10 @@ class InvoiceController extends BaseController
return View::make('invoices.history', $data); return View::make('invoices.history', $data);
} }
public function checkInvoiceNumber($invoiceNumber) public function checkInvoiceNumber()
{ {
$invoiceNumber = request()->invoice_number;
$count = Invoice::scope() $count = Invoice::scope()
->whereInvoiceNumber($invoiceNumber) ->whereInvoiceNumber($invoiceNumber)
->withTrashed() ->withTrashed()

View File

@ -154,11 +154,7 @@ class QuoteController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
if ($action == 'restore' && $count == 1) { return $this->returnBulk(ENTITY_QUOTE, $action, $ids);
return Redirect::to('quotes/'.Utils::getFirst($ids));
} else {
return Redirect::to('quotes');
}
} }
public function approve($invitationKey) public function approve($invitationKey)

View File

@ -11,6 +11,7 @@ use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Task;
/** /**
* Class ReportController * Class ReportController
@ -56,8 +57,8 @@ class ReportController extends BaseController
if (Input::all()) { if (Input::all()) {
$reportType = Input::get('report_type'); $reportType = Input::get('report_type');
$dateField = Input::get('date_field'); $dateField = Input::get('date_field');
$startDate = Utils::toSqlDate(Input::get('start_date'), false); $startDate = date_create(Input::get('start_date'));
$endDate = Utils::toSqlDate(Input::get('end_date'), false); $endDate = date_create(Input::get('end_date'));
} else { } else {
$reportType = ENTITY_INVOICE; $reportType = ENTITY_INVOICE;
$dateField = FILTER_INVOICE_DATE; $dateField = FILTER_INVOICE_DATE;
@ -71,15 +72,17 @@ class ReportController extends BaseController
ENTITY_PRODUCT => trans('texts.product'), ENTITY_PRODUCT => trans('texts.product'),
ENTITY_PAYMENT => trans('texts.payment'), ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expense'), ENTITY_EXPENSE => trans('texts.expense'),
ENTITY_TASK => trans('texts.task'),
ENTITY_TAX_RATE => trans('texts.tax'), ENTITY_TAX_RATE => trans('texts.tax'),
]; ];
$params = [ $params = [
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)), 'startDate' => $startDate->format('Y-m-d'),
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)), 'endDate' => $endDate->format('Y-m-d'),
'reportTypes' => $reportTypes, 'reportTypes' => $reportTypes,
'reportType' => $reportType, 'reportType' => $reportType,
'title' => trans('texts.charts_and_reports'), 'title' => trans('texts.charts_and_reports'),
'account' => Auth::user()->account,
]; ];
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) { if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
@ -120,9 +123,37 @@ class ReportController extends BaseController
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport); return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
} elseif ($reportType == ENTITY_EXPENSE) { } elseif ($reportType == ENTITY_EXPENSE) {
return $this->generateExpenseReport($startDate, $endDate, $isExport); return $this->generateExpenseReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_TASK) {
return $this->generateTaskReport($startDate, $endDate, $isExport);
} }
} }
private function generateTaskReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'date', 'description', 'duration'];
$displayData = [];
$tasks = Task::scope()
->with('client.contacts')
->withArchived()
->dateRange($startDate, $endDate);
foreach ($tasks->get() as $task) {
$displayData[] = [
$task->client ? ($isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'),
link_to($task->present()->url, $task->getStartTime()),
$task->present()->description,
Utils::formatTime($task->getDuration()),
];
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => [],
];
}
/** /**
* @param $startDate * @param $startDate
* @param $endDate * @param $endDate

View File

@ -300,11 +300,7 @@ class TaskController extends BaseController
$message = Utils::pluralize($action.'d_task', $count); $message = Utils::pluralize($action.'d_task', $count);
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { return $this->returnBulk($this->entityType, $action, $ids);
return Redirect::to('tasks/'.$ids[0].'/edit');
} else {
return Redirect::to('tasks');
}
} }
} }

View File

@ -185,10 +185,6 @@ class VendorController extends BaseController
$message = Utils::pluralize($action.'d_vendor', $count); $message = Utils::pluralize($action.'d_vendor', $count);
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { return $this->returnBulk($this->entityType, $action, $ids);
return Redirect::to('vendors/' . Utils::getFirst($ids));
} else {
return Redirect::to('vendors');
}
} }
} }

View File

@ -25,6 +25,7 @@ class ApiCheck {
{ {
$loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register'); $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register');
$headers = Utils::getApiHeaders(); $headers = Utils::getApiHeaders();
$hasApiSecret = false;
if ($secret = env(API_SECRET)) { if ($secret = env(API_SECRET)) {
$hasApiSecret = hash_equals($request->api_secret ?: '', $secret); $hasApiSecret = hash_equals($request->api_secret ?: '', $secret);
@ -34,7 +35,7 @@ class ApiCheck {
// check API secret // check API secret
if ( ! $hasApiSecret) { if ( ! $hasApiSecret) {
sleep(ERROR_DELAY); sleep(ERROR_DELAY);
return Response::json('Invalid secret', 403, $headers); return Response::json('Invalid value for API_SECRET', 403, $headers);
} }
} else { } else {
// check for a valid token // check for a valid token

View File

@ -13,7 +13,7 @@ class VerifyCsrfToken extends BaseVerifier
* @var array * @var array
*/ */
private $openRoutes = [ private $openRoutes = [
'complete', 'complete/*',
'signup/register', 'signup/register',
'api/v1/*', 'api/v1/*',
'api/v1/login', 'api/v1/login',

View File

@ -127,7 +127,7 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('hide_message', 'HomeController@hideMessage'); Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']); Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']);
Route::get('check_invoice_number/{invoice_number}', 'InvoiceController@checkInvoiceNumber'); Route::get('check_invoice_number', 'InvoiceController@checkInvoiceNumber');
Route::get('save_sidebar_state', 'UserController@saveSidebarState'); Route::get('save_sidebar_state', 'UserController@saveSidebarState');
Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::get('settings/user_details', 'AccountController@showUserDetails');
@ -623,7 +623,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '2.7.0' . env('NINJA_VERSION_SUFFIX')); define('NINJA_VERSION', '2.7.1' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));

View File

@ -48,9 +48,15 @@ class HistoryUtils
$entity = $activity->client; $entity = $activity->client;
} else if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_TASK || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_TASK) { } else if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_TASK || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_TASK) {
$entity = $activity->task; $entity = $activity->task;
if ( ! $entity) {
continue;
}
$entity->setRelation('client', $activity->client); $entity->setRelation('client', $activity->client);
} else { } else {
$entity = $activity->invoice; $entity = $activity->invoice;
if ( ! $entity) {
continue;
}
$entity->setRelation('client', $activity->client); $entity->setRelation('client', $activity->client);
} }

View File

@ -165,6 +165,14 @@ class Task extends EntityModel
return '#' . $this->public_id; return '#' . $this->public_id;
} }
public function scopeDateRange($query, $startDate, $endDate)
{
$query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) >= ' . $startDate->format('U'));
$query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->format('U'));
return $query;
}
} }

View File

@ -184,6 +184,7 @@ trait PresentsInvoice
'quote_to', 'quote_to',
'details', 'details',
'invoice_no', 'invoice_no',
'quote_no',
'valid_until', 'valid_until',
'client_name', 'client_name',
'address1', 'address1',

View File

@ -557,6 +557,7 @@ class BasePaymentDriver
$paymentMethod->setRelation('account_gateway_token', $customer); $paymentMethod->setRelation('account_gateway_token', $customer);
$paymentMethod = $this->creatingPaymentMethod($paymentMethod); $paymentMethod = $this->creatingPaymentMethod($paymentMethod);
if ($paymentMethod) {
// archive the old payment method // archive the old payment method
$oldPaymentMethod = PaymentMethod::clientId($this->client()->id) $oldPaymentMethod = PaymentMethod::clientId($this->client()->id)
->wherePaymentTypeId($paymentMethod->payment_type_id) ->wherePaymentTypeId($paymentMethod->payment_type_id)
@ -566,7 +567,6 @@ class BasePaymentDriver
$oldPaymentMethod->delete(); $oldPaymentMethod->delete();
} }
if ($paymentMethod) {
$paymentMethod->save(); $paymentMethod->save();
} }
@ -833,8 +833,8 @@ class BasePaymentDriver
return true; return true;
} }
$accountGatewaySettings = AccountGatewaySettings::scope()->where('account_gateway_settings.gateway_type_id', $accountGatewaySettings = AccountGatewaySettings::scope(false, $this->invitation->account_id)
'=', $gatewayTypeId)->first(); ->where('account_gateway_settings.gateway_type_id', '=', $gatewayTypeId)->first();
if ($accountGatewaySettings) { if ($accountGatewaySettings) {
$invoice = $this->invoice(); $invoice = $this->invoice();

View File

@ -21,6 +21,11 @@ class TaskPresenter extends EntityPresenter
return $this->entity->user->getDisplayName(); return $this->entity->user->getDisplayName();
} }
public function description()
{
return substr($this->entity->description, 0, 40) . (strlen($this->entity->description) > 40 ? '...' : '');
}
/** /**
* @param $account * @param $account
* @return mixed * @return mixed

View File

@ -78,7 +78,6 @@ class ClientRepository extends BaseRepository
$client = Client::createNew(); $client = Client::createNew();
} else { } else {
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = Client::scope($publicId)->with('contacts')->firstOrFail();
\Log::warning('Entity not set in client repo save');
} }
// convert currency code to id // convert currency code to id

View File

@ -68,7 +68,6 @@ class TaskRepository
// do nothing // do nothing
} elseif ($publicId) { } elseif ($publicId) {
$task = Task::scope($publicId)->firstOrFail(); $task = Task::scope($publicId)->firstOrFail();
\Log::warning('Entity not set in task repo save');
} else { } else {
$task = Task::createNew(); $task = Task::createNew();
} }

View File

@ -24,7 +24,7 @@ class ActivityTransformer extends EntityTransformer
return [ return [
'id' => $activity->key(), 'id' => $activity->key(),
'activity_type_id' => $activity->activity_type_id, 'activity_type_id' => $activity->activity_type_id,
'client_id' => $activity->client->public_id, 'client_id' => $activity->client ? $activity->client->public_id : null,
'user_id' => $activity->user->public_id + 1, 'user_id' => $activity->user->public_id + 1,
'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null, 'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null,
'payment_id' => $activity->payment ? $activity->payment->public_id : null, 'payment_id' => $activity->payment ? $activity->payment->public_id : null,

View File

@ -36,3 +36,11 @@ Want to find out everything there is to know about how to use your Invoice Ninja
data_visualizations data_visualizations
api_tokens api_tokens
user_management user_management
.. _self_host:
.. toctree::
:maxdepth: 1
:caption: Self Host
iphone_app

46
docs/iphone_app.rst Normal file
View File

@ -0,0 +1,46 @@
iPhone Application
==================
The Invoice Ninja iPhone application allows a user to connect to their self-hosted Invoice Ninja web application.
Connecting your iPhone to your self-hosted invoice ninja installation requires a couple of easy steps.
Web app configuration
"""""""""""""""""""""
Firstly you'll need to add an additional field to your .env file which is located in the root directory of your self-hosted Invoice Ninja installation.
The additional field to add is API_SECRET, set this to your own defined alphanumeric string.
Save your .env file and now open Invoice Ninja on your iPhone.
iPhone configuration
""""""""""""""""""""
Once you have completed the in-app purchase to unlock the iPhone to connect to your own server, you'll be presented with two fields.
The first is the Base URL of your self-hosted installation, ie http://ninja.yourapp.com
The second field is the API_SECRET, enter in the API_SECRET you used in your .env file.
Click SAVE.
You should be able to login now from your iPhone!
FAQ:
""""
Q: I get a HTTP 500 error.
A: Most likely you have not entered your API_SECRET in your .env file
Q: I get a HTTP 403 error when i attempt to login with the iPhone.
A: Most likely your API_SECRET on the iPhone does not match that on your self-hosted installation.
Q: Do I need to create a token on the server?
A: No, this is not required. The server will automagically create a token if one does not exist on first login.

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

10530
public/js/Chart.min.js vendored

File diff suppressed because one or more lines are too long

9559
public/js/d3.min.js vendored

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

@ -44,6 +44,7 @@
.twitter-typeahead .tt-suggestion { .twitter-typeahead .tt-suggestion {
padding: 3px 20px; padding: 3px 20px;
white-space: nowrap; white-space: nowrap;
cursor: pointer;
} }
/*Added to menu element when it contains no content*/ /*Added to menu element when it contains no content*/

View File

@ -210,8 +210,12 @@ NINJA.decodeJavascript = function(invoice, javascript)
if (invoice.partial > 0 && field == 'balance_due') { if (invoice.partial > 0 && field == 'balance_due') {
field = 'partial_due'; field = 'partial_due';
} else if (invoice.is_quote) { } else if (invoice.is_quote) {
if (field == 'due_date') {
field = 'valid_until';
} else {
field = field.replace('invoice', 'quote'); field = field.replace('invoice', 'quote');
} }
}
var label = invoiceLabels[field]; var label = invoiceLabels[field];
if (match.indexOf('UC') >= 0) { if (match.indexOf('UC') >= 0) {
label = label.toUpperCase(); label = label.toUpperCase();

View File

@ -643,6 +643,7 @@ $LANG = array(
'custom' => 'Custom', 'custom' => 'Custom',
'invoice_to' => 'Invoice to', 'invoice_to' => 'Invoice to',
'invoice_no' => 'Invoice No.', 'invoice_no' => 'Invoice No.',
'quote_no' => 'Quote No.',
'recent_payments' => 'Recent Payments', 'recent_payments' => 'Recent Payments',
'outstanding' => 'Outstanding', 'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies', 'manage_companies' => 'Manage Companies',
@ -2068,7 +2069,7 @@ $LANG = array(
'bot_emailed_notify_viewed' => 'I\'ll email you when it\'s viewed.', 'bot_emailed_notify_viewed' => 'I\'ll email you when it\'s viewed.',
'bot_emailed_notify_paid' => 'I\'ll email you when it\'s paid.', 'bot_emailed_notify_paid' => 'I\'ll email you when it\'s paid.',
'add_product_to_invoice' => 'Add 1 :product', 'add_product_to_invoice' => 'Add 1 :product',
'not_authorized' => 'Your are not authorized', 'not_authorized' => 'You are not authorized',
'bot_get_email' => 'Hi! (wave)<br/>Thanks for trying the Invoice Ninja Bot.<br/>Send me your account email to get started.', 'bot_get_email' => 'Hi! (wave)<br/>Thanks for trying the Invoice Ninja Bot.<br/>Send me your account email to get started.',
'bot_get_code' => 'Thanks! I\'ve sent a you an email with your security code.', 'bot_get_code' => 'Thanks! I\'ve sent a you an email with your security code.',
'bot_welcome' => 'That\'s it, your account is verified.<br/>', 'bot_welcome' => 'That\'s it, your account is verified.<br/>',
@ -2129,6 +2130,11 @@ $LANG = array(
'max' => 'Max', 'max' => 'Max',
'limits_not_met' => 'This invoice does not meet the limits for that payment type.', 'limits_not_met' => 'This invoice does not meet the limits for that payment type.',
'date_range' => 'Date Range',
'raw' => 'Raw',
'raw_html' => 'Raw HTML',
'update' => 'Update',
); );
return $LANG; return $LANG;

View File

@ -72,16 +72,17 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="panel-body">
<div class="row" style="text-align:center"> <div class="row" style="text-align:center">
<div class="col-xs-12"> <div class="col-xs-12">
<div id="payment-limits-slider"></div> <div id="payment-limits-slider"></div>
</div> </div>
</div> </div><br/>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div id="payment-limit-min-container"> <div id="payment-limit-min-container">
<label for="payment-limit-min">{{ trans('texts.min') }}</label><br> <label for="payment-limit-min">{{ trans('texts.min') }}</label><br>
<div class="input-group"> <div class="input-group" style="padding-bottom:8px">
<span class="input-group-addon">{{ $currency->symbol }}</span> <span class="input-group-addon">{{ $currency->symbol }}</span>
<input type="number" class="form-control" min="0" id="payment-limit-min" <input type="number" class="form-control" min="0" id="payment-limit-min"
name="limit_min"> name="limit_min">
@ -94,7 +95,7 @@
<div id="payment-limit-max-container"> <div id="payment-limit-max-container">
<label for="payment-limit-max">{{ trans('texts.max') }}</label><br> <label for="payment-limit-max">{{ trans('texts.max') }}</label><br>
<div class="input-group"> <div class="input-group" style="padding-bottom:8px">
<span class="input-group-addon">{{ $currency->symbol }}</span> <span class="input-group-addon">{{ $currency->symbol }}</span>
<input type="number" class="form-control" min="0" id="payment-limit-max" <input type="number" class="form-control" min="0" id="payment-limit-max"
name="limit_max"> name="limit_max">
@ -106,6 +107,7 @@
</div> </div>
<input type="hidden" name="gateway_type_id" id="payment-limit-gateway-type"> <input type="hidden" name="gateway_type_id" id="payment-limit-gateway-type">
</div> </div>
</div>
<div class="modal-footer" style="margin-top: 0px"> <div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" <button type="button" class="btn btn-default"
@ -168,24 +170,24 @@
}); });
limitsSlider.noUiSlider.on('update', function (values, handle) { limitsSlider.noUiSlider.on('update', function (values, handle) {
var value = values[handle]; var value = Math.round(values[handle]);
if (handle == 1) { if (handle == 1) {
$('#payment-limit-max').val(Math.round(value)).removeAttr('disabled'); $('#payment-limit-max').val(value).removeAttr('disabled');
$('#payment-limit-max-enable').prop('checked', true); $('#payment-limit-max-enable').prop('checked', true);
} else { } else {
$('#payment-limit-min').val(Math.round(value)).removeAttr('disabled'); $('#payment-limit-min').val(value).removeAttr('disabled');
$('#payment-limit-min-enable').prop('checked', true); $('#payment-limit-min-enable').prop('checked', true);
} }
}); });
$('#payment-limit-min').on('change keyup', function () { $('#payment-limit-min').on('change input', function () {
setTimeout(function () { setTimeout(function () {
limitsSlider.noUiSlider.set([$('#payment-limit-min').val(), null]); limitsSlider.noUiSlider.set([$('#payment-limit-min').val(), null]);
}, 100); }, 100);
$('#payment-limit-min-enable').attr('checked', 'checked'); $('#payment-limit-min-enable').attr('checked', 'checked');
}); });
$('#payment-limit-max').on('change keyup', function () { $('#payment-limit-max').on('change input', function () {
setTimeout(function () { setTimeout(function () {
limitsSlider.noUiSlider.set([null, $('#payment-limit-max').val()]); limitsSlider.noUiSlider.set([null, $('#payment-limit-max').val()]);
}, 100); }, 100);

View File

@ -64,10 +64,11 @@
</div> </div>
<p>&nbsp;<p/> <p>&nbsp;<p/>
<div class="row"> <div class="row">
<div class="col-md-10 show-when-ready" style="display:none"> <div class="col-md-9 show-when-ready" style="display:none">
@include('partials/quill_toolbar', ['name' => $field]) @include('partials/quill_toolbar', ['name' => $field])
</div> </div>
<div class="col-md-2 pull-right" style="padding-top:10px"> <div class="col-md-3 pull-right" style="padding-top:10px">
{!! Button::normal(trans('texts.raw'))->withAttributes(['onclick' => 'showRaw("'.$field.'")'])->small() !!}
{!! Button::primary(trans('texts.preview'))->withAttributes(['onclick' => 'serverPreview("'.$field.'")'])->small() !!} {!! Button::primary(trans('texts.preview'))->withAttributes(['onclick' => 'serverPreview("'.$field.'")'])->small() !!}
</div> </div>
</div> </div>

View File

@ -104,6 +104,25 @@
</div> </div>
</div> </div>
<div class="modal fade" id="rawModal" tabindex="-1" role="dialog" aria-labelledby="rawModalLabel" aria-hidden="true">
<div class="modal-dialog" style="width:800px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="rawModalLabel">{{ trans('texts.raw_html') }}</h4>
</div>
<div class="modal-body">
<textarea id="raw-textarea" rows="20" style="width:100%"></textarea>
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.close') }}</button>
<button type="button" onclick="updateRaw()" class="btn btn-success" data-dismiss="modal">{{ trans('texts.update') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="templateHelpModal" tabindex="-1" role="dialog" aria-labelledby="templateHelpModalLabel" aria-hidden="true"> <div class="modal fade" id="templateHelpModal" tabindex="-1" role="dialog" aria-labelledby="templateHelpModalLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px"> <div class="modal-dialog" style="min-width:150px">
@ -184,7 +203,6 @@
} }
function serverPreview(field) { function serverPreview(field) {
console.log(field);
$('#templatePreviewModal').modal('show'); $('#templatePreviewModal').modal('show');
var template = $('#email_template_' + field).val(); var template = $('#email_template_' + field).val();
var url = '{{ URL::to('settings/email_preview') }}?template=' + template; var url = '{{ URL::to('settings/email_preview') }}?template=' + template;
@ -314,6 +332,55 @@
}); });
} }
function showRaw(field) {
window.rawHtmlField = field;
var template = $('#email_template_' + field).val();
$('#raw-textarea').val(formatXml(template));
$('#rawModal').modal('show');
}
function updateRaw() {
var value = $('#raw-textarea').val();
var field = window.rawHtmlField;
editors[field].setHTML(value);
value = editors[field].getHTML();
var fieldName = 'email_template_' + field;
$('#' + fieldName).val(value);
refreshPreview();
}
// https://gist.github.com/sente/1083506
function formatXml(xml) {
var formatted = '';
var reg = /(>)(<)(\/*)/g;
xml = xml.replace(reg, '$1\r\n$2$3');
var pad = 0;
jQuery.each(xml.split('\r\n'), function(index, node) {
var indent = 0;
if (node.match( /.+<\/\w[^>]*>$/ )) {
indent = 0;
} else if (node.match( /^<\/\w/ )) {
if (pad != 0) {
pad -= 1;
}
} else if (node.match( /^<\w[^>]*[^\/]>.*$/ )) {
indent = 1;
} else {
indent = 0;
}
var padding = '';
for (var i = 0; i < pad; i++) {
padding += ' ';
}
formatted += padding + node + '\r\n';
pad += indent;
});
return formatted;
}
</script> </script>
@stop @stop

View File

@ -45,4 +45,14 @@
@include('export.payments') @include('export.payments')
@endif @endif
@if (isset($vendors) && $vendors && count($vendors))
<tr><td>{{ strtoupper(trans('texts.vendors')) }}</td></tr>
@include('export.vendors')
@endif
@if (isset($vendor_contacts) && $vendor_contacts && count($vendor_contacts))
<tr><td>{{ strtoupper(trans('texts.vendor_contacts')) }}</td></tr>
@include('export.vendor_contacts')
@endif
</html> </html>

View File

@ -9,10 +9,10 @@
<td>{{ trans('texts.phone') }}</td> <td>{{ trans('texts.phone') }}</td>
</tr> </tr>
@foreach ($contacts as $contact) @foreach ($vendor_contacts as $contact)
@if (!$contact->client->is_deleted) @if (!$contact->vendor->is_deleted)
<tr> <tr>
<td>{{ $contact->client->getDisplayName() }}</td> <td>{{ $contact->vendor->getDisplayName() }}</td>
@if ($multiUser) @if ($multiUser)
<td>{{ $contact->user->getDisplayName() }}</td> <td>{{ $contact->user->getDisplayName() }}</td>
@endif @endif

View File

@ -3,42 +3,26 @@
@if ($multiUser) @if ($multiUser)
<td>{{ trans('texts.user') }}</td> <td>{{ trans('texts.user') }}</td>
@endif @endif
<td>{{ trans('texts.balance') }}</td>
<td>{{ trans('texts.paid_to_date') }}</td>
<td>{{ trans('texts.address1') }}</td> <td>{{ trans('texts.address1') }}</td>
<td>{{ trans('texts.address2') }}</td> <td>{{ trans('texts.address2') }}</td>
<td>{{ trans('texts.city') }}</td> <td>{{ trans('texts.city') }}</td>
<td>{{ trans('texts.state') }}</td> <td>{{ trans('texts.state') }}</td>
<td>{{ trans('texts.postal_code') }}</td> <td>{{ trans('texts.postal_code') }}</td>
<td>{{ trans('texts.country') }}</td> <td>{{ trans('texts.country') }}</td>
@if ($account->custom_client_label1)
<td>{{ $account->custom_client_label1 }}</td>
@endif
@if ($account->custom_client_label2)
<td>{{ $account->custom_client_label2 }}</td>
@endif
</tr> </tr>
@foreach ($clients as $client) @foreach ($vendors as $vendor)
<tr> <tr>
<td>{{ $client->getDisplayName() }}</td> <td>{{ $vendor->getDisplayName() }}</td>
@if ($multiUser) @if ($multiUser)
<td>{{ $client->user->getDisplayName() }}</td> <td>{{ $vendor->user->getDisplayName() }}</td>
@endif
<td>{{ $account->formatMoney($client->balance, $client) }}</td>
<td>{{ $account->formatMoney($client->paid_to_date, $client) }}</td>
<td>{{ $client->address1 }}</td>
<td>{{ $client->address2 }}</td>
<td>{{ $client->city }}</td>
<td>{{ $client->state }}</td>
<td>{{ $client->postal_code }}</td>
<td>{{ $client->present()->country }}</td>
@if ($account->custom_client_label1)
<td>{{ $client->custom_value1 }}</td>
@endif
@if ($account->custom_client_label2)
<td>{{ $client->custom_value2 }}</td>
@endif @endif
<td>{{ $vendor->address1 }}</td>
<td>{{ $vendor->address2 }}</td>
<td>{{ $vendor->city }}</td>
<td>{{ $vendor->state }}</td>
<td>{{ $vendor->postal_code }}</td>
<td>{{ $vendor->present()->country }}</td>
</tr> </tr>
@endforeach @endforeach

View File

@ -199,8 +199,11 @@
} }
window.loadedSearchData = false; window.loadedSearchData = false;
function onSearchFocus() { function onSearchBlur() {
$('#search').typeahead('val', ''); $('#search').typeahead('val', '');
}
function onSearchFocus() {
$('#search-form').show(); $('#search-form').show();
if (!window.loadedSearchData) { if (!window.loadedSearchData) {
@ -319,6 +322,7 @@
// Focus the search input if the user clicks forward slash // Focus the search input if the user clicks forward slash
$('#search').focusin(onSearchFocus); $('#search').focusin(onSearchFocus);
$('#search').blur(onSearchBlur);
$('body').keypress(function(event) { $('body').keypress(function(event) {
if (event.which == 47 && !$('*:focus').length) { if (event.which == 47 && !$('*:focus').length) {

View File

@ -262,8 +262,8 @@
</div> </div>
</td> </td>
<td> <td>
<textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}" <textarea data-bind="value: notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}"
rows="1" cols="60" style="resize: vertical" class="form-control word-wrap"></textarea> rows="1" cols="60" style="resize: vertical;height:42px" class="form-control word-wrap"></textarea>
<input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/> <input type="text" data-bind="value: task_public_id, attr: {name: 'invoice_items[' + $index() + '][task_public_id]'}" style="display: none"/>
<input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/> <input type="text" data-bind="value: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/>
</td> </td>
@ -340,11 +340,11 @@
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px"> <div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px">
{!! Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'") {!! Former::textarea('public_notes')->data_bind("value: public_notes, valueUpdate: 'afterkeydown'")
->label(null)->style('resize: none; width: 500px;')->rows(4) !!} ->label(null)->style('resize: none; width: 500px;')->rows(4) !!}
</div> </div>
<div role="tabpanel" class="tab-pane" id="terms"> <div role="tabpanel" class="tab-pane" id="terms">
{!! Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: terms_placeholder, valueUpdate: 'afterkeydown'") {!! Former::textarea('terms')->data_bind("value:terms, placeholder: terms_placeholder, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; width: 500px')->rows(4) ->label(false)->style('resize: none; width: 500px')->rows(4)
->help('<div class="checkbox"> ->help('<div class="checkbox">
<label> <label>
@ -356,7 +356,7 @@
</div>') !!} </div>') !!}
</div> </div>
<div role="tabpanel" class="tab-pane" id="footer"> <div role="tabpanel" class="tab-pane" id="footer">
{!! Former::textarea('invoice_footer')->data_bind("value:wrapped_footer, placeholder: footer_placeholder, valueUpdate: 'afterkeydown'") {!! 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('resize: none; width: 500px')->rows(4)
->help('<div class="checkbox"> ->help('<div class="checkbox">
<label> <label>
@ -1108,10 +1108,9 @@
}); });
$('textarea').on('keyup focus', function(e) { $('textarea').on('keyup focus', function(e) {
while($(this).outerHeight() < this.scrollHeight + parseFloat($(this).css("borderTopWidth")) + parseFloat($(this).css("borderBottomWidth"))) { $(this).height(0).height(this.scrollHeight-18);
$(this).height($(this).height()+1);
};
}); });
} }
function createInvoiceModel() { function createInvoiceModel() {

View File

@ -296,40 +296,6 @@ function InvoiceModel(data) {
} }
}) })
self.wrapped_terms = ko.computed({
read: function() {
return this.terms();
},
write: function(value) {
value = wordWrapText(value, 300);
self.terms(value);
},
owner: this
});
self.wrapped_notes = ko.computed({
read: function() {
return this.public_notes();
},
write: function(value) {
value = wordWrapText(value, 300);
self.public_notes(value);
},
owner: this
});
self.wrapped_footer = ko.computed({
read: function() {
return this.invoice_footer();
},
write: function(value) {
value = wordWrapText(value, 600);
self.invoice_footer(value);
},
owner: this
});
self.removeItem = function(item) { self.removeItem = function(item) {
self.invoice_items.remove(item); self.invoice_items.remove(item);
refreshPDF(true); refreshPDF(true);
@ -745,18 +711,6 @@ function ItemModel(data) {
ko.mapping.fromJS(data, {}, this); ko.mapping.fromJS(data, {}, this);
} }
self.wrapped_notes = ko.computed({
read: function() {
return this.notes();
},
write: function(value) {
value = wordWrapText(value, 235);
self.notes(value);
onItemChange();
},
owner: this
});
this.totals = ko.observable(); this.totals = ko.observable();
this.totals.rawTotal = ko.computed(function() { this.totals.rawTotal = ko.computed(function() {
@ -899,7 +853,7 @@ ko.bindingHandlers.productTypeahead = {
}; };
function checkInvoiceNumber() { function checkInvoiceNumber() {
var url = '{{ url('check_invoice_number') }}/' + $('#invoice_number').val(); var url = '{{ url('check_invoice_number') }}?invoice_number=' + encodeURIComponent($('#invoice_number').val());
$.get(url, function(data) { $.get(url, function(data) {
var isValid = data == '{{ RESULT_SUCCESS }}' ? true : false; var isValid = data == '{{ RESULT_SUCCESS }}' ? true : false;
if (isValid) { if (isValid) {

View File

@ -7,6 +7,7 @@
<div style="display:none"> <div style="display:none">
{!! Former::text('action') !!} {!! Former::text('action') !!}
{!! Former::text('public_id') !!} {!! Former::text('public_id') !!}
{!! Former::text('datatable')->value('true') !!}
</div> </div>
@can('create', 'invoice') @can('create', 'invoice')

View File

@ -1,5 +1,23 @@
@extends('payments.payment_method') @extends('payments.payment_method')
@section('head')
@parent
<script type="text/javascript">
$(function() {
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
return true;
});
});
</script>
@stop
@section('payment_details') @section('payment_details')
{!! Former::vertical_open($url) {!! Former::vertical_open($url)
@ -256,18 +274,4 @@
{!! Former::close() !!} {!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('.payment-form').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
return true;
});
});
</script>
@stop @stop

View File

@ -9,7 +9,7 @@
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}'); Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
$(function() { $(function() {
var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!}; var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!};
$('.payment-form').submit(function(event) { $('.payment-form').unbind('submit').submit(function(event) {
if($('[name=plaidAccountId]').length)return; if($('[name=plaidAccountId]').length)return;
var $form = $(this); var $form = $(this);

View File

@ -6,6 +6,7 @@
<script type="text/javascript" src="https://static.wepay.com/min/js/tokenization.v2.js"></script> <script type="text/javascript" src="https://static.wepay.com/min/js/tokenization.v2.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$("#state").attr('maxlength', '3');
var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!}; var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!};
WePay.set_endpoint('{{ WEPAY_ENVIRONMENT }}'); WePay.set_endpoint('{{ WEPAY_ENVIRONMENT }}');
var $form = $('.payment-form'); var $form = $('.payment-form');

View File

@ -1,9 +1,52 @@
@extends('header') @extends('header')
@section('head')
@parent
<script src="{{ asset('js/daterangepicker.min.js') }}" type="text/javascript"></script>
<link href="{{ asset('css/daterangepicker.css') }}" rel="stylesheet" type="text/css"/>
@stop
@section('content') @section('content')
@parent @parent
@include('accounts.nav', ['selected' => ACCOUNT_REPORTS, 'advanced' => true]) @include('accounts.nav', ['selected' => ACCOUNT_REPORTS, 'advanced' => true])
<script type="text/javascript">
$(function() {
var chartStartDate = moment("{{ $startDate }}");
var chartEndDate = moment("{{ $endDate }}");
// Initialize date range selector
function cb(start, end) {
$('#reportrange span').html(start.format('{{ $account->getMomentDateFormat() }}') + ' - ' + end.format('{{ $account->getMomentDateFormat() }}'));
$('#start_date').val(start.format('YYYY-MM-DD'));
$('#end_date').val(end.format('YYYY-MM-DD'));
}
$('#reportrange').daterangepicker({
locale: {
"format": "{{ $account->getMomentDateFormat() }}",
},
startDate: chartStartDate,
endDate: chartEndDate,
linkedCalendars: false,
ranges: {
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
}, cb);
cb(chartStartDate, chartEndDate);
});
</script>
{!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required'])->addClass('warn-on-exit') !!} {!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required'])->addClass('warn-on-exit') !!}
@ -24,13 +67,25 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{!! Former::text('start_date')->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT)) <div class="form-group">
->addGroupClass('start_date') <label for="reportrange" class="control-label col-lg-4 col-sm-4">
->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'start_date\')"></i>') !!} {{ trans('texts.date_range') }}
{!! Former::text('end_date')->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT)) </label>
->addGroupClass('end_date') <div class="col-lg-8 col-sm-8">
->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'end_date\')"></i>') !!} <div id="reportrange" style="background: #f9f9f9; cursor: pointer; padding: 9px 14px; border: 1px solid #dfe0e1; margin-top: 0px; margin-left:18px">
<i class="glyphicon glyphicon-calendar fa fa-calendar"></i>&nbsp;
<span></span> <b class="caret"></b>
</div>
<div style="display:none">
{!! Former::text('start_date') !!}
{!! Former::text('end_date') !!}
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p> <p>&nbsp;</p>
{!! Former::actions( {!! Former::actions(
Button::primary(trans('texts.export'))->withAttributes(array('onclick' => 'onExportClick()'))->appendIcon(Icon::create('export')), Button::primary(trans('texts.export'))->withAttributes(array('onclick' => 'onExportClick()'))->appendIcon(Icon::create('export')),

View File

@ -12,7 +12,7 @@
<style type="text/css"> <style type="text/css">
input.time-input { input.time-input {
width: 110px; width: 100%;
font-size: 14px !important; font-size: 14px !important;
} }

View File

@ -7,7 +7,7 @@
}, },
{ {
"stack": "$accountDetails", "stack": "$accountDetails",
"margin": [40, 0, 0, 0] "margin": [7, 0, 0, 0]
}, },
{ {
"stack": "$accountAddress" "stack": "$accountAddress"