mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
58ffb9b4a3
@ -1,6 +1,7 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use DB;
|
||||
use Mail;
|
||||
use Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@ -51,9 +52,12 @@ class CheckData extends Command {
|
||||
*/
|
||||
protected $description = 'Check/fix data';
|
||||
|
||||
protected $log = '';
|
||||
protected $isValid = true;
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running CheckData...');
|
||||
$this->logMessage(date('Y-m-d') . ' Running CheckData...');
|
||||
|
||||
if (!$this->option('client_id')) {
|
||||
$this->checkPaidToDate();
|
||||
@ -66,7 +70,21 @@ class CheckData extends Command {
|
||||
$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()
|
||||
@ -76,7 +94,11 @@ class CheckData extends Command {
|
||||
->where('json_backup', '=', '')
|
||||
->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()
|
||||
@ -131,7 +153,8 @@ class CheckData extends Command {
|
||||
->get(["{$table}.id", 'clients.account_id', 'clients.user_id']);
|
||||
|
||||
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') {
|
||||
foreach ($records as $record) {
|
||||
@ -161,7 +184,11 @@ class CheckData extends Command {
|
||||
->groupBy('clients.id')
|
||||
->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')]);
|
||||
$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') {
|
||||
foreach ($clients as $client) {
|
||||
@ -178,6 +205,7 @@ class CheckData extends Command {
|
||||
$clients = DB::table('clients')
|
||||
->join('invoices', 'invoices.client_id', '=', 'clients.id')
|
||||
->join('accounts', 'accounts.id', '=', 'clients.account_id')
|
||||
->where('accounts.id', '!=', 20432)
|
||||
->where('clients.is_deleted', '=', 0)
|
||||
->where('invoices.is_deleted', '=', 0)
|
||||
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
|
||||
@ -187,14 +215,18 @@ class CheckData extends Command {
|
||||
if ($this->option('client_id')) {
|
||||
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||
}
|
||||
|
||||
|
||||
$clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
|
||||
->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')]);
|
||||
$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) {
|
||||
$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;
|
||||
$lastBalance = 0;
|
||||
$lastAdjustment = 0;
|
||||
@ -204,7 +236,7 @@ class CheckData extends Command {
|
||||
->where('client_id', '=', $client->id)
|
||||
->orderBy('activities.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) {
|
||||
|
||||
@ -251,19 +283,19 @@ class CheckData extends Command {
|
||||
|
||||
// **Fix for ninja invoices which didn't have the invoice_type_id value set
|
||||
if ($noAdjustment && $client->account_id == 20432) {
|
||||
$this->info("No adjustment for ninja invoice");
|
||||
$this->logMessage("No adjustment for ninja invoice");
|
||||
$foundProblem = true;
|
||||
$clientFix += $invoice->amount;
|
||||
$activityFix = $invoice->amount;
|
||||
// **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) {
|
||||
$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;
|
||||
$clientFix += $invoice->amount;
|
||||
$activityFix = $invoice->amount;
|
||||
// **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)) {
|
||||
$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;
|
||||
$clientFix -= $activity->adjustment;
|
||||
$activityFix = 0;
|
||||
@ -271,7 +303,7 @@ class CheckData extends Command {
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
|
||||
// **Fix for updating balance when deleting a recurring invoice**
|
||||
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;
|
||||
if ($activity->balance != $lastBalance) {
|
||||
$clientFix -= $activity->adjustment;
|
||||
@ -281,7 +313,7 @@ class CheckData extends Command {
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
|
||||
// **Fix for updating balance when archiving an invoice**
|
||||
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;
|
||||
$activityFix = 0;
|
||||
$clientFix += $activity->adjustment;
|
||||
@ -289,12 +321,12 @@ class CheckData extends Command {
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
|
||||
// **Fix for updating balance when updating recurring invoice**
|
||||
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;
|
||||
$clientFix -= $activity->adjustment;
|
||||
$activityFix = 0;
|
||||
} 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;
|
||||
$clientFix -= $activity->adjustment;
|
||||
$activityFix = 0;
|
||||
@ -302,7 +334,7 @@ class CheckData extends Command {
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
|
||||
// **Fix for updating balance when updating a quote**
|
||||
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;
|
||||
$clientFix += $lastBalance - $activity->balance;
|
||||
$activityFix = 0;
|
||||
@ -310,7 +342,7 @@ class CheckData extends Command {
|
||||
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
|
||||
// **Fix for deleting payment after deleting invoice**
|
||||
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;
|
||||
$activityFix = 0;
|
||||
$clientFix -= $activity->adjustment;
|
||||
@ -339,7 +371,7 @@ class CheckData extends Command {
|
||||
}
|
||||
|
||||
if ($activity->balance + $clientFix != $client->actual_balance) {
|
||||
$this->info("** Creating 'recovered update' activity **");
|
||||
$this->logMessage("** Creating 'recovered update' activity **");
|
||||
if ($this->option('fix') == 'true') {
|
||||
DB::table('activities')->insert([
|
||||
'created_at' => new Carbon,
|
||||
@ -353,7 +385,7 @@ class CheckData extends Command {
|
||||
}
|
||||
|
||||
$data = ['balance' => $client->actual_balance];
|
||||
$this->info("Corrected balance:{$client->actual_balance}");
|
||||
$this->logMessage("Corrected balance:{$client->actual_balance}");
|
||||
if ($this->option('fix') == 'true') {
|
||||
DB::table('clients')
|
||||
->where('id', $client->id)
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Utils;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
|
||||
@ -20,4 +21,20 @@ class BaseController extends Controller
|
||||
$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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,10 +221,6 @@ class ClientController extends BaseController
|
||||
$message = Utils::pluralize($action.'d_client', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('clients/'.Utils::getFirst($ids));
|
||||
} else {
|
||||
return Redirect::to('clients');
|
||||
}
|
||||
return $this->returnBulk(ENTITY_CLIENT, $action, $ids);
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,12 @@ class DashboardApiController extends BaseAPIController
|
||||
|
||||
$data = [
|
||||
'id' => 1,
|
||||
'paidToDate' => $paidToDate[0]->value ? $paidToDate[0]->value : 0,
|
||||
'paidToDateCurrency' => $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0,
|
||||
'balances' => $balances[0]->value ? $balances[0]->value : 0,
|
||||
'balancesCurrency' => $balances[0]->currency_id ? $balances[0]->currency_id : 0,
|
||||
'averageInvoice' => (count($averageInvoice) && $averageInvoice[0]->invoice_avg) ? $averageInvoice[0]->invoice_avg : 0,
|
||||
'averageInvoiceCurrency' => $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0,
|
||||
'paidToDate' => count($paidToDate) && $paidToDate[0]->value ? $paidToDate[0]->value : 0,
|
||||
'paidToDateCurrency' => count($paidToDate) && $paidToDate[0]->currency_id ? $paidToDate[0]->currency_id : 0,
|
||||
'balances' => count($balances) && $balances[0]->value ? $balances[0]->value : 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,
|
||||
'averageInvoiceCurrency' => count($averageInvoice) && $averageInvoice[0]->currency_id ? $averageInvoice[0]->currency_id : 0,
|
||||
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
|
||||
'activeClients' => $metrics ? $metrics->active_clients : 0,
|
||||
'activities' => $this->createCollection($activities, new ActivityTransformer(), ENTITY_ACTIVITY),
|
||||
|
@ -245,7 +245,7 @@ class ExpenseController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('expenses');
|
||||
return $this->returnBulk($this->entityType, $action, $ids);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
|
@ -330,7 +330,7 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Tax rate $options
|
||||
$account = Auth::user()->account;
|
||||
$rates = TaxRate::scope()->orderBy('name')->get();
|
||||
@ -531,11 +531,7 @@ class InvoiceController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to("{$entityType}s/".Utils::getFirst($ids));
|
||||
} else {
|
||||
return Redirect::to("{$entityType}s");
|
||||
}
|
||||
return $this->returnBulk($entityType, $action, $ids);
|
||||
}
|
||||
|
||||
public function convertQuote(InvoiceRequest $request)
|
||||
@ -612,8 +608,10 @@ class InvoiceController extends BaseController
|
||||
return View::make('invoices.history', $data);
|
||||
}
|
||||
|
||||
public function checkInvoiceNumber($invoiceNumber)
|
||||
public function checkInvoiceNumber()
|
||||
{
|
||||
$invoiceNumber = request()->invoice_number;
|
||||
|
||||
$count = Invoice::scope()
|
||||
->whereInvoiceNumber($invoiceNumber)
|
||||
->withTrashed()
|
||||
|
@ -154,11 +154,7 @@ class QuoteController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('quotes/'.Utils::getFirst($ids));
|
||||
} else {
|
||||
return Redirect::to('quotes');
|
||||
}
|
||||
return $this->returnBulk(ENTITY_QUOTE, $action, $ids);
|
||||
}
|
||||
|
||||
public function approve($invitationKey)
|
||||
|
@ -11,6 +11,7 @@ use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Task;
|
||||
|
||||
/**
|
||||
* Class ReportController
|
||||
@ -56,8 +57,8 @@ class ReportController extends BaseController
|
||||
if (Input::all()) {
|
||||
$reportType = Input::get('report_type');
|
||||
$dateField = Input::get('date_field');
|
||||
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
|
||||
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
|
||||
$startDate = date_create(Input::get('start_date'));
|
||||
$endDate = date_create(Input::get('end_date'));
|
||||
} else {
|
||||
$reportType = ENTITY_INVOICE;
|
||||
$dateField = FILTER_INVOICE_DATE;
|
||||
@ -71,15 +72,17 @@ class ReportController extends BaseController
|
||||
ENTITY_PRODUCT => trans('texts.product'),
|
||||
ENTITY_PAYMENT => trans('texts.payment'),
|
||||
ENTITY_EXPENSE => trans('texts.expense'),
|
||||
ENTITY_TASK => trans('texts.task'),
|
||||
ENTITY_TAX_RATE => trans('texts.tax'),
|
||||
];
|
||||
|
||||
$params = [
|
||||
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'startDate' => $startDate->format('Y-m-d'),
|
||||
'endDate' => $endDate->format('Y-m-d'),
|
||||
'reportTypes' => $reportTypes,
|
||||
'reportType' => $reportType,
|
||||
'title' => trans('texts.charts_and_reports'),
|
||||
'account' => Auth::user()->account,
|
||||
];
|
||||
|
||||
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
||||
@ -120,9 +123,37 @@ class ReportController extends BaseController
|
||||
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
|
||||
} elseif ($reportType == ENTITY_EXPENSE) {
|
||||
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 $endDate
|
||||
|
@ -300,11 +300,7 @@ class TaskController extends BaseController
|
||||
$message = Utils::pluralize($action.'d_task', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('tasks/'.$ids[0].'/edit');
|
||||
} else {
|
||||
return Redirect::to('tasks');
|
||||
}
|
||||
return $this->returnBulk($this->entityType, $action, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ class VendorController extends BaseController
|
||||
public function show(VendorRequest $request)
|
||||
{
|
||||
$vendor = $request->entity();
|
||||
|
||||
|
||||
$actionLinks = [
|
||||
['label' => trans('texts.new_vendor'), 'url' => URL::to('/vendors/create/' . $vendor->public_id)]
|
||||
];
|
||||
@ -185,10 +185,6 @@ class VendorController extends BaseController
|
||||
$message = Utils::pluralize($action.'d_vendor', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('vendors/' . Utils::getFirst($ids));
|
||||
} else {
|
||||
return Redirect::to('vendors');
|
||||
}
|
||||
return $this->returnBulk($this->entityType, $action, $ids);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ class ApiCheck {
|
||||
{
|
||||
$loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register');
|
||||
$headers = Utils::getApiHeaders();
|
||||
$hasApiSecret = false;
|
||||
|
||||
if ($secret = env(API_SECRET)) {
|
||||
$hasApiSecret = hash_equals($request->api_secret ?: '', $secret);
|
||||
@ -34,7 +35,7 @@ class ApiCheck {
|
||||
// check API secret
|
||||
if ( ! $hasApiSecret) {
|
||||
sleep(ERROR_DELAY);
|
||||
return Response::json('Invalid secret', 403, $headers);
|
||||
return Response::json('Invalid value for API_SECRET', 403, $headers);
|
||||
}
|
||||
} else {
|
||||
// check for a valid token
|
||||
|
@ -13,7 +13,7 @@ class VerifyCsrfToken extends BaseVerifier
|
||||
* @var array
|
||||
*/
|
||||
private $openRoutes = [
|
||||
'complete',
|
||||
'complete/*',
|
||||
'signup/register',
|
||||
'api/v1/*',
|
||||
'api/v1/login',
|
||||
|
@ -127,7 +127,7 @@ Route::group(['middleware' => 'auth:user'], function() {
|
||||
Route::get('hide_message', 'HomeController@hideMessage');
|
||||
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
|
||||
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('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_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
|
||||
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_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
|
||||
|
@ -48,9 +48,15 @@ class HistoryUtils
|
||||
$entity = $activity->client;
|
||||
} else if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_TASK || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_TASK) {
|
||||
$entity = $activity->task;
|
||||
if ( ! $entity) {
|
||||
continue;
|
||||
}
|
||||
$entity->setRelation('client', $activity->client);
|
||||
} else {
|
||||
$entity = $activity->invoice;
|
||||
if ( ! $entity) {
|
||||
continue;
|
||||
}
|
||||
$entity->setRelation('client', $activity->client);
|
||||
}
|
||||
|
||||
|
@ -165,6 +165,14 @@ class Task extends EntityModel
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -184,6 +184,7 @@ trait PresentsInvoice
|
||||
'quote_to',
|
||||
'details',
|
||||
'invoice_no',
|
||||
'quote_no',
|
||||
'valid_until',
|
||||
'client_name',
|
||||
'address1',
|
||||
|
@ -557,16 +557,16 @@ class BasePaymentDriver
|
||||
$paymentMethod->setRelation('account_gateway_token', $customer);
|
||||
$paymentMethod = $this->creatingPaymentMethod($paymentMethod);
|
||||
|
||||
// archive the old payment method
|
||||
$oldPaymentMethod = PaymentMethod::clientId($this->client()->id)
|
||||
->wherePaymentTypeId($paymentMethod->payment_type_id)
|
||||
->first();
|
||||
|
||||
if ($oldPaymentMethod) {
|
||||
$oldPaymentMethod->delete();
|
||||
}
|
||||
|
||||
if ($paymentMethod) {
|
||||
// archive the old payment method
|
||||
$oldPaymentMethod = PaymentMethod::clientId($this->client()->id)
|
||||
->wherePaymentTypeId($paymentMethod->payment_type_id)
|
||||
->first();
|
||||
|
||||
if ($oldPaymentMethod) {
|
||||
$oldPaymentMethod->delete();
|
||||
}
|
||||
|
||||
$paymentMethod->save();
|
||||
}
|
||||
|
||||
@ -833,8 +833,8 @@ class BasePaymentDriver
|
||||
return true;
|
||||
}
|
||||
|
||||
$accountGatewaySettings = AccountGatewaySettings::scope()->where('account_gateway_settings.gateway_type_id',
|
||||
'=', $gatewayTypeId)->first();
|
||||
$accountGatewaySettings = AccountGatewaySettings::scope(false, $this->invitation->account_id)
|
||||
->where('account_gateway_settings.gateway_type_id', '=', $gatewayTypeId)->first();
|
||||
|
||||
if ($accountGatewaySettings) {
|
||||
$invoice = $this->invoice();
|
||||
|
@ -21,6 +21,11 @@ class TaskPresenter extends EntityPresenter
|
||||
return $this->entity->user->getDisplayName();
|
||||
}
|
||||
|
||||
public function description()
|
||||
{
|
||||
return substr($this->entity->description, 0, 40) . (strlen($this->entity->description) > 40 ? '...' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $account
|
||||
* @return mixed
|
||||
|
@ -78,7 +78,6 @@ class ClientRepository extends BaseRepository
|
||||
$client = Client::createNew();
|
||||
} else {
|
||||
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
|
||||
\Log::warning('Entity not set in client repo save');
|
||||
}
|
||||
|
||||
// convert currency code to id
|
||||
|
@ -68,7 +68,6 @@ class TaskRepository
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
$task = Task::scope($publicId)->firstOrFail();
|
||||
\Log::warning('Entity not set in task repo save');
|
||||
} else {
|
||||
$task = Task::createNew();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class ActivityTransformer extends EntityTransformer
|
||||
return [
|
||||
'id' => $activity->key(),
|
||||
'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,
|
||||
'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null,
|
||||
'payment_id' => $activity->payment ? $activity->payment->public_id : null,
|
||||
|
@ -36,3 +36,11 @@ Want to find out everything there is to know about how to use your Invoice Ninja
|
||||
data_visualizations
|
||||
api_tokens
|
||||
user_management
|
||||
|
||||
.. _self_host:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Self Host
|
||||
|
||||
iphone_app
|
||||
|
46
docs/iphone_app.rst
Normal file
46
docs/iphone_app.rst
Normal 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
2
public/css/built.css
vendored
2
public/css/built.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
9549
public/css/built.public.css
vendored
9549
public/css/built.public.css
vendored
File diff suppressed because one or more lines are too long
271
public/css/daterangepicker.css
vendored
271
public/css/daterangepicker.css
vendored
File diff suppressed because one or more lines are too long
10530
public/js/Chart.min.js
vendored
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
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
1621
public/js/daterangepicker.min.js
vendored
1621
public/js/daterangepicker.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
74412
public/pdf.built.js
74412
public/pdf.built.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -44,6 +44,7 @@
|
||||
.twitter-typeahead .tt-suggestion {
|
||||
padding: 3px 20px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*Added to menu element when it contains no content*/
|
||||
@ -68,4 +69,4 @@
|
||||
/*Added to the element that wraps highlighted text*/
|
||||
.twitter-typeahead .tt-highlight {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +210,11 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
||||
if (invoice.partial > 0 && field == 'balance_due') {
|
||||
field = 'partial_due';
|
||||
} else if (invoice.is_quote) {
|
||||
field = field.replace('invoice', 'quote');
|
||||
if (field == 'due_date') {
|
||||
field = 'valid_until';
|
||||
} else {
|
||||
field = field.replace('invoice', 'quote');
|
||||
}
|
||||
}
|
||||
var label = invoiceLabels[field];
|
||||
if (match.indexOf('UC') >= 0) {
|
||||
|
@ -643,6 +643,7 @@ $LANG = array(
|
||||
'custom' => 'Custom',
|
||||
'invoice_to' => 'Invoice to',
|
||||
'invoice_no' => 'Invoice No.',
|
||||
'quote_no' => 'Quote No.',
|
||||
'recent_payments' => 'Recent Payments',
|
||||
'outstanding' => 'Outstanding',
|
||||
'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_paid' => 'I\'ll email you when it\'s paid.',
|
||||
'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_code' => 'Thanks! I\'ve sent a you an email with your security code.',
|
||||
'bot_welcome' => 'That\'s it, your account is verified.<br/>',
|
||||
@ -2129,6 +2130,11 @@ $LANG = array(
|
||||
'max' => 'Max',
|
||||
'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;
|
||||
|
@ -37,7 +37,7 @@
|
||||
{{ Session::get("show_trash:gateway") ? 'checked' : ''}}/> {{ trans('texts.show_archived_deleted')}} {{ Utils::transFlowText('gateways') }}
|
||||
</label>
|
||||
-->
|
||||
|
||||
|
||||
@if ($showAdd)
|
||||
{!! Button::primary(trans('texts.add_gateway'))
|
||||
->asLinkTo(URL::to('/gateways/create'))
|
||||
@ -72,39 +72,41 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row" style="text-align:center">
|
||||
<div class="col-xs-12">
|
||||
<div id="payment-limits-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div id="payment-limit-min-container">
|
||||
<label for="payment-limit-min">{{ trans('texts.min') }}</label><br>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{ $currency->symbol }}</span>
|
||||
<input type="number" class="form-control" min="0" id="payment-limit-min"
|
||||
name="limit_min">
|
||||
</div>
|
||||
<label><input type="checkbox" id="payment-limit-min-enable"
|
||||
name="limit_min_enable"> {{ trans('texts.enable_min') }}</label>
|
||||
<div class="panel-body">
|
||||
<div class="row" style="text-align:center">
|
||||
<div class="col-xs-12">
|
||||
<div id="payment-limits-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="payment-limit-max-container">
|
||||
<label for="payment-limit-max">{{ trans('texts.max') }}</label><br>
|
||||
</div><br/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div id="payment-limit-min-container">
|
||||
<label for="payment-limit-min">{{ trans('texts.min') }}</label><br>
|
||||
<div class="input-group" style="padding-bottom:8px">
|
||||
<span class="input-group-addon">{{ $currency->symbol }}</span>
|
||||
<input type="number" class="form-control" min="0" id="payment-limit-min"
|
||||
name="limit_min">
|
||||
</div>
|
||||
<label><input type="checkbox" id="payment-limit-min-enable"
|
||||
name="limit_min_enable"> {{ trans('texts.enable_min') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="payment-limit-max-container">
|
||||
<label for="payment-limit-max">{{ trans('texts.max') }}</label><br>
|
||||
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{ $currency->symbol }}</span>
|
||||
<input type="number" class="form-control" min="0" id="payment-limit-max"
|
||||
name="limit_max">
|
||||
<div class="input-group" style="padding-bottom:8px">
|
||||
<span class="input-group-addon">{{ $currency->symbol }}</span>
|
||||
<input type="number" class="form-control" min="0" id="payment-limit-max"
|
||||
name="limit_max">
|
||||
</div>
|
||||
<label><input type="checkbox" id="payment-limit-max-enable"
|
||||
name="limit_max_enable"> {{ trans('texts.enable_max') }}</label>
|
||||
</div>
|
||||
<label><input type="checkbox" id="payment-limit-max-enable"
|
||||
name="limit_max_enable"> {{ trans('texts.enable_max') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="gateway_type_id" id="payment-limit-gateway-type">
|
||||
</div>
|
||||
<input type="hidden" name="gateway_type_id" id="payment-limit-gateway-type">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
@ -168,24 +170,24 @@
|
||||
});
|
||||
|
||||
limitsSlider.noUiSlider.on('update', function (values, handle) {
|
||||
var value = values[handle];
|
||||
var value = Math.round(values[handle]);
|
||||
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);
|
||||
} 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').on('change keyup', function () {
|
||||
$('#payment-limit-min').on('change input', function () {
|
||||
setTimeout(function () {
|
||||
limitsSlider.noUiSlider.set([$('#payment-limit-min').val(), null]);
|
||||
}, 100);
|
||||
$('#payment-limit-min-enable').attr('checked', 'checked');
|
||||
});
|
||||
|
||||
$('#payment-limit-max').on('change keyup', function () {
|
||||
$('#payment-limit-max').on('change input', function () {
|
||||
setTimeout(function () {
|
||||
limitsSlider.noUiSlider.set([null, $('#payment-limit-max').val()]);
|
||||
}, 100);
|
||||
|
@ -64,10 +64,11 @@
|
||||
</div>
|
||||
<p> <p/>
|
||||
<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])
|
||||
</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() !!}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -104,6 +104,25 @@
|
||||
</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">×</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-dialog" style="min-width:150px">
|
||||
@ -184,7 +203,6 @@
|
||||
}
|
||||
|
||||
function serverPreview(field) {
|
||||
console.log(field);
|
||||
$('#templatePreviewModal').modal('show');
|
||||
var template = $('#email_template_' + field).val();
|
||||
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>
|
||||
|
||||
@stop
|
||||
|
@ -45,4 +45,14 @@
|
||||
@include('export.payments')
|
||||
@endif
|
||||
|
||||
</html>
|
||||
@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>
|
||||
|
@ -9,10 +9,10 @@
|
||||
<td>{{ trans('texts.phone') }}</td>
|
||||
</tr>
|
||||
|
||||
@foreach ($contacts as $contact)
|
||||
@if (!$contact->client->is_deleted)
|
||||
@foreach ($vendor_contacts as $contact)
|
||||
@if (!$contact->vendor->is_deleted)
|
||||
<tr>
|
||||
<td>{{ $contact->client->getDisplayName() }}</td>
|
||||
<td>{{ $contact->vendor->getDisplayName() }}</td>
|
||||
@if ($multiUser)
|
||||
<td>{{ $contact->user->getDisplayName() }}</td>
|
||||
@endif
|
||||
@ -24,4 +24,4 @@
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<tr><td></td></tr>
|
||||
<tr><td></td></tr>
|
||||
|
@ -3,43 +3,27 @@
|
||||
@if ($multiUser)
|
||||
<td>{{ trans('texts.user') }}</td>
|
||||
@endif
|
||||
<td>{{ trans('texts.balance') }}</td>
|
||||
<td>{{ trans('texts.paid_to_date') }}</td>
|
||||
<td>{{ trans('texts.address1') }}</td>
|
||||
<td>{{ trans('texts.address2') }}</td>
|
||||
<td>{{ trans('texts.city') }}</td>
|
||||
<td>{{ trans('texts.state') }}</td>
|
||||
<td>{{ trans('texts.postal_code') }}</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>
|
||||
|
||||
@foreach ($clients as $client)
|
||||
@foreach ($vendors as $vendor)
|
||||
<tr>
|
||||
<td>{{ $client->getDisplayName() }}</td>
|
||||
<td>{{ $vendor->getDisplayName() }}</td>
|
||||
@if ($multiUser)
|
||||
<td>{{ $client->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>
|
||||
<td>{{ $vendor->user->getDisplayName() }}</td>
|
||||
@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>
|
||||
@endforeach
|
||||
|
||||
<tr><td></td></tr>
|
||||
<tr><td></td></tr>
|
||||
|
@ -199,8 +199,11 @@
|
||||
}
|
||||
|
||||
window.loadedSearchData = false;
|
||||
function onSearchBlur() {
|
||||
$('#search').typeahead('val', '');
|
||||
}
|
||||
|
||||
function onSearchFocus() {
|
||||
$('#search').typeahead('val', '');
|
||||
$('#search-form').show();
|
||||
|
||||
if (!window.loadedSearchData) {
|
||||
@ -319,6 +322,7 @@
|
||||
|
||||
// Focus the search input if the user clicks forward slash
|
||||
$('#search').focusin(onSearchFocus);
|
||||
$('#search').blur(onSearchBlur);
|
||||
|
||||
$('body').keypress(function(event) {
|
||||
if (event.which == 47 && !$('*:focus').length) {
|
||||
|
@ -262,8 +262,8 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<textarea data-bind="value: wrapped_notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}"
|
||||
rows="1" cols="60" style="resize: vertical" class="form-control word-wrap"></textarea>
|
||||
<textarea data-bind="value: notes, valueUpdate: 'afterkeydown', attr: {name: 'invoice_items[' + $index() + '][notes]'}"
|
||||
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: expense_public_id, attr: {name: 'invoice_items[' + $index() + '][expense_public_id]'}" style="display: none"/>
|
||||
</td>
|
||||
@ -340,11 +340,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: wrapped_notes, valueUpdate: 'afterkeydown'")
|
||||
{!! Former::textarea('public_notes')->data_bind("value: public_notes, valueUpdate: 'afterkeydown'")
|
||||
->label(null)->style('resize: none; width: 500px;')->rows(4) !!}
|
||||
</div>
|
||||
<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)
|
||||
->help('<div class="checkbox">
|
||||
<label>
|
||||
@ -356,7 +356,7 @@
|
||||
</div>') !!}
|
||||
</div>
|
||||
<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)
|
||||
->help('<div class="checkbox">
|
||||
<label>
|
||||
@ -1108,10 +1108,9 @@
|
||||
});
|
||||
|
||||
$('textarea').on('keyup focus', function(e) {
|
||||
while($(this).outerHeight() < this.scrollHeight + parseFloat($(this).css("borderTopWidth")) + parseFloat($(this).css("borderBottomWidth"))) {
|
||||
$(this).height($(this).height()+1);
|
||||
};
|
||||
$(this).height(0).height(this.scrollHeight-18);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function createInvoiceModel() {
|
||||
|
@ -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.invoice_items.remove(item);
|
||||
refreshPDF(true);
|
||||
@ -745,18 +711,6 @@ function ItemModel(data) {
|
||||
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.rawTotal = ko.computed(function() {
|
||||
@ -899,7 +853,7 @@ ko.bindingHandlers.productTypeahead = {
|
||||
};
|
||||
|
||||
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) {
|
||||
var isValid = data == '{{ RESULT_SUCCESS }}' ? true : false;
|
||||
if (isValid) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
<div style="display:none">
|
||||
{!! Former::text('action') !!}
|
||||
{!! Former::text('public_id') !!}
|
||||
{!! Former::text('datatable')->value('true') !!}
|
||||
</div>
|
||||
|
||||
@can('create', 'invoice')
|
||||
@ -88,7 +89,7 @@
|
||||
@endif
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function submitForm(action) {
|
||||
|
@ -1,5 +1,23 @@
|
||||
@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')
|
||||
|
||||
{!! Former::vertical_open($url)
|
||||
@ -256,18 +274,4 @@
|
||||
|
||||
{!! 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
|
||||
|
@ -9,7 +9,7 @@
|
||||
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
|
||||
$(function() {
|
||||
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;
|
||||
|
||||
var $form = $(this);
|
||||
|
@ -6,6 +6,7 @@
|
||||
<script type="text/javascript" src="https://static.wepay.com/min/js/tokenization.v2.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$("#state").attr('maxlength', '3');
|
||||
var countries = {!! Cache::get('countries')->pluck('iso_3166_2','id') !!};
|
||||
WePay.set_endpoint('{{ WEPAY_ENVIRONMENT }}');
|
||||
var $form = $('.payment-form');
|
||||
|
@ -1,9 +1,52 @@
|
||||
@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')
|
||||
@parent
|
||||
@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') !!}
|
||||
|
||||
@ -24,13 +67,25 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
{!! Former::text('start_date')->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))
|
||||
->addGroupClass('start_date')
|
||||
->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'start_date\')"></i>') !!}
|
||||
{!! Former::text('end_date')->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))
|
||||
->addGroupClass('end_date')
|
||||
->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'end_date\')"></i>') !!}
|
||||
<div class="form-group">
|
||||
<label for="reportrange" class="control-label col-lg-4 col-sm-4">
|
||||
{{ trans('texts.date_range') }}
|
||||
</label>
|
||||
<div class="col-lg-8 col-sm-8">
|
||||
<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>
|
||||
<span></span> <b class="caret"></b>
|
||||
</div>
|
||||
|
||||
<div style="display:none">
|
||||
{!! Former::text('start_date') !!}
|
||||
{!! Former::text('end_date') !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
{!! Former::actions(
|
||||
Button::primary(trans('texts.export'))->withAttributes(array('onclick' => 'onExportClick()'))->appendIcon(Icon::create('export')),
|
||||
|
@ -12,7 +12,7 @@
|
||||
<style type="text/css">
|
||||
|
||||
input.time-input {
|
||||
width: 110px;
|
||||
width: 100%;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
{
|
||||
"stack": "$accountDetails",
|
||||
"margin": [40, 0, 0, 0]
|
||||
"margin": [7, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
"stack": "$accountAddress"
|
||||
@ -215,4 +215,4 @@
|
||||
}
|
||||
},
|
||||
"pageMargins": [40, 40, 40, 60]
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user