mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-11 05:32:39 +01:00
Merge branch 'release-2.8.0'
This commit is contained in:
commit
b620356db1
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,6 +32,7 @@ Thumbs.db
|
||||
/.project
|
||||
tests/_output/
|
||||
tests/_bootstrap.php
|
||||
tests/_support/_generated/
|
||||
|
||||
# composer stuff
|
||||
/c3.php
|
||||
|
@ -65,6 +65,7 @@ before_script:
|
||||
- sleep 5
|
||||
# Make sure the app is up-to-date
|
||||
- curl -L http://ninja.dev:8000/update
|
||||
#- php artisan ninja:create-test-data 25
|
||||
|
||||
script:
|
||||
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
|
||||
|
36
CHANGELOG.md
36
CHANGELOG.md
@ -1,36 +0,0 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Auto billing uses credits if they exist
|
||||
|
||||
|
||||
## [2.6.4] - 2016-07-19
|
||||
|
||||
### Added
|
||||
- Added 'Buy Now' buttons
|
||||
|
||||
### Fixed
|
||||
- Setting default tax rate breaks invoice creation #974
|
||||
|
||||
|
||||
## [2.6] - 2016-07-12
|
||||
|
||||
### Added
|
||||
- Configuration for first day of the week #950
|
||||
- StyleCI configuration #929
|
||||
- Added expense category
|
||||
|
||||
### Changed
|
||||
- Removed `invoiceninja.komodoproject` from Git #932
|
||||
- `APP_CIPHER` changed from `rinjdael-128` to `AES-256-CBC` #898
|
||||
- Improved options when exporting data
|
||||
|
||||
### Fixed
|
||||
- "Manual entry" untranslatable #562
|
||||
- Using a database table prefix breaks the dashboard #203
|
||||
- Request statically called in StartupCheck.php #977
|
@ -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()
|
||||
@ -75,9 +93,14 @@ class CheckData extends Command {
|
||||
->where('activity_type_id', '=', 5)
|
||||
->where('json_backup', '=', '')
|
||||
->whereNotIn('id', [634386, 756352, 756353, 756356])
|
||||
->whereNotIn('id', [634386, 756352, 756353, 756356, 820872])
|
||||
->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()
|
||||
@ -132,7 +155,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) {
|
||||
@ -162,7 +186,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) {
|
||||
@ -179,6 +207,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)
|
||||
@ -192,10 +221,14 @@ class CheckData extends Command {
|
||||
$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;
|
||||
@ -205,7 +238,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) {
|
||||
|
||||
@ -252,19 +285,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;
|
||||
@ -272,7 +305,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;
|
||||
@ -282,7 +315,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;
|
||||
@ -290,12 +323,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;
|
||||
@ -303,7 +336,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;
|
||||
@ -311,7 +344,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;
|
||||
@ -340,7 +373,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,
|
||||
@ -354,7 +387,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)
|
||||
|
28
app/Events/TaskWasArchived.php
Normal file
28
app/Events/TaskWasArchived.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TaskWasArchived
|
||||
*/
|
||||
class TaskWasArchived extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Task $task
|
||||
*/
|
||||
public function __construct(Task $task)
|
||||
{
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
}
|
28
app/Events/TaskWasDeleted.php
Normal file
28
app/Events/TaskWasDeleted.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TaskWasDeleted
|
||||
*/
|
||||
class TaskWasDeleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Task $task
|
||||
*/
|
||||
public function __construct(Task $task)
|
||||
{
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
}
|
29
app/Events/TaskWasRestored.php
Normal file
29
app/Events/TaskWasRestored.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class TaskWasRestored
|
||||
*/
|
||||
class TaskWasRestored extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Task $task
|
||||
*/
|
||||
public function __construct(Task $task)
|
||||
{
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<?php namespace App\Exceptions;
|
||||
|
||||
use Braintree\Util;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Redirect;
|
||||
use Utils;
|
||||
use Exception;
|
||||
@ -69,7 +71,7 @@ class Handler extends ExceptionHandler
|
||||
{
|
||||
if ($e instanceof ModelNotFoundException) {
|
||||
return Redirect::to('/');
|
||||
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) {
|
||||
} if ($e instanceof \Illuminate\Session\TokenMismatchException) {
|
||||
// prevent loop since the page auto-submits
|
||||
if ($request->path() != 'get_started') {
|
||||
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
|
||||
@ -82,6 +84,40 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
}
|
||||
|
||||
if($this->isHttpException($e))
|
||||
{
|
||||
switch ($e->getStatusCode())
|
||||
{
|
||||
// not found
|
||||
case 404:
|
||||
if($request->header('X-Ninja-Token') != '') {
|
||||
//API request which has hit a route which does not exist
|
||||
|
||||
$error['error'] = ['message'=>'Route does not exist'];
|
||||
$error = json_encode($error, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return response()->make($error, 404, $headers);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
// internal error
|
||||
case '500':
|
||||
if($request->header('X-Ninja-Token') != '') {
|
||||
//API request which produces 500 error
|
||||
|
||||
$error['error'] = ['message'=>'Internal Server Error'];
|
||||
$error = json_encode($error, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return response()->make($error, 500, $headers);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// In production, except for maintenance mode, we'll show a custom error screen
|
||||
if (Utils::isNinjaProd()
|
||||
&& !Utils::isDownForMaintenance()
|
||||
|
@ -4,6 +4,9 @@ use Auth;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Cache;
|
||||
use Socialite;
|
||||
use Exception;
|
||||
use App\Services\AuthService;
|
||||
use App\Models\Account;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use Illuminate\Http\Request;
|
||||
@ -181,4 +184,30 @@ class AccountApiController extends BaseAPIController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function oauthLogin(Request $request)
|
||||
{
|
||||
$user = false;
|
||||
$token = $request->input('token');
|
||||
$provider = $request->input('provider');
|
||||
|
||||
try {
|
||||
$user = Socialite::driver($provider)->userFromToken($token);
|
||||
} catch (Exception $exception) {
|
||||
return $this->errorResponse(['message' => $exception->getMessage()], 401);
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$providerId = AuthService::getProviderId($provider);
|
||||
$user = $this->accountRepo->findUserByOauth($providerId, $user->id);
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
Auth::login($user);
|
||||
return $this->processLogin($request);
|
||||
} else {
|
||||
sleep(ERROR_DELAY);
|
||||
return $this->errorResponse(['message' => 'Invalid credentials'], 401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AccountGateway;
|
||||
use App\Models\AccountGatewaySettings;
|
||||
use App\Models\GatewayType;
|
||||
use App\Services\TemplateService;
|
||||
use Auth;
|
||||
use File;
|
||||
@ -165,6 +167,10 @@ class AccountController extends BaseController
|
||||
$term = Input::get('plan_term');
|
||||
$numUsers = Input::get('num_users');
|
||||
|
||||
if ($plan != PLAN_ENTERPRISE) {
|
||||
$numUsers = 1;
|
||||
}
|
||||
|
||||
$planDetails = $account->getPlanDetails(false, false);
|
||||
|
||||
$newPlan = [
|
||||
@ -193,7 +199,9 @@ class AccountController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$hasPaid = false;
|
||||
if (!empty($planDetails['paid']) && $plan != PLAN_FREE) {
|
||||
$hasPaid = true;
|
||||
$time_used = $planDetails['paid']->diff(date_create());
|
||||
$days_used = $time_used->days;
|
||||
|
||||
@ -209,7 +217,11 @@ class AccountController extends BaseController
|
||||
|
||||
if ($newPlan['price'] > $credit) {
|
||||
$invitation = $this->accountRepo->enablePlan($newPlan, $credit);
|
||||
return Redirect::to('view/' . $invitation->invitation_key);
|
||||
if ($hasPaid) {
|
||||
return Redirect::to('view/' . $invitation->invitation_key);
|
||||
} else {
|
||||
return Redirect::to('payment/' . $invitation->invitation_key);
|
||||
}
|
||||
} else {
|
||||
|
||||
if ($plan != PLAN_FREE) {
|
||||
@ -417,6 +429,7 @@ class AccountController extends BaseController
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'title' => trans('texts.localization'),
|
||||
'weekdays' => Utils::getTranslatedWeekdayNames(),
|
||||
'months' => Utils::getMonthOptions(),
|
||||
];
|
||||
|
||||
return View::make('accounts.localization', $data);
|
||||
@ -458,10 +471,12 @@ class AccountController extends BaseController
|
||||
}
|
||||
|
||||
return View::make('accounts.payments', [
|
||||
'showAdd' => $count < count(Gateway::$alternate) + 1,
|
||||
'title' => trans('texts.online_payments'),
|
||||
'showAdd' => $count < count(Gateway::$alternate) + 1,
|
||||
'title' => trans('texts.online_payments'),
|
||||
'tokenBillingOptions' => $tokenBillingOptions,
|
||||
'account' => $account,
|
||||
'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY),
|
||||
'currencies'),
|
||||
'account' => $account,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -471,16 +486,9 @@ class AccountController extends BaseController
|
||||
*/
|
||||
private function showProducts()
|
||||
{
|
||||
$columns = ['product', 'description', 'unit_cost'];
|
||||
if (Auth::user()->account->invoice_item_taxes) {
|
||||
$columns[] = 'tax_rate';
|
||||
}
|
||||
$columns[] = 'action';
|
||||
|
||||
$data = [
|
||||
'account' => Auth::user()->account,
|
||||
'title' => trans('texts.product_library'),
|
||||
'columns' => Utils::trans($columns),
|
||||
];
|
||||
|
||||
return View::make('accounts.products', $data);
|
||||
@ -667,11 +675,9 @@ class AccountController extends BaseController
|
||||
* @param $section
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function doSection($section = ACCOUNT_COMPANY_DETAILS)
|
||||
public function doSection($section)
|
||||
{
|
||||
if ($section === ACCOUNT_COMPANY_DETAILS) {
|
||||
return AccountController::saveDetails();
|
||||
} elseif ($section === ACCOUNT_LOCALIZATION) {
|
||||
if ($section === ACCOUNT_LOCALIZATION) {
|
||||
return AccountController::saveLocalization();
|
||||
} elseif ($section == ACCOUNT_PAYMENTS) {
|
||||
return self::saveOnlinePayments();
|
||||
@ -697,9 +703,27 @@ class AccountController extends BaseController
|
||||
return AccountController::saveTaxRates();
|
||||
} elseif ($section === ACCOUNT_PAYMENT_TERMS) {
|
||||
return AccountController::savePaymetTerms();
|
||||
} elseif ($section === ACCOUNT_MANAGEMENT) {
|
||||
return AccountController::saveAccountManagement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
private function saveAccountManagement()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$modules = Input::get('modules');
|
||||
|
||||
$account->enabled_modules = $modules ? array_sum($modules) : 0;
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
||||
return Redirect::to('settings/'.ACCOUNT_MANAGEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
@ -723,12 +747,7 @@ class AccountController extends BaseController
|
||||
private function saveClientPortal()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->enable_client_portal = !!Input::get('enable_client_portal');
|
||||
$account->enable_client_portal_dashboard = !!Input::get('enable_client_portal_dashboard');
|
||||
$account->enable_portal_password = !!Input::get('enable_portal_password');
|
||||
$account->send_portal_password = !!Input::get('send_portal_password');
|
||||
$account->enable_buy_now_buttons = !!Input::get('enable_buy_now_buttons');
|
||||
$account->fill(Input::all());
|
||||
|
||||
// Only allowed for pro Invoice Ninja users or white labeled self-hosted users
|
||||
if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) {
|
||||
@ -1200,6 +1219,7 @@ class AccountController extends BaseController
|
||||
$account->military_time = Input::get('military_time') ? true : false;
|
||||
$account->show_currency_code = Input::get('show_currency_code') ? true : false;
|
||||
$account->start_of_week = Input::get('start_of_week') ? Input::get('start_of_week') : 0;
|
||||
$account->financial_year_start = Input::get('financial_year_start') ? Input::get('financial_year_start') : null;
|
||||
$account->save();
|
||||
|
||||
event(new UserSettingsChanged());
|
||||
@ -1226,6 +1246,35 @@ class AccountController extends BaseController
|
||||
return Redirect::to('settings/'.ACCOUNT_PAYMENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function savePaymentGatewayLimits()
|
||||
{
|
||||
$gateway_type_id = intval(Input::get('gateway_type_id'));
|
||||
$gateway_settings = AccountGatewaySettings::scope()->where('gateway_type_id', '=', $gateway_type_id)->first();
|
||||
|
||||
if ( ! $gateway_settings) {
|
||||
$gateway_settings = AccountGatewaySettings::createNew();
|
||||
$gateway_settings->gateway_type_id = $gateway_type_id;
|
||||
}
|
||||
|
||||
$gateway_settings->min_limit = Input::get('limit_min_enable') ? intval(Input::get('limit_min')) : null;
|
||||
$gateway_settings->max_limit = Input::get('limit_max_enable') ? intval(Input::get('limit_max')) : null;
|
||||
|
||||
if ($gateway_settings->max_limit !== null && $gateway_settings->min_limit > $gateway_settings->max_limit) {
|
||||
$gateway_settings->max_limit = $gateway_settings->min_limit;
|
||||
}
|
||||
|
||||
$gateway_settings->save();
|
||||
|
||||
event(new UserSettingsChanged());
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
|
@ -48,8 +48,10 @@ class AccountGatewayController extends BaseController
|
||||
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
|
||||
$config = $accountGateway->getConfig();
|
||||
|
||||
foreach ($config as $field => $value) {
|
||||
$config->$field = str_repeat('*', strlen($value));
|
||||
if ($accountGateway->gateway_id != GATEWAY_CUSTOM) {
|
||||
foreach ($config as $field => $value) {
|
||||
$config->$field = str_repeat('*', strlen($value));
|
||||
}
|
||||
}
|
||||
|
||||
$data = self::getViewModel($accountGateway);
|
||||
@ -60,8 +62,6 @@ class AccountGatewayController extends BaseController
|
||||
$data['hiddenFields'] = Gateway::$hiddenFields;
|
||||
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
|
||||
|
||||
$this->testGateway($accountGateway);
|
||||
|
||||
return View::make('accounts.account_gateway', $data);
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ class AccountGatewayController extends BaseController
|
||||
|
||||
if ($otherProviders) {
|
||||
$availableGatewaysIds = $account->availableGatewaysIds();
|
||||
$data['primaryGateways'] = Gateway::primary($availableGatewaysIds)->orderBy('name', 'desc')->get();
|
||||
$data['primaryGateways'] = Gateway::primary($availableGatewaysIds)->orderBy('sort_order')->get();
|
||||
$data['secondaryGateways'] = Gateway::secondary($availableGatewaysIds)->orderBy('name')->get();
|
||||
$data['hiddenFields'] = Gateway::$hiddenFields;
|
||||
|
||||
@ -132,7 +132,9 @@ class AccountGatewayController extends BaseController
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
$fields = $gateway->getFields();
|
||||
asort($fields);
|
||||
if ( ! $gateway->isCustom()) {
|
||||
asort($fields);
|
||||
}
|
||||
$gateway->fields = $gateway->id == GATEWAY_WEPAY ? [] : $fields;
|
||||
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
|
||||
$accountGateway->fields = $gateway->fields;
|
||||
@ -247,6 +249,8 @@ class AccountGatewayController extends BaseController
|
||||
}
|
||||
if (!$value && ($field == 'testMode' || $field == 'developerMode')) {
|
||||
// do nothing
|
||||
} elseif ($gatewayId == GATEWAY_CUSTOM) {
|
||||
$config->$field = strip_tags($value);
|
||||
} else {
|
||||
$config->$field = $value;
|
||||
}
|
||||
@ -312,14 +316,17 @@ class AccountGatewayController extends BaseController
|
||||
if (isset($wepayResponse)) {
|
||||
return $wepayResponse;
|
||||
} else {
|
||||
$this->testGateway($accountGateway);
|
||||
|
||||
if ($accountGatewayPublicId) {
|
||||
$message = trans('texts.updated_gateway');
|
||||
Session::flash('message', $message);
|
||||
return Redirect::to("gateways/{$accountGateway->public_id}/edit");
|
||||
} else {
|
||||
$message = trans('texts.created_gateway');
|
||||
Session::flash('message', $message);
|
||||
return Redirect::to("/settings/online_payments");
|
||||
}
|
||||
|
||||
Session::flash('message', $message);
|
||||
return Redirect::to("gateways/{$accountGateway->public_id}/edit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,24 @@ class BaseController extends Controller
|
||||
$this->layout = View::make($this->layout);
|
||||
}
|
||||
}
|
||||
|
||||
protected function returnBulk($entityType, $action, $ids)
|
||||
{
|
||||
if ( ! is_array($ids)) {
|
||||
$ids = [$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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ClientRequest;
|
||||
use Response;
|
||||
use Input;
|
||||
use App\Models\Client;
|
||||
@ -52,6 +53,31 @@ class ClientApiController extends BaseAPIController
|
||||
return $this->listResponse($clients);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/clients/{client_id}",
|
||||
* summary="Individual Client",
|
||||
* tags={"client"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A single client",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
public function show(ClientRequest $request)
|
||||
{
|
||||
return $this->itemResponse($request->entity());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/clients",
|
||||
|
@ -95,7 +95,7 @@ class ClientController extends BaseController
|
||||
if($user->can('create', ENTITY_TASK)){
|
||||
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
|
||||
}
|
||||
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_INVOICE)) {
|
||||
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_QUOTE)) {
|
||||
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\ActivityRepository;
|
||||
use App\Ninja\Repositories\DocumentRepository;
|
||||
use App\Ninja\Repositories\CreditRepository;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Services\PaymentService;
|
||||
@ -33,13 +34,14 @@ class ClientPortalController extends BaseController
|
||||
private $paymentRepo;
|
||||
private $documentRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService)
|
||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService, CreditRepository $creditRepo)
|
||||
{
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
$this->activityRepo = $activityRepo;
|
||||
$this->documentRepo = $documentRepo;
|
||||
$this->paymentService = $paymentService;
|
||||
$this->creditRepo = $creditRepo;
|
||||
}
|
||||
|
||||
public function view($invitationKey)
|
||||
@ -102,7 +104,9 @@ class ClientPortalController extends BaseController
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes) == 1) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
if ($paymentTypes[0]['gatewayTypeId'] == GATEWAY_TYPE_CUSTOM) {
|
||||
// do nothing
|
||||
} elseif (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
$paymentURL = URL::to($paymentURL);
|
||||
}
|
||||
}
|
||||
@ -141,7 +145,12 @@ class ClientPortalController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
if ($accountGateway = $account->getGatewayByType(GATEWAY_TYPE_CUSTOM)) {
|
||||
$data += [
|
||||
'customGatewayName' => $accountGateway->getConfigField('name'),
|
||||
'customGatewayText' => $accountGateway->getConfigField('text'),
|
||||
];
|
||||
}
|
||||
|
||||
if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){
|
||||
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
|
||||
@ -155,18 +164,6 @@ class ClientPortalController extends BaseController
|
||||
return View::make('invoices.view', $data);
|
||||
}
|
||||
|
||||
public function contactIndex($contactKey) {
|
||||
if (!$contact = Contact::where('contact_key', '=', $contactKey)->first()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$client = $contact->client;
|
||||
|
||||
Session::put('contact_key', $contactKey);// track current contact
|
||||
|
||||
return redirect()->to($client->account->enable_client_portal_dashboard?'/client/dashboard':'/client/invoices/');
|
||||
}
|
||||
|
||||
private function getPaymentTypes($account, $client, $invitation)
|
||||
{
|
||||
$links = [];
|
||||
@ -201,19 +198,41 @@ class ClientPortalController extends BaseController
|
||||
return $pdfString;
|
||||
}
|
||||
|
||||
public function dashboard()
|
||||
public function sign($invitationKey)
|
||||
{
|
||||
if (!$contact = $this->getContact()) {
|
||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return RESULT_FAILURE;
|
||||
}
|
||||
|
||||
$invitation->signature_base64 = Input::get('signature');
|
||||
$invitation->signature_date = date_create();
|
||||
$invitation->save();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
public function dashboard($contactKey = false)
|
||||
{
|
||||
if ($contactKey) {
|
||||
if (!$contact = Contact::where('contact_key', '=', $contactKey)->first()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
Session::put('contact_key', $contactKey);// track current contact
|
||||
} else if (!$contact = $this->getContact()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$client = $contact->client;
|
||||
$account = $client->account;
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
$customer = false;
|
||||
|
||||
if (!$account->enable_client_portal || !$account->enable_client_portal_dashboard) {
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
} elseif (!$account->enable_client_portal_dashboard) {
|
||||
return redirect()->to('/client/invoices/');
|
||||
}
|
||||
|
||||
if ($paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN)) {
|
||||
@ -273,6 +292,7 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
@ -300,6 +320,7 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
@ -346,12 +367,14 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
@ -416,12 +439,14 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
@ -444,6 +469,42 @@ class ClientPortalController extends BaseController
|
||||
return $this->invoiceRepo->getClientDatatable($contact->id, ENTITY_QUOTE, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
public function creditIndex()
|
||||
{
|
||||
if (!$contact = $this->getContact()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.credits'),
|
||||
'entityType' => ENTITY_CREDIT,
|
||||
'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance']),
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
}
|
||||
|
||||
public function creditDatatable()
|
||||
{
|
||||
if (!$contact = $this->getContact()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->creditRepo->getClientDatatable($contact->client_id);
|
||||
}
|
||||
|
||||
public function documentIndex()
|
||||
{
|
||||
if (!$contact = $this->getContact()) {
|
||||
@ -451,12 +512,14 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
$account->loadLocalizationSettings($contact->client);
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
|
@ -23,8 +23,8 @@ class DashboardApiController extends BaseAPIController
|
||||
|
||||
$dashboardRepo = $this->dashboardRepo;
|
||||
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
|
||||
$paidToDate = $dashboardRepo->paidToDate($accountId, $userId, $viewAll);
|
||||
$averageInvoice = $dashboardRepo->averages($accountId, $userId, $viewAll);
|
||||
$paidToDate = $dashboardRepo->paidToDate($user->account, $userId, $viewAll);
|
||||
$averageInvoice = $dashboardRepo->averages($user->account, $userId, $viewAll);
|
||||
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
|
||||
$activities = $dashboardRepo->activities($accountId, $userId, $viewAll);
|
||||
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
|
||||
|
@ -33,9 +33,9 @@ class DashboardController extends BaseController
|
||||
|
||||
$dashboardRepo = $this->dashboardRepo;
|
||||
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
|
||||
$paidToDate = $dashboardRepo->paidToDate($accountId, $userId, $viewAll);
|
||||
$averageInvoice = $dashboardRepo->averages($accountId, $userId, $viewAll);
|
||||
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
|
||||
$paidToDate = $dashboardRepo->paidToDate($account, $userId, $viewAll);
|
||||
$averageInvoice = $dashboardRepo->averages($account, $userId, $viewAll);
|
||||
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
|
||||
$activities = $dashboardRepo->activities($accountId, $userId, $viewAll);
|
||||
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
|
||||
$upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll);
|
||||
|
@ -1,8 +1,12 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Ninja\Repositories\ExpenseRepository;
|
||||
use App\Services\ExpenseService;
|
||||
use App\Http\Requests\ExpenseRequest;
|
||||
use App\Http\Requests\CreateExpenseRequest;
|
||||
use App\Http\Requests\UpdateExpenseRequest;
|
||||
|
||||
class ExpenseApiController extends BaseAPIController
|
||||
{
|
||||
@ -20,6 +24,22 @@ class ExpenseApiController extends BaseAPIController
|
||||
$this->expenseService = $expenseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/expenses",
|
||||
* summary="List of expenses",
|
||||
* tags={"expense"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with expenses",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Expense"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$expenses = Expense::scope()
|
||||
@ -30,23 +50,103 @@ class ExpenseApiController extends BaseAPIController
|
||||
return $this->listResponse($expenses);
|
||||
}
|
||||
|
||||
public function update()
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/expenses",
|
||||
* tags={"expense"},
|
||||
* summary="Create a expense",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Expense")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New expense",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(CreateExpenseRequest $request)
|
||||
{
|
||||
//stub
|
||||
$expense = $this->expenseRepo->save($request->input());
|
||||
|
||||
$expense = Expense::scope($expense->public_id)
|
||||
->with('client', 'invoice', 'vendor')
|
||||
->first();
|
||||
|
||||
return $this->itemResponse($expense);
|
||||
}
|
||||
|
||||
public function store()
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/expenses/{expense_id}",
|
||||
* tags={"expense"},
|
||||
* summary="Update a expense",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Expense")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Update expense",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateExpenseRequest $request, $publicId)
|
||||
{
|
||||
//stub
|
||||
if ($request->action) {
|
||||
return $this->handleAction($request);
|
||||
}
|
||||
|
||||
$data = $request->input();
|
||||
$data['public_id'] = $publicId;
|
||||
$expense = $this->expenseRepo->save($data, $request->entity());
|
||||
|
||||
return $this->itemResponse($expense);
|
||||
}
|
||||
|
||||
public function destroy()
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/expenses/{expense_id}",
|
||||
* tags={"expense"},
|
||||
* summary="Delete a expense",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Expense")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Delete expense",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function destroy(ExpenseRequest $request)
|
||||
{
|
||||
//stub
|
||||
$expense = $request->entity();
|
||||
|
||||
$this->expenseRepo->delete($expense);
|
||||
|
||||
return $this->itemResponse($expense);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
41
app/Http/Controllers/ExpenseCategoryApiController.php
Normal file
41
app/Http/Controllers/ExpenseCategoryApiController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use View;
|
||||
use Utils;
|
||||
use Input;
|
||||
use Session;
|
||||
use App\Services\ExpenseCategoryService;
|
||||
use App\Http\Requests\CreateExpenseCategoryRequest;
|
||||
use App\Http\Requests\UpdateExpenseCategoryRequest;
|
||||
use App\Ninja\Repositories\ExpenseCategoryRepository;
|
||||
|
||||
|
||||
class ExpenseCategoryApiController extends BaseAPIController
|
||||
{
|
||||
protected $categoryRepo;
|
||||
protected $categoryService;
|
||||
protected $entityType = ENTITY_EXPENSE_CATEGORY;
|
||||
|
||||
public function __construct(ExpenseCategoryRepository $categoryRepo, ExpenseCategoryService $categoryService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->categoryRepo = $categoryRepo;
|
||||
$this->categoryService = $categoryService;
|
||||
}
|
||||
|
||||
public function update(UpdateExpenseCategoryRequest $request)
|
||||
{
|
||||
$category = $this->categoryRepo->save($request->input(), $request->entity());
|
||||
|
||||
return $this->itemResponse($category);
|
||||
}
|
||||
|
||||
public function store(CreateExpenseCategoryRequest $request)
|
||||
{
|
||||
$category = $this->categoryRepo->save($request->input());
|
||||
|
||||
return $this->itemResponse($category);
|
||||
|
||||
}
|
||||
}
|
@ -134,6 +134,7 @@ class ExpenseController extends BaseController
|
||||
$data = [
|
||||
'vendor' => null,
|
||||
'expense' => $expense,
|
||||
'entity' => $expense,
|
||||
'method' => 'PUT',
|
||||
'url' => 'expenses/'.$expense->public_id,
|
||||
'title' => 'Edit Expense',
|
||||
@ -245,7 +246,7 @@ class ExpenseController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('expenses');
|
||||
return $this->returnBulk($this->entityType, $action, $ids);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
|
@ -38,7 +38,7 @@ class HomeController extends BaseController
|
||||
public function showIndex()
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
|
||||
if (!Utils::isNinja() && (!Utils::isDatabaseSetup() || Account::count() == 0)) {
|
||||
return Redirect::to('/setup');
|
||||
} elseif (Auth::check()) {
|
||||
@ -76,10 +76,8 @@ class HomeController extends BaseController
|
||||
}
|
||||
|
||||
// Track the referral/campaign code
|
||||
foreach (['rc', 'utm_campaign'] as $code) {
|
||||
if (Input::has($code)) {
|
||||
Session::set(SESSION_REFERRAL_CODE, Input::get($code));
|
||||
}
|
||||
if (Input::has('rc')) {
|
||||
Session::set(SESSION_REFERRAL_CODE, Input::get('rc'));
|
||||
}
|
||||
|
||||
if (Auth::check()) {
|
||||
@ -115,7 +113,7 @@ class HomeController extends BaseController
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Session::forget('news_feed_message');
|
||||
|
||||
return 'success';
|
||||
|
@ -31,6 +31,11 @@ class ImportController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! count($files)) {
|
||||
Session::flash('error', trans('texts.select_file'));
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($source === IMPORT_CSV) {
|
||||
$data = $this->importService->mapCSV($files);
|
||||
|
@ -176,7 +176,7 @@ class InvoiceApiController extends BaseAPIController
|
||||
if (isset($data['email_invoice']) && $data['email_invoice']) {
|
||||
if ($payment) {
|
||||
$this->mailer->sendPaymentConfirmation($payment);
|
||||
} else {
|
||||
} elseif ( ! $invoice->is_recurring) {
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +218,7 @@ class InvoiceController extends BaseController
|
||||
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
|
||||
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
|
||||
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
|
||||
$contact->invitation_signature_svg = $invitation->signatureDiv();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,6 +270,9 @@ class InvoiceController extends BaseController
|
||||
private static function getViewModel($invoice)
|
||||
{
|
||||
$recurringHelp = '';
|
||||
$recurringDueDateHelp = '';
|
||||
$recurringDueDates = [];
|
||||
|
||||
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
|
||||
$parts = explode('=>', $line);
|
||||
if (count($parts) > 1) {
|
||||
@ -279,7 +283,6 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$recurringDueDateHelp = '';
|
||||
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_due_date_help')) as $line) {
|
||||
$parts = explode('=>', $line);
|
||||
if (count($parts) > 1) {
|
||||
@ -409,10 +412,10 @@ class InvoiceController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'email') {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
$this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
return url($invoice->getRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -435,14 +438,14 @@ class InvoiceController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'clone') {
|
||||
return $this->cloneInvoice($request, $invoice->public_id);
|
||||
return url(sprintf('%ss/%s/clone', $entityType, $invoice->public_id));
|
||||
} elseif ($action == 'convert') {
|
||||
return $this->convertQuote($request, $invoice->public_id);
|
||||
} elseif ($action == 'email') {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
$this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
return url($invoice->getRoute());
|
||||
}
|
||||
|
||||
|
||||
@ -469,8 +472,6 @@ class InvoiceController extends BaseController
|
||||
} else {
|
||||
Session::flash('error', $response);
|
||||
}
|
||||
|
||||
return Redirect::to("{$entityType}s/{$invoice->public_id}/edit");
|
||||
}
|
||||
|
||||
private function emailRecurringInvoice(&$invoice)
|
||||
@ -527,11 +528,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)
|
||||
@ -540,7 +537,7 @@ class InvoiceController extends BaseController
|
||||
|
||||
Session::flash('message', trans('texts.converted_to_invoice'));
|
||||
|
||||
return Redirect::to('invoices/' . $clone->public_id);
|
||||
return url('invoices/' . $clone->public_id);
|
||||
}
|
||||
|
||||
public function cloneInvoice(InvoiceRequest $request, $publicId)
|
||||
@ -608,12 +605,19 @@ class InvoiceController extends BaseController
|
||||
return View::make('invoices.history', $data);
|
||||
}
|
||||
|
||||
public function checkInvoiceNumber($invoiceNumber)
|
||||
public function checkInvoiceNumber($invoicePublicId = false)
|
||||
{
|
||||
$count = Invoice::scope()
|
||||
$invoiceNumber = request()->invoice_number;
|
||||
|
||||
$query = Invoice::scope()
|
||||
->whereInvoiceNumber($invoiceNumber)
|
||||
->withTrashed()
|
||||
->count();
|
||||
->withTrashed();
|
||||
|
||||
if ($invoicePublicId) {
|
||||
$query->where('public_id', '!=', $invoicePublicId);
|
||||
}
|
||||
|
||||
$count = $query->count();
|
||||
|
||||
return $count ? RESULT_FAILURE : RESULT_SUCCESS;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use App\Http\Requests\CreateOnlinePaymentRequest;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Services\InvoiceService;
|
||||
use App\Models\GatewayType;
|
||||
|
||||
/**
|
||||
* Class OnlinePaymentController
|
||||
@ -60,7 +61,7 @@ class OnlinePaymentController extends BaseController
|
||||
* @param bool $sourceId
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function showPayment($invitationKey, $gatewayType = false, $sourceId = false)
|
||||
public function showPayment($invitationKey, $gatewayTypeAlias = false, $sourceId = false)
|
||||
{
|
||||
if ( ! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return response()->view('error', [
|
||||
@ -69,17 +70,23 @@ class OnlinePaymentController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
if ( ! floatval($invitation->invoice->balance)) {
|
||||
if ( ! $invitation->invoice->canBePaid()) {
|
||||
return redirect()->to('view/' . $invitation->invitation_key);
|
||||
}
|
||||
|
||||
$invitation = $invitation->load('invoice.client.account.account_gateways.gateway');
|
||||
$account = $invitation->account;
|
||||
$account->loadLocalizationSettings($invitation->invoice->client);
|
||||
|
||||
if ( ! $gatewayType) {
|
||||
$gatewayType = Session::get($invitation->id . 'gateway_type');
|
||||
if ( ! $gatewayTypeAlias) {
|
||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||
} elseif ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
|
||||
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||
} else {
|
||||
$gatewayTypeId = $gatewayTypeAlias;
|
||||
}
|
||||
|
||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
|
||||
$paymentDriver = $account->paymentDriver($invitation, $gatewayTypeId);
|
||||
|
||||
try {
|
||||
return $paymentDriver->startPurchase(Input::all(), $sourceId);
|
||||
@ -95,8 +102,12 @@ class OnlinePaymentController extends BaseController
|
||||
public function doPayment(CreateOnlinePaymentRequest $request)
|
||||
{
|
||||
$invitation = $request->invitation;
|
||||
$gatewayType = Session::get($invitation->id . 'gateway_type');
|
||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
|
||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
|
||||
|
||||
if ( ! $invitation->invoice->canBePaid()) {
|
||||
return redirect()->to('view/' . $invitation->invitation_key);
|
||||
}
|
||||
|
||||
try {
|
||||
$paymentDriver->completeOnsitePurchase($request->all());
|
||||
@ -114,17 +125,24 @@ class OnlinePaymentController extends BaseController
|
||||
|
||||
/**
|
||||
* @param bool $invitationKey
|
||||
* @param bool $gatewayType
|
||||
* @param mixed $gatewayTypeAlias
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function offsitePayment($invitationKey = false, $gatewayType = false)
|
||||
public function offsitePayment($invitationKey = false, $gatewayTypeAlias = false)
|
||||
{
|
||||
$invitationKey = $invitationKey ?: Session::get('invitation_key');
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')
|
||||
->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
|
||||
$gatewayType = $gatewayType ?: Session::get($invitation->id . 'gateway_type');
|
||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType);
|
||||
if ( ! $gatewayTypeAlias) {
|
||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||
} elseif ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
|
||||
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||
} else {
|
||||
$gatewayTypeId = $gatewayTypeAlias;
|
||||
}
|
||||
|
||||
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
|
||||
|
||||
if ($error = Input::get('error_description') ?: Input::get('error')) {
|
||||
return $this->error($paymentDriver, $error);
|
||||
@ -228,7 +246,7 @@ class OnlinePaymentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function handleBuyNow(ClientRepository $clientRepo, InvoiceService $invoiceService, $gatewayType = false)
|
||||
public function handleBuyNow(ClientRepository $clientRepo, InvoiceService $invoiceService, $gatewayTypeAlias = false)
|
||||
{
|
||||
if (Crawler::isCrawler()) {
|
||||
return redirect()->to(NINJA_WEB_URL, 301);
|
||||
@ -267,6 +285,8 @@ class OnlinePaymentController extends BaseController
|
||||
|
||||
$data = [
|
||||
'client_id' => $client->id,
|
||||
'tax_rate1' => $account->default_tax_rate ? $account->default_tax_rate->rate : 0,
|
||||
'tax_name1' => $account->default_tax_rate ? $account->default_tax_rate->name : '',
|
||||
'invoice_items' => [[
|
||||
'product_key' => $product->product_key,
|
||||
'notes' => $product->notes,
|
||||
@ -280,8 +300,8 @@ class OnlinePaymentController extends BaseController
|
||||
$invitation = $invoice->invitations[0];
|
||||
$link = $invitation->getLink();
|
||||
|
||||
if ($gatewayType) {
|
||||
return redirect()->to($invitation->getLink('payment') . "/{$gatewayType}");
|
||||
if ($gatewayTypeAlias) {
|
||||
return redirect()->to($invitation->getLink('payment') . "/{$gatewayTypeAlias}");
|
||||
} else {
|
||||
return redirect()->to($invitation->getLink());
|
||||
}
|
||||
|
@ -142,11 +142,13 @@ class PaymentController extends BaseController
|
||||
'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false)
|
||||
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
|
||||
'payment' => $payment,
|
||||
'entity' => $payment,
|
||||
'method' => 'PUT',
|
||||
'url' => 'payments/'.$payment->public_id,
|
||||
'title' => trans('texts.edit_payment'),
|
||||
'paymentTypes' => Cache::get('paymentTypes'),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ];
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
];
|
||||
|
||||
return View::make('payments.edit', $data);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use Auth;
|
||||
use URL;
|
||||
use View;
|
||||
use Utils;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
@ -37,15 +38,40 @@ class ProductController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
|
||||
$columns = [
|
||||
'checkbox',
|
||||
'product',
|
||||
'description',
|
||||
'unit_cost'
|
||||
];
|
||||
|
||||
if (Auth::user()->account->invoice_item_taxes) {
|
||||
$columns[] = 'tax_rate';
|
||||
}
|
||||
$columns[] = 'action';
|
||||
|
||||
return View::make('list', [
|
||||
'entityType' => ENTITY_PRODUCT,
|
||||
'title' => trans('texts.products'),
|
||||
'sortCol' => '4',
|
||||
'columns' => Utils::trans($columns),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show($publicId)
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to("products/$publicId/edit");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->productService->getDatatable(Auth::user()->account_id);
|
||||
return $this->productService->getDatatable(Auth::user()->account_id, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,11 +81,13 @@ class ProductController extends BaseController
|
||||
public function edit($publicId)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$product = Product::scope($publicId)->withTrashed()->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'account' => $account,
|
||||
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
|
||||
'product' => Product::scope($publicId)->firstOrFail(),
|
||||
'product' => $product,
|
||||
'entity' => $product,
|
||||
'method' => 'PUT',
|
||||
'url' => 'products/'.$publicId,
|
||||
'title' => trans('texts.edit_product'),
|
||||
@ -111,7 +139,7 @@ class ProductController extends BaseController
|
||||
private function save($productPublicId = false)
|
||||
{
|
||||
if ($productPublicId) {
|
||||
$product = Product::scope($productPublicId)->firstOrFail();
|
||||
$product = Product::scope($productPublicId)->withTrashed()->firstOrFail();
|
||||
} else {
|
||||
$product = Product::createNew();
|
||||
}
|
||||
@ -134,12 +162,12 @@ class ProductController extends BaseController
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->productService->bulk($ids, $action);
|
||||
|
||||
Session::flash('message', trans('texts.archived_product'));
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
|
||||
return $this->returnBulk(ENTITY_PRODUCT, $action, $ids);
|
||||
}
|
||||
}
|
||||
|
@ -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::get('report_type')) {
|
||||
$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
|
||||
@ -354,7 +385,7 @@ class ReportController extends BaseController
|
||||
$isExport ? $client->getDisplayName() : $client->present()->link,
|
||||
$isExport ? $invoice->invoice_number : $invoice->present()->link,
|
||||
$invoice->present()->invoice_date,
|
||||
$invoiceItem->qty,
|
||||
round($invoiceItem->qty, 2),
|
||||
$invoiceItem->product_key,
|
||||
];
|
||||
//$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0);
|
||||
@ -433,36 +464,31 @@ class ReportController extends BaseController
|
||||
*/
|
||||
private function generateExpenseReport($startDate, $endDate, $isExport)
|
||||
{
|
||||
$columns = ['vendor', 'client', 'date', 'expense_amount', 'invoiced_amount'];
|
||||
$columns = ['vendor', 'client', 'date', 'expense_amount'];
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$displayData = [];
|
||||
$reportTotals = [];
|
||||
|
||||
$expenses = Expense::scope()
|
||||
->withTrashed()
|
||||
->withArchived()
|
||||
->with('client.contacts', 'vendor')
|
||||
->where('expense_date', '>=', $startDate)
|
||||
->where('expense_date', '<=', $endDate);
|
||||
|
||||
|
||||
foreach ($expenses->get() as $expense) {
|
||||
$amount = $expense->amount;
|
||||
$invoiced = $expense->present()->invoiced_amount;
|
||||
$amount = $expense->amountWithTax();
|
||||
|
||||
$displayData[] = [
|
||||
$expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '',
|
||||
$expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '',
|
||||
$expense->present()->expense_date,
|
||||
Utils::formatMoney($amount, $expense->currency_id),
|
||||
Utils::formatMoney($invoiced, $expense->invoice_currency_id),
|
||||
];
|
||||
|
||||
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount);
|
||||
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0);
|
||||
|
||||
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'invoiced', $invoiced);
|
||||
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'invoiced', 0);
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Codedge\Updater\UpdaterManager;
|
||||
|
||||
use App\Http\Requests;
|
||||
use Utils;
|
||||
use Redirect;
|
||||
use Codedge\Updater\UpdaterManager;
|
||||
|
||||
class SelfUpdateController extends BaseController
|
||||
{
|
||||
@ -21,6 +20,10 @@ class SelfUpdateController extends BaseController
|
||||
*/
|
||||
public function __construct(UpdaterManager $updater)
|
||||
{
|
||||
if (Utils::isNinjaProd()) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->updater = $updater;
|
||||
}
|
||||
|
||||
|
@ -177,15 +177,14 @@ class TaskController extends BaseController
|
||||
|
||||
$data = [
|
||||
'task' => $task,
|
||||
'entity' => $task,
|
||||
'clientPublicId' => $task->client ? $task->client->public_id : 0,
|
||||
'method' => 'PUT',
|
||||
'url' => 'tasks/'.$task->public_id,
|
||||
'title' => trans('texts.edit_task'),
|
||||
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
|
||||
'actions' => $actions,
|
||||
'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE,
|
||||
'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(),
|
||||
//'entityStatus' => $task->present()->status,
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
@ -295,16 +294,12 @@ class TaskController extends BaseController
|
||||
return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data);
|
||||
}
|
||||
} else {
|
||||
$count = $this->taskRepo->bulk($ids, $action);
|
||||
$count = $this->taskService->bulk($ids, $action);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,9 @@ class UserController extends BaseController
|
||||
public function edit($publicId)
|
||||
{
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $publicId)->firstOrFail();
|
||||
->where('public_id', '=', $publicId)
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'user' => $user,
|
||||
@ -157,7 +159,9 @@ class UserController extends BaseController
|
||||
|
||||
if ($userPublicId) {
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)->firstOrFail();
|
||||
->where('public_id', '=', $userPublicId)
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
|
||||
$rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
|
||||
} else {
|
||||
@ -334,7 +338,13 @@ class UserController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to($referer);
|
||||
// If the user is looking at an entity redirect to the dashboard
|
||||
preg_match('/\/[0-9*][\/edit]*$/', $referer, $matches);
|
||||
if (count($matches)) {
|
||||
return Redirect::to('/dashboard');
|
||||
} else {
|
||||
return Redirect::to($referer);
|
||||
}
|
||||
}
|
||||
|
||||
public function unlinkAccount($userAccountId, $userId)
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
// vendor
|
||||
use App\Http\Requests\UpdateVendorRequest;
|
||||
use App\Http\Requests\VendorRequest;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
@ -83,4 +85,73 @@ class VendorApiController extends BaseAPIController
|
||||
|
||||
return $this->itemResponse($vendor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/vendors/{vendor_id}",
|
||||
* tags={"vendor"},
|
||||
* summary="Update a vendor",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Vendor")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Update vendor",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
public function update(UpdateVendorRequest $request, $publicId)
|
||||
{
|
||||
if ($request->action) {
|
||||
return $this->handleAction($request);
|
||||
}
|
||||
|
||||
$data = $request->input();
|
||||
$data['public_id'] = $publicId;
|
||||
$vendor = $this->vendorRepo->save($data, $request->entity());
|
||||
|
||||
$vendor->load(['vendor_contacts']);
|
||||
|
||||
return $this->itemResponse($vendor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/vendors/{vendor_id}",
|
||||
* tags={"vendor"},
|
||||
* summary="Delete a vendor",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Vendor")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Delete vendor",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
public function destroy(VendorRequest $request)
|
||||
{
|
||||
$vendor = $request->entity();
|
||||
|
||||
$this->vendorRepo->delete($vendor);
|
||||
|
||||
return $this->itemResponse($vendor);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ class Kernel extends HttpKernel {
|
||||
'App\Http\Middleware\VerifyCsrfToken',
|
||||
'App\Http\Middleware\DuplicateSubmissionCheck',
|
||||
'App\Http\Middleware\QueryLogging',
|
||||
'App\Http\Middleware\SessionDataCheckMiddleware',
|
||||
'App\Http\Middleware\StartupCheck',
|
||||
];
|
||||
|
||||
|
@ -23,18 +23,22 @@ class ApiCheck {
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register');
|
||||
$loggingIn = $request->is('api/v1/login')
|
||||
|| $request->is('api/v1/register')
|
||||
|| $request->is('api/v1/oauth_login');
|
||||
$headers = Utils::getApiHeaders();
|
||||
$hasApiSecret = false;
|
||||
|
||||
if ($secret = env(API_SECRET)) {
|
||||
$hasApiSecret = hash_equals($request->api_secret ?: '', $secret);
|
||||
$requestSecret = Request::header('X-Ninja-Secret') ?: ($request->api_secret ?: '');
|
||||
$hasApiSecret = hash_equals($requestSecret, $secret);
|
||||
}
|
||||
|
||||
if ($loggingIn) {
|
||||
// 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
|
||||
|
@ -15,8 +15,7 @@ class DuplicateSubmissionCheck
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
|
||||
if ($request->is('api/v1/*')) {
|
||||
if ($request->is('api/v1/*') || $request->is('documents')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ class QueryLogging
|
||||
// Enable query logging for development
|
||||
if (Utils::isNinjaDev()) {
|
||||
DB::enableQueryLog();
|
||||
$timeStart = microtime(true);
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
@ -32,7 +33,9 @@ class QueryLogging
|
||||
if (strstr($request->url(), '_debugbar') === false) {
|
||||
$queries = DB::getQueryLog();
|
||||
$count = count($queries);
|
||||
Log::info($request->method() . ' - ' . $request->url() . ": $count queries");
|
||||
$timeEnd = microtime(true);
|
||||
$time = $timeEnd - $timeStart;
|
||||
Log::info($request->method() . ' - ' . $request->url() . ": $count queries - " . $time);
|
||||
//Log::info($queries);
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Auth;
|
||||
use Session;
|
||||
|
||||
// https://arjunphp.com/laravel5-inactivity-idle-session-logout/
|
||||
class SessionDataCheckMiddleware {
|
||||
|
||||
/**
|
||||
* Check session data, if role is not valid logout the request
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$bag = Session::getMetadataBag();
|
||||
$max = env('IDLE_TIMEOUT_MINUTES', 6 * 60) * 60; // minute to second conversion
|
||||
$elapsed = time() - $bag->getLastUsed();
|
||||
|
||||
if ( ! $bag || $elapsed > $max) {
|
||||
$request->session()->flush();
|
||||
Auth::logout();
|
||||
$request->session()->flash('warning', trans('texts.inactive_logout'));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ use Event;
|
||||
use Schema;
|
||||
use App\Models\Language;
|
||||
use App\Models\InvoiceDesign;
|
||||
use App\Events\UserSettingsChanged;
|
||||
use App\Events\UserLoggedIn;
|
||||
use App\Libraries\CurlUtils;
|
||||
|
||||
/**
|
||||
@ -118,13 +118,13 @@ class StartupCheck
|
||||
|
||||
// Make sure the account/user localization settings are in the session
|
||||
if (Auth::check() && !Session::has(SESSION_TIMEZONE)) {
|
||||
Event::fire(new UserSettingsChanged());
|
||||
Event::fire(new UserLoggedIn());
|
||||
}
|
||||
|
||||
// Check if the user is claiming a license (ie, additional invoices, white label, etc.)
|
||||
if (isset($_SERVER['REQUEST_URI'])) {
|
||||
if ( ! Utils::isNinjaProd() && isset($_SERVER['REQUEST_URI'])) {
|
||||
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
|
||||
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id')) {
|
||||
if ( ! $claimingLicense && Input::has('license_key') && Input::has('product_id')) {
|
||||
$licenseKey = Input::get('license_key');
|
||||
$productId = Input::get('product_id');
|
||||
|
||||
@ -154,6 +154,8 @@ class StartupCheck
|
||||
$company->save();
|
||||
|
||||
Session::flash('message', trans('texts.bought_white_label'));
|
||||
} else {
|
||||
Session::flash('error', trans('texts.invalid_white_label_license'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,11 +36,11 @@ class EntityRequest extends Request {
|
||||
|
||||
$class = Utils::getEntityClass($this->entityType);
|
||||
|
||||
if (method_exists($class, 'trashed')) {
|
||||
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
|
||||
} else {
|
||||
$this->entity = $class::scope($publicId)->firstOrFail();
|
||||
}
|
||||
if (method_exists($class, 'trashed')) {
|
||||
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
|
||||
} else {
|
||||
$this->entity = $class::scope($publicId)->firstOrFail();
|
||||
}
|
||||
|
||||
return $this->entity;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
class UpdateTaxRateRequest extends TaxRateRequest
|
||||
{
|
||||
// Expenses
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
52
app/Http/ViewComposers/ClientPortalHeaderComposer.php
Normal file
52
app/Http/ViewComposers/ClientPortalHeaderComposer.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\ViewComposers;
|
||||
|
||||
use DB;
|
||||
use Cache;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Contact;
|
||||
|
||||
/**
|
||||
* ClientPortalHeaderComposer.php.
|
||||
*
|
||||
* @copyright See LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
class ClientPortalHeaderComposer
|
||||
{
|
||||
/**
|
||||
* Bind data to the view.
|
||||
*
|
||||
* @param View $view
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function compose(View $view)
|
||||
{
|
||||
$contactKey = session('contact_key');
|
||||
|
||||
if ( ! $contactKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$contact = Contact::where('contact_key', '=', $contactKey)
|
||||
->with('client')
|
||||
->first();
|
||||
|
||||
if ( ! $contact || $contact->is_deleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = $contact->client;
|
||||
|
||||
$hasDocuments = DB::table('invoices')
|
||||
->where('invoices.client_id', '=', $client->id)
|
||||
->whereNull('invoices.deleted_at')
|
||||
->join('documents', 'documents.invoice_id', '=', 'invoices.id')
|
||||
->count();
|
||||
|
||||
$view->with('hasQuotes', $client->quotes->count());
|
||||
$view->with('hasCredits', $client->creditsWithBalance->count());
|
||||
$view->with('hasDocuments', $hasDocuments);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Routes
|
||||
@ -39,6 +38,7 @@ Route::post('/get_started', 'AccountController@getStarted');
|
||||
Route::group(['middleware' => 'auth:client'], function() {
|
||||
Route::get('view/{invitation_key}', 'ClientPortalController@view');
|
||||
Route::get('download/{invitation_key}', 'ClientPortalController@download');
|
||||
Route::put('sign/{invitation_key}', 'ClientPortalController@sign');
|
||||
Route::get('view', 'HomeController@viewLogo');
|
||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||
Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment');
|
||||
@ -52,18 +52,19 @@ Route::group(['middleware' => 'auth:client'], function() {
|
||||
Route::post('client/payment_methods/default', 'ClientPortalController@setDefaultPaymentMethod');
|
||||
Route::post('client/payment_methods/{source_id}/remove', 'ClientPortalController@removePaymentMethod');
|
||||
Route::get('client/quotes', 'ClientPortalController@quoteIndex');
|
||||
Route::get('client/credits', 'ClientPortalController@creditIndex');
|
||||
Route::get('client/invoices', 'ClientPortalController@invoiceIndex');
|
||||
Route::get('client/invoices/recurring', 'ClientPortalController@recurringInvoiceIndex');
|
||||
Route::post('client/invoices/auto_bill', 'ClientPortalController@setAutoBill');
|
||||
Route::get('client/documents', 'ClientPortalController@documentIndex');
|
||||
Route::get('client/payments', 'ClientPortalController@paymentIndex');
|
||||
Route::get('client/dashboard', 'ClientPortalController@dashboard');
|
||||
Route::get('client/dashboard/{contact_key}', 'ClientPortalController@contactIndex');
|
||||
Route::get('client/dashboard/{contact_key?}', 'ClientPortalController@dashboard');
|
||||
Route::get('client/documents/js/{documents}/{filename}', 'ClientPortalController@getDocumentVFSJS');
|
||||
Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'ClientPortalController@getDocument');
|
||||
Route::get('client/documents/{invitation_key}/{filename?}', 'ClientPortalController@getInvoiceDocumentsZip');
|
||||
|
||||
Route::get('api/client.quotes', ['as'=>'api.client.quotes', 'uses'=>'ClientPortalController@quoteDatatable']);
|
||||
Route::get('api/client.credits', ['as'=>'api.client.credits', 'uses'=>'ClientPortalController@creditDatatable']);
|
||||
Route::get('api/client.invoices', ['as'=>'api.client.invoices', 'uses'=>'ClientPortalController@invoiceDatatable']);
|
||||
Route::get('api/client.recurring_invoices', ['as'=>'api.client.recurring_invoices', 'uses'=>'ClientPortalController@recurringInvoiceDatatable']);
|
||||
Route::get('api/client.documents', ['as'=>'api.client.documents', 'uses'=>'ClientPortalController@documentDatatable']);
|
||||
@ -128,11 +129,12 @@ 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('save_sidebar_state', 'UserController@saveSidebarState');
|
||||
Route::get('check_invoice_number/{invoice_id?}', 'InvoiceController@checkInvoiceNumber');
|
||||
Route::post('save_sidebar_state', 'UserController@saveSidebarState');
|
||||
|
||||
Route::get('settings/user_details', 'AccountController@showUserDetails');
|
||||
Route::post('settings/user_details', 'AccountController@saveUserDetails');
|
||||
Route::post('settings/payment_gateway_limits', 'AccountController@savePaymentGatewayLimits');
|
||||
Route::post('users/change_password', 'UserController@changePassword');
|
||||
|
||||
Route::resource('clients', 'ClientController');
|
||||
@ -186,6 +188,11 @@ Route::group(['middleware' => 'auth:user'], function() {
|
||||
Route::get('api/credits/{client_id?}', ['as'=>'api.credits', 'uses'=>'CreditController@getDatatable']);
|
||||
Route::post('credits/bulk', 'CreditController@bulk');
|
||||
|
||||
Route::get('api/products', ['as'=>'api.products', 'uses'=>'ProductController@getDatatable']);
|
||||
Route::resource('products', 'ProductController');
|
||||
Route::post('products/bulk', 'ProductController@bulk');
|
||||
|
||||
|
||||
Route::get('/resend_confirmation', 'AccountController@resendConfirmation');
|
||||
Route::post('/update_setup', 'AppController@updateSetup');
|
||||
|
||||
@ -228,10 +235,6 @@ Route::group([
|
||||
Route::resource('tokens', 'TokenController');
|
||||
Route::post('tokens/bulk', 'TokenController@bulk');
|
||||
|
||||
Route::get('api/products', ['as'=>'api.products', 'uses'=>'ProductController@getDatatable']);
|
||||
Route::resource('products', 'ProductController');
|
||||
Route::post('products/bulk', 'ProductController@bulk');
|
||||
|
||||
Route::get('api/tax_rates', ['as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable']);
|
||||
Route::resource('tax_rates', 'TaxRateController');
|
||||
Route::post('tax_rates/bulk', 'TaxRateController@bulk');
|
||||
@ -281,6 +284,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
|
||||
{
|
||||
Route::get('ping', 'AccountApiController@ping');
|
||||
Route::post('login', 'AccountApiController@login');
|
||||
Route::post('oauth_login', 'AccountApiController@oauthLogin');
|
||||
Route::post('register', 'AccountApiController@register');
|
||||
Route::get('static', 'AccountApiController@getStaticData');
|
||||
Route::get('accounts', 'AccountApiController@show');
|
||||
@ -305,12 +309,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
|
||||
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
|
||||
Route::get('dashboard', 'DashboardApiController@index');
|
||||
Route::resource('documents', 'DocumentAPIController');
|
||||
|
||||
// Vendor
|
||||
Route::resource('vendors', 'VendorApiController');
|
||||
|
||||
//Expense
|
||||
Route::resource('expenses', 'ExpenseApiController');
|
||||
Route::resource('expense_categories', 'ExpenseCategoryApiController');
|
||||
});
|
||||
|
||||
// Redirects for legacy links
|
||||
@ -430,56 +430,50 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
|
||||
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
|
||||
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
|
||||
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
|
||||
define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
|
||||
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
|
||||
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
|
||||
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
|
||||
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
|
||||
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
|
||||
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
|
||||
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
|
||||
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
|
||||
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
|
||||
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
|
||||
define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
|
||||
define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
|
||||
|
||||
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
|
||||
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
|
||||
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
|
||||
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
|
||||
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
|
||||
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
|
||||
|
||||
// Vendors
|
||||
define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
|
||||
define('ACTIVITY_TYPE_DELETE_VENDOR', 32);
|
||||
define('ACTIVITY_TYPE_RESTORE_VENDOR', 33);
|
||||
|
||||
// expenses
|
||||
define('ACTIVITY_TYPE_CREATE_EXPENSE', 34);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35);
|
||||
define('ACTIVITY_TYPE_DELETE_EXPENSE', 36);
|
||||
define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37);
|
||||
|
||||
// tasks
|
||||
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
|
||||
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
|
||||
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
|
||||
define('ACTIVITY_TYPE_CREATE_TASK', 42);
|
||||
define('ACTIVITY_TYPE_UPDATE_TASK', 43);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_TASK', 44);
|
||||
define('ACTIVITY_TYPE_DELETE_TASK', 45);
|
||||
define('ACTIVITY_TYPE_RESTORE_TASK', 46);
|
||||
define('ACTIVITY_TYPE_UPDATE_EXPENSE', 47);
|
||||
|
||||
|
||||
define('DEFAULT_INVOICE_NUMBER', '0001');
|
||||
define('RECENTLY_VIEWED_LIMIT', 20);
|
||||
@ -491,6 +485,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('MAX_IFRAME_URL_LENGTH', 250);
|
||||
define('MAX_LOGO_FILE_SIZE', 200); // KB
|
||||
define('MAX_FAILED_LOGINS', 10);
|
||||
define('MAX_INVOICE_ITEMS', env('MAX_INVOICE_ITEMS', 100));
|
||||
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
|
||||
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
|
||||
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
|
||||
@ -547,6 +542,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
|
||||
define('SESSION_TIMEZONE', 'timezone');
|
||||
define('SESSION_CURRENCY', 'currency');
|
||||
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
|
||||
define('SESSION_DATE_FORMAT', 'dateFormat');
|
||||
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
|
||||
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
|
||||
@ -601,6 +597,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('GATEWAY_CYBERSOURCE', 49);
|
||||
define('GATEWAY_WEPAY', 60);
|
||||
define('GATEWAY_BRAINTREE', 61);
|
||||
define('GATEWAY_CUSTOM', 62);
|
||||
|
||||
// The customer exists, but only as a local concept
|
||||
// The remote gateway doesn't understand the concept of customers
|
||||
@ -623,6 +620,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
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.2' . env('NINJA_VERSION_SUFFIX'));
|
||||
define('NINJA_VERSION', '2.8.0' . 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'));
|
||||
@ -639,6 +637,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'));
|
||||
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
|
||||
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
|
||||
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
|
||||
|
||||
define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
|
||||
define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application');
|
||||
@ -704,11 +703,12 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
|
||||
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
|
||||
|
||||
define('GATEWAY_TYPE_CREDIT_CARD', 'credit_card');
|
||||
define('GATEWAY_TYPE_BANK_TRANSFER', 'bank_transfer');
|
||||
define('GATEWAY_TYPE_PAYPAL', 'paypal');
|
||||
define('GATEWAY_TYPE_BITCOIN', 'bitcoin');
|
||||
define('GATEWAY_TYPE_DWOLLA', 'dwolla');
|
||||
define('GATEWAY_TYPE_CREDIT_CARD', 1);
|
||||
define('GATEWAY_TYPE_BANK_TRANSFER', 2);
|
||||
define('GATEWAY_TYPE_PAYPAL', 3);
|
||||
define('GATEWAY_TYPE_BITCOIN', 4);
|
||||
define('GATEWAY_TYPE_DWOLLA', 5);
|
||||
define('GATEWAY_TYPE_CUSTOM', 6);
|
||||
define('GATEWAY_TYPE_TOKEN', 'token');
|
||||
|
||||
define('REMINDER1', 'reminder1');
|
||||
@ -744,6 +744,10 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
|
||||
define('BANK_LIBRARY_OFX', 1);
|
||||
|
||||
define('CURRENCY_DECORATOR_CODE', 'code');
|
||||
define('CURRENCY_DECORATOR_SYMBOL', 'symbol');
|
||||
define('CURRENCY_DECORATOR_NONE', 'none');
|
||||
|
||||
define('RESELLER_REVENUE_SHARE', 'A');
|
||||
define('RESELLER_LIMITED_USERS', 'B');
|
||||
|
||||
@ -852,6 +856,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
'invoiceStatus' => 'App\Models\InvoiceStatus',
|
||||
'frequencies' => 'App\Models\Frequency',
|
||||
'gateways' => 'App\Models\Gateway',
|
||||
'gatewayTypes' => 'App\Models\GatewayType',
|
||||
'fonts' => 'App\Models\Font',
|
||||
'banks' => 'App\Models\Bank',
|
||||
];
|
||||
|
@ -1,21 +1,21 @@
|
||||
<?php
|
||||
|
||||
class parseCSV {
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Class: parseCSV v0.3.2
|
||||
http://code.google.com/p/parsecsv-for-php/
|
||||
|
||||
|
||||
|
||||
|
||||
Fully conforms to the specifications lined out on wikipedia:
|
||||
- http://en.wikipedia.org/wiki/Comma-separated_values
|
||||
|
||||
|
||||
Based on the concept of Ming Hong Ng's CsvFileParser class:
|
||||
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2007 Jim Myhrberg (jim@zydev.info).
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@ -35,9 +35,9 @@ class parseCSV {
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Code Examples
|
||||
----------------
|
||||
# general usage
|
||||
@ -74,7 +74,7 @@ class parseCSV {
|
||||
$csv = new parseCSV();
|
||||
$csv->output (true, 'movies.csv', $array);
|
||||
----------------
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
@ -83,87 +83,87 @@ class parseCSV {
|
||||
* Configuration
|
||||
* - set these options with $object->var_name = 'value';
|
||||
*/
|
||||
|
||||
|
||||
# use first line/entry as field names
|
||||
var $heading = true;
|
||||
|
||||
|
||||
# override field names
|
||||
var $fields = [];
|
||||
|
||||
|
||||
# sort entries by this field
|
||||
var $sort_by = null;
|
||||
var $sort_reverse = false;
|
||||
|
||||
|
||||
# delimiter (comma) and enclosure (double quote)
|
||||
var $delimiter = ',';
|
||||
var $enclosure = '"';
|
||||
|
||||
|
||||
# basic SQL-like conditions for row matching
|
||||
var $conditions = null;
|
||||
|
||||
|
||||
# number of rows to ignore from beginning of data
|
||||
var $offset = null;
|
||||
|
||||
|
||||
# limits the number of returned rows to specified amount
|
||||
var $limit = null;
|
||||
|
||||
|
||||
# number of rows to analyze when attempting to auto-detect delimiter
|
||||
var $auto_depth = 15;
|
||||
|
||||
|
||||
# characters to ignore when attempting to auto-detect delimiter
|
||||
var $auto_non_chars = "a-zA-Z0-9\n\r";
|
||||
|
||||
|
||||
# preferred delimiter characters, only used when all filtering method
|
||||
# returns multiple possible delimiters (happens very rarely)
|
||||
var $auto_preferred = ",;\t.:|";
|
||||
|
||||
|
||||
# character encoding options
|
||||
var $convert_encoding = false;
|
||||
var $input_encoding = 'ISO-8859-1';
|
||||
var $output_encoding = 'ISO-8859-1';
|
||||
|
||||
|
||||
# used by unparse(), save(), and output() functions
|
||||
var $linefeed = "\r\n";
|
||||
|
||||
|
||||
# only used by output() function
|
||||
var $output_delimiter = ',';
|
||||
var $output_filename = 'data.csv';
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal variables
|
||||
*/
|
||||
|
||||
|
||||
# current file
|
||||
var $file;
|
||||
|
||||
|
||||
# loaded file contents
|
||||
var $file_data;
|
||||
|
||||
|
||||
# array of field values in data parsed
|
||||
var $titles = [];
|
||||
|
||||
|
||||
# two dimentional array of CSV data
|
||||
var $data = [];
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param input CSV file or string
|
||||
* @return nothing
|
||||
*/
|
||||
function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) {
|
||||
function __construct ($input = null, $offset = null, $limit = null, $conditions = null) {
|
||||
if ( $offset !== null ) $this->offset = $offset;
|
||||
if ( $limit !== null ) $this->limit = $limit;
|
||||
if ( count($conditions) > 0 ) $this->conditions = $conditions;
|
||||
if ( !empty($input) ) $this->parse($input);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Main Functions ] ---------------------
|
||||
// ==============================================
|
||||
|
||||
|
||||
/**
|
||||
* Parse CSV file or string
|
||||
* @param input CSV file or string
|
||||
@ -184,7 +184,7 @@ class parseCSV {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save changes, or new file and/or data
|
||||
* @param file file to save to
|
||||
@ -199,7 +199,7 @@ class parseCSV {
|
||||
$is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ;
|
||||
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate CSV based string for output
|
||||
* @param output if true, prints headers and strings to browser
|
||||
@ -220,7 +220,7 @@ class parseCSV {
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert character encoding
|
||||
* @param input input character encoding, uses default if left blank
|
||||
@ -232,7 +232,7 @@ class parseCSV {
|
||||
if ( $input !== null ) $this->input_encoding = $input;
|
||||
if ( $output !== null ) $this->output_encoding = $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
|
||||
* rows to determine most probable delimiter character
|
||||
@ -244,13 +244,13 @@ class parseCSV {
|
||||
* @return delimiter character
|
||||
*/
|
||||
function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
|
||||
|
||||
|
||||
if ( $file === null ) $file = $this->file;
|
||||
if ( empty($search_depth) ) $search_depth = $this->auto_depth;
|
||||
if ( $enclosure === null ) $enclosure = $this->enclosure;
|
||||
|
||||
|
||||
if ( $preferred === null ) $preferred = $this->auto_preferred;
|
||||
|
||||
|
||||
if ( empty($this->file_data) ) {
|
||||
if ( $this->_check_data($file) ) {
|
||||
$data = &$this->file_data;
|
||||
@ -258,24 +258,24 @@ class parseCSV {
|
||||
} else {
|
||||
$data = &$this->file_data;
|
||||
}
|
||||
|
||||
|
||||
$chars = [];
|
||||
$strlen = strlen($data);
|
||||
$enclosed = false;
|
||||
$n = 1;
|
||||
$to_end = true;
|
||||
|
||||
|
||||
// walk specific depth finding posssible delimiter characters
|
||||
for ( $i=0; $i < $strlen; $i++ ) {
|
||||
$ch = $data{$i};
|
||||
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
|
||||
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
|
||||
|
||||
|
||||
// open and closing quotes
|
||||
if ( $ch == $enclosure && (!$enclosed || $nch != $enclosure) ) {
|
||||
$enclosed = ( $enclosed ) ? false : true ;
|
||||
|
||||
// inline quotes
|
||||
|
||||
// inline quotes
|
||||
} elseif ( $ch == $enclosure && $enclosed ) {
|
||||
$i++;
|
||||
|
||||
@ -287,7 +287,7 @@ class parseCSV {
|
||||
} else {
|
||||
$n++;
|
||||
}
|
||||
|
||||
|
||||
// count character
|
||||
} elseif (!$enclosed) {
|
||||
if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) {
|
||||
@ -299,7 +299,7 @@ class parseCSV {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// filtering
|
||||
$depth = ( $to_end ) ? $n-1 : $n ;
|
||||
$filtered = [];
|
||||
@ -308,24 +308,24 @@ class parseCSV {
|
||||
$filtered[$match] = $char;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// capture most probable delimiter
|
||||
ksort($filtered);
|
||||
$delimiter = reset($filtered);
|
||||
$this->delimiter = $delimiter;
|
||||
|
||||
|
||||
// parse data
|
||||
if ( $parse ) $this->data = $this->parse_string();
|
||||
|
||||
|
||||
return $delimiter;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Core Functions ] ---------------------
|
||||
// ==============================================
|
||||
|
||||
|
||||
/**
|
||||
* Read file to string and call parse_string()
|
||||
* @param file local CSV file
|
||||
@ -336,7 +336,7 @@ class parseCSV {
|
||||
if ( empty($this->file_data) ) $this->load_data($file);
|
||||
return ( !empty($this->file_data) ) ? $this->parse_string() : false ;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse CSV strings to arrays
|
||||
* @param data CSV string
|
||||
@ -348,7 +348,7 @@ class parseCSV {
|
||||
$data = &$this->file_data;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
||||
$rows = [];
|
||||
$row = [];
|
||||
$row_count = 0;
|
||||
@ -358,19 +358,19 @@ class parseCSV {
|
||||
$enclosed = false;
|
||||
$was_enclosed = false;
|
||||
$strlen = strlen($data);
|
||||
|
||||
|
||||
// walk through each character
|
||||
for ( $i=0; $i < $strlen; $i++ ) {
|
||||
$ch = $data{$i};
|
||||
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
|
||||
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
|
||||
|
||||
|
||||
// open and closing quotes
|
||||
if ( $ch == $this->enclosure && (!$enclosed || $nch != $this->enclosure) ) {
|
||||
$enclosed = ( $enclosed ) ? false : true ;
|
||||
if ( $enclosed ) $was_enclosed = true;
|
||||
|
||||
// inline quotes
|
||||
|
||||
// inline quotes
|
||||
} elseif ( $ch == $this->enclosure && $enclosed ) {
|
||||
$current .= $ch;
|
||||
$i++;
|
||||
@ -382,7 +382,7 @@ class parseCSV {
|
||||
$row[$key] = $current;
|
||||
$current = '';
|
||||
$col++;
|
||||
|
||||
|
||||
// end of row
|
||||
if ( $ch == "\n" || $ch == "\r" ) {
|
||||
if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) {
|
||||
@ -406,7 +406,7 @@ class parseCSV {
|
||||
$i = $strlen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// append character to current field
|
||||
} else {
|
||||
$current .= $ch;
|
||||
@ -421,7 +421,7 @@ class parseCSV {
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create CSV data from array
|
||||
* @param data 2D array with data
|
||||
@ -436,10 +436,10 @@ class parseCSV {
|
||||
if ( !is_array($data) || empty($data) ) $data = &$this->data;
|
||||
if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles;
|
||||
if ( $delimiter === null ) $delimiter = $this->delimiter;
|
||||
|
||||
|
||||
$string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ;
|
||||
$entry = [];
|
||||
|
||||
|
||||
// create heading
|
||||
if ( $this->heading && !$append ) {
|
||||
foreach( $fields as $key => $value ) {
|
||||
@ -448,7 +448,7 @@ class parseCSV {
|
||||
$string .= implode($delimiter, $entry).$this->linefeed;
|
||||
$entry = [];
|
||||
}
|
||||
|
||||
|
||||
// create data
|
||||
foreach( $data as $key => $row ) {
|
||||
foreach( $row as $field => $value ) {
|
||||
@ -457,10 +457,10 @@ class parseCSV {
|
||||
$string .= implode($delimiter, $entry).$this->linefeed;
|
||||
$entry = [];
|
||||
}
|
||||
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load local file or string
|
||||
* @param input local CSV file
|
||||
@ -488,16 +488,16 @@ class parseCSV {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Internal Functions ] -----------------
|
||||
// ==============================================
|
||||
|
||||
|
||||
/**
|
||||
* Validate a row against specified conditions
|
||||
* @param row array with values from a row
|
||||
* @param conditions specified conditions that the row must match
|
||||
* @param conditions specified conditions that the row must match
|
||||
* @return true of false
|
||||
*/
|
||||
function _validate_row_conditions ($row = [], $conditions = null) {
|
||||
@ -523,11 +523,11 @@ class parseCSV {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate a row against a single condition
|
||||
* @param row array with values from a row
|
||||
* @param condition specified condition that the row must match
|
||||
* @param condition specified condition that the row must match
|
||||
* @return true of false
|
||||
*/
|
||||
function _validate_row_condition ($row, $condition) {
|
||||
@ -583,7 +583,7 @@ class parseCSV {
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates if the row is within the offset or not if sorting is disabled
|
||||
* @param current_row the current row number being processed
|
||||
@ -593,7 +593,7 @@ class parseCSV {
|
||||
if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enclose values if needed
|
||||
* - only used by unparse()
|
||||
@ -611,7 +611,7 @@ class parseCSV {
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check file data
|
||||
* @param file local filename
|
||||
@ -624,8 +624,8 @@ class parseCSV {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if passed info might be delimiter
|
||||
* - only used by find_delimiter()
|
||||
@ -656,7 +656,7 @@ class parseCSV {
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read local file
|
||||
* @param file local filename
|
||||
@ -689,7 +689,7 @@ class parseCSV {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
?>
|
||||
|
@ -24,6 +24,8 @@ class HistoryUtils
|
||||
ACTIVITY_TYPE_CREATE_CLIENT,
|
||||
ACTIVITY_TYPE_CREATE_TASK,
|
||||
ACTIVITY_TYPE_UPDATE_TASK,
|
||||
ACTIVITY_TYPE_CREATE_EXPENSE,
|
||||
ACTIVITY_TYPE_UPDATE_EXPENSE,
|
||||
ACTIVITY_TYPE_CREATE_INVOICE,
|
||||
ACTIVITY_TYPE_UPDATE_INVOICE,
|
||||
ACTIVITY_TYPE_EMAIL_INVOICE,
|
||||
@ -35,7 +37,7 @@ class HistoryUtils
|
||||
];
|
||||
|
||||
$activities = Activity::scope()
|
||||
->with(['client.contacts', 'invoice', 'task'])
|
||||
->with(['client.contacts', 'invoice', 'task', 'expense'])
|
||||
->whereIn('user_id', $userIds)
|
||||
->whereIn('activity_type_id', $activityTypes)
|
||||
->orderBy('id', 'asc')
|
||||
@ -52,6 +54,12 @@ class HistoryUtils
|
||||
continue;
|
||||
}
|
||||
$entity->setRelation('client', $activity->client);
|
||||
} else if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_EXPENSE || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_EXPENSE) {
|
||||
$entity = $activity->expense;
|
||||
if ( ! $entity) {
|
||||
continue;
|
||||
}
|
||||
$entity->setRelation('client', $activity->client);
|
||||
} else {
|
||||
$entity = $activity->invoice;
|
||||
if ( ! $entity) {
|
||||
|
@ -22,6 +22,9 @@ class Utils
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
|
||||
];
|
||||
|
||||
public static $months = [
|
||||
'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december',
|
||||
];
|
||||
|
||||
public static function isRegistered()
|
||||
{
|
||||
@ -92,6 +95,17 @@ class Utils
|
||||
return Utils::getResllerType() ? true : false;
|
||||
}
|
||||
|
||||
public static function isWhiteLabel()
|
||||
{
|
||||
if (Utils::isNinjaProd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$account = \App\Models\Account::first();
|
||||
|
||||
return $account && $account->hasFeature(FEATURE_WHITE_LABEL);
|
||||
}
|
||||
|
||||
public static function getResllerType()
|
||||
{
|
||||
return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false;
|
||||
@ -151,6 +165,11 @@ class Utils
|
||||
return Auth::check() && Auth::user()->isTrial();
|
||||
}
|
||||
|
||||
public static function isPaidPro()
|
||||
{
|
||||
return static::isPro() && ! static::isTrial();
|
||||
}
|
||||
|
||||
public static function isEnglish()
|
||||
{
|
||||
return App::getLocale() == 'en';
|
||||
@ -186,7 +205,7 @@ class Utils
|
||||
$response = new stdClass();
|
||||
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
|
||||
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
|
||||
$response->version = env('NINJA_SELF_HOST_VERSION', NINJA_VERSION);
|
||||
$response->version = NINJA_VERSION;
|
||||
|
||||
return $response;
|
||||
}
|
||||
@ -354,7 +373,7 @@ class Utils
|
||||
return $data->first();
|
||||
}
|
||||
|
||||
public static function formatMoney($value, $currencyId = false, $countryId = false, $showCode = false)
|
||||
public static function formatMoney($value, $currencyId = false, $countryId = false, $decorator = false)
|
||||
{
|
||||
$value = floatval($value);
|
||||
|
||||
@ -362,6 +381,10 @@ class Utils
|
||||
$currencyId = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY);
|
||||
}
|
||||
|
||||
if (!$decorator) {
|
||||
$decorator = Session::get(SESSION_CURRENCY_DECORATOR, CURRENCY_DECORATOR_SYMBOL);
|
||||
}
|
||||
|
||||
if (!$countryId && Auth::check()) {
|
||||
$countryId = Auth::user()->account->country_id;
|
||||
}
|
||||
@ -387,7 +410,9 @@ class Utils
|
||||
$value = number_format($value, $precision, $decimal, $thousand);
|
||||
$symbol = $currency->symbol;
|
||||
|
||||
if ($showCode || !$symbol) {
|
||||
if ($decorator == CURRENCY_DECORATOR_NONE) {
|
||||
return $value;
|
||||
} elseif ($decorator == CURRENCY_DECORATOR_CODE || ! $symbol) {
|
||||
return "{$value} {$code}";
|
||||
} elseif ($swapSymbol) {
|
||||
return "{$value} " . trim($symbol);
|
||||
@ -635,11 +660,22 @@ class Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMonthOptions()
|
||||
{
|
||||
$months = [];
|
||||
|
||||
for ($i=1; $i<=count(static::$months); $i++) {
|
||||
$month = static::$months[$i-1];
|
||||
$number = $i < 10 ? '0' . $i : $i;
|
||||
$months["2000-{$number}-01"] = trans("texts.{$month}");
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
private static function getMonth($offset)
|
||||
{
|
||||
$months = ['january', 'february', 'march', 'april', 'may', 'june',
|
||||
'july', 'august', 'september', 'october', 'november', 'december', ];
|
||||
|
||||
$months = static::$months;
|
||||
$month = intval(date('n')) - 1;
|
||||
|
||||
$month += $offset;
|
||||
@ -1029,4 +1065,66 @@ class Utils
|
||||
return trans('texts.'.strtolower($day));
|
||||
});
|
||||
}
|
||||
|
||||
public static function getDocsUrl($path)
|
||||
{
|
||||
$page = '';
|
||||
$parts = explode('/', $path);
|
||||
$first = count($parts) ? $parts[0] : false;
|
||||
$second = count($parts) > 1 ? $parts[1] : false;
|
||||
|
||||
$entityTypes = [
|
||||
'clients',
|
||||
'invoices',
|
||||
'payments',
|
||||
'recurring_invoices',
|
||||
'credits',
|
||||
'quotes',
|
||||
'tasks',
|
||||
'expenses',
|
||||
'vendors',
|
||||
];
|
||||
|
||||
if ($path == 'dashboard') {
|
||||
$page = '/introduction.html#dashboard';
|
||||
} elseif (in_array($path, $entityTypes)) {
|
||||
$page = "/{$path}.html#list-" . str_replace('_', '-', $path);
|
||||
} elseif (in_array($first, $entityTypes)) {
|
||||
$action = ($first == 'payments' || $first == 'credits') ? 'enter' : 'create';
|
||||
$page = "/{$first}.html#{$action}-" . substr(str_replace('_', '-', $first), 0, -1);
|
||||
} elseif ($first == 'expense_categories') {
|
||||
$page = '/expenses.html#expense-categories';
|
||||
} elseif ($first == 'settings') {
|
||||
if ($second == 'bank_accounts') {
|
||||
$page = ''; // TODO write docs
|
||||
} elseif (in_array($second, \App\Models\Account::$basicSettings)) {
|
||||
if ($second == 'products') {
|
||||
$second = 'product_library';
|
||||
} elseif ($second == 'notifications') {
|
||||
$second = 'email_notifications';
|
||||
}
|
||||
$page = '/settings.html#' . str_replace('_', '-', $second);
|
||||
} elseif (in_array($second, \App\Models\Account::$advancedSettings)) {
|
||||
$page = "/{$second}.html";
|
||||
} elseif ($second == 'customize_design') {
|
||||
$page = '/invoice_design.html#customize';
|
||||
}
|
||||
} elseif ($first == 'tax_rates') {
|
||||
$page = '/settings.html#tax-rates';
|
||||
} elseif ($first == 'products') {
|
||||
$page = '/settings.html#product-library';
|
||||
} elseif ($first == 'users') {
|
||||
$page = '/user_management.html#create-user';
|
||||
}
|
||||
|
||||
return url(NINJA_DOCS_URL . $page);
|
||||
}
|
||||
|
||||
public static function calculateTaxes($amount, $taxRate1, $taxRate2)
|
||||
{
|
||||
$tax1 = round($amount * $taxRate1 / 100, 2);
|
||||
$tax2 = round($amount * $taxRate2 / 100, 2);
|
||||
|
||||
return round($amount + $tax1 + $tax2, 2);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php namespace App\Listeners;
|
||||
|
||||
use App\Events\TaskWasCreated;
|
||||
use App\Events\TaskWasUpdated;
|
||||
use App\Models\Invoice;
|
||||
use App\Events\ClientWasCreated;
|
||||
use App\Events\ClientWasDeleted;
|
||||
@ -33,6 +31,16 @@ use App\Events\CreditWasCreated;
|
||||
use App\Events\CreditWasDeleted;
|
||||
use App\Events\CreditWasArchived;
|
||||
use App\Events\CreditWasRestored;
|
||||
use App\Events\TaskWasCreated;
|
||||
use App\Events\TaskWasUpdated;
|
||||
use App\Events\TaskWasArchived;
|
||||
use App\Events\TaskWasRestored;
|
||||
use App\Events\TaskWasDeleted;
|
||||
use App\Events\ExpenseWasCreated;
|
||||
use App\Events\ExpenseWasUpdated;
|
||||
use App\Events\ExpenseWasArchived;
|
||||
use App\Events\ExpenseWasRestored;
|
||||
use App\Events\ExpenseWasDeleted;
|
||||
use App\Ninja\Repositories\ActivityRepository;
|
||||
|
||||
/**
|
||||
@ -123,7 +131,9 @@ class ActivityListener
|
||||
return;
|
||||
}
|
||||
|
||||
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($event->invoice->id);
|
||||
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')
|
||||
->withArchived()
|
||||
->find($event->invoice->id);
|
||||
|
||||
$activity = $this->activityRepo->create(
|
||||
$event->invoice,
|
||||
@ -489,9 +499,92 @@ class ActivityListener
|
||||
*/
|
||||
public function updatedTask(TaskWasUpdated $event)
|
||||
{
|
||||
if ( ! $event->task->isChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityRepo->create(
|
||||
$event->task,
|
||||
ACTIVITY_TYPE_UPDATE_TASK
|
||||
);
|
||||
}
|
||||
|
||||
public function archivedTask(TaskWasArchived $event)
|
||||
{
|
||||
if ($event->task->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityRepo->create(
|
||||
$event->task,
|
||||
ACTIVITY_TYPE_ARCHIVE_TASK
|
||||
);
|
||||
}
|
||||
|
||||
public function deletedTask(TaskWasDeleted $event)
|
||||
{
|
||||
$this->activityRepo->create(
|
||||
$event->task,
|
||||
ACTIVITY_TYPE_DELETE_TASK
|
||||
);
|
||||
}
|
||||
|
||||
public function restoredTask(TaskWasRestored $event)
|
||||
{
|
||||
$this->activityRepo->create(
|
||||
$event->task,
|
||||
ACTIVITY_TYPE_RESTORE_TASK
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function createdExpense(ExpenseWasCreated $event)
|
||||
{
|
||||
$this->activityRepo->create(
|
||||
$event->expense,
|
||||
ACTIVITY_TYPE_CREATE_EXPENSE
|
||||
);
|
||||
}
|
||||
|
||||
public function updatedExpense(ExpenseWasUpdated $event)
|
||||
{
|
||||
if ( ! $event->expense->isChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityRepo->create(
|
||||
$event->expense,
|
||||
ACTIVITY_TYPE_UPDATE_EXPENSE
|
||||
);
|
||||
}
|
||||
|
||||
public function archivedExpense(ExpenseWasArchived $event)
|
||||
{
|
||||
if ($event->expense->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activityRepo->create(
|
||||
$event->expense,
|
||||
ACTIVITY_TYPE_ARCHIVE_EXPENSE
|
||||
);
|
||||
}
|
||||
|
||||
public function deletedExpense(ExpenseWasDeleted $event)
|
||||
{
|
||||
$this->activityRepo->create(
|
||||
$event->expense,
|
||||
ACTIVITY_TYPE_DELETE_EXPENSE
|
||||
);
|
||||
}
|
||||
|
||||
public function restoredExpense(ExpenseWasRestored $event)
|
||||
{
|
||||
$this->activityRepo->create(
|
||||
$event->expense,
|
||||
ACTIVITY_TYPE_RESTORE_EXPENSE
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -69,6 +69,16 @@ class Account extends Eloquent
|
||||
'enable_second_tax_rate',
|
||||
'include_item_taxes_inline',
|
||||
'start_of_week',
|
||||
'financial_year_start',
|
||||
'enable_client_portal',
|
||||
'enable_client_portal_dashboard',
|
||||
'enable_portal_password',
|
||||
'send_portal_password',
|
||||
'enable_buy_now_buttons',
|
||||
'show_accept_invoice_terms',
|
||||
'show_accept_quote_terms',
|
||||
'require_invoice_signature',
|
||||
'require_quote_signature',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -102,6 +112,21 @@ class Account extends Eloquent
|
||||
ACCOUNT_USER_MANAGEMENT,
|
||||
];
|
||||
|
||||
public static $modules = [
|
||||
ENTITY_RECURRING_INVOICE => 1,
|
||||
ENTITY_CREDIT => 2,
|
||||
ENTITY_QUOTE => 4,
|
||||
ENTITY_TASK => 8,
|
||||
ENTITY_EXPENSE => 16,
|
||||
ENTITY_VENDOR => 32,
|
||||
];
|
||||
|
||||
public static $dashboardSections = [
|
||||
'total_revenue' => 1,
|
||||
'average_invoice' => 2,
|
||||
'outstanding' => 4,
|
||||
];
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
@ -438,7 +463,7 @@ class Account extends Eloquent
|
||||
* @param bool $hideSymbol
|
||||
* @return string
|
||||
*/
|
||||
public function formatMoney($amount, $client = null, $hideSymbol = false)
|
||||
public function formatMoney($amount, $client = null, $decorator = false)
|
||||
{
|
||||
if ($client && $client->currency_id) {
|
||||
$currencyId = $client->currency_id;
|
||||
@ -456,9 +481,11 @@ class Account extends Eloquent
|
||||
$countryId = false;
|
||||
}
|
||||
|
||||
$hideSymbol = $this->show_currency_code || $hideSymbol;
|
||||
if ( ! $decorator) {
|
||||
$decorator = $this->show_currency_code ? CURRENCY_DECORATOR_CODE : CURRENCY_DECORATOR_SYMBOL;
|
||||
}
|
||||
|
||||
return Utils::formatMoney($amount, $currencyId, $countryId, $hideSymbol);
|
||||
return Utils::formatMoney($amount, $currencyId, $countryId, $decorator);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -610,14 +637,14 @@ class Account extends Eloquent
|
||||
|
||||
/**
|
||||
* @param bool $invitation
|
||||
* @param bool $gatewayType
|
||||
* @param mixed $gatewayTypeId
|
||||
* @return bool
|
||||
*/
|
||||
public function paymentDriver($invitation = false, $gatewayType = false)
|
||||
public function paymentDriver($invitation = false, $gatewayTypeId = false)
|
||||
{
|
||||
/** @var AccountGateway $accountGateway */
|
||||
if ($accountGateway = $this->getGatewayByType($gatewayType)) {
|
||||
return $accountGateway->paymentDriver($invitation, $gatewayType);
|
||||
if ($accountGateway = $this->getGatewayByType($gatewayTypeId)) {
|
||||
return $accountGateway->paymentDriver($invitation, $gatewayTypeId);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -735,6 +762,22 @@ class Account extends Eloquent
|
||||
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
||||
}
|
||||
|
||||
public function getLogoPath()
|
||||
{
|
||||
if ( ! $this->hasLogo()){
|
||||
return null;
|
||||
}
|
||||
|
||||
$disk = $this->getLogoDisk();
|
||||
$adapter = $disk->getAdapter();
|
||||
|
||||
if ($adapter instanceof \League\Flysystem\Adapter\Local) {
|
||||
return $adapter->applyPathPrefix($this->logo);
|
||||
} else {
|
||||
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -1024,6 +1067,7 @@ class Account extends Eloquent
|
||||
$locale = ($client && $client->language_id) ? $client->language->locale : ($this->language_id ? $this->Language->locale : DEFAULT_LOCALE);
|
||||
|
||||
Session::put(SESSION_CURRENCY, $currencyId);
|
||||
Session::put(SESSION_CURRENCY_DECORATOR, $this->show_currency_code ? CURRENCY_DECORATOR_CODE : CURRENCY_DECORATOR_SYMBOL);
|
||||
Session::put(SESSION_LOCALE, $locale);
|
||||
|
||||
App::setLocale($locale);
|
||||
@ -1812,6 +1856,43 @@ class Account extends Eloquent
|
||||
public function getFontFolders(){
|
||||
return array_map(function($item){return $item['folder'];}, $this->getFontsData());
|
||||
}
|
||||
|
||||
public function isModuleEnabled($entityType)
|
||||
{
|
||||
if (in_array($entityType, [
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_PRODUCT,
|
||||
ENTITY_PAYMENT,
|
||||
])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->enabled_modules & static::$modules[$entityType];
|
||||
}
|
||||
|
||||
public function showAuthenticatePanel($invoice)
|
||||
{
|
||||
return $this->showAcceptTerms($invoice) || $this->showSignature($invoice);
|
||||
}
|
||||
|
||||
public function showAcceptTerms($invoice)
|
||||
{
|
||||
if ( ! $this->isPro() || ! $invoice->terms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $invoice->is_quote ? $this->show_accept_quote_terms : $this->show_accept_invoice_terms;
|
||||
}
|
||||
|
||||
public function showSignature($invoice)
|
||||
{
|
||||
if ( ! $this->isPro()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $invoice->is_quote ? $this->require_quote_signature : $this->require_invoice_signature;
|
||||
}
|
||||
}
|
||||
|
||||
Account::updated(function ($account)
|
||||
|
@ -73,14 +73,14 @@ class AccountGateway extends EntityModel
|
||||
|
||||
/**
|
||||
* @param bool $invitation
|
||||
* @param bool $gatewayType
|
||||
* @param mixed $gatewayTypeId
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentDriver($invitation = false, $gatewayType = false)
|
||||
public function paymentDriver($invitation = false, $gatewayTypeId = false)
|
||||
{
|
||||
$class = static::paymentDriverClass($this->gateway->provider);
|
||||
|
||||
return new $class($this, $invitation, $gatewayType);
|
||||
return new $class($this, $invitation, $gatewayTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
32
app/Models/AccountGatewaySettings.php
Normal file
32
app/Models/AccountGatewaySettings.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Auth;
|
||||
|
||||
/**
|
||||
* Class AccountGatewaySettings
|
||||
*/
|
||||
class AccountGatewaySettings extends EntityModel
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['updated_at'];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $hasPublicId = false;
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function gatewayType()
|
||||
{
|
||||
return $this->belongsTo('App\Models\GatewayType');
|
||||
}
|
||||
|
||||
public function setCreatedAtAttribute($value)
|
||||
{
|
||||
// to Disable created_at
|
||||
}
|
||||
}
|
@ -83,6 +83,11 @@ class Activity extends Eloquent
|
||||
return $this->belongsTo('App\Models\Task')->withTrashed();
|
||||
}
|
||||
|
||||
public function expense()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Expense')->withTrashed();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return sprintf('%s-%s-%s', $this->activity_type_id, $this->client_id, $this->created_at->timestamp);
|
||||
@ -101,9 +106,8 @@ class Activity extends Eloquent
|
||||
$contactId = $this->contact_id;
|
||||
$payment = $this->payment;
|
||||
$credit = $this->credit;
|
||||
$expense = $this->expense;
|
||||
$isSystem = $this->is_system;
|
||||
|
||||
/** @var Task $task */
|
||||
$task = $this->task;
|
||||
|
||||
$data = [
|
||||
@ -117,6 +121,7 @@ class Activity extends Eloquent
|
||||
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : null,
|
||||
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
|
||||
'task' => $task ? link_to($task->getRoute(), substr($task->description, 0, 30).'...') : null,
|
||||
'expense' => $expense ? link_to($expense->getRoute(), substr($expense->public_notes, 0, 30).'...') : null,
|
||||
];
|
||||
|
||||
return trans("texts.activity_{$activityTypeId}", $data);
|
||||
|
@ -89,6 +89,10 @@ class Client extends EntityModel
|
||||
* @var string
|
||||
*/
|
||||
public static $fieldWebsite = 'website';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $fieldVatNumber = 'vat_number';
|
||||
|
||||
/**
|
||||
* @return array
|
||||
@ -106,6 +110,7 @@ class Client extends EntityModel
|
||||
Client::$fieldCountry,
|
||||
Client::$fieldNotes,
|
||||
Client::$fieldWebsite,
|
||||
Client::$fieldVatNumber,
|
||||
Contact::$fieldFirstName,
|
||||
Contact::$fieldLastName,
|
||||
Contact::$fieldPhone,
|
||||
@ -132,6 +137,7 @@ class Client extends EntityModel
|
||||
'country' => 'country',
|
||||
'note' => 'notes',
|
||||
'site|website' => 'website',
|
||||
'vat' => 'vat_number',
|
||||
];
|
||||
}
|
||||
|
||||
@ -159,6 +165,14 @@ class Client extends EntityModel
|
||||
return $this->hasMany('App\Models\Invoice');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function quotes()
|
||||
{
|
||||
return $this->hasMany('App\Models\Invoice')->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
@ -223,6 +237,14 @@ class Client extends EntityModel
|
||||
return $this->hasMany('App\Models\Credit');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function creditsWithBalance()
|
||||
{
|
||||
return $this->hasMany('App\Models\Credit')->where('balance', '>', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -329,7 +351,7 @@ class Client extends EntityModel
|
||||
|
||||
$contact = $this->contacts[0];
|
||||
|
||||
return $contact->getDisplayName() ?: trans('texts.unnamed_client');
|
||||
return $contact->getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use Auth;
|
||||
use Eloquent;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Utils;
|
||||
use Validator;
|
||||
|
||||
@ -14,6 +15,12 @@ class EntityModel extends Eloquent
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $hasPublicId = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -56,13 +63,16 @@ class EntityModel extends Eloquent
|
||||
$lastEntity = $className::whereAccountId($entity->account_id);
|
||||
}
|
||||
|
||||
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
$entity->public_id = $lastEntity->public_id + 1;
|
||||
} else {
|
||||
$entity->public_id = 1;
|
||||
if (static::$hasPublicId) {
|
||||
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
$entity->public_id = $lastEntity->public_id + 1;
|
||||
} else {
|
||||
$entity->public_id = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $entity;
|
||||
@ -244,6 +254,7 @@ class EntityModel extends Eloquent
|
||||
$icons = [
|
||||
'dashboard' => 'tachometer',
|
||||
'clients' => 'users',
|
||||
'products' => 'cube',
|
||||
'invoices' => 'file-pdf-o',
|
||||
'payments' => 'credit-card',
|
||||
'recurring_invoices' => 'files-o',
|
||||
@ -253,9 +264,21 @@ class EntityModel extends Eloquent
|
||||
'expenses' => 'file-image-o',
|
||||
'vendors' => 'building',
|
||||
'settings' => 'cog',
|
||||
'self-update' => 'download',
|
||||
];
|
||||
|
||||
return array_get($icons, $entityType);
|
||||
}
|
||||
|
||||
// isDirty return true if the field's new value is the same as the old one
|
||||
public function isChanged()
|
||||
{
|
||||
foreach ($this->fillable as $field) {
|
||||
if ($this->$field != $this->getOriginal($field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Utils;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Events\ExpenseWasCreated;
|
||||
@ -31,6 +32,7 @@ class Expense extends EntityModel
|
||||
'client_id',
|
||||
'vendor_id',
|
||||
'expense_currency_id',
|
||||
'expense_date',
|
||||
'invoice_currency_id',
|
||||
'amount',
|
||||
'foreign_amount',
|
||||
@ -46,6 +48,29 @@ class Expense extends EntityModel
|
||||
'tax_name2',
|
||||
];
|
||||
|
||||
public static function getImportColumns()
|
||||
{
|
||||
return [
|
||||
'client',
|
||||
'vendor',
|
||||
'amount',
|
||||
'public_notes',
|
||||
'expense_category',
|
||||
'expense_date',
|
||||
];
|
||||
}
|
||||
|
||||
public static function getImportMap()
|
||||
{
|
||||
return [
|
||||
'amount|total' => 'amount',
|
||||
'category' => 'expense_category',
|
||||
'client' => 'client',
|
||||
'vendor' => 'vendor',
|
||||
'notes|details' => 'public_notes',
|
||||
'date' => 'expense_date',
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
@ -107,7 +132,13 @@ class Expense extends EntityModel
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->transaction_id ?: '#' . $this->public_id;
|
||||
if ($this->transaction_id) {
|
||||
return $this->transaction_id;
|
||||
} elseif ($this->public_notes) {
|
||||
return mb_strimwidth($this->public_notes, 0, 16, "...");
|
||||
} else {
|
||||
return '#' . $this->public_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,6 +206,11 @@ class Expense extends EntityModel
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function amountWithTax()
|
||||
{
|
||||
return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2);
|
||||
}
|
||||
}
|
||||
|
||||
Expense::creating(function ($expense) {
|
||||
@ -196,7 +232,3 @@ Expense::updated(function ($expense) {
|
||||
Expense::deleting(function ($expense) {
|
||||
$expense->setNullValues();
|
||||
});
|
||||
|
||||
Expense::deleted(function ($expense) {
|
||||
event(new ExpenseWasDeleted($expense));
|
||||
});
|
||||
|
@ -14,6 +14,12 @@ class Gateway extends Eloquent
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = [
|
||||
'provider',
|
||||
'is_offsite',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -39,6 +45,7 @@ class Gateway extends Eloquent
|
||||
GATEWAY_BRAINTREE,
|
||||
GATEWAY_AUTHORIZE_NET,
|
||||
GATEWAY_MOLLIE,
|
||||
GATEWAY_CUSTOM,
|
||||
];
|
||||
|
||||
// allow adding these gateway if another gateway
|
||||
@ -174,6 +181,18 @@ class Gateway extends Eloquent
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return Omnipay::create($this->provider)->getDefaultParameters();
|
||||
if ($this->isCustom()) {
|
||||
return [
|
||||
'name' => '',
|
||||
'text' => '',
|
||||
];
|
||||
} else {
|
||||
return Omnipay::create($this->provider)->getDefaultParameters();
|
||||
}
|
||||
}
|
||||
|
||||
public function isCustom()
|
||||
{
|
||||
return $this->id === GATEWAY_CUSTOM;
|
||||
}
|
||||
}
|
||||
|
34
app/Models/GatewayType.php
Normal file
34
app/Models/GatewayType.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Eloquent;
|
||||
use Cache;
|
||||
use Utils;
|
||||
|
||||
/**
|
||||
* Class GatewayType
|
||||
*/
|
||||
class GatewayType extends Eloquent
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public static function getAliasFromId($id)
|
||||
{
|
||||
return Utils::getFromCache($id, 'gatewayTypes')->alias;
|
||||
}
|
||||
|
||||
public static function getIdFromAlias($alias)
|
||||
{
|
||||
return Cache::get('gatewayTypes')->where('alias', $alias)->first()->id;
|
||||
}
|
||||
}
|
@ -134,4 +134,13 @@ class Invitation extends EntityModel
|
||||
$invoice->markViewed();
|
||||
$client->markLoggedIn();
|
||||
}
|
||||
|
||||
public function signatureDiv()
|
||||
{
|
||||
if ( ! $this->signature_base64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sprintf('<img src="data:image/svg+xml;base64,%s"></img><p/>%s: %s', $this->signature_base64, trans('texts.signed'), Utils::fromSqlDateTime($this->signature_date));
|
||||
}
|
||||
}
|
||||
|
@ -514,6 +514,11 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf';
|
||||
}
|
||||
|
||||
public function canBePaid()
|
||||
{
|
||||
return floatval($this->balance) > 0 && ! $this->is_deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $invoice
|
||||
* @return string
|
||||
|
@ -11,4 +11,12 @@ class PaymentType extends Eloquent
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function gatewayType()
|
||||
{
|
||||
return $this->belongsTo('App\Models\GatewayType');
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,16 @@ class Task extends EntityModel
|
||||
use SoftDeletes;
|
||||
use PresentableTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'description',
|
||||
'time_log',
|
||||
'is_running',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -82,6 +92,18 @@ class Task extends EntityModel
|
||||
return self::calcStartTime($this);
|
||||
}
|
||||
|
||||
public function getLastStartTime()
|
||||
{
|
||||
$parts = json_decode($this->time_log) ?: [];
|
||||
|
||||
if (count($parts)) {
|
||||
$index = count($parts) - 1;
|
||||
return $parts[$index][0];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $task
|
||||
* @return int
|
||||
@ -165,6 +187,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -87,6 +87,7 @@ trait PresentsInvoice
|
||||
'invoice.partial_due',
|
||||
'invoice.custom_text_value1',
|
||||
'invoice.custom_text_value2',
|
||||
'.blank',
|
||||
],
|
||||
INVOICE_FIELDS_CLIENT => [
|
||||
'client.client_name',
|
||||
@ -97,9 +98,11 @@ trait PresentsInvoice
|
||||
'client.city_state_postal',
|
||||
'client.country',
|
||||
'client.email',
|
||||
'client.phone',
|
||||
'client.contact_name',
|
||||
'client.custom_value1',
|
||||
'client.custom_value2',
|
||||
'.blank',
|
||||
],
|
||||
INVOICE_FIELDS_ACCOUNT => [
|
||||
'account.company_name',
|
||||
@ -114,6 +117,7 @@ trait PresentsInvoice
|
||||
'account.country',
|
||||
'account.custom_value1',
|
||||
'account.custom_value2',
|
||||
'.blank',
|
||||
]
|
||||
];
|
||||
|
||||
@ -198,6 +202,7 @@ trait PresentsInvoice
|
||||
'company_name',
|
||||
'website',
|
||||
'phone',
|
||||
'blank',
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
|
@ -5,19 +5,14 @@ use Event;
|
||||
use App\Libraries\Utils;
|
||||
use App\Events\UserSettingsChanged;
|
||||
use App\Events\UserSignedUp;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract {
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -27,8 +22,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'edit_all' => 0b0100,
|
||||
];
|
||||
|
||||
use Authenticatable, Authorizable, CanResetPassword;
|
||||
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
@ -102,26 +95,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return PERSON_USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique identifier for the user.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuthIdentifier()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password for the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address where password reminders are sent.
|
||||
*
|
||||
@ -258,31 +231,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return MAX_NUM_VENDORS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRememberToken()
|
||||
{
|
||||
return $this->remember_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function setRememberToken($value)
|
||||
{
|
||||
$this->remember_token = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRememberTokenName()
|
||||
{
|
||||
return 'remember_token';
|
||||
}
|
||||
|
||||
public function clearSession()
|
||||
{
|
||||
$keys = [
|
||||
@ -425,7 +373,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
|
||||
public function caddAddUsers()
|
||||
{
|
||||
if ( ! Utils::isNinja()) {
|
||||
if ( ! Utils::isNinjaProd()) {
|
||||
return true;
|
||||
} elseif ( ! $this->hasFeature(FEATURE_USERS)) {
|
||||
return false;
|
||||
@ -441,6 +389,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
|
||||
return $numUsers < $company->num_users;
|
||||
}
|
||||
|
||||
public function canCreateOrEdit($entityType, $entity = false)
|
||||
{
|
||||
return (($entity && $this->can('edit', $entity))
|
||||
|| (!$entity && $this->can('create', $entityType)));
|
||||
}
|
||||
}
|
||||
|
||||
User::updating(function ($user) {
|
||||
|
@ -211,7 +211,8 @@ class Vendor extends EntityModel
|
||||
*/
|
||||
public function addVendorContact($data, $isPrimary = false)
|
||||
{
|
||||
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
|
||||
//$publicId = isset($data['public_id']) ? $data['public_id'] : false;
|
||||
$publicId = isset($data['public_id']) ? $data['public_id'] : (isset($data['id']) ? $data['id'] : false);
|
||||
|
||||
if ($publicId && $publicId != '-1') {
|
||||
$contact = VendorContact::scope($publicId)->firstOrFail();
|
||||
|
@ -1,10 +1,17 @@
|
||||
<?php namespace App\Ninja\Datatables;
|
||||
|
||||
use App\Models\AccountGatewaySettings;
|
||||
use App\Models\GatewayType;
|
||||
use URL;
|
||||
use Cache;
|
||||
use Utils;
|
||||
use Session;
|
||||
use App\Models\AccountGateway;
|
||||
|
||||
class AccountGatewayDatatable extends EntityDatatable
|
||||
{
|
||||
private static $accountGateways;
|
||||
|
||||
public $entityType = ENTITY_ACCOUNT_GATEWAY;
|
||||
|
||||
public function columns()
|
||||
@ -15,10 +22,14 @@ class AccountGatewayDatatable extends EntityDatatable
|
||||
function ($model) {
|
||||
if ($model->deleted_at) {
|
||||
return $model->name;
|
||||
} elseif ($model->gateway_id == GATEWAY_CUSTOM) {
|
||||
$accountGateway = $this->getAccountGateway($model->id);
|
||||
$name = $accountGateway->getConfigField('name') . ' [' . trans('texts.custom') . ']';
|
||||
return link_to("gateways/{$model->public_id}/edit", $name)->toHtml();
|
||||
} elseif ($model->gateway_id != GATEWAY_WEPAY) {
|
||||
return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml();
|
||||
} else {
|
||||
$accountGateway = AccountGateway::find($model->id);
|
||||
$accountGateway = $this->getAccountGateway($model->id);
|
||||
$config = $accountGateway->getConfig();
|
||||
$endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
|
||||
$wepayAccountId = $config->accountId;
|
||||
@ -45,12 +56,56 @@ class AccountGatewayDatatable extends EntityDatatable
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'limit',
|
||||
function ($model) {
|
||||
if ($model->gateway_id == GATEWAY_CUSTOM) {
|
||||
$gatewayTypes = [GATEWAY_TYPE_CUSTOM];
|
||||
} else {
|
||||
$accountGateway = $this->getAccountGateway($model->id);
|
||||
$paymentDriver = $accountGateway->paymentDriver();
|
||||
$gatewayTypes = $paymentDriver->gatewayTypes();
|
||||
$gatewayTypes = array_diff($gatewayTypes, array(GATEWAY_TYPE_TOKEN));
|
||||
}
|
||||
|
||||
$html = '';
|
||||
foreach ($gatewayTypes as $gatewayTypeId) {
|
||||
$accountGatewaySettings = AccountGatewaySettings::scope()->where('account_gateway_settings.gateway_type_id',
|
||||
'=', $gatewayTypeId)->first();
|
||||
$gatewayType = GatewayType::find($gatewayTypeId);
|
||||
|
||||
if (count($gatewayTypes) > 1) {
|
||||
if ($html) {
|
||||
$html .= '<br>';
|
||||
}
|
||||
|
||||
$html .= $gatewayType->name . ' — ';
|
||||
}
|
||||
|
||||
if ($accountGatewaySettings && $accountGatewaySettings->min_limit !== null && $accountGatewaySettings->max_limit !== null) {
|
||||
$html .= Utils::formatMoney($accountGatewaySettings->min_limit) . ' - ' . Utils::formatMoney($accountGatewaySettings->max_limit);
|
||||
} elseif ($accountGatewaySettings && $accountGatewaySettings->min_limit !== null) {
|
||||
$html .= trans('texts.min_limit',
|
||||
array('min' => Utils::formatMoney($accountGatewaySettings->min_limit))
|
||||
);
|
||||
} elseif ($accountGatewaySettings && $accountGatewaySettings->max_limit !== null) {
|
||||
$html .= trans('texts.max_limit',
|
||||
array('max' => Utils::formatMoney($accountGatewaySettings->max_limit))
|
||||
);
|
||||
} else {
|
||||
$html .= trans('texts.no_limit');
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
$actions = [
|
||||
[
|
||||
uctrans('texts.resend_confirmation_email'),
|
||||
function ($model) {
|
||||
@ -78,7 +133,7 @@ class AccountGatewayDatatable extends EntityDatatable
|
||||
], [
|
||||
uctrans('texts.manage_account'),
|
||||
function ($model) {
|
||||
$accountGateway = AccountGateway::find($model->id);
|
||||
$accountGateway = $this->getAccountGateway($model->id);
|
||||
$endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
|
||||
return [
|
||||
'url' => $endpoint.'account/'.$accountGateway->getConfig()->accountId,
|
||||
@ -98,6 +153,46 @@ class AccountGatewayDatatable extends EntityDatatable
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
foreach (Cache::get('gatewayTypes') as $gatewayType) {
|
||||
$actions[] = [
|
||||
trans('texts.set_limits', ['gateway_type' => $gatewayType->name]),
|
||||
function () use ($gatewayType) {
|
||||
$accountGatewaySettings = AccountGatewaySettings::scope()
|
||||
->where('account_gateway_settings.gateway_type_id', '=', $gatewayType->id)
|
||||
->first();
|
||||
$min = $accountGatewaySettings && $accountGatewaySettings->min_limit !== null ? $accountGatewaySettings->min_limit : 'null';
|
||||
$max = $accountGatewaySettings && $accountGatewaySettings->max_limit !== null ? $accountGatewaySettings->max_limit : 'null';
|
||||
|
||||
return "javascript:showLimitsModal('{$gatewayType->name}', {$gatewayType->id}, $min, $max)";
|
||||
},
|
||||
function ($model) use ($gatewayType) {
|
||||
// Only show this action if the given gateway supports this gateway type
|
||||
if ($model->gateway_id == GATEWAY_CUSTOM) {
|
||||
return $gatewayType->id == GATEWAY_TYPE_CUSTOM;
|
||||
} else {
|
||||
$accountGateway = $this->getAccountGateway($model->id);
|
||||
$paymentDriver = $accountGateway->paymentDriver();
|
||||
$gatewayTypes = $paymentDriver->gatewayTypes();
|
||||
|
||||
return in_array($gatewayType->id, $gatewayTypes);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
private function getAccountGateway($id)
|
||||
{
|
||||
if (isset(static::$accountGateways[$id])) {
|
||||
return static::$accountGateways[$id];
|
||||
}
|
||||
|
||||
static::$accountGateways[$id] = AccountGateway::find($id);
|
||||
|
||||
return static::$accountGateways[$id];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,9 @@ class ActivityDatatable extends EntityDatatable
|
||||
'payment' => $model->payment ?: '',
|
||||
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
|
||||
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
|
||||
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null
|
||||
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null,
|
||||
'task' => $model->task_public_id ? link_to('/tasks/' . $model->task_public_id, substr($model->task_description, 0, 30).'...') : null,
|
||||
'expense' => $model->expense_public_id ? link_to('/expenses/' . $model->expense_public_id, substr($model->expense_public_notes, 0, 30).'...') : null,
|
||||
];
|
||||
|
||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||
|
@ -93,7 +93,7 @@ class ClientDatatable extends EntityDatatable
|
||||
return URL::to("quotes/create/{$model->public_id}");
|
||||
},
|
||||
function ($model) {
|
||||
return Auth::user()->hasFeature(FEATURE_QUOTES) && Auth::user()->can('create', ENTITY_INVOICE);
|
||||
return Auth::user()->hasFeature(FEATURE_QUOTES) && Auth::user()->can('create', ENTITY_QUOTE);
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -46,7 +46,7 @@ class ExpenseDatatable extends EntityDatatable
|
||||
[
|
||||
'expense_date',
|
||||
function ($model) {
|
||||
if(!Auth::user()->can('editByOwner', [ENTITY_EXPENSE, $model->user_id])){
|
||||
if(!Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])){
|
||||
return Utils::fromSqlDate($model->expense_date);
|
||||
}
|
||||
|
||||
@ -56,14 +56,16 @@ class ExpenseDatatable extends EntityDatatable
|
||||
[
|
||||
'amount',
|
||||
function ($model) {
|
||||
$amount = Utils::calculateTaxes($model->amount, $model->tax_rate1, $model->tax_rate2);
|
||||
$str = Utils::formatMoney($amount, $model->expense_currency_id);
|
||||
|
||||
// show both the amount and the converted amount
|
||||
if ($model->exchange_rate != 1) {
|
||||
$converted = round($model->amount * $model->exchange_rate, 2);
|
||||
return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' .
|
||||
Utils::formatMoney($converted, $model->invoice_currency_id);
|
||||
} else {
|
||||
return Utils::formatMoney($model->amount, $model->expense_currency_id);
|
||||
$converted = round($amount * $model->exchange_rate, 2);
|
||||
$str .= ' | ' . Utils::formatMoney($converted, $model->invoice_currency_id);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -16,7 +16,7 @@ class InvoiceDatatable extends EntityDatatable
|
||||
[
|
||||
'invoice_number',
|
||||
function ($model) use ($entityType) {
|
||||
if(!Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id])){
|
||||
if(!Auth::user()->can('viewByOwner', [ENTITY_INVOICE, $model->user_id])){
|
||||
return $model->invoice_number;
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ class InvoiceDatatable extends EntityDatatable
|
||||
$class = 'success';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class PaymentDatatable extends EntityDatatable
|
||||
[
|
||||
'invoice_number',
|
||||
function ($model) {
|
||||
if(!Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id])){
|
||||
if(!Auth::user()->can('viewByOwner', [ENTITY_INVOICE, $model->invoice_user_id])){
|
||||
return $model->invoice_number;
|
||||
}
|
||||
|
||||
@ -89,7 +89,11 @@ class PaymentDatatable extends EntityDatatable
|
||||
[
|
||||
'payment_date',
|
||||
function ($model) {
|
||||
return Utils::dateToString($model->payment_date);
|
||||
if ($model->is_deleted) {
|
||||
return Utils::dateToString($model->payment_date);
|
||||
} else {
|
||||
return link_to("payments/{$model->public_id}/edit", Utils::dateToString($model->payment_date))->toHtml();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
@ -123,12 +127,11 @@ class PaymentDatatable extends EntityDatatable
|
||||
return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')";
|
||||
},
|
||||
function ($model) {
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]) && $model->payment_status_id >= PAYMENT_STATUS_COMPLETED &&
|
||||
$model->refunded < $model->amount &&
|
||||
(
|
||||
($model->transaction_reference && in_array($model->gateway_id , static::$refundableGateways))
|
||||
|| $model->payment_type_id == PAYMENT_TYPE_CREDIT
|
||||
);
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id])
|
||||
&& $model->payment_status_id >= PAYMENT_STATUS_COMPLETED
|
||||
&& $model->refunded < $model->amount
|
||||
&& $model->transaction_reference
|
||||
&& in_array($model->gateway_id , static::$refundableGateways);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
@ -58,7 +58,17 @@ class RecurringInvoiceDatatable extends EntityDatatable
|
||||
function ($model) {
|
||||
return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]);
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
trans("texts.clone_invoice"),
|
||||
function ($model) {
|
||||
return URL::to("invoices/{$model->public_id}/clone");
|
||||
},
|
||||
function ($model) {
|
||||
return Auth::user()->can('create', ENTITY_INVOICE);
|
||||
}
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,9 @@ class TaskDatatable extends EntityDatatable
|
||||
[
|
||||
'created_at',
|
||||
function ($model) {
|
||||
if(!Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])){
|
||||
return Task::calcStartTime($model);
|
||||
}
|
||||
return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml();
|
||||
}
|
||||
],
|
||||
|
@ -182,10 +182,21 @@ class BaseTransformer extends TransformerAbstract
|
||||
* @param $name
|
||||
* @return null
|
||||
*/
|
||||
protected function getVendorId($name)
|
||||
public function getVendorId($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return null
|
||||
*/
|
||||
public function getExpenseCategoryId($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ class ClientTransformer extends BaseTransformer
|
||||
'postal_code' => $this->getString($data, 'postal_code'),
|
||||
'private_notes' => $this->getString($data, 'notes'),
|
||||
'website' => $this->getString($data, 'website'),
|
||||
'vat_number' => $this->getString($data, 'vat_number'),
|
||||
'contacts' => [
|
||||
[
|
||||
'first_name' => $this->getString($data, 'first_name'),
|
||||
|
28
app/Ninja/Import/CSV/ExpenseTransformer.php
Normal file
28
app/Ninja/Import/CSV/ExpenseTransformer.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php namespace App\Ninja\Import\CSV;
|
||||
|
||||
use App\Ninja\Import\BaseTransformer;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
* Class InvoiceTransformer
|
||||
*/
|
||||
class ExpenseTransformer extends BaseTransformer
|
||||
{
|
||||
/**
|
||||
* @param $data
|
||||
* @return bool|Item
|
||||
*/
|
||||
public function transform($data)
|
||||
{
|
||||
return new Item($data, function ($data) {
|
||||
return [
|
||||
'amount' => isset($data->amount) ? (float) $data->amount : null,
|
||||
'vendor_id' => isset($data->vendor) ? $this->getVendorId($data->vendor) : null,
|
||||
'client_id' => isset($data->client) ? $this->getClientId($data->client) : null,
|
||||
'expense_date' => isset($data->expense_date) ? date('Y-m-d', strtotime($data->expense_date)) : null,
|
||||
'public_notes' => $this->getString($data, 'public_notes'),
|
||||
'expense_category_id' => isset($data->expense_category) ? $this->getExpenseCategoryId($data->expense_category) : null,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Ninja\Mailers;
|
||||
|
||||
use Utils;
|
||||
use Exception;
|
||||
use Mail;
|
||||
use App\Models\Invoice;
|
||||
@ -29,7 +30,7 @@ class Mailer
|
||||
if (stristr($toEmail, '@example.com')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (isset($_ENV['POSTMARK_API_TOKEN'])) {
|
||||
$views = 'emails.'.$view.'_html';
|
||||
} else {
|
||||
@ -55,7 +56,7 @@ class Mailer
|
||||
if (!empty($data['pdfString']) && !empty($data['pdfFileName'])) {
|
||||
$message->attachData($data['pdfString'], $data['pdfFileName']);
|
||||
}
|
||||
|
||||
|
||||
// Attach documents to the email
|
||||
if(!empty($data['documents'])){
|
||||
foreach($data['documents'] as $document){
|
||||
@ -90,7 +91,7 @@ class Mailer
|
||||
|
||||
$invoice->markInvitationSent($invitation, $messageId);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -107,11 +108,13 @@ class Mailer
|
||||
} else {
|
||||
$emailError = $exception->getMessage();
|
||||
}
|
||||
|
||||
|
||||
if (isset($data['invitation'])) {
|
||||
$invitation = $data['invitation'];
|
||||
$invitation->email_error = $emailError;
|
||||
$invitation->save();
|
||||
} elseif ( ! Utils::isNinja()) {
|
||||
Utils::logError(Utils::getErrorString($exception));
|
||||
}
|
||||
|
||||
return $emailError;
|
||||
|
@ -2,16 +2,19 @@
|
||||
|
||||
use URL;
|
||||
use Session;
|
||||
use Utils;
|
||||
use Request;
|
||||
use Omnipay;
|
||||
use Exception;
|
||||
use CreditCard;
|
||||
use DateTime;
|
||||
use App\Models\AccountGatewayToken;
|
||||
use App\Models\AccountGatewaySettings;
|
||||
use App\Models\Account;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Models\Country;
|
||||
use App\Models\GatewayType;
|
||||
|
||||
class BasePaymentDriver
|
||||
{
|
||||
@ -119,6 +122,12 @@ class BasePaymentDriver
|
||||
|
||||
$gateway = $this->accountGateway->gateway;
|
||||
|
||||
if ( ! $this->meetsGatewayTypeLimits($this->gatewayType)) {
|
||||
// The customer must have hacked the URL
|
||||
Session::flash('error', trans('texts.limits_not_met'));
|
||||
return redirect()->to('view/' . $this->invitation->invitation_key);
|
||||
}
|
||||
|
||||
if ($this->isGatewayType(GATEWAY_TYPE_TOKEN) || $gateway->is_offsite) {
|
||||
if (Session::has('error')) {
|
||||
Session::reflash();
|
||||
@ -158,12 +167,14 @@ class BasePaymentDriver
|
||||
// check if a custom view exists for this provider
|
||||
protected function paymentView()
|
||||
{
|
||||
$file = sprintf('%s/views/payments/%s/%s.blade.php', resource_path(), $this->providerName(), $this->gatewayType);
|
||||
$gatewayTypeAlias = GatewayType::getAliasFromId($this->gatewayType);
|
||||
|
||||
$file = sprintf('%s/views/payments/%s/%s.blade.php', resource_path(), $this->providerName(), $gatewayTypeAlias);
|
||||
|
||||
if (file_exists($file)) {
|
||||
return sprintf('payments.%s/%s', $this->providerName(), $this->gatewayType);
|
||||
return sprintf('payments.%s/%s', $this->providerName(), $gatewayTypeAlias);
|
||||
} else {
|
||||
return sprintf('payments.%s', $this->gatewayType);
|
||||
return sprintf('payments.%s', $gatewayTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,8 +253,22 @@ class BasePaymentDriver
|
||||
->wherePublicId($this->sourceId)
|
||||
->firstOrFail();
|
||||
}
|
||||
} elseif ($this->shouldCreateToken()) {
|
||||
$paymentMethod = $this->createToken();
|
||||
|
||||
if ( ! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) {
|
||||
// The customer must have hacked the URL
|
||||
Session::flash('error', trans('texts.limits_not_met'));
|
||||
return redirect()->to('view/' . $this->invitation->invitation_key);
|
||||
}
|
||||
} else {
|
||||
if ($this->shouldCreateToken()) {
|
||||
$paymentMethod = $this->createToken();
|
||||
}
|
||||
|
||||
if ( ! $this->meetsGatewayTypeLimits($this->gatewayType)) {
|
||||
// The customer must have hacked the URL
|
||||
Session::flash('error', trans('texts.limits_not_met'));
|
||||
return redirect()->to('view/' . $this->invitation->invitation_key);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isTwoStep()) {
|
||||
@ -323,7 +348,8 @@ class BasePaymentDriver
|
||||
protected function paymentDetails($paymentMethod = false)
|
||||
{
|
||||
$invoice = $this->invoice();
|
||||
$completeUrl = url('complete/' . $this->invitation->invitation_key . '/' . $this->gatewayType);
|
||||
$gatewayTypeAlias = $this->gatewayType == GATEWAY_TYPE_TOKEN ? $this->gatewayType : GatewayType::getAliasFromId($this->gatewayType);
|
||||
$completeUrl = url('complete/' . $this->invitation->invitation_key . '/' . $gatewayTypeAlias);
|
||||
|
||||
$data = [
|
||||
'amount' => $invoice->getRequestedAmount(),
|
||||
@ -760,6 +786,10 @@ class BasePaymentDriver
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = URL::to("/payment/{$this->invitation->invitation_key}/token/".$paymentMethod->public_id);
|
||||
|
||||
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
|
||||
@ -787,27 +817,68 @@ class BasePaymentDriver
|
||||
{
|
||||
$links = [];
|
||||
|
||||
foreach ($this->gatewayTypes() as $gatewayType) {
|
||||
if ($gatewayType === GATEWAY_TYPE_TOKEN) {
|
||||
foreach ($this->gatewayTypes() as $gatewayTypeId) {
|
||||
if ($gatewayTypeId === GATEWAY_TYPE_TOKEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $this->meetsGatewayTypeLimits($gatewayTypeId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gatewayTypeAlias = GatewayType::getAliasFromId($gatewayTypeId);
|
||||
|
||||
if ($gatewayTypeId == GATEWAY_TYPE_CUSTOM) {
|
||||
$url = "javascript:showCustomModal();";
|
||||
$label = e($this->accountGateway->getConfigField('name'));
|
||||
} else {
|
||||
$url = $this->paymentUrl($gatewayTypeAlias);
|
||||
$label = trans("texts.{$gatewayTypeAlias}");
|
||||
}
|
||||
|
||||
$links[] = [
|
||||
'url' => $this->paymentUrl($gatewayType),
|
||||
'label' => trans("texts.{$gatewayType}")
|
||||
'gatewayTypeId' => $gatewayTypeId,
|
||||
'url' => $url,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
protected function paymentUrl($gatewayType)
|
||||
protected function meetsGatewayTypeLimits($gatewayTypeId)
|
||||
{
|
||||
if ( !$gatewayTypeId ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$accountGatewaySettings = AccountGatewaySettings::scope(false, $this->invitation->account_id)
|
||||
->where('account_gateway_settings.gateway_type_id', '=', $gatewayTypeId)->first();
|
||||
|
||||
if ($accountGatewaySettings) {
|
||||
$invoice = $this->invoice();
|
||||
|
||||
if ($accountGatewaySettings->min_limit !== null && $invoice->balance < $accountGatewaySettings->min_limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($accountGatewaySettings->max_limit !== null && $invoice->balance > $accountGatewaySettings->max_limit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function paymentUrl($gatewayTypeAlias)
|
||||
{
|
||||
$account = $this->account();
|
||||
$url = URL::to("/payment/{$this->invitation->invitation_key}/{$gatewayType}");
|
||||
$url = URL::to("/payment/{$this->invitation->invitation_key}/{$gatewayTypeAlias}");
|
||||
|
||||
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||
|
||||
// PayPal doesn't allow being run in an iframe so we need to open in new tab
|
||||
if ($gatewayType === GATEWAY_TYPE_PAYPAL) {
|
||||
if ($gatewayTypeId === GATEWAY_TYPE_PAYPAL) {
|
||||
$url .= '#braintree_paypal';
|
||||
|
||||
if ($account->iframe_url) {
|
||||
|
12
app/Ninja/PaymentDrivers/CustomPaymentDriver.php
Normal file
12
app/Ninja/PaymentDrivers/CustomPaymentDriver.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php namespace App\Ninja\PaymentDrivers;
|
||||
|
||||
class CustomPaymentDriver extends BasePaymentDriver
|
||||
{
|
||||
public function gatewayTypes()
|
||||
{
|
||||
return [
|
||||
GATEWAY_TYPE_CUSTOM
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -151,14 +151,17 @@ class StripePaymentDriver extends BasePaymentDriver
|
||||
protected function creatingPaymentMethod($paymentMethod)
|
||||
{
|
||||
$data = $this->tokenResponse;
|
||||
$source = false;
|
||||
|
||||
if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
|
||||
$source = $data;
|
||||
} elseif (!empty($data['object']) && $data['object'] == 'customer') {
|
||||
$sources = !empty($data['sources']) ? $data['sources'] : $data['cards'];
|
||||
$source = reset($sources['data']);
|
||||
} else {
|
||||
$source = !empty($data['source']) ? $data['source'] : $data['card'];
|
||||
} elseif (!empty($data['source'])) {
|
||||
$source = $data['source'];
|
||||
} elseif (!empty($data['card'])) {
|
||||
$source = $data['card'];
|
||||
}
|
||||
|
||||
if ( ! $source) {
|
||||
|
@ -23,22 +23,4 @@ class ClientPresenter extends EntityPresenter {
|
||||
|
||||
return $account->formatMoney($client->paid_to_date, $client);
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
$class = $text = '';
|
||||
|
||||
if ($this->entity->is_deleted) {
|
||||
$class = 'danger';
|
||||
$text = trans('texts.deleted');
|
||||
} elseif ($this->entity->trashed()) {
|
||||
$class = 'warning';
|
||||
$text = trans('texts.archived');
|
||||
} else {
|
||||
$class = 'success';
|
||||
$text = trans('texts.active');
|
||||
}
|
||||
|
||||
return "<span class=\"label label-{$class}\">{$text}</span>";
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,24 @@ class EntityPresenter extends Presenter
|
||||
return URL::to($link);
|
||||
}
|
||||
|
||||
public function statusLabel()
|
||||
{
|
||||
$class = $text = '';
|
||||
|
||||
if ($this->entity->is_deleted) {
|
||||
$class = 'danger';
|
||||
$text = trans('texts.deleted');
|
||||
} elseif ($this->entity->trashed()) {
|
||||
$class = 'warning';
|
||||
$text = trans('texts.archived');
|
||||
} else {
|
||||
//$class = 'success';
|
||||
//$text = trans('texts.active');
|
||||
}
|
||||
|
||||
return "<span style=\"font-size:13px\" class=\"label label-{$class}\">{$text}</span>";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -24,12 +24,4 @@ class ExpensePresenter extends EntityPresenter
|
||||
return Utils::fromSqlDate($this->entity->expense_date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function invoiced_amount()
|
||||
{
|
||||
return $this->entity->invoice_id ? $this->entity->convertedAmount() : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use Auth;
|
||||
use Request;
|
||||
use Input;
|
||||
use Session;
|
||||
use Utils;
|
||||
use URL;
|
||||
@ -27,6 +28,11 @@ class AccountRepository
|
||||
public function create($firstName = '', $lastName = '', $email = '', $password = '')
|
||||
{
|
||||
$company = new Company();
|
||||
$company->utm_source = Input::get('utm_source');
|
||||
$company->utm_medium = Input::get('utm_medium');
|
||||
$company->utm_campaign = Input::get('utm_campaign');
|
||||
$company->utm_term = Input::get('utm_term');
|
||||
$company->utm_content = Input::get('utm_content');
|
||||
$company->save();
|
||||
|
||||
$account = new Account();
|
||||
|
@ -74,6 +74,8 @@ class ActivityRepository
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'activities.invoice_id')
|
||||
->leftJoin('payments', 'payments.id', '=', 'activities.payment_id')
|
||||
->leftJoin('credits', 'credits.id', '=', 'activities.credit_id')
|
||||
->leftJoin('tasks', 'tasks.id', '=', 'activities.task_id')
|
||||
->leftJoin('expenses', 'expenses.id', '=', 'activities.expense_id')
|
||||
->where('clients.id', '=', $clientId)
|
||||
->where('contacts.is_primary', '=', 1)
|
||||
->whereNull('contacts.deleted_at')
|
||||
@ -102,7 +104,11 @@ class ActivityRepository
|
||||
'contacts.email as email',
|
||||
'payments.transaction_reference as payment',
|
||||
'payments.amount as payment_amount',
|
||||
'credits.amount as credit'
|
||||
'credits.amount as credit',
|
||||
'tasks.description as task_description',
|
||||
'tasks.public_id as task_public_id',
|
||||
'expenses.public_notes as expense_public_notes',
|
||||
'expenses.public_id as expense_public_id'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ class BaseRepository
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function getClassName()
|
||||
public function getClassName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -40,7 +40,7 @@ class BaseRepository
|
||||
if ($entity->trashed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$entity->delete();
|
||||
|
||||
$className = $this->getEventClass($entity, 'Archived');
|
||||
@ -83,7 +83,7 @@ class BaseRepository
|
||||
if ($entity->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$entity->is_deleted = true;
|
||||
$entity->save();
|
||||
|
||||
|
@ -31,6 +31,7 @@ class ClientRepository extends BaseRepository
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
@ -78,7 +79,10 @@ class ClientRepository extends BaseRepository
|
||||
$client = Client::createNew();
|
||||
} else {
|
||||
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
|
||||
\Log::warning('Entity not set in client repo save');
|
||||
}
|
||||
|
||||
if ($client->is_deleted) {
|
||||
return $client;
|
||||
}
|
||||
|
||||
// convert currency code to id
|
||||
@ -107,7 +111,11 @@ class ClientRepository extends BaseRepository
|
||||
|
||||
// If the primary is set ensure it's listed first
|
||||
usort($contacts, function ($left, $right) {
|
||||
return (isset($right['is_primary']) ? $right['is_primary'] : 1) - (isset($left['is_primary']) ? $left['is_primary'] : 0);
|
||||
if (isset($right['is_primary']) && isset($left['is_primary'])) {
|
||||
return $right['is_primary'] - $left['is_primary'];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
|
@ -58,10 +58,36 @@ class CreditRepository extends BaseRepository
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getClientDatatable($clientId)
|
||||
{
|
||||
$query = DB::table('credits')
|
||||
->join('accounts', 'accounts.id', '=', 'credits.account_id')
|
||||
->join('clients', 'clients.id', '=', 'credits.client_id')
|
||||
->where('credits.client_id', '=', $clientId)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('credits.deleted_at', '=', null)
|
||||
->where('credits.balance', '>', 0)
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
'credits.amount',
|
||||
'credits.balance',
|
||||
'credits.credit_date'
|
||||
);
|
||||
|
||||
$table = \Datatable::query($query)
|
||||
->addColumn('credit_date', function ($model) { return Utils::fromSqlDate($model->credit_date); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
||||
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); })
|
||||
->make();
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
public function save($input, $credit = null)
|
||||
{
|
||||
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
|
||||
|
||||
|
||||
if ($credit) {
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
|
@ -39,13 +39,15 @@ class DashboardRepository
|
||||
|
||||
$data = [];
|
||||
$count = 0;
|
||||
$balance = 0;
|
||||
$records = $this->rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId);
|
||||
|
||||
array_map(function ($item) use (&$data, &$count, $groupBy) {
|
||||
array_map(function ($item) use (&$data, &$count, &$balance, $groupBy) {
|
||||
$data[$item->$groupBy] = $item->total;
|
||||
$count += $item->count;
|
||||
}, $records->get());
|
||||
|
||||
$balance += isset($item->balance) ? $item->balance : 0;
|
||||
}, $records);
|
||||
|
||||
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
|
||||
$endDate->modify('+1 '.$padding);
|
||||
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
|
||||
@ -84,9 +86,9 @@ class DashboardRepository
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$totals->invoices = array_sum($data);
|
||||
$totals->average = $count ? round($totals->invoices / $count, 2) : 0;
|
||||
$totals->balance = $balance;
|
||||
} elseif ($entityType == ENTITY_PAYMENT) {
|
||||
$totals->revenue = array_sum($data);
|
||||
$totals->balance = $totals->invoices - $totals->revenue;
|
||||
} elseif ($entityType == ENTITY_EXPENSE) {
|
||||
//$totals->profit = $totals->revenue - array_sum($data);
|
||||
$totals->expenses = array_sum($data);
|
||||
@ -106,6 +108,10 @@ class DashboardRepository
|
||||
|
||||
private function rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId)
|
||||
{
|
||||
if ( ! in_array($groupBy, ['DAYOFYEAR', 'WEEK', 'MONTH'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$accountId = $account->id;
|
||||
$currencyId = intval($currencyId);
|
||||
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
|
||||
@ -128,7 +134,7 @@ class DashboardRepository
|
||||
}
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$records->select(DB::raw('sum(invoices.amount) as total, count(invoices.id) as count, '.$timeframe.' as '.$groupBy))
|
||||
$records->select(DB::raw('sum(invoices.amount) as total, sum(invoices.balance) as balance, count(invoices.id) as count, '.$timeframe.' as '.$groupBy))
|
||||
->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
|
||||
->where('is_recurring', '=', false);
|
||||
} elseif ($entityType == ENTITY_PAYMENT) {
|
||||
@ -137,10 +143,10 @@ class DashboardRepository
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->whereNotIn('payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
|
||||
} elseif ($entityType == ENTITY_EXPENSE) {
|
||||
$records->select(DB::raw('sum(expenses.amount) as total, count(expenses.id) as count, '.$timeframe.' as '.$groupBy));
|
||||
$records->select(DB::raw('sum(expenses.amount + (expenses.amount * expenses.tax_rate1 / 100) + (expenses.amount * expenses.tax_rate2 / 100)) as total, count(expenses.id) as count, '.$timeframe.' as '.$groupBy));
|
||||
}
|
||||
|
||||
return $records;
|
||||
return $records->get();
|
||||
}
|
||||
|
||||
public function totals($accountId, $userId, $viewAll)
|
||||
@ -175,29 +181,39 @@ class DashboardRepository
|
||||
return $metrics->groupBy('accounts.id')->first();
|
||||
}
|
||||
|
||||
public function paidToDate($accountId, $userId, $viewAll)
|
||||
public function paidToDate($account, $userId, $viewAll)
|
||||
{
|
||||
$accountId = $account->id;
|
||||
$select = DB::raw(
|
||||
'SUM('.DB::getQueryGrammar()->wrap('clients.paid_to_date', true).') as value,'
|
||||
'SUM('.DB::getQueryGrammar()->wrap('payments.amount', true).' - '.DB::getQueryGrammar()->wrap('payments.refunded', true).') as value,'
|
||||
.DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
|
||||
);
|
||||
$paidToDate = DB::table('accounts')
|
||||
$paidToDate = DB::table('payments')
|
||||
->select($select)
|
||||
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
|
||||
->where('accounts.id', '=', $accountId)
|
||||
->where('clients.is_deleted', '=', false);
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('payments.account_id', '=', $accountId)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->whereNotIn('payments.payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
|
||||
|
||||
if (!$viewAll){
|
||||
$paidToDate = $paidToDate->where('clients.user_id', '=', $userId);
|
||||
$paidToDate->where('invoices.user_id', '=', $userId);
|
||||
}
|
||||
|
||||
return $paidToDate->groupBy('accounts.id')
|
||||
->groupBy(DB::raw('CASE WHEN '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' IS NULL THEN CASE WHEN '.DB::getQueryGrammar()->wrap('accounts.currency_id', true).' IS NULL THEN 1 ELSE '.DB::getQueryGrammar()->wrap('accounts.currency_id', true).' END ELSE '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' END'))
|
||||
if ($account->financial_year_start) {
|
||||
$yearStart = str_replace('2000', date('Y'), $account->financial_year_start);
|
||||
$paidToDate->where('payments.payment_date', '>=', $yearStart);
|
||||
}
|
||||
|
||||
return $paidToDate->groupBy('payments.account_id')
|
||||
->groupBy(DB::raw('CASE WHEN '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' IS NULL THEN '.($account->currency_id ?: DEFAULT_CURRENCY).' ELSE '.DB::getQueryGrammar()->wrap('clients.currency_id', true).' END'))
|
||||
->get();
|
||||
}
|
||||
|
||||
public function averages($accountId, $userId, $viewAll)
|
||||
public function averages($account, $userId, $viewAll)
|
||||
{
|
||||
$accountId = $account->id;
|
||||
$select = DB::raw(
|
||||
'AVG('.DB::getQueryGrammar()->wrap('invoices.amount', true).') as invoice_avg, '
|
||||
.DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
|
||||
@ -213,7 +229,12 @@ class DashboardRepository
|
||||
->where('invoices.is_recurring', '=', false);
|
||||
|
||||
if (!$viewAll){
|
||||
$averageInvoice = $averageInvoice->where('invoices.user_id', '=', $userId);
|
||||
$averageInvoice->where('invoices.user_id', '=', $userId);
|
||||
}
|
||||
|
||||
if ($account->financial_year_start) {
|
||||
$yearStart = str_replace('2000', date('Y'), $account->financial_year_start);
|
||||
$averageInvoice->where('invoices.invoice_date', '>=', $yearStart);
|
||||
}
|
||||
|
||||
return $averageInvoice->groupBy('accounts.id')
|
||||
@ -252,7 +273,7 @@ class DashboardRepository
|
||||
}
|
||||
|
||||
return $activities->orderBy('activities.created_at', 'desc')
|
||||
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task')
|
||||
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task', 'expense', 'contact')
|
||||
->take(50)
|
||||
->get();
|
||||
}
|
||||
@ -335,8 +356,12 @@ class DashboardRepository
|
||||
|
||||
public function expenses($accountId, $userId, $viewAll)
|
||||
{
|
||||
$amountField = DB::getQueryGrammar()->wrap('expenses.amount', true);
|
||||
$taxRate1Field = DB::getQueryGrammar()->wrap('expenses.tax_rate1', true);
|
||||
$taxRate2Field = DB::getQueryGrammar()->wrap('expenses.tax_rate2', true);
|
||||
|
||||
$select = DB::raw(
|
||||
'SUM('.DB::getQueryGrammar()->wrap('expenses.amount', true).') as value,'
|
||||
"SUM({$amountField} + ({$amountField} * {$taxRate1Field} / 100) + ({$amountField} * {$taxRate2Field} / 100)) as value,"
|
||||
.DB::getQueryGrammar()->wrap('expenses.expense_currency_id', true).' as currency_id'
|
||||
);
|
||||
$paidToDate = DB::table('accounts')
|
||||
|
@ -12,6 +12,11 @@ class ExpenseCategoryRepository extends BaseRepository
|
||||
return 'App\Models\ExpenseCategory';
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return ExpenseCategory::scope()->get();
|
||||
}
|
||||
|
||||
public function find($filter = null)
|
||||
{
|
||||
$query = DB::table('expense_categories')
|
||||
|
@ -76,6 +76,8 @@ class ExpenseRepository extends BaseRepository
|
||||
'expenses.expense_currency_id',
|
||||
'expenses.invoice_currency_id',
|
||||
'expenses.user_id',
|
||||
'expenses.tax_rate1',
|
||||
'expenses.tax_rate2',
|
||||
'expense_categories.name as category',
|
||||
'invoices.public_id as invoice_public_id',
|
||||
'invoices.user_id as invoice_user_id',
|
||||
@ -118,20 +120,24 @@ class ExpenseRepository extends BaseRepository
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
$expense = Expense::scope($publicId)->firstOrFail();
|
||||
\Log::warning('Entity not set in expense repo save');
|
||||
if (Utils::isNinjaDev()) {
|
||||
\Log::warning('Entity not set in expense repo save');
|
||||
}
|
||||
} else {
|
||||
$expense = Expense::createNew();
|
||||
}
|
||||
|
||||
if ($expense->is_deleted) {
|
||||
return $expense;
|
||||
}
|
||||
|
||||
// First auto fill
|
||||
$expense->fill($input);
|
||||
|
||||
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
|
||||
|
||||
if (isset($input['private_notes'])) {
|
||||
$expense->private_notes = trim($input['private_notes']);
|
||||
if (isset($input['expense_date'])) {
|
||||
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
|
||||
}
|
||||
$expense->public_notes = trim($input['public_notes']);
|
||||
|
||||
$expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false;
|
||||
|
||||
if ( ! $expense->expense_currency_id) {
|
||||
@ -143,7 +149,9 @@ class ExpenseRepository extends BaseRepository
|
||||
|
||||
$rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1;
|
||||
$expense->exchange_rate = round($rate, 4);
|
||||
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
|
||||
if (isset($input['amount'])) {
|
||||
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
|
||||
}
|
||||
|
||||
$expense->save();
|
||||
|
||||
@ -173,27 +181,4 @@ class ExpenseRepository extends BaseRepository
|
||||
|
||||
return $expense;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action)
|
||||
{
|
||||
$expenses = Expense::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($expenses as $expense) {
|
||||
if ($action == 'restore') {
|
||||
$expense->restore();
|
||||
|
||||
$expense->is_deleted = false;
|
||||
$expense->save();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$expense->is_deleted = true;
|
||||
$expense->save();
|
||||
}
|
||||
|
||||
$expense->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($tasks);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ class InvoiceRepository extends BaseRepository
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
@ -280,7 +281,13 @@ class InvoiceRepository extends BaseRepository
|
||||
}
|
||||
} else {
|
||||
$invoice = Invoice::scope($publicId)->firstOrFail();
|
||||
\Log::warning('Entity not set in invoice repo save');
|
||||
if (Utils::isNinjaDev()) {
|
||||
\Log::warning('Entity not set in invoice repo save');
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoice->is_deleted) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$invoice->fill($data);
|
||||
@ -306,9 +313,6 @@ class InvoiceRepository extends BaseRepository
|
||||
if (isset($data['is_amount_discount'])) {
|
||||
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
|
||||
}
|
||||
if (isset($data['partial'])) {
|
||||
$invoice->partial = round(Utils::parseFloat($data['partial']), 2);
|
||||
}
|
||||
if (isset($data['invoice_date_sql'])) {
|
||||
$invoice->invoice_date = $data['invoice_date_sql'];
|
||||
} elseif (isset($data['invoice_date'])) {
|
||||
@ -477,6 +481,10 @@ class InvoiceRepository extends BaseRepository
|
||||
$invoice->balance = $total;
|
||||
}
|
||||
|
||||
if (isset($data['partial'])) {
|
||||
$invoice->partial = max(0,min(round(Utils::parseFloat($data['partial']), 2), $invoice->balance));
|
||||
}
|
||||
|
||||
$invoice->amount = $total;
|
||||
$invoice->save();
|
||||
|
||||
@ -653,6 +661,9 @@ class InvoiceRepository extends BaseRepository
|
||||
if ($quotePublicId) {
|
||||
$clone->invoice_type_id = INVOICE_TYPE_STANDARD;
|
||||
$clone->quote_id = $quotePublicId;
|
||||
if ($account->invoice_terms) {
|
||||
$clone->terms = $account->invoice_terms;
|
||||
}
|
||||
}
|
||||
|
||||
$clone->save();
|
||||
|
@ -150,11 +150,17 @@ class PaymentRepository extends BaseRepository
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
$payment = Payment::scope($publicId)->firstOrFail();
|
||||
\Log::warning('Entity not set in payment repo save');
|
||||
if (Utils::isNinjaDev()) {
|
||||
\Log::warning('Entity not set in payment repo save');
|
||||
}
|
||||
} else {
|
||||
$payment = Payment::createNew();
|
||||
}
|
||||
|
||||
if ($payment->is_deleted) {
|
||||
return $payment;
|
||||
}
|
||||
|
||||
$paymentTypeId = false;
|
||||
if (isset($input['payment_type_id'])) {
|
||||
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
|
||||
|
@ -17,15 +17,14 @@ class ProductRepository extends BaseRepository
|
||||
->get();
|
||||
}
|
||||
|
||||
public function find($accountId)
|
||||
public function find($accountId, $filter = null)
|
||||
{
|
||||
return DB::table('products')
|
||||
$query = DB::table('products')
|
||||
->leftJoin('tax_rates', function($join) {
|
||||
$join->on('tax_rates.id', '=', 'products.default_tax_rate_id')
|
||||
->whereNull('tax_rates.deleted_at');
|
||||
})
|
||||
->where('products.account_id', '=', $accountId)
|
||||
->where('products.deleted_at', '=', null)
|
||||
->select(
|
||||
'products.public_id',
|
||||
'products.product_key',
|
||||
@ -35,6 +34,19 @@ class ProductRepository extends BaseRepository
|
||||
'tax_rates.rate as tax_rate',
|
||||
'products.deleted_at'
|
||||
);
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('products.product_key', 'like', '%'.$filter.'%')
|
||||
->orWhere('products.notes', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
if (!\Session::get('show_trash:product')) {
|
||||
$query->where('products.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function save($data, $product = null)
|
||||
|
@ -5,8 +5,13 @@ use Session;
|
||||
use App\Models\Client;
|
||||
use App\Models\Task;
|
||||
|
||||
class TaskRepository
|
||||
class TaskRepository extends BaseRepository
|
||||
{
|
||||
public function getClassName()
|
||||
{
|
||||
return 'App\Models\Task';
|
||||
}
|
||||
|
||||
public function find($clientPublicId = null, $filter = null)
|
||||
{
|
||||
$query = \DB::table('tasks')
|
||||
@ -67,12 +72,15 @@ class TaskRepository
|
||||
if ($task) {
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
$task = Task::scope($publicId)->firstOrFail();
|
||||
\Log::warning('Entity not set in task repo save');
|
||||
$task = Task::scope($publicId)->withTrashed()->firstOrFail();
|
||||
} else {
|
||||
$task = Task::createNew();
|
||||
}
|
||||
|
||||
if ($task->is_deleted) {
|
||||
return $task;
|
||||
}
|
||||
|
||||
if (isset($data['client']) && $data['client']) {
|
||||
$task->client_id = Client::getPrivateId($data['client']);
|
||||
}
|
||||
@ -109,26 +117,4 @@ class TaskRepository
|
||||
return $task;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action)
|
||||
{
|
||||
$tasks = Task::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
if ($action == 'restore') {
|
||||
$task->restore();
|
||||
|
||||
$task->is_deleted = false;
|
||||
$task->save();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$task->is_deleted = true;
|
||||
$task->save();
|
||||
}
|
||||
|
||||
$task->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($tasks);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Ninja\Repositories;
|
||||
|
||||
use Utils;
|
||||
use DB;
|
||||
use App\Models\Vendor;
|
||||
|
||||
@ -70,7 +71,13 @@ class VendorRepository extends BaseRepository
|
||||
$vendor = Vendor::createNew();
|
||||
} else {
|
||||
$vendor = Vendor::scope($publicId)->with('vendor_contacts')->firstOrFail();
|
||||
\Log::warning('Entity not set in vendor repo save');
|
||||
if (Utils::isNinjaDev()) {
|
||||
\Log::warning('Entity not set in vendor repo save');
|
||||
}
|
||||
}
|
||||
|
||||
if ($vendor->is_deleted) {
|
||||
return $vendor;
|
||||
}
|
||||
|
||||
$vendor->fill($data);
|
||||
@ -78,12 +85,22 @@ class VendorRepository extends BaseRepository
|
||||
|
||||
$first = true;
|
||||
$vendorcontacts = isset($data['vendor_contact']) ? [$data['vendor_contact']] : $data['vendor_contacts'];
|
||||
$vendorcontactIds = [];
|
||||
|
||||
foreach ($vendorcontacts as $vendorcontact) {
|
||||
$vendorcontact = $vendor->addVendorContact($vendorcontact, $first);
|
||||
$vendorcontactIds[] = $vendorcontact->public_id;
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ( ! $vendor->wasRecentlyCreated) {
|
||||
foreach ($vendor->vendor_contacts as $contact) {
|
||||
if (!in_array($contact->public_id, $vendorcontactIds)) {
|
||||
$contact->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $vendor;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class ActivityTransformer extends EntityTransformer
|
||||
protected $availableIncludes = [ ];
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
* @param Activity $activity
|
||||
* @return array
|
||||
*/
|
||||
public function transform(Activity $activity)
|
||||
@ -29,7 +29,10 @@ class ActivityTransformer extends EntityTransformer
|
||||
'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null,
|
||||
'payment_id' => $activity->payment ? $activity->payment->public_id : null,
|
||||
'credit_id' => $activity->credit ? $activity->credit->public_id : null,
|
||||
'updated_at' => $this->getTimestamp($activity->updated_at)
|
||||
'updated_at' => $this->getTimestamp($activity->updated_at),
|
||||
'expense_id' => $activity->expense_id ? $activity->expense->public_id : null,
|
||||
'is_system' => (bool) $activity->is_system ? $activity->is_system : null,
|
||||
'contact_id' => $activity->contact_id ? $activity->contact->public_id : null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -26,4 +26,4 @@ class ContactTransformer extends EntityTransformer
|
||||
'send_invoice' => (bool) $contact->send_invoice,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +23,16 @@ class ExpenseTransformer extends EntityTransformer
|
||||
'transaction_id' => $expense->transaction_id,
|
||||
'bank_id' => $expense->bank_id,
|
||||
'expense_currency_id' => (int) $expense->expense_currency_id,
|
||||
'expense_category_id' => (int) $expense->expense_category_id,
|
||||
'amount' => (float) $expense->amount,
|
||||
'expense_date' => $expense->expense_date,
|
||||
'exchange_rate' => (float) $expense->exchange_rate,
|
||||
'invoice_currency_id' => (int) $expense->invoice_currency_id,
|
||||
'is_deleted' => (bool) $expense->is_deleted,
|
||||
'tax_name1' => $expense->tax_name1,
|
||||
'tax_name2' => $expense->tax_name2,
|
||||
'tax_rate1' => $expense->tax_rate1,
|
||||
'tax_rate2' => $expense->tax_rate2,
|
||||
'client_id' => $this->client ? $this->client->public_id : (isset($expense->client->public_id) ? (int) $expense->client->public_id : null),
|
||||
'invoice_id' => isset($expense->invoice->public_id) ? (int) $expense->invoice->public_id : null,
|
||||
'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,
|
||||
|
@ -10,7 +10,7 @@ class UserAccountTransformer extends EntityTransformer
|
||||
];
|
||||
|
||||
protected $tokenName;
|
||||
|
||||
|
||||
public function __construct(Account $account, $serializer, $tokenName)
|
||||
{
|
||||
parent::__construct($account, $serializer);
|
||||
@ -31,8 +31,9 @@ class UserAccountTransformer extends EntityTransformer
|
||||
'name' => $user->account->present()->name,
|
||||
'token' => $user->account->getToken($user->id, $this->tokenName),
|
||||
'default_url' => SITE_URL,
|
||||
'plan' => $user->account->company->plan,
|
||||
'logo' => $user->account->logo,
|
||||
'logo_url' => $user->account->getLogoURL(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user