1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-22 01:11:34 +02:00

Merge branch 'release-2.8.0'

This commit is contained in:
Hillel Coren 2016-11-06 19:29:19 +02:00
commit b620356db1
236 changed files with 4898 additions and 2286 deletions

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ Thumbs.db
/.project /.project
tests/_output/ tests/_output/
tests/_bootstrap.php tests/_bootstrap.php
tests/_support/_generated/
# composer stuff # composer stuff
/c3.php /c3.php

View File

@ -65,6 +65,7 @@ before_script:
- sleep 5 - sleep 5
# Make sure the app is up-to-date # Make sure the app is up-to-date
- curl -L http://ninja.dev:8000/update - curl -L http://ninja.dev:8000/update
#- php artisan ninja:create-test-data 25
script: script:
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php

View File

@ -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

View File

@ -1,6 +1,7 @@
<?php namespace App\Console\Commands; <?php namespace App\Console\Commands;
use DB; use DB;
use Mail;
use Carbon; use Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -51,9 +52,12 @@ class CheckData extends Command {
*/ */
protected $description = 'Check/fix data'; protected $description = 'Check/fix data';
protected $log = '';
protected $isValid = true;
public function fire() public function fire()
{ {
$this->info(date('Y-m-d') . ' Running CheckData...'); $this->logMessage(date('Y-m-d') . ' Running CheckData...');
if (!$this->option('client_id')) { if (!$this->option('client_id')) {
$this->checkPaidToDate(); $this->checkPaidToDate();
@ -66,7 +70,21 @@ class CheckData extends Command {
$this->checkAccountData(); $this->checkAccountData();
} }
$this->info('Done'); $this->logMessage('Done');
$errorEmail = env('ERROR_EMAIL');
if ( ! $this->isValid && $errorEmail) {
Mail::raw($this->log, function ($message) use ($errorEmail) {
$message->to($errorEmail)
->from(CONTACT_EMAIL)
->subject('Check-Data');
});
}
}
private function logMessage($str)
{
$this->log .= $str . "\n";
} }
private function checkBlankInvoiceHistory() private function checkBlankInvoiceHistory()
@ -75,9 +93,14 @@ class CheckData extends Command {
->where('activity_type_id', '=', 5) ->where('activity_type_id', '=', 5)
->where('json_backup', '=', '') ->where('json_backup', '=', '')
->whereNotIn('id', [634386, 756352, 756353, 756356]) ->whereNotIn('id', [634386, 756352, 756353, 756356])
->whereNotIn('id', [634386, 756352, 756353, 756356, 820872])
->count(); ->count();
$this->info($count . ' activities with blank invoice backup'); if ($count > 0) {
$this->isValid = false;
}
$this->logMessage($count . ' activities with blank invoice backup');
} }
private function checkAccountData() private function checkAccountData()
@ -132,7 +155,8 @@ class CheckData extends Command {
->get(["{$table}.id", 'clients.account_id', 'clients.user_id']); ->get(["{$table}.id", 'clients.account_id', 'clients.user_id']);
if (count($records)) { if (count($records)) {
$this->info(count($records) . " {$table} records with incorrect {$entityType} account id"); $this->isValid = false;
$this->logMessage(count($records) . " {$table} records with incorrect {$entityType} account id");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
foreach ($records as $record) { foreach ($records as $record) {
@ -162,7 +186,11 @@ class CheckData extends Command {
->groupBy('clients.id') ->groupBy('clients.id')
->havingRaw('clients.paid_to_date != sum(payments.amount - payments.refunded) and clients.paid_to_date != 999999999.9999') ->havingRaw('clients.paid_to_date != sum(payments.amount - payments.refunded) and clients.paid_to_date != 999999999.9999')
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]); ->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
$this->info(count($clients) . ' clients with incorrect paid to date'); $this->logMessage(count($clients) . ' clients with incorrect paid to date');
if (count($clients) > 0) {
$this->isValid = false;
}
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
foreach ($clients as $client) { foreach ($clients as $client) {
@ -179,6 +207,7 @@ class CheckData extends Command {
$clients = DB::table('clients') $clients = DB::table('clients')
->join('invoices', 'invoices.client_id', '=', 'clients.id') ->join('invoices', 'invoices.client_id', '=', 'clients.id')
->join('accounts', 'accounts.id', '=', 'clients.account_id') ->join('accounts', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '!=', 20432)
->where('clients.is_deleted', '=', 0) ->where('clients.is_deleted', '=', 0)
->where('invoices.is_deleted', '=', 0) ->where('invoices.is_deleted', '=', 0)
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
@ -192,10 +221,14 @@ class CheckData extends Command {
$clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at') $clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
->orderBy('accounts.company_id', 'DESC') ->orderBy('accounts.company_id', 'DESC')
->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]); ->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]);
$this->info(count($clients) . ' clients with incorrect balance/activities'); $this->logMessage(count($clients) . ' clients with incorrect balance/activities');
if (count($clients) > 0) {
$this->isValid = false;
}
foreach ($clients as $client) { foreach ($clients as $client) {
$this->info("=== Company: {$client->company_id} Account:{$client->account_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ==="); $this->logMessage("=== Company: {$client->company_id} Account:{$client->account_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ===");
$foundProblem = false; $foundProblem = false;
$lastBalance = 0; $lastBalance = 0;
$lastAdjustment = 0; $lastAdjustment = 0;
@ -205,7 +238,7 @@ class CheckData extends Command {
->where('client_id', '=', $client->id) ->where('client_id', '=', $client->id)
->orderBy('activities.id') ->orderBy('activities.id')
->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']); ->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
//$this->info(var_dump($activities)); //$this->logMessage(var_dump($activities));
foreach ($activities as $activity) { foreach ($activities as $activity) {
@ -252,19 +285,19 @@ class CheckData extends Command {
// **Fix for ninja invoices which didn't have the invoice_type_id value set // **Fix for ninja invoices which didn't have the invoice_type_id value set
if ($noAdjustment && $client->account_id == 20432) { if ($noAdjustment && $client->account_id == 20432) {
$this->info("No adjustment for ninja invoice"); $this->logMessage("No adjustment for ninja invoice");
$foundProblem = true; $foundProblem = true;
$clientFix += $invoice->amount; $clientFix += $invoice->amount;
$activityFix = $invoice->amount; $activityFix = $invoice->amount;
// **Fix for allowing converting a recurring invoice to a normal one without updating the balance** // **Fix for allowing converting a recurring invoice to a normal one without updating the balance**
} elseif ($noAdjustment && $invoice->invoice_type_id == INVOICE_TYPE_STANDARD && !$invoice->is_recurring) { } elseif ($noAdjustment && $invoice->invoice_type_id == INVOICE_TYPE_STANDARD && !$invoice->is_recurring) {
$this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $this->logMessage("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}");
$foundProblem = true; $foundProblem = true;
$clientFix += $invoice->amount; $clientFix += $invoice->amount;
$activityFix = $invoice->amount; $activityFix = $invoice->amount;
// **Fix for updating balance when creating a quote or recurring invoice** // **Fix for updating balance when creating a quote or recurring invoice**
} elseif ($activity->adjustment != 0 && ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE || $invoice->is_recurring)) { } elseif ($activity->adjustment != 0 && ($invoice->invoice_type_id == INVOICE_TYPE_QUOTE || $invoice->is_recurring)) {
$this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}"); $this->logMessage("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} invoiceTypeId:{$invoice->invoice_type_id} isRecurring:{$invoice->is_recurring}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
@ -272,7 +305,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
// **Fix for updating balance when deleting a recurring invoice** // **Fix for updating balance when deleting a recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) { if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
if ($activity->balance != $lastBalance) { if ($activity->balance != $lastBalance) {
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
@ -282,7 +315,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
// **Fix for updating balance when archiving an invoice** // **Fix for updating balance when archiving an invoice**
if ($activity->adjustment != 0 && !$invoice->is_recurring) { if ($activity->adjustment != 0 && !$invoice->is_recurring) {
$this->info("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$activityFix = 0; $activityFix = 0;
$clientFix += $activity->adjustment; $clientFix += $activity->adjustment;
@ -290,12 +323,12 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
// **Fix for updating balance when updating recurring invoice** // **Fix for updating balance when updating recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) { if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
} else if ((strtotime($activity->created_at) - strtotime($lastCreatedAt) <= 1) && $activity->adjustment > 0 && $activity->adjustment == $lastAdjustment) { } else if ((strtotime($activity->created_at) - strtotime($lastCreatedAt) <= 1) && $activity->adjustment > 0 && $activity->adjustment == $lastAdjustment) {
$this->info("Duplicate adjustment for updated invoice adjustment:{$activity->adjustment}"); $this->logMessage("Duplicate adjustment for updated invoice adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
$activityFix = 0; $activityFix = 0;
@ -303,7 +336,7 @@ class CheckData extends Command {
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) { } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
// **Fix for updating balance when updating a quote** // **Fix for updating balance when updating a quote**
if ($activity->balance != $lastBalance) { if ($activity->balance != $lastBalance) {
$this->info("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$clientFix += $lastBalance - $activity->balance; $clientFix += $lastBalance - $activity->balance;
$activityFix = 0; $activityFix = 0;
@ -311,7 +344,7 @@ class CheckData extends Command {
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) { } else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
// **Fix for deleting payment after deleting invoice** // **Fix for deleting payment after deleting invoice**
if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) { if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) {
$this->info("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}"); $this->logMessage("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}");
$foundProblem = true; $foundProblem = true;
$activityFix = 0; $activityFix = 0;
$clientFix -= $activity->adjustment; $clientFix -= $activity->adjustment;
@ -340,7 +373,7 @@ class CheckData extends Command {
} }
if ($activity->balance + $clientFix != $client->actual_balance) { if ($activity->balance + $clientFix != $client->actual_balance) {
$this->info("** Creating 'recovered update' activity **"); $this->logMessage("** Creating 'recovered update' activity **");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
DB::table('activities')->insert([ DB::table('activities')->insert([
'created_at' => new Carbon, 'created_at' => new Carbon,
@ -354,7 +387,7 @@ class CheckData extends Command {
} }
$data = ['balance' => $client->actual_balance]; $data = ['balance' => $client->actual_balance];
$this->info("Corrected balance:{$client->actual_balance}"); $this->logMessage("Corrected balance:{$client->actual_balance}");
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
DB::table('clients') DB::table('clients')
->where('id', $client->id) ->where('id', $client->id)

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -1,5 +1,7 @@
<?php namespace App\Exceptions; <?php namespace App\Exceptions;
use Braintree\Util;
use Illuminate\Support\Facades\Response;
use Redirect; use Redirect;
use Utils; use Utils;
use Exception; use Exception;
@ -69,7 +71,7 @@ class Handler extends ExceptionHandler
{ {
if ($e instanceof ModelNotFoundException) { if ($e instanceof ModelNotFoundException) {
return Redirect::to('/'); return Redirect::to('/');
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) { } if ($e instanceof \Illuminate\Session\TokenMismatchException) {
// prevent loop since the page auto-submits // prevent loop since the page auto-submits
if ($request->path() != 'get_started') { if ($request->path() != 'get_started') {
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e // 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 // In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() if (Utils::isNinjaProd()
&& !Utils::isDownForMaintenance() && !Utils::isDownForMaintenance()

View File

@ -4,6 +4,9 @@ use Auth;
use Utils; use Utils;
use Response; use Response;
use Cache; use Cache;
use Socialite;
use Exception;
use App\Services\AuthService;
use App\Models\Account; use App\Models\Account;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
use Illuminate\Http\Request; 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);
}
}
} }

View File

@ -1,6 +1,8 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Models\AccountGateway; use App\Models\AccountGateway;
use App\Models\AccountGatewaySettings;
use App\Models\GatewayType;
use App\Services\TemplateService; use App\Services\TemplateService;
use Auth; use Auth;
use File; use File;
@ -165,6 +167,10 @@ class AccountController extends BaseController
$term = Input::get('plan_term'); $term = Input::get('plan_term');
$numUsers = Input::get('num_users'); $numUsers = Input::get('num_users');
if ($plan != PLAN_ENTERPRISE) {
$numUsers = 1;
}
$planDetails = $account->getPlanDetails(false, false); $planDetails = $account->getPlanDetails(false, false);
$newPlan = [ $newPlan = [
@ -193,7 +199,9 @@ class AccountController extends BaseController
} }
} }
$hasPaid = false;
if (!empty($planDetails['paid']) && $plan != PLAN_FREE) { if (!empty($planDetails['paid']) && $plan != PLAN_FREE) {
$hasPaid = true;
$time_used = $planDetails['paid']->diff(date_create()); $time_used = $planDetails['paid']->diff(date_create());
$days_used = $time_used->days; $days_used = $time_used->days;
@ -209,8 +217,12 @@ class AccountController extends BaseController
if ($newPlan['price'] > $credit) { if ($newPlan['price'] > $credit) {
$invitation = $this->accountRepo->enablePlan($newPlan, $credit); $invitation = $this->accountRepo->enablePlan($newPlan, $credit);
if ($hasPaid) {
return Redirect::to('view/' . $invitation->invitation_key); return Redirect::to('view/' . $invitation->invitation_key);
} else { } else {
return Redirect::to('payment/' . $invitation->invitation_key);
}
} else {
if ($plan != PLAN_FREE) { if ($plan != PLAN_FREE) {
$company->plan_term = $term; $company->plan_term = $term;
@ -417,6 +429,7 @@ class AccountController extends BaseController
'currencies' => Cache::get('currencies'), 'currencies' => Cache::get('currencies'),
'title' => trans('texts.localization'), 'title' => trans('texts.localization'),
'weekdays' => Utils::getTranslatedWeekdayNames(), 'weekdays' => Utils::getTranslatedWeekdayNames(),
'months' => Utils::getMonthOptions(),
]; ];
return View::make('accounts.localization', $data); return View::make('accounts.localization', $data);
@ -461,6 +474,8 @@ class AccountController extends BaseController
'showAdd' => $count < count(Gateway::$alternate) + 1, 'showAdd' => $count < count(Gateway::$alternate) + 1,
'title' => trans('texts.online_payments'), 'title' => trans('texts.online_payments'),
'tokenBillingOptions' => $tokenBillingOptions, 'tokenBillingOptions' => $tokenBillingOptions,
'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY),
'currencies'),
'account' => $account, 'account' => $account,
]); ]);
} }
@ -471,16 +486,9 @@ class AccountController extends BaseController
*/ */
private function showProducts() private function showProducts()
{ {
$columns = ['product', 'description', 'unit_cost'];
if (Auth::user()->account->invoice_item_taxes) {
$columns[] = 'tax_rate';
}
$columns[] = 'action';
$data = [ $data = [
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'title' => trans('texts.product_library'), 'title' => trans('texts.product_library'),
'columns' => Utils::trans($columns),
]; ];
return View::make('accounts.products', $data); return View::make('accounts.products', $data);
@ -667,11 +675,9 @@ class AccountController extends BaseController
* @param $section * @param $section
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function doSection($section = ACCOUNT_COMPANY_DETAILS) public function doSection($section)
{ {
if ($section === ACCOUNT_COMPANY_DETAILS) { if ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveDetails();
} elseif ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveLocalization(); return AccountController::saveLocalization();
} elseif ($section == ACCOUNT_PAYMENTS) { } elseif ($section == ACCOUNT_PAYMENTS) {
return self::saveOnlinePayments(); return self::saveOnlinePayments();
@ -697,9 +703,27 @@ class AccountController extends BaseController
return AccountController::saveTaxRates(); return AccountController::saveTaxRates();
} elseif ($section === ACCOUNT_PAYMENT_TERMS) { } elseif ($section === ACCOUNT_PAYMENT_TERMS) {
return AccountController::savePaymetTerms(); 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 * @return \Illuminate\Http\RedirectResponse
*/ */
@ -723,12 +747,7 @@ class AccountController extends BaseController
private function saveClientPortal() private function saveClientPortal()
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$account->fill(Input::all());
$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');
// Only allowed for pro Invoice Ninja users or white labeled self-hosted users // Only allowed for pro Invoice Ninja users or white labeled self-hosted users
if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { 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->military_time = Input::get('military_time') ? true : false;
$account->show_currency_code = Input::get('show_currency_code') ? 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->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(); $account->save();
event(new UserSettingsChanged()); event(new UserSettingsChanged());
@ -1226,6 +1246,35 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_PAYMENTS); 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 * @return \Illuminate\Http\RedirectResponse
*/ */

View File

@ -48,9 +48,11 @@ class AccountGatewayController extends BaseController
$accountGateway = AccountGateway::scope($publicId)->firstOrFail(); $accountGateway = AccountGateway::scope($publicId)->firstOrFail();
$config = $accountGateway->getConfig(); $config = $accountGateway->getConfig();
if ($accountGateway->gateway_id != GATEWAY_CUSTOM) {
foreach ($config as $field => $value) { foreach ($config as $field => $value) {
$config->$field = str_repeat('*', strlen($value)); $config->$field = str_repeat('*', strlen($value));
} }
}
$data = self::getViewModel($accountGateway); $data = self::getViewModel($accountGateway);
$data['url'] = 'gateways/'.$publicId; $data['url'] = 'gateways/'.$publicId;
@ -60,8 +62,6 @@ class AccountGatewayController extends BaseController
$data['hiddenFields'] = Gateway::$hiddenFields; $data['hiddenFields'] = Gateway::$hiddenFields;
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get(); $data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
$this->testGateway($accountGateway);
return View::make('accounts.account_gateway', $data); return View::make('accounts.account_gateway', $data);
} }
@ -100,7 +100,7 @@ class AccountGatewayController extends BaseController
if ($otherProviders) { if ($otherProviders) {
$availableGatewaysIds = $account->availableGatewaysIds(); $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['secondaryGateways'] = Gateway::secondary($availableGatewaysIds)->orderBy('name')->get();
$data['hiddenFields'] = Gateway::$hiddenFields; $data['hiddenFields'] = Gateway::$hiddenFields;
@ -132,7 +132,9 @@ class AccountGatewayController extends BaseController
foreach ($gateways as $gateway) { foreach ($gateways as $gateway) {
$fields = $gateway->getFields(); $fields = $gateway->getFields();
if ( ! $gateway->isCustom()) {
asort($fields); asort($fields);
}
$gateway->fields = $gateway->id == GATEWAY_WEPAY ? [] : $fields; $gateway->fields = $gateway->id == GATEWAY_WEPAY ? [] : $fields;
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) { if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
$accountGateway->fields = $gateway->fields; $accountGateway->fields = $gateway->fields;
@ -247,6 +249,8 @@ class AccountGatewayController extends BaseController
} }
if (!$value && ($field == 'testMode' || $field == 'developerMode')) { if (!$value && ($field == 'testMode' || $field == 'developerMode')) {
// do nothing // do nothing
} elseif ($gatewayId == GATEWAY_CUSTOM) {
$config->$field = strip_tags($value);
} else { } else {
$config->$field = $value; $config->$field = $value;
} }
@ -312,14 +316,17 @@ class AccountGatewayController extends BaseController
if (isset($wepayResponse)) { if (isset($wepayResponse)) {
return $wepayResponse; return $wepayResponse;
} else { } else {
$this->testGateway($accountGateway);
if ($accountGatewayPublicId) { if ($accountGatewayPublicId) {
$message = trans('texts.updated_gateway'); $message = trans('texts.updated_gateway');
} else {
$message = trans('texts.created_gateway');
}
Session::flash('message', $message); Session::flash('message', $message);
return Redirect::to("gateways/{$accountGateway->public_id}/edit"); return Redirect::to("gateways/{$accountGateway->public_id}/edit");
} else {
$message = trans('texts.created_gateway');
Session::flash('message', $message);
return Redirect::to("/settings/online_payments");
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Utils;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@ -20,4 +21,24 @@ class BaseController extends Controller
$this->layout = View::make($this->layout); $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}");
}
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Http\Requests\ClientRequest;
use Response; use Response;
use Input; use Input;
use App\Models\Client; use App\Models\Client;
@ -52,6 +53,31 @@ class ClientApiController extends BaseAPIController
return $this->listResponse($clients); 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( * @SWG\Post(
* path="/clients", * path="/clients",

View File

@ -95,7 +95,7 @@ class ClientController extends BaseController
if($user->can('create', ENTITY_TASK)){ if($user->can('create', ENTITY_TASK)){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)]; $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)]; $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); $message = Utils::pluralize($action.'d_client', $count);
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { return $this->returnBulk(ENTITY_CLIENT, $action, $ids);
return Redirect::to('clients/'.Utils::getFirst($ids));
} else {
return Redirect::to('clients');
}
} }
} }

View File

@ -22,6 +22,7 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository; use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\DocumentRepository; use App\Ninja\Repositories\DocumentRepository;
use App\Ninja\Repositories\CreditRepository;
use App\Events\InvoiceInvitationWasViewed; use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService; use App\Services\PaymentService;
@ -33,13 +34,14 @@ class ClientPortalController extends BaseController
private $paymentRepo; private $paymentRepo;
private $documentRepo; 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->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->activityRepo = $activityRepo; $this->activityRepo = $activityRepo;
$this->documentRepo = $documentRepo; $this->documentRepo = $documentRepo;
$this->paymentService = $paymentService; $this->paymentService = $paymentService;
$this->creditRepo = $creditRepo;
} }
public function view($invitationKey) public function view($invitationKey)
@ -102,7 +104,9 @@ class ClientPortalController extends BaseController
$paymentURL = ''; $paymentURL = '';
if (count($paymentTypes) == 1) { if (count($paymentTypes) == 1) {
$paymentURL = $paymentTypes[0]['url']; $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); $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()){ if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size); $zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
@ -155,18 +164,6 @@ class ClientPortalController extends BaseController
return View::make('invoices.view', $data); 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) private function getPaymentTypes($account, $client, $invitation)
{ {
$links = []; $links = [];
@ -201,19 +198,41 @@ class ClientPortalController extends BaseController
return $pdfString; 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(); return $this->returnError();
} }
$client = $contact->client; $client = $contact->client;
$account = $client->account; $account = $client->account;
$account->loadLocalizationSettings($client);
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$customer = false; $customer = false;
if (!$account->enable_client_portal || !$account->enable_client_portal_dashboard) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
} elseif (!$account->enable_client_portal_dashboard) {
return redirect()->to('/client/invoices/');
} }
if ($paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN)) { if ($paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN)) {
@ -273,6 +292,7 @@ class ClientPortalController extends BaseController
} }
$account = $contact->account; $account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
@ -300,6 +320,7 @@ class ClientPortalController extends BaseController
} }
$account = $contact->account; $account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
@ -346,12 +367,14 @@ class ClientPortalController extends BaseController
} }
$account = $contact->account; $account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
} }
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,
@ -416,12 +439,14 @@ class ClientPortalController extends BaseController
} }
$account = $contact->account; $account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
} }
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,
@ -444,6 +469,42 @@ class ClientPortalController extends BaseController
return $this->invoiceRepo->getClientDatatable($contact->id, ENTITY_QUOTE, Input::get('sSearch')); 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() public function documentIndex()
{ {
if (!$contact = $this->getContact()) { if (!$contact = $this->getContact()) {
@ -451,12 +512,14 @@ class ClientPortalController extends BaseController
} }
$account = $contact->account; $account = $contact->account;
$account->loadLocalizationSettings($contact->client);
if (!$account->enable_client_portal) { if (!$account->enable_client_portal) {
return $this->returnError(); return $this->returnError();
} }
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,

View File

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

View File

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

View File

@ -1,8 +1,12 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Models\Expense; use App\Models\Expense;
use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\ExpenseRepository;
use App\Services\ExpenseService; use App\Services\ExpenseService;
use App\Http\Requests\ExpenseRequest;
use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\UpdateExpenseRequest;
class ExpenseApiController extends BaseAPIController class ExpenseApiController extends BaseAPIController
{ {
@ -20,6 +24,22 @@ class ExpenseApiController extends BaseAPIController
$this->expenseService = $expenseService; $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() public function index()
{ {
$expenses = Expense::scope() $expenses = Expense::scope()
@ -30,23 +50,103 @@ class ExpenseApiController extends BaseAPIController
return $this->listResponse($expenses); 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);
} }
public function destroy() $data = $request->input();
{ $data['public_id'] = $publicId;
//stub $expense = $this->expenseRepo->save($data, $request->entity());
return $this->itemResponse($expense);
} }
/**
* @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)
{
$expense = $request->entity();
$this->expenseRepo->delete($expense);
return $this->itemResponse($expense);
}
} }

View 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);
}
}

View File

@ -134,6 +134,7 @@ class ExpenseController extends BaseController
$data = [ $data = [
'vendor' => null, 'vendor' => null,
'expense' => $expense, 'expense' => $expense,
'entity' => $expense,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'expenses/'.$expense->public_id, 'url' => 'expenses/'.$expense->public_id,
'title' => 'Edit Expense', 'title' => 'Edit Expense',
@ -245,7 +246,7 @@ class ExpenseController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
return Redirect::to('expenses'); return $this->returnBulk($this->entityType, $action, $ids);
} }
private static function getViewModel() private static function getViewModel()

View File

@ -76,10 +76,8 @@ class HomeController extends BaseController
} }
// Track the referral/campaign code // Track the referral/campaign code
foreach (['rc', 'utm_campaign'] as $code) { if (Input::has('rc')) {
if (Input::has($code)) { Session::set(SESSION_REFERRAL_CODE, Input::get('rc'));
Session::set(SESSION_REFERRAL_CODE, Input::get($code));
}
} }
if (Auth::check()) { if (Auth::check()) {

View File

@ -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 { try {
if ($source === IMPORT_CSV) { if ($source === IMPORT_CSV) {
$data = $this->importService->mapCSV($files); $data = $this->importService->mapCSV($files);

View File

@ -176,7 +176,7 @@ class InvoiceApiController extends BaseAPIController
if (isset($data['email_invoice']) && $data['email_invoice']) { if (isset($data['email_invoice']) && $data['email_invoice']) {
if ($payment) { if ($payment) {
$this->mailer->sendPaymentConfirmation($payment); $this->mailer->sendPaymentConfirmation($payment);
} else { } elseif ( ! $invoice->is_recurring) {
$this->mailer->sendInvoice($invoice); $this->mailer->sendInvoice($invoice);
} }
} }

View File

@ -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_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_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_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) private static function getViewModel($invoice)
{ {
$recurringHelp = ''; $recurringHelp = '';
$recurringDueDateHelp = '';
$recurringDueDates = [];
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) { foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
$parts = explode('=>', $line); $parts = explode('=>', $line);
if (count($parts) > 1) { 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) { foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_due_date_help')) as $line) {
$parts = explode('=>', $line); $parts = explode('=>', $line);
if (count($parts) > 1) { if (count($parts) > 1) {
@ -409,10 +412,10 @@ class InvoiceController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'email') { 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); Session::flash('message', $message);
if ($action == 'clone') { if ($action == 'clone') {
return $this->cloneInvoice($request, $invoice->public_id); return url(sprintf('%ss/%s/clone', $entityType, $invoice->public_id));
} elseif ($action == 'convert') { } elseif ($action == 'convert') {
return $this->convertQuote($request, $invoice->public_id); return $this->convertQuote($request, $invoice->public_id);
} elseif ($action == 'email') { } 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 { } else {
Session::flash('error', $response); Session::flash('error', $response);
} }
return Redirect::to("{$entityType}s/{$invoice->public_id}/edit");
} }
private function emailRecurringInvoice(&$invoice) private function emailRecurringInvoice(&$invoice)
@ -527,11 +528,7 @@ class InvoiceController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
if ($action == 'restore' && $count == 1) { return $this->returnBulk($entityType, $action, $ids);
return Redirect::to("{$entityType}s/".Utils::getFirst($ids));
} else {
return Redirect::to("{$entityType}s");
}
} }
public function convertQuote(InvoiceRequest $request) public function convertQuote(InvoiceRequest $request)
@ -540,7 +537,7 @@ class InvoiceController extends BaseController
Session::flash('message', trans('texts.converted_to_invoice')); 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) public function cloneInvoice(InvoiceRequest $request, $publicId)
@ -608,12 +605,19 @@ class InvoiceController extends BaseController
return View::make('invoices.history', $data); 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) ->whereInvoiceNumber($invoiceNumber)
->withTrashed() ->withTrashed();
->count();
if ($invoicePublicId) {
$query->where('public_id', '!=', $invoicePublicId);
}
$count = $query->count();
return $count ? RESULT_FAILURE : RESULT_SUCCESS; return $count ? RESULT_FAILURE : RESULT_SUCCESS;
} }

View File

@ -20,6 +20,7 @@ use App\Http\Requests\CreateOnlinePaymentRequest;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Services\InvoiceService; use App\Services\InvoiceService;
use App\Models\GatewayType;
/** /**
* Class OnlinePaymentController * Class OnlinePaymentController
@ -60,7 +61,7 @@ class OnlinePaymentController extends BaseController
* @param bool $sourceId * @param bool $sourceId
* @return \Illuminate\Http\RedirectResponse * @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)) { if ( ! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [ 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); return redirect()->to('view/' . $invitation->invitation_key);
} }
$invitation = $invitation->load('invoice.client.account.account_gateways.gateway'); $invitation = $invitation->load('invoice.client.account.account_gateways.gateway');
$account = $invitation->account;
$account->loadLocalizationSettings($invitation->invoice->client);
if ( ! $gatewayType) { if ( ! $gatewayTypeAlias) {
$gatewayType = Session::get($invitation->id . 'gateway_type'); $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 { try {
return $paymentDriver->startPurchase(Input::all(), $sourceId); return $paymentDriver->startPurchase(Input::all(), $sourceId);
@ -95,8 +102,12 @@ class OnlinePaymentController extends BaseController
public function doPayment(CreateOnlinePaymentRequest $request) public function doPayment(CreateOnlinePaymentRequest $request)
{ {
$invitation = $request->invitation; $invitation = $request->invitation;
$gatewayType = Session::get($invitation->id . 'gateway_type'); $gatewayTypeId = Session::get($invitation->id . 'gateway_type');
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType); $paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayTypeId);
if ( ! $invitation->invoice->canBePaid()) {
return redirect()->to('view/' . $invitation->invitation_key);
}
try { try {
$paymentDriver->completeOnsitePurchase($request->all()); $paymentDriver->completeOnsitePurchase($request->all());
@ -114,17 +125,24 @@ class OnlinePaymentController extends BaseController
/** /**
* @param bool $invitationKey * @param bool $invitationKey
* @param bool $gatewayType * @param mixed $gatewayTypeAlias
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function offsitePayment($invitationKey = false, $gatewayType = false) public function offsitePayment($invitationKey = false, $gatewayTypeAlias = false)
{ {
$invitationKey = $invitationKey ?: Session::get('invitation_key'); $invitationKey = $invitationKey ?: Session::get('invitation_key');
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway') $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')
->where('invitation_key', '=', $invitationKey)->firstOrFail(); ->where('invitation_key', '=', $invitationKey)->firstOrFail();
$gatewayType = $gatewayType ?: Session::get($invitation->id . 'gateway_type'); if ( ! $gatewayTypeAlias) {
$paymentDriver = $invitation->account->paymentDriver($invitation, $gatewayType); $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')) { if ($error = Input::get('error_description') ?: Input::get('error')) {
return $this->error($paymentDriver, $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()) { if (Crawler::isCrawler()) {
return redirect()->to(NINJA_WEB_URL, 301); return redirect()->to(NINJA_WEB_URL, 301);
@ -267,6 +285,8 @@ class OnlinePaymentController extends BaseController
$data = [ $data = [
'client_id' => $client->id, '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' => [[ 'invoice_items' => [[
'product_key' => $product->product_key, 'product_key' => $product->product_key,
'notes' => $product->notes, 'notes' => $product->notes,
@ -280,8 +300,8 @@ class OnlinePaymentController extends BaseController
$invitation = $invoice->invitations[0]; $invitation = $invoice->invitations[0];
$link = $invitation->getLink(); $link = $invitation->getLink();
if ($gatewayType) { if ($gatewayTypeAlias) {
return redirect()->to($invitation->getLink('payment') . "/{$gatewayType}"); return redirect()->to($invitation->getLink('payment') . "/{$gatewayTypeAlias}");
} else { } else {
return redirect()->to($invitation->getLink()); return redirect()->to($invitation->getLink());
} }

View File

@ -142,11 +142,13 @@ class PaymentController extends BaseController
'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false) 'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false)
->with('client', 'invoice_status')->orderBy('invoice_number')->get(), ->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'payment' => $payment, 'payment' => $payment,
'entity' => $payment,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'payments/'.$payment->public_id, 'url' => 'payments/'.$payment->public_id,
'title' => trans('texts.edit_payment'), 'title' => trans('texts.edit_payment'),
'paymentTypes' => Cache::get('paymentTypes'), '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); return View::make('payments.edit', $data);
} }

View File

@ -3,6 +3,7 @@
use Auth; use Auth;
use URL; use URL;
use View; use View;
use Utils;
use Input; use Input;
use Session; use Session;
use Redirect; use Redirect;
@ -37,15 +38,40 @@ class ProductController extends BaseController
*/ */
public function index() 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 * @return \Illuminate\Http\JsonResponse
*/ */
public function getDatatable() 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) public function edit($publicId)
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$product = Product::scope($publicId)->withTrashed()->firstOrFail();
$data = [ $data = [
'account' => $account, 'account' => $account,
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null, 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
'product' => Product::scope($publicId)->firstOrFail(), 'product' => $product,
'entity' => $product,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'products/'.$publicId, 'url' => 'products/'.$publicId,
'title' => trans('texts.edit_product'), 'title' => trans('texts.edit_product'),
@ -111,7 +139,7 @@ class ProductController extends BaseController
private function save($productPublicId = false) private function save($productPublicId = false)
{ {
if ($productPublicId) { if ($productPublicId) {
$product = Product::scope($productPublicId)->firstOrFail(); $product = Product::scope($productPublicId)->withTrashed()->firstOrFail();
} else { } else {
$product = Product::createNew(); $product = Product::createNew();
} }
@ -134,12 +162,12 @@ class ProductController extends BaseController
*/ */
public function bulk() public function bulk()
{ {
$action = Input::get('bulk_action'); $action = Input::get('action');
$ids = Input::get('bulk_public_id'); $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->productService->bulk($ids, $action); $count = $this->productService->bulk($ids, $action);
Session::flash('message', trans('texts.archived_product')); Session::flash('message', trans('texts.archived_product'));
return Redirect::to('settings/' . ACCOUNT_PRODUCTS); return $this->returnBulk(ENTITY_PRODUCT, $action, $ids);
} }
} }

View File

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

View File

@ -11,6 +11,7 @@ use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Task;
/** /**
* Class ReportController * Class ReportController
@ -56,8 +57,8 @@ class ReportController extends BaseController
if (Input::get('report_type')) { if (Input::get('report_type')) {
$reportType = Input::get('report_type'); $reportType = Input::get('report_type');
$dateField = Input::get('date_field'); $dateField = Input::get('date_field');
$startDate = Utils::toSqlDate(Input::get('start_date'), false); $startDate = date_create(Input::get('start_date'));
$endDate = Utils::toSqlDate(Input::get('end_date'), false); $endDate = date_create(Input::get('end_date'));
} else { } else {
$reportType = ENTITY_INVOICE; $reportType = ENTITY_INVOICE;
$dateField = FILTER_INVOICE_DATE; $dateField = FILTER_INVOICE_DATE;
@ -71,15 +72,17 @@ class ReportController extends BaseController
ENTITY_PRODUCT => trans('texts.product'), ENTITY_PRODUCT => trans('texts.product'),
ENTITY_PAYMENT => trans('texts.payment'), ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expense'), ENTITY_EXPENSE => trans('texts.expense'),
ENTITY_TASK => trans('texts.task'),
ENTITY_TAX_RATE => trans('texts.tax'), ENTITY_TAX_RATE => trans('texts.tax'),
]; ];
$params = [ $params = [
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)), 'startDate' => $startDate->format('Y-m-d'),
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)), 'endDate' => $endDate->format('Y-m-d'),
'reportTypes' => $reportTypes, 'reportTypes' => $reportTypes,
'reportType' => $reportType, 'reportType' => $reportType,
'title' => trans('texts.charts_and_reports'), 'title' => trans('texts.charts_and_reports'),
'account' => Auth::user()->account,
]; ];
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) { if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
@ -120,9 +123,37 @@ class ReportController extends BaseController
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport); return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
} elseif ($reportType == ENTITY_EXPENSE) { } elseif ($reportType == ENTITY_EXPENSE) {
return $this->generateExpenseReport($startDate, $endDate, $isExport); return $this->generateExpenseReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_TASK) {
return $this->generateTaskReport($startDate, $endDate, $isExport);
} }
} }
private function generateTaskReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'date', 'description', 'duration'];
$displayData = [];
$tasks = Task::scope()
->with('client.contacts')
->withArchived()
->dateRange($startDate, $endDate);
foreach ($tasks->get() as $task) {
$displayData[] = [
$task->client ? ($isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'),
link_to($task->present()->url, $task->getStartTime()),
$task->present()->description,
Utils::formatTime($task->getDuration()),
];
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => [],
];
}
/** /**
* @param $startDate * @param $startDate
* @param $endDate * @param $endDate
@ -354,7 +385,7 @@ class ReportController extends BaseController
$isExport ? $client->getDisplayName() : $client->present()->link, $isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link, $isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date, $invoice->present()->invoice_date,
$invoiceItem->qty, round($invoiceItem->qty, 2),
$invoiceItem->product_key, $invoiceItem->product_key,
]; ];
//$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0); //$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) private function generateExpenseReport($startDate, $endDate, $isExport)
{ {
$columns = ['vendor', 'client', 'date', 'expense_amount', 'invoiced_amount']; $columns = ['vendor', 'client', 'date', 'expense_amount'];
$account = Auth::user()->account; $account = Auth::user()->account;
$displayData = []; $displayData = [];
$reportTotals = []; $reportTotals = [];
$expenses = Expense::scope() $expenses = Expense::scope()
->withTrashed() ->withArchived()
->with('client.contacts', 'vendor') ->with('client.contacts', 'vendor')
->where('expense_date', '>=', $startDate) ->where('expense_date', '>=', $startDate)
->where('expense_date', '<=', $endDate); ->where('expense_date', '<=', $endDate);
foreach ($expenses->get() as $expense) { foreach ($expenses->get() as $expense) {
$amount = $expense->amount; $amount = $expense->amountWithTax();
$invoiced = $expense->present()->invoiced_amount;
$displayData[] = [ $displayData[] = [
$expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '', $expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '',
$expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '', $expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '',
$expense->present()->expense_date, $expense->present()->expense_date,
Utils::formatMoney($amount, $expense->currency_id), 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->expense_currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0); $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 [ return [

View File

@ -2,10 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Codedge\Updater\UpdaterManager; use Utils;
use App\Http\Requests;
use Redirect; use Redirect;
use Codedge\Updater\UpdaterManager;
class SelfUpdateController extends BaseController class SelfUpdateController extends BaseController
{ {
@ -21,6 +20,10 @@ class SelfUpdateController extends BaseController
*/ */
public function __construct(UpdaterManager $updater) public function __construct(UpdaterManager $updater)
{ {
if (Utils::isNinjaProd()) {
exit;
}
$this->updater = $updater; $this->updater = $updater;
} }

View File

@ -177,15 +177,14 @@ class TaskController extends BaseController
$data = [ $data = [
'task' => $task, 'task' => $task,
'entity' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0, 'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'tasks/'.$task->public_id, 'url' => 'tasks/'.$task->public_id,
'title' => trans('texts.edit_task'), 'title' => trans('texts.edit_task'),
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions, 'actions' => $actions,
'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE, 'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE,
'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(), 'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(),
//'entityStatus' => $task->present()->status,
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
@ -295,16 +294,12 @@ class TaskController extends BaseController
return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data); return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data);
} }
} else { } else {
$count = $this->taskRepo->bulk($ids, $action); $count = $this->taskService->bulk($ids, $action);
$message = Utils::pluralize($action.'d_task', $count); $message = Utils::pluralize($action.'d_task', $count);
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'restore' && $count == 1) { return $this->returnBulk($this->entityType, $action, $ids);
return Redirect::to('tasks/'.$ids[0].'/edit');
} else {
return Redirect::to('tasks');
}
} }
} }

View File

@ -66,7 +66,9 @@ class UserController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$user = User::where('account_id', '=', Auth::user()->account_id) $user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $publicId)->firstOrFail(); ->where('public_id', '=', $publicId)
->withTrashed()
->firstOrFail();
$data = [ $data = [
'user' => $user, 'user' => $user,
@ -157,7 +159,9 @@ class UserController extends BaseController
if ($userPublicId) { if ($userPublicId) {
$user = User::where('account_id', '=', Auth::user()->account_id) $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'; $rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
} else { } else {
@ -334,8 +338,14 @@ class UserController extends BaseController
} }
} }
// 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); return Redirect::to($referer);
} }
}
public function unlinkAccount($userAccountId, $userId) public function unlinkAccount($userAccountId, $userId)
{ {

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
// vendor // vendor
use App\Http\Requests\UpdateVendorRequest;
use App\Http\Requests\VendorRequest;
use Utils; use Utils;
use Response; use Response;
use Input; use Input;
@ -83,4 +85,73 @@ class VendorApiController extends BaseAPIController
return $this->itemResponse($vendor); 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);
}
} }

View File

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

View File

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

View File

@ -23,18 +23,22 @@ class ApiCheck {
*/ */
public function handle($request, Closure $next) 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(); $headers = Utils::getApiHeaders();
$hasApiSecret = false;
if ($secret = env(API_SECRET)) { 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) { if ($loggingIn) {
// check API secret // check API secret
if ( ! $hasApiSecret) { if ( ! $hasApiSecret) {
sleep(ERROR_DELAY); sleep(ERROR_DELAY);
return Response::json('Invalid secret', 403, $headers); return Response::json('Invalid value for API_SECRET', 403, $headers);
} }
} else { } else {
// check for a valid token // check for a valid token

View File

@ -15,8 +15,7 @@ class DuplicateSubmissionCheck
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if ($request->is('api/v1/*') || $request->is('documents')) {
if ($request->is('api/v1/*')) {
return $next($request); return $next($request);
} }

View File

@ -23,6 +23,7 @@ class QueryLogging
// Enable query logging for development // Enable query logging for development
if (Utils::isNinjaDev()) { if (Utils::isNinjaDev()) {
DB::enableQueryLog(); DB::enableQueryLog();
$timeStart = microtime(true);
} }
$response = $next($request); $response = $next($request);
@ -32,7 +33,9 @@ class QueryLogging
if (strstr($request->url(), '_debugbar') === false) { if (strstr($request->url(), '_debugbar') === false) {
$queries = DB::getQueryLog(); $queries = DB::getQueryLog();
$count = count($queries); $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); //Log::info($queries);
} }
} }

View File

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

View File

@ -13,7 +13,7 @@ use Event;
use Schema; use Schema;
use App\Models\Language; use App\Models\Language;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Events\UserSettingsChanged; use App\Events\UserLoggedIn;
use App\Libraries\CurlUtils; use App\Libraries\CurlUtils;
/** /**
@ -118,13 +118,13 @@ class StartupCheck
// Make sure the account/user localization settings are in the session // Make sure the account/user localization settings are in the session
if (Auth::check() && !Session::has(SESSION_TIMEZONE)) { 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.) // 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'); $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'); $licenseKey = Input::get('license_key');
$productId = Input::get('product_id'); $productId = Input::get('product_id');
@ -154,6 +154,8 @@ class StartupCheck
$company->save(); $company->save();
Session::flash('message', trans('texts.bought_white_label')); Session::flash('message', trans('texts.bought_white_label'));
} else {
Session::flash('error', trans('texts.invalid_white_label_license'));
} }
} }
} }

View File

@ -4,7 +4,6 @@
class UpdateTaxRateRequest extends TaxRateRequest class UpdateTaxRateRequest extends TaxRateRequest
{ {
// Expenses
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *

View 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);
}
}

View File

@ -1,6 +1,5 @@
<?php <?php
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Routes | Application Routes
@ -39,6 +38,7 @@ Route::post('/get_started', 'AccountController@getStarted');
Route::group(['middleware' => 'auth:client'], function() { Route::group(['middleware' => 'auth:client'], function() {
Route::get('view/{invitation_key}', 'ClientPortalController@view'); Route::get('view/{invitation_key}', 'ClientPortalController@view');
Route::get('download/{invitation_key}', 'ClientPortalController@download'); Route::get('download/{invitation_key}', 'ClientPortalController@download');
Route::put('sign/{invitation_key}', 'ClientPortalController@sign');
Route::get('view', 'HomeController@viewLogo'); Route::get('view', 'HomeController@viewLogo');
Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment'); 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/default', 'ClientPortalController@setDefaultPaymentMethod');
Route::post('client/payment_methods/{source_id}/remove', 'ClientPortalController@removePaymentMethod'); Route::post('client/payment_methods/{source_id}/remove', 'ClientPortalController@removePaymentMethod');
Route::get('client/quotes', 'ClientPortalController@quoteIndex'); Route::get('client/quotes', 'ClientPortalController@quoteIndex');
Route::get('client/credits', 'ClientPortalController@creditIndex');
Route::get('client/invoices', 'ClientPortalController@invoiceIndex'); Route::get('client/invoices', 'ClientPortalController@invoiceIndex');
Route::get('client/invoices/recurring', 'ClientPortalController@recurringInvoiceIndex'); Route::get('client/invoices/recurring', 'ClientPortalController@recurringInvoiceIndex');
Route::post('client/invoices/auto_bill', 'ClientPortalController@setAutoBill'); Route::post('client/invoices/auto_bill', 'ClientPortalController@setAutoBill');
Route::get('client/documents', 'ClientPortalController@documentIndex'); Route::get('client/documents', 'ClientPortalController@documentIndex');
Route::get('client/payments', 'ClientPortalController@paymentIndex'); Route::get('client/payments', 'ClientPortalController@paymentIndex');
Route::get('client/dashboard', 'ClientPortalController@dashboard'); Route::get('client/dashboard/{contact_key?}', 'ClientPortalController@dashboard');
Route::get('client/dashboard/{contact_key}', 'ClientPortalController@contactIndex');
Route::get('client/documents/js/{documents}/{filename}', 'ClientPortalController@getDocumentVFSJS'); 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}/{documents}/{filename?}', 'ClientPortalController@getDocument');
Route::get('client/documents/{invitation_key}/{filename?}', 'ClientPortalController@getInvoiceDocumentsZip'); 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.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.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.recurring_invoices', ['as'=>'api.client.recurring_invoices', 'uses'=>'ClientPortalController@recurringInvoiceDatatable']);
Route::get('api/client.documents', ['as'=>'api.client.documents', 'uses'=>'ClientPortalController@documentDatatable']); 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('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']); Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']);
Route::get('check_invoice_number/{invoice_number}', 'InvoiceController@checkInvoiceNumber'); Route::get('check_invoice_number/{invoice_id?}', 'InvoiceController@checkInvoiceNumber');
Route::get('save_sidebar_state', 'UserController@saveSidebarState'); Route::post('save_sidebar_state', 'UserController@saveSidebarState');
Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::get('settings/user_details', 'AccountController@showUserDetails');
Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails');
Route::post('settings/payment_gateway_limits', 'AccountController@savePaymentGatewayLimits');
Route::post('users/change_password', 'UserController@changePassword'); Route::post('users/change_password', 'UserController@changePassword');
Route::resource('clients', 'ClientController'); 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::get('api/credits/{client_id?}', ['as'=>'api.credits', 'uses'=>'CreditController@getDatatable']);
Route::post('credits/bulk', 'CreditController@bulk'); 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::get('/resend_confirmation', 'AccountController@resendConfirmation');
Route::post('/update_setup', 'AppController@updateSetup'); Route::post('/update_setup', 'AppController@updateSetup');
@ -228,10 +235,6 @@ Route::group([
Route::resource('tokens', 'TokenController'); Route::resource('tokens', 'TokenController');
Route::post('tokens/bulk', 'TokenController@bulk'); 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::get('api/tax_rates', ['as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable']);
Route::resource('tax_rates', 'TaxRateController'); Route::resource('tax_rates', 'TaxRateController');
Route::post('tax_rates/bulk', 'TaxRateController@bulk'); 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::get('ping', 'AccountApiController@ping');
Route::post('login', 'AccountApiController@login'); Route::post('login', 'AccountApiController@login');
Route::post('oauth_login', 'AccountApiController@oauthLogin');
Route::post('register', 'AccountApiController@register'); Route::post('register', 'AccountApiController@register');
Route::get('static', 'AccountApiController@getStaticData'); Route::get('static', 'AccountApiController@getStaticData');
Route::get('accounts', 'AccountApiController@show'); Route::get('accounts', 'AccountApiController@show');
@ -305,12 +309,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::post('update_notifications', 'AccountApiController@updatePushNotifications'); Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
Route::get('dashboard', 'DashboardApiController@index'); Route::get('dashboard', 'DashboardApiController@index');
Route::resource('documents', 'DocumentAPIController'); Route::resource('documents', 'DocumentAPIController');
// Vendor
Route::resource('vendors', 'VendorApiController'); Route::resource('vendors', 'VendorApiController');
Route::resource('expense_categories', 'ExpenseCategoryApiController');
//Expense
Route::resource('expenses', 'ExpenseApiController');
}); });
// Redirects for legacy links // Redirects for legacy links
@ -430,56 +430,50 @@ if (!defined('CONTACT_EMAIL')) {
define('ACTIVITY_TYPE_CREATE_CLIENT', 1); define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2); define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3); define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('ACTIVITY_TYPE_CREATE_INVOICE', 4); define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5); define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6); define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('ACTIVITY_TYPE_VIEW_INVOICE', 7); define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8); define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('ACTIVITY_TYPE_DELETE_INVOICE', 9); define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10); define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11); //define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12); define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13); 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_CREATE_CREDIT', 14);
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15); //define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16); define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17); define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('ACTIVITY_TYPE_CREATE_QUOTE', 18); define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19); define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20); define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('ACTIVITY_TYPE_VIEW_QUOTE', 21); define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22); define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('ACTIVITY_TYPE_DELETE_QUOTE', 23); define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24); define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25); define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26); define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27); define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28); define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29); define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
// Vendors
define('ACTIVITY_TYPE_CREATE_VENDOR', 30); define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31); define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
define('ACTIVITY_TYPE_DELETE_VENDOR', 32); define('ACTIVITY_TYPE_DELETE_VENDOR', 32);
define('ACTIVITY_TYPE_RESTORE_VENDOR', 33); define('ACTIVITY_TYPE_RESTORE_VENDOR', 33);
// expenses
define('ACTIVITY_TYPE_CREATE_EXPENSE', 34); define('ACTIVITY_TYPE_CREATE_EXPENSE', 34);
define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35); define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35);
define('ACTIVITY_TYPE_DELETE_EXPENSE', 36); define('ACTIVITY_TYPE_DELETE_EXPENSE', 36);
define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37); define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37);
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
// tasks define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
define('ACTIVITY_TYPE_CREATE_TASK', 42); define('ACTIVITY_TYPE_CREATE_TASK', 42);
define('ACTIVITY_TYPE_UPDATE_TASK', 43); 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('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 20); define('RECENTLY_VIEWED_LIMIT', 20);
@ -491,6 +485,7 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_IFRAME_URL_LENGTH', 250); define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10); 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_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total 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) 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_TIMEZONE', 'timezone');
define('SESSION_CURRENCY', 'currency'); define('SESSION_CURRENCY', 'currency');
define('SESSION_CURRENCY_DECORATOR', 'currency_decorator');
define('SESSION_DATE_FORMAT', 'dateFormat'); define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat'); define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat'); define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
@ -601,6 +597,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_CYBERSOURCE', 49); define('GATEWAY_CYBERSOURCE', 49);
define('GATEWAY_WEPAY', 60); define('GATEWAY_WEPAY', 60);
define('GATEWAY_BRAINTREE', 61); define('GATEWAY_BRAINTREE', 61);
define('GATEWAY_CUSTOM', 62);
// The customer exists, but only as a local concept // The customer exists, but only as a local concept
// The remote gateway doesn't understand the concept of customers // 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_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '2.7.2' . env('NINJA_VERSION_SUFFIX')); 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_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
@ -639,6 +637,7 @@ if (!defined('CONTACT_EMAIL')) {
define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup')); 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('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('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_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application'); 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_VERIFICATION_FAILED', 'verification_failed');
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified'); define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
define('GATEWAY_TYPE_CREDIT_CARD', 'credit_card'); define('GATEWAY_TYPE_CREDIT_CARD', 1);
define('GATEWAY_TYPE_BANK_TRANSFER', 'bank_transfer'); define('GATEWAY_TYPE_BANK_TRANSFER', 2);
define('GATEWAY_TYPE_PAYPAL', 'paypal'); define('GATEWAY_TYPE_PAYPAL', 3);
define('GATEWAY_TYPE_BITCOIN', 'bitcoin'); define('GATEWAY_TYPE_BITCOIN', 4);
define('GATEWAY_TYPE_DWOLLA', 'dwolla'); define('GATEWAY_TYPE_DWOLLA', 5);
define('GATEWAY_TYPE_CUSTOM', 6);
define('GATEWAY_TYPE_TOKEN', 'token'); define('GATEWAY_TYPE_TOKEN', 'token');
define('REMINDER1', 'reminder1'); define('REMINDER1', 'reminder1');
@ -744,6 +744,10 @@ if (!defined('CONTACT_EMAIL')) {
define('BANK_LIBRARY_OFX', 1); 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_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B'); define('RESELLER_LIMITED_USERS', 'B');
@ -852,6 +856,7 @@ if (!defined('CONTACT_EMAIL')) {
'invoiceStatus' => 'App\Models\InvoiceStatus', 'invoiceStatus' => 'App\Models\InvoiceStatus',
'frequencies' => 'App\Models\Frequency', 'frequencies' => 'App\Models\Frequency',
'gateways' => 'App\Models\Gateway', 'gateways' => 'App\Models\Gateway',
'gatewayTypes' => 'App\Models\GatewayType',
'fonts' => 'App\Models\Font', 'fonts' => 'App\Models\Font',
'banks' => 'App\Models\Bank', 'banks' => 'App\Models\Bank',
]; ];

View File

@ -152,7 +152,7 @@ class parseCSV {
* @param input CSV file or string * @param input CSV file or string
* @return nothing * @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 ( $offset !== null ) $this->offset = $offset;
if ( $limit !== null ) $this->limit = $limit; if ( $limit !== null ) $this->limit = $limit;
if ( count($conditions) > 0 ) $this->conditions = $conditions; if ( count($conditions) > 0 ) $this->conditions = $conditions;

View File

@ -24,6 +24,8 @@ class HistoryUtils
ACTIVITY_TYPE_CREATE_CLIENT, ACTIVITY_TYPE_CREATE_CLIENT,
ACTIVITY_TYPE_CREATE_TASK, ACTIVITY_TYPE_CREATE_TASK,
ACTIVITY_TYPE_UPDATE_TASK, ACTIVITY_TYPE_UPDATE_TASK,
ACTIVITY_TYPE_CREATE_EXPENSE,
ACTIVITY_TYPE_UPDATE_EXPENSE,
ACTIVITY_TYPE_CREATE_INVOICE, ACTIVITY_TYPE_CREATE_INVOICE,
ACTIVITY_TYPE_UPDATE_INVOICE, ACTIVITY_TYPE_UPDATE_INVOICE,
ACTIVITY_TYPE_EMAIL_INVOICE, ACTIVITY_TYPE_EMAIL_INVOICE,
@ -35,7 +37,7 @@ class HistoryUtils
]; ];
$activities = Activity::scope() $activities = Activity::scope()
->with(['client.contacts', 'invoice', 'task']) ->with(['client.contacts', 'invoice', 'task', 'expense'])
->whereIn('user_id', $userIds) ->whereIn('user_id', $userIds)
->whereIn('activity_type_id', $activityTypes) ->whereIn('activity_type_id', $activityTypes)
->orderBy('id', 'asc') ->orderBy('id', 'asc')
@ -52,6 +54,12 @@ class HistoryUtils
continue; continue;
} }
$entity->setRelation('client', $activity->client); $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 { } else {
$entity = $activity->invoice; $entity = $activity->invoice;
if ( ! $entity) { if ( ! $entity) {

View File

@ -22,6 +22,9 @@ class Utils
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "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() public static function isRegistered()
{ {
@ -92,6 +95,17 @@ class Utils
return Utils::getResllerType() ? true : false; 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() public static function getResllerType()
{ {
return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false; return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false;
@ -151,6 +165,11 @@ class Utils
return Auth::check() && Auth::user()->isTrial(); return Auth::check() && Auth::user()->isTrial();
} }
public static function isPaidPro()
{
return static::isPro() && ! static::isTrial();
}
public static function isEnglish() public static function isEnglish()
{ {
return App::getLocale() == 'en'; return App::getLocale() == 'en';
@ -186,7 +205,7 @@ class Utils
$response = new stdClass(); $response = new stdClass();
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : ''; $response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : ''; $response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
$response->version = env('NINJA_SELF_HOST_VERSION', NINJA_VERSION); $response->version = NINJA_VERSION;
return $response; return $response;
} }
@ -354,7 +373,7 @@ class Utils
return $data->first(); 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); $value = floatval($value);
@ -362,6 +381,10 @@ class Utils
$currencyId = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY); $currencyId = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY);
} }
if (!$decorator) {
$decorator = Session::get(SESSION_CURRENCY_DECORATOR, CURRENCY_DECORATOR_SYMBOL);
}
if (!$countryId && Auth::check()) { if (!$countryId && Auth::check()) {
$countryId = Auth::user()->account->country_id; $countryId = Auth::user()->account->country_id;
} }
@ -387,7 +410,9 @@ class Utils
$value = number_format($value, $precision, $decimal, $thousand); $value = number_format($value, $precision, $decimal, $thousand);
$symbol = $currency->symbol; $symbol = $currency->symbol;
if ($showCode || !$symbol) { if ($decorator == CURRENCY_DECORATOR_NONE) {
return $value;
} elseif ($decorator == CURRENCY_DECORATOR_CODE || ! $symbol) {
return "{$value} {$code}"; return "{$value} {$code}";
} elseif ($swapSymbol) { } elseif ($swapSymbol) {
return "{$value} " . trim($symbol); 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) private static function getMonth($offset)
{ {
$months = ['january', 'february', 'march', 'april', 'may', 'june', $months = static::$months;
'july', 'august', 'september', 'october', 'november', 'december', ];
$month = intval(date('n')) - 1; $month = intval(date('n')) - 1;
$month += $offset; $month += $offset;
@ -1029,4 +1065,66 @@ class Utils
return trans('texts.'.strtolower($day)); 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);
}
} }

View File

@ -1,7 +1,5 @@
<?php namespace App\Listeners; <?php namespace App\Listeners;
use App\Events\TaskWasCreated;
use App\Events\TaskWasUpdated;
use App\Models\Invoice; use App\Models\Invoice;
use App\Events\ClientWasCreated; use App\Events\ClientWasCreated;
use App\Events\ClientWasDeleted; use App\Events\ClientWasDeleted;
@ -33,6 +31,16 @@ use App\Events\CreditWasCreated;
use App\Events\CreditWasDeleted; use App\Events\CreditWasDeleted;
use App\Events\CreditWasArchived; use App\Events\CreditWasArchived;
use App\Events\CreditWasRestored; 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; use App\Ninja\Repositories\ActivityRepository;
/** /**
@ -123,7 +131,9 @@ class ActivityListener
return; 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( $activity = $this->activityRepo->create(
$event->invoice, $event->invoice,
@ -489,9 +499,92 @@ class ActivityListener
*/ */
public function updatedTask(TaskWasUpdated $event) public function updatedTask(TaskWasUpdated $event)
{ {
if ( ! $event->task->isChanged()) {
return;
}
$this->activityRepo->create( $this->activityRepo->create(
$event->task, $event->task,
ACTIVITY_TYPE_UPDATE_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
);
}
} }

View File

@ -69,6 +69,16 @@ class Account extends Eloquent
'enable_second_tax_rate', 'enable_second_tax_rate',
'include_item_taxes_inline', 'include_item_taxes_inline',
'start_of_week', '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, 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 * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
@ -438,7 +463,7 @@ class Account extends Eloquent
* @param bool $hideSymbol * @param bool $hideSymbol
* @return string * @return string
*/ */
public function formatMoney($amount, $client = null, $hideSymbol = false) public function formatMoney($amount, $client = null, $decorator = false)
{ {
if ($client && $client->currency_id) { if ($client && $client->currency_id) {
$currencyId = $client->currency_id; $currencyId = $client->currency_id;
@ -456,9 +481,11 @@ class Account extends Eloquent
$countryId = false; $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 $invitation
* @param bool $gatewayType * @param mixed $gatewayTypeId
* @return bool * @return bool
*/ */
public function paymentDriver($invitation = false, $gatewayType = false) public function paymentDriver($invitation = false, $gatewayTypeId = false)
{ {
/** @var AccountGateway $accountGateway */ /** @var AccountGateway $accountGateway */
if ($accountGateway = $this->getGatewayByType($gatewayType)) { if ($accountGateway = $this->getGatewayByType($gatewayTypeId)) {
return $accountGateway->paymentDriver($invitation, $gatewayType); return $accountGateway->paymentDriver($invitation, $gatewayTypeId);
} }
return false; return false;
@ -735,6 +762,22 @@ class Account extends Eloquent
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk()); 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 * @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); $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, $currencyId);
Session::put(SESSION_CURRENCY_DECORATOR, $this->show_currency_code ? CURRENCY_DECORATOR_CODE : CURRENCY_DECORATOR_SYMBOL);
Session::put(SESSION_LOCALE, $locale); Session::put(SESSION_LOCALE, $locale);
App::setLocale($locale); App::setLocale($locale);
@ -1812,6 +1856,43 @@ class Account extends Eloquent
public function getFontFolders(){ public function getFontFolders(){
return array_map(function($item){return $item['folder'];}, $this->getFontsData()); 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) Account::updated(function ($account)

View File

@ -73,14 +73,14 @@ class AccountGateway extends EntityModel
/** /**
* @param bool $invitation * @param bool $invitation
* @param bool $gatewayType * @param mixed $gatewayTypeId
* @return mixed * @return mixed
*/ */
public function paymentDriver($invitation = false, $gatewayType = false) public function paymentDriver($invitation = false, $gatewayTypeId = false)
{ {
$class = static::paymentDriverClass($this->gateway->provider); $class = static::paymentDriverClass($this->gateway->provider);
return new $class($this, $invitation, $gatewayType); return new $class($this, $invitation, $gatewayTypeId);
} }
/** /**

View 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
}
}

View File

@ -83,6 +83,11 @@ class Activity extends Eloquent
return $this->belongsTo('App\Models\Task')->withTrashed(); return $this->belongsTo('App\Models\Task')->withTrashed();
} }
public function expense()
{
return $this->belongsTo('App\Models\Expense')->withTrashed();
}
public function key() public function key()
{ {
return sprintf('%s-%s-%s', $this->activity_type_id, $this->client_id, $this->created_at->timestamp); 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; $contactId = $this->contact_id;
$payment = $this->payment; $payment = $this->payment;
$credit = $this->credit; $credit = $this->credit;
$expense = $this->expense;
$isSystem = $this->is_system; $isSystem = $this->is_system;
/** @var Task $task */
$task = $this->task; $task = $this->task;
$data = [ $data = [
@ -117,6 +121,7 @@ class Activity extends Eloquent
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : null, 'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : null,
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null, 'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
'task' => $task ? link_to($task->getRoute(), substr($task->description, 0, 30).'...') : 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); return trans("texts.activity_{$activityTypeId}", $data);

View File

@ -89,6 +89,10 @@ class Client extends EntityModel
* @var string * @var string
*/ */
public static $fieldWebsite = 'website'; public static $fieldWebsite = 'website';
/**
* @var string
*/
public static $fieldVatNumber = 'vat_number';
/** /**
* @return array * @return array
@ -106,6 +110,7 @@ class Client extends EntityModel
Client::$fieldCountry, Client::$fieldCountry,
Client::$fieldNotes, Client::$fieldNotes,
Client::$fieldWebsite, Client::$fieldWebsite,
Client::$fieldVatNumber,
Contact::$fieldFirstName, Contact::$fieldFirstName,
Contact::$fieldLastName, Contact::$fieldLastName,
Contact::$fieldPhone, Contact::$fieldPhone,
@ -132,6 +137,7 @@ class Client extends EntityModel
'country' => 'country', 'country' => 'country',
'note' => 'notes', 'note' => 'notes',
'site|website' => 'website', 'site|website' => 'website',
'vat' => 'vat_number',
]; ];
} }
@ -159,6 +165,14 @@ class Client extends EntityModel
return $this->hasMany('App\Models\Invoice'); 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 * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
@ -223,6 +237,14 @@ class Client extends EntityModel
return $this->hasMany('App\Models\Credit'); 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 * @return mixed
*/ */
@ -329,7 +351,7 @@ class Client extends EntityModel
$contact = $this->contacts[0]; $contact = $this->contacts[0];
return $contact->getDisplayName() ?: trans('texts.unnamed_client'); return $contact->getDisplayName();
} }
/** /**

View File

@ -2,6 +2,7 @@
use Auth; use Auth;
use Eloquent; use Eloquent;
use Illuminate\Database\QueryException;
use Utils; use Utils;
use Validator; use Validator;
@ -14,6 +15,12 @@ class EntityModel extends Eloquent
* @var bool * @var bool
*/ */
public $timestamps = true; public $timestamps = true;
/**
* @var bool
*/
protected static $hasPublicId = true;
/** /**
* @var array * @var array
*/ */
@ -56,6 +63,8 @@ class EntityModel extends Eloquent
$lastEntity = $className::whereAccountId($entity->account_id); $lastEntity = $className::whereAccountId($entity->account_id);
} }
if (static::$hasPublicId) {
$lastEntity = $lastEntity->orderBy('public_id', 'DESC') $lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first(); ->first();
@ -64,6 +73,7 @@ class EntityModel extends Eloquent
} else { } else {
$entity->public_id = 1; $entity->public_id = 1;
} }
}
return $entity; return $entity;
} }
@ -244,6 +254,7 @@ class EntityModel extends Eloquent
$icons = [ $icons = [
'dashboard' => 'tachometer', 'dashboard' => 'tachometer',
'clients' => 'users', 'clients' => 'users',
'products' => 'cube',
'invoices' => 'file-pdf-o', 'invoices' => 'file-pdf-o',
'payments' => 'credit-card', 'payments' => 'credit-card',
'recurring_invoices' => 'files-o', 'recurring_invoices' => 'files-o',
@ -253,9 +264,21 @@ class EntityModel extends Eloquent
'expenses' => 'file-image-o', 'expenses' => 'file-image-o',
'vendors' => 'building', 'vendors' => 'building',
'settings' => 'cog', 'settings' => 'cog',
'self-update' => 'download',
]; ];
return array_get($icons, $entityType); 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;
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Models; <?php namespace App\Models;
use Utils;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use App\Events\ExpenseWasCreated; use App\Events\ExpenseWasCreated;
@ -31,6 +32,7 @@ class Expense extends EntityModel
'client_id', 'client_id',
'vendor_id', 'vendor_id',
'expense_currency_id', 'expense_currency_id',
'expense_date',
'invoice_currency_id', 'invoice_currency_id',
'amount', 'amount',
'foreign_amount', 'foreign_amount',
@ -46,6 +48,29 @@ class Expense extends EntityModel
'tax_name2', '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 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
@ -107,7 +132,13 @@ class Expense extends EntityModel
*/ */
public function getName() 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; return $query;
} }
public function amountWithTax()
{
return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2);
}
} }
Expense::creating(function ($expense) { Expense::creating(function ($expense) {
@ -196,7 +232,3 @@ Expense::updated(function ($expense) {
Expense::deleting(function ($expense) { Expense::deleting(function ($expense) {
$expense->setNullValues(); $expense->setNullValues();
}); });
Expense::deleted(function ($expense) {
event(new ExpenseWasDeleted($expense));
});

View File

@ -14,6 +14,12 @@ class Gateway extends Eloquent
*/ */
public $timestamps = true; public $timestamps = true;
protected $fillable = [
'provider',
'is_offsite',
'sort_order',
];
/** /**
* @var array * @var array
*/ */
@ -39,6 +45,7 @@ class Gateway extends Eloquent
GATEWAY_BRAINTREE, GATEWAY_BRAINTREE,
GATEWAY_AUTHORIZE_NET, GATEWAY_AUTHORIZE_NET,
GATEWAY_MOLLIE, GATEWAY_MOLLIE,
GATEWAY_CUSTOM,
]; ];
// allow adding these gateway if another gateway // allow adding these gateway if another gateway
@ -174,6 +181,18 @@ class Gateway extends Eloquent
*/ */
public function getFields() public function getFields()
{ {
if ($this->isCustom()) {
return [
'name' => '',
'text' => '',
];
} else {
return Omnipay::create($this->provider)->getDefaultParameters(); return Omnipay::create($this->provider)->getDefaultParameters();
} }
}
public function isCustom()
{
return $this->id === GATEWAY_CUSTOM;
}
} }

View 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;
}
}

View File

@ -134,4 +134,13 @@ class Invitation extends EntityModel
$invoice->markViewed(); $invoice->markViewed();
$client->markLoggedIn(); $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));
}
} }

View File

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

View File

@ -11,4 +11,12 @@ class PaymentType extends Eloquent
* @var bool * @var bool
*/ */
public $timestamps = false; public $timestamps = false;
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function gatewayType()
{
return $this->belongsTo('App\Models\GatewayType');
}
} }

View File

@ -14,6 +14,16 @@ class Task extends EntityModel
use SoftDeletes; use SoftDeletes;
use PresentableTrait; use PresentableTrait;
/**
* @var array
*/
protected $fillable = [
'client_id',
'description',
'time_log',
'is_running',
];
/** /**
* @return mixed * @return mixed
*/ */
@ -82,6 +92,18 @@ class Task extends EntityModel
return self::calcStartTime($this); 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 * @param $task
* @return int * @return int
@ -165,6 +187,14 @@ class Task extends EntityModel
return '#' . $this->public_id; return '#' . $this->public_id;
} }
public function scopeDateRange($query, $startDate, $endDate)
{
$query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) >= ' . $startDate->format('U'));
$query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->format('U'));
return $query;
}
} }

View File

@ -87,6 +87,7 @@ trait PresentsInvoice
'invoice.partial_due', 'invoice.partial_due',
'invoice.custom_text_value1', 'invoice.custom_text_value1',
'invoice.custom_text_value2', 'invoice.custom_text_value2',
'.blank',
], ],
INVOICE_FIELDS_CLIENT => [ INVOICE_FIELDS_CLIENT => [
'client.client_name', 'client.client_name',
@ -97,9 +98,11 @@ trait PresentsInvoice
'client.city_state_postal', 'client.city_state_postal',
'client.country', 'client.country',
'client.email', 'client.email',
'client.phone',
'client.contact_name', 'client.contact_name',
'client.custom_value1', 'client.custom_value1',
'client.custom_value2', 'client.custom_value2',
'.blank',
], ],
INVOICE_FIELDS_ACCOUNT => [ INVOICE_FIELDS_ACCOUNT => [
'account.company_name', 'account.company_name',
@ -114,6 +117,7 @@ trait PresentsInvoice
'account.country', 'account.country',
'account.custom_value1', 'account.custom_value1',
'account.custom_value2', 'account.custom_value2',
'.blank',
] ]
]; ];
@ -198,6 +202,7 @@ trait PresentsInvoice
'company_name', 'company_name',
'website', 'website',
'phone', 'phone',
'blank',
]; ];
foreach ($fields as $field) { foreach ($fields as $field) {

View File

@ -5,19 +5,14 @@ use Event;
use App\Libraries\Utils; use App\Libraries\Utils;
use App\Events\UserSettingsChanged; use App\Events\UserSettingsChanged;
use App\Events\UserSignedUp; use App\Events\UserSignedUp;
use Illuminate\Auth\Authenticatable; use Illuminate\Foundation\Auth\User as 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\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
/** /**
* Class User * Class User
*/ */
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { class User extends Authenticatable
{
/** /**
* @var array * @var array
*/ */
@ -27,8 +22,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
'edit_all' => 0b0100, 'edit_all' => 0b0100,
]; ];
use Authenticatable, Authorizable, CanResetPassword;
/** /**
* The database table used by the model. * The database table used by the model.
* *
@ -102,26 +95,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
return PERSON_USER; 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. * 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 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() public function clearSession()
{ {
$keys = [ $keys = [
@ -425,7 +373,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
public function caddAddUsers() public function caddAddUsers()
{ {
if ( ! Utils::isNinja()) { if ( ! Utils::isNinjaProd()) {
return true; return true;
} elseif ( ! $this->hasFeature(FEATURE_USERS)) { } elseif ( ! $this->hasFeature(FEATURE_USERS)) {
return false; return false;
@ -441,6 +389,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
return $numUsers < $company->num_users; 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) { User::updating(function ($user) {

View File

@ -211,7 +211,8 @@ class Vendor extends EntityModel
*/ */
public function addVendorContact($data, $isPrimary = false) 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') { if ($publicId && $publicId != '-1') {
$contact = VendorContact::scope($publicId)->firstOrFail(); $contact = VendorContact::scope($publicId)->firstOrFail();

View File

@ -1,10 +1,17 @@
<?php namespace App\Ninja\Datatables; <?php namespace App\Ninja\Datatables;
use App\Models\AccountGatewaySettings;
use App\Models\GatewayType;
use URL; use URL;
use Cache;
use Utils;
use Session;
use App\Models\AccountGateway; use App\Models\AccountGateway;
class AccountGatewayDatatable extends EntityDatatable class AccountGatewayDatatable extends EntityDatatable
{ {
private static $accountGateways;
public $entityType = ENTITY_ACCOUNT_GATEWAY; public $entityType = ENTITY_ACCOUNT_GATEWAY;
public function columns() public function columns()
@ -15,10 +22,14 @@ class AccountGatewayDatatable extends EntityDatatable
function ($model) { function ($model) {
if ($model->deleted_at) { if ($model->deleted_at) {
return $model->name; 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) { } elseif ($model->gateway_id != GATEWAY_WEPAY) {
return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml();
} else { } else {
$accountGateway = AccountGateway::find($model->id); $accountGateway = $this->getAccountGateway($model->id);
$config = $accountGateway->getConfig(); $config = $accountGateway->getConfig();
$endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/'; $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
$wepayAccountId = $config->accountId; $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 . ' &mdash; ';
}
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() public function actions()
{ {
return [ $actions = [
[ [
uctrans('texts.resend_confirmation_email'), uctrans('texts.resend_confirmation_email'),
function ($model) { function ($model) {
@ -78,7 +133,7 @@ class AccountGatewayDatatable extends EntityDatatable
], [ ], [
uctrans('texts.manage_account'), uctrans('texts.manage_account'),
function ($model) { 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/'; $endpoint = WEPAY_ENVIRONMENT == WEPAY_STAGE ? 'https://stage.wepay.com/' : 'https://www.wepay.com/';
return [ return [
'url' => $endpoint.'account/'.$accountGateway->getConfig()->accountId, '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];
} }
} }

View File

@ -27,7 +27,9 @@ class ActivityDatatable extends EntityDatatable
'payment' => $model->payment ?: '', 'payment' => $model->payment ?: '',
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '', '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, '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); return trans("texts.activity_{$model->activity_type_id}", $data);

View File

@ -93,7 +93,7 @@ class ClientDatatable extends EntityDatatable
return URL::to("quotes/create/{$model->public_id}"); return URL::to("quotes/create/{$model->public_id}");
}, },
function ($model) { 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);
} }
], ],
[ [

View File

@ -46,7 +46,7 @@ class ExpenseDatatable extends EntityDatatable
[ [
'expense_date', 'expense_date',
function ($model) { 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); return Utils::fromSqlDate($model->expense_date);
} }
@ -56,14 +56,16 @@ class ExpenseDatatable extends EntityDatatable
[ [
'amount', 'amount',
function ($model) { 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 // show both the amount and the converted amount
if ($model->exchange_rate != 1) { if ($model->exchange_rate != 1) {
$converted = round($model->amount * $model->exchange_rate, 2); $converted = round($amount * $model->exchange_rate, 2);
return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . $str .= ' | ' . Utils::formatMoney($converted, $model->invoice_currency_id);
Utils::formatMoney($converted, $model->invoice_currency_id);
} else {
return Utils::formatMoney($model->amount, $model->expense_currency_id);
} }
return $str;
} }
], ],
[ [

View File

@ -16,7 +16,7 @@ class InvoiceDatatable extends EntityDatatable
[ [
'invoice_number', 'invoice_number',
function ($model) use ($entityType) { 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; return $model->invoice_number;
} }

View File

@ -21,7 +21,7 @@ class PaymentDatatable extends EntityDatatable
[ [
'invoice_number', 'invoice_number',
function ($model) { 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; return $model->invoice_number;
} }
@ -89,7 +89,11 @@ class PaymentDatatable extends EntityDatatable
[ [
'payment_date', 'payment_date',
function ($model) { function ($model) {
if ($model->is_deleted) {
return Utils::dateToString($model->payment_date); 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}')"; return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')";
}, },
function ($model) { function ($model) {
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]) && $model->payment_status_id >= PAYMENT_STATUS_COMPLETED && return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id])
$model->refunded < $model->amount && && $model->payment_status_id >= PAYMENT_STATUS_COMPLETED
( && $model->refunded < $model->amount
($model->transaction_reference && in_array($model->gateway_id , static::$refundableGateways)) && $model->transaction_reference
|| $model->payment_type_id == PAYMENT_TYPE_CREDIT && in_array($model->gateway_id , static::$refundableGateways);
);
} }
] ]
]; ];

View File

@ -58,7 +58,17 @@ class RecurringInvoiceDatatable extends EntityDatatable
function ($model) { function ($model) {
return Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); 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);
}
],
]; ];
} }

View File

@ -26,6 +26,9 @@ class TaskDatatable extends EntityDatatable
[ [
'created_at', 'created_at',
function ($model) { 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(); return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml();
} }
], ],

View File

@ -182,10 +182,21 @@ class BaseTransformer extends TransformerAbstract
* @param $name * @param $name
* @return null * @return null
*/ */
protected function getVendorId($name) public function getVendorId($name)
{ {
$name = strtolower($name); $name = strtolower($name);
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null; 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;
}
} }

View File

@ -28,6 +28,7 @@ class ClientTransformer extends BaseTransformer
'postal_code' => $this->getString($data, 'postal_code'), 'postal_code' => $this->getString($data, 'postal_code'),
'private_notes' => $this->getString($data, 'notes'), 'private_notes' => $this->getString($data, 'notes'),
'website' => $this->getString($data, 'website'), 'website' => $this->getString($data, 'website'),
'vat_number' => $this->getString($data, 'vat_number'),
'contacts' => [ 'contacts' => [
[ [
'first_name' => $this->getString($data, 'first_name'), 'first_name' => $this->getString($data, 'first_name'),

View 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,
];
});
}
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Mailers; <?php namespace App\Ninja\Mailers;
use Utils;
use Exception; use Exception;
use Mail; use Mail;
use App\Models\Invoice; use App\Models\Invoice;
@ -112,6 +113,8 @@ class Mailer
$invitation = $data['invitation']; $invitation = $data['invitation'];
$invitation->email_error = $emailError; $invitation->email_error = $emailError;
$invitation->save(); $invitation->save();
} elseif ( ! Utils::isNinja()) {
Utils::logError(Utils::getErrorString($exception));
} }
return $emailError; return $emailError;

View File

@ -2,16 +2,19 @@
use URL; use URL;
use Session; use Session;
use Utils;
use Request; use Request;
use Omnipay; use Omnipay;
use Exception; use Exception;
use CreditCard; use CreditCard;
use DateTime; use DateTime;
use App\Models\AccountGatewayToken; use App\Models\AccountGatewayToken;
use App\Models\AccountGatewaySettings;
use App\Models\Account; use App\Models\Account;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentMethod; use App\Models\PaymentMethod;
use App\Models\Country; use App\Models\Country;
use App\Models\GatewayType;
class BasePaymentDriver class BasePaymentDriver
{ {
@ -119,6 +122,12 @@ class BasePaymentDriver
$gateway = $this->accountGateway->gateway; $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 ($this->isGatewayType(GATEWAY_TYPE_TOKEN) || $gateway->is_offsite) {
if (Session::has('error')) { if (Session::has('error')) {
Session::reflash(); Session::reflash();
@ -158,12 +167,14 @@ class BasePaymentDriver
// check if a custom view exists for this provider // check if a custom view exists for this provider
protected function paymentView() 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)) { if (file_exists($file)) {
return sprintf('payments.%s/%s', $this->providerName(), $this->gatewayType); return sprintf('payments.%s/%s', $this->providerName(), $gatewayTypeAlias);
} else { } else {
return sprintf('payments.%s', $this->gatewayType); return sprintf('payments.%s', $gatewayTypeAlias);
} }
} }
@ -242,10 +253,24 @@ class BasePaymentDriver
->wherePublicId($this->sourceId) ->wherePublicId($this->sourceId)
->firstOrFail(); ->firstOrFail();
} }
} elseif ($this->shouldCreateToken()) {
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(); $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()) { if ($this->isTwoStep()) {
return; return;
} }
@ -323,7 +348,8 @@ class BasePaymentDriver
protected function paymentDetails($paymentMethod = false) protected function paymentDetails($paymentMethod = false)
{ {
$invoice = $this->invoice(); $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 = [ $data = [
'amount' => $invoice->getRequestedAmount(), 'amount' => $invoice->getRequestedAmount(),
@ -760,6 +786,10 @@ class BasePaymentDriver
continue; continue;
} }
if ( ! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) {
continue;
}
$url = URL::to("/payment/{$this->invitation->invitation_key}/token/".$paymentMethod->public_id); $url = URL::to("/payment/{$this->invitation->invitation_key}/token/".$paymentMethod->public_id);
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) { if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
@ -787,27 +817,68 @@ class BasePaymentDriver
{ {
$links = []; $links = [];
foreach ($this->gatewayTypes() as $gatewayType) { foreach ($this->gatewayTypes() as $gatewayTypeId) {
if ($gatewayType === GATEWAY_TYPE_TOKEN) { if ($gatewayTypeId === GATEWAY_TYPE_TOKEN) {
continue; 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[] = [ $links[] = [
'url' => $this->paymentUrl($gatewayType), 'gatewayTypeId' => $gatewayTypeId,
'label' => trans("texts.{$gatewayType}") 'url' => $url,
'label' => $label,
]; ];
} }
return $links; 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(); $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 // 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'; $url .= '#braintree_paypal';
if ($account->iframe_url) { if ($account->iframe_url) {

View File

@ -0,0 +1,12 @@
<?php namespace App\Ninja\PaymentDrivers;
class CustomPaymentDriver extends BasePaymentDriver
{
public function gatewayTypes()
{
return [
GATEWAY_TYPE_CUSTOM
];
}
}

View File

@ -151,14 +151,17 @@ class StripePaymentDriver extends BasePaymentDriver
protected function creatingPaymentMethod($paymentMethod) protected function creatingPaymentMethod($paymentMethod)
{ {
$data = $this->tokenResponse; $data = $this->tokenResponse;
$source = false;
if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) { if (!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
$source = $data; $source = $data;
} elseif (!empty($data['object']) && $data['object'] == 'customer') { } elseif (!empty($data['object']) && $data['object'] == 'customer') {
$sources = !empty($data['sources']) ? $data['sources'] : $data['cards']; $sources = !empty($data['sources']) ? $data['sources'] : $data['cards'];
$source = reset($sources['data']); $source = reset($sources['data']);
} else { } elseif (!empty($data['source'])) {
$source = !empty($data['source']) ? $data['source'] : $data['card']; $source = $data['source'];
} elseif (!empty($data['card'])) {
$source = $data['card'];
} }
if ( ! $source) { if ( ! $source) {

View File

@ -23,22 +23,4 @@ class ClientPresenter extends EntityPresenter {
return $account->formatMoney($client->paid_to_date, $client); 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>";
}
} }

View File

@ -17,6 +17,24 @@ class EntityPresenter extends Presenter
return URL::to($link); 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 * @return mixed
*/ */

View File

@ -24,12 +24,4 @@ class ExpensePresenter extends EntityPresenter
return Utils::fromSqlDate($this->entity->expense_date); return Utils::fromSqlDate($this->entity->expense_date);
} }
/**
* @return int
*/
public function invoiced_amount()
{
return $this->entity->invoice_id ? $this->entity->convertedAmount() : 0;
}
} }

View File

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

View File

@ -2,6 +2,7 @@
use Auth; use Auth;
use Request; use Request;
use Input;
use Session; use Session;
use Utils; use Utils;
use URL; use URL;
@ -27,6 +28,11 @@ class AccountRepository
public function create($firstName = '', $lastName = '', $email = '', $password = '') public function create($firstName = '', $lastName = '', $email = '', $password = '')
{ {
$company = new Company(); $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(); $company->save();
$account = new Account(); $account = new Account();

View File

@ -74,6 +74,8 @@ class ActivityRepository
->leftJoin('invoices', 'invoices.id', '=', 'activities.invoice_id') ->leftJoin('invoices', 'invoices.id', '=', 'activities.invoice_id')
->leftJoin('payments', 'payments.id', '=', 'activities.payment_id') ->leftJoin('payments', 'payments.id', '=', 'activities.payment_id')
->leftJoin('credits', 'credits.id', '=', 'activities.credit_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('clients.id', '=', $clientId)
->where('contacts.is_primary', '=', 1) ->where('contacts.is_primary', '=', 1)
->whereNull('contacts.deleted_at') ->whereNull('contacts.deleted_at')
@ -102,7 +104,11 @@ class ActivityRepository
'contacts.email as email', 'contacts.email as email',
'payments.transaction_reference as payment', 'payments.transaction_reference as payment',
'payments.amount as payment_amount', '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'
); );
} }

View File

@ -31,6 +31,7 @@ class ClientRepository extends BaseRepository
->where('clients.account_id', '=', \Auth::user()->account_id) ->where('clients.account_id', '=', \Auth::user()->account_id)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->where('contacts.deleted_at', '=', null) ->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( ->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
@ -78,7 +79,10 @@ class ClientRepository extends BaseRepository
$client = Client::createNew(); $client = Client::createNew();
} else { } else {
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = Client::scope($publicId)->with('contacts')->firstOrFail();
\Log::warning('Entity not set in client repo save'); }
if ($client->is_deleted) {
return $client;
} }
// convert currency code to id // convert currency code to id
@ -107,7 +111,11 @@ class ClientRepository extends BaseRepository
// If the primary is set ensure it's listed first // If the primary is set ensure it's listed first
usort($contacts, function ($left, $right) { 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) { foreach ($contacts as $contact) {

View File

@ -58,6 +58,32 @@ class CreditRepository extends BaseRepository
return $query; 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) public function save($input, $credit = null)
{ {
$publicId = isset($data['public_id']) ? $data['public_id'] : false; $publicId = isset($data['public_id']) ? $data['public_id'] : false;

View File

@ -39,12 +39,14 @@ class DashboardRepository
$data = []; $data = [];
$count = 0; $count = 0;
$balance = 0;
$records = $this->rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId); $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; $data[$item->$groupBy] = $item->total;
$count += $item->count; $count += $item->count;
}, $records->get()); $balance += isset($item->balance) ? $item->balance : 0;
}, $records);
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month'); $padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
$endDate->modify('+1 '.$padding); $endDate->modify('+1 '.$padding);
@ -84,9 +86,9 @@ class DashboardRepository
if ($entityType == ENTITY_INVOICE) { if ($entityType == ENTITY_INVOICE) {
$totals->invoices = array_sum($data); $totals->invoices = array_sum($data);
$totals->average = $count ? round($totals->invoices / $count, 2) : 0; $totals->average = $count ? round($totals->invoices / $count, 2) : 0;
$totals->balance = $balance;
} elseif ($entityType == ENTITY_PAYMENT) { } elseif ($entityType == ENTITY_PAYMENT) {
$totals->revenue = array_sum($data); $totals->revenue = array_sum($data);
$totals->balance = $totals->invoices - $totals->revenue;
} elseif ($entityType == ENTITY_EXPENSE) { } elseif ($entityType == ENTITY_EXPENSE) {
//$totals->profit = $totals->revenue - array_sum($data); //$totals->profit = $totals->revenue - array_sum($data);
$totals->expenses = array_sum($data); $totals->expenses = array_sum($data);
@ -106,6 +108,10 @@ class DashboardRepository
private function rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId) private function rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId)
{ {
if ( ! in_array($groupBy, ['DAYOFYEAR', 'WEEK', 'MONTH'])) {
return [];
}
$accountId = $account->id; $accountId = $account->id;
$currencyId = intval($currencyId); $currencyId = intval($currencyId);
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))'; $timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
@ -128,7 +134,7 @@ class DashboardRepository
} }
if ($entityType == ENTITY_INVOICE) { 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('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('is_recurring', '=', false); ->where('is_recurring', '=', false);
} elseif ($entityType == ENTITY_PAYMENT) { } elseif ($entityType == ENTITY_PAYMENT) {
@ -137,10 +143,10 @@ class DashboardRepository
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->whereNotIn('payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]); ->whereNotIn('payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
} elseif ($entityType == ENTITY_EXPENSE) { } 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) public function totals($accountId, $userId, $viewAll)
@ -175,29 +181,39 @@ class DashboardRepository
return $metrics->groupBy('accounts.id')->first(); return $metrics->groupBy('accounts.id')->first();
} }
public function paidToDate($accountId, $userId, $viewAll) public function paidToDate($account, $userId, $viewAll)
{ {
$accountId = $account->id;
$select = DB::raw( $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' .DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
); );
$paidToDate = DB::table('accounts') $paidToDate = DB::table('payments')
->select($select) ->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') ->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->where('accounts.id', '=', $accountId) ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
->where('clients.is_deleted', '=', false); ->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){ if (!$viewAll){
$paidToDate = $paidToDate->where('clients.user_id', '=', $userId); $paidToDate->where('invoices.user_id', '=', $userId);
} }
return $paidToDate->groupBy('accounts.id') if ($account->financial_year_start) {
->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')) $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(); ->get();
} }
public function averages($accountId, $userId, $viewAll) public function averages($account, $userId, $viewAll)
{ {
$accountId = $account->id;
$select = DB::raw( $select = DB::raw(
'AVG('.DB::getQueryGrammar()->wrap('invoices.amount', true).') as invoice_avg, ' 'AVG('.DB::getQueryGrammar()->wrap('invoices.amount', true).') as invoice_avg, '
.DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id' .DB::getQueryGrammar()->wrap('clients.currency_id', true).' as currency_id'
@ -213,7 +229,12 @@ class DashboardRepository
->where('invoices.is_recurring', '=', false); ->where('invoices.is_recurring', '=', false);
if (!$viewAll){ 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') return $averageInvoice->groupBy('accounts.id')
@ -252,7 +273,7 @@ class DashboardRepository
} }
return $activities->orderBy('activities.created_at', 'desc') 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) ->take(50)
->get(); ->get();
} }
@ -335,8 +356,12 @@ class DashboardRepository
public function expenses($accountId, $userId, $viewAll) 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( $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' .DB::getQueryGrammar()->wrap('expenses.expense_currency_id', true).' as currency_id'
); );
$paidToDate = DB::table('accounts') $paidToDate = DB::table('accounts')

View File

@ -12,6 +12,11 @@ class ExpenseCategoryRepository extends BaseRepository
return 'App\Models\ExpenseCategory'; return 'App\Models\ExpenseCategory';
} }
public function all()
{
return ExpenseCategory::scope()->get();
}
public function find($filter = null) public function find($filter = null)
{ {
$query = DB::table('expense_categories') $query = DB::table('expense_categories')

View File

@ -76,6 +76,8 @@ class ExpenseRepository extends BaseRepository
'expenses.expense_currency_id', 'expenses.expense_currency_id',
'expenses.invoice_currency_id', 'expenses.invoice_currency_id',
'expenses.user_id', 'expenses.user_id',
'expenses.tax_rate1',
'expenses.tax_rate2',
'expense_categories.name as category', 'expense_categories.name as category',
'invoices.public_id as invoice_public_id', 'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id', 'invoices.user_id as invoice_user_id',
@ -118,20 +120,24 @@ class ExpenseRepository extends BaseRepository
// do nothing // do nothing
} elseif ($publicId) { } elseif ($publicId) {
$expense = Expense::scope($publicId)->firstOrFail(); $expense = Expense::scope($publicId)->firstOrFail();
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in expense repo save'); \Log::warning('Entity not set in expense repo save');
}
} else { } else {
$expense = Expense::createNew(); $expense = Expense::createNew();
} }
if ($expense->is_deleted) {
return $expense;
}
// First auto fill // First auto fill
$expense->fill($input); $expense->fill($input);
if (isset($input['expense_date'])) {
$expense->expense_date = Utils::toSqlDate($input['expense_date']); $expense->expense_date = Utils::toSqlDate($input['expense_date']);
if (isset($input['private_notes'])) {
$expense->private_notes = trim($input['private_notes']);
} }
$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; $expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false;
if ( ! $expense->expense_currency_id) { if ( ! $expense->expense_currency_id) {
@ -143,7 +149,9 @@ class ExpenseRepository extends BaseRepository
$rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1; $rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1;
$expense->exchange_rate = round($rate, 4); $expense->exchange_rate = round($rate, 4);
if (isset($input['amount'])) {
$expense->amount = round(Utils::parseFloat($input['amount']), 2); $expense->amount = round(Utils::parseFloat($input['amount']), 2);
}
$expense->save(); $expense->save();
@ -173,27 +181,4 @@ class ExpenseRepository extends BaseRepository
return $expense; 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);
}
} }

View File

@ -50,6 +50,7 @@ class InvoiceRepository extends BaseRepository
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true) ->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( ->select(
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
@ -280,8 +281,14 @@ class InvoiceRepository extends BaseRepository
} }
} else { } else {
$invoice = Invoice::scope($publicId)->firstOrFail(); $invoice = Invoice::scope($publicId)->firstOrFail();
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in invoice repo save'); \Log::warning('Entity not set in invoice repo save');
} }
}
if ($invoice->is_deleted) {
return $invoice;
}
$invoice->fill($data); $invoice->fill($data);
@ -306,9 +313,6 @@ class InvoiceRepository extends BaseRepository
if (isset($data['is_amount_discount'])) { if (isset($data['is_amount_discount'])) {
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false; $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'])) { if (isset($data['invoice_date_sql'])) {
$invoice->invoice_date = $data['invoice_date_sql']; $invoice->invoice_date = $data['invoice_date_sql'];
} elseif (isset($data['invoice_date'])) { } elseif (isset($data['invoice_date'])) {
@ -477,6 +481,10 @@ class InvoiceRepository extends BaseRepository
$invoice->balance = $total; $invoice->balance = $total;
} }
if (isset($data['partial'])) {
$invoice->partial = max(0,min(round(Utils::parseFloat($data['partial']), 2), $invoice->balance));
}
$invoice->amount = $total; $invoice->amount = $total;
$invoice->save(); $invoice->save();
@ -653,6 +661,9 @@ class InvoiceRepository extends BaseRepository
if ($quotePublicId) { if ($quotePublicId) {
$clone->invoice_type_id = INVOICE_TYPE_STANDARD; $clone->invoice_type_id = INVOICE_TYPE_STANDARD;
$clone->quote_id = $quotePublicId; $clone->quote_id = $quotePublicId;
if ($account->invoice_terms) {
$clone->terms = $account->invoice_terms;
}
} }
$clone->save(); $clone->save();

View File

@ -150,11 +150,17 @@ class PaymentRepository extends BaseRepository
// do nothing // do nothing
} elseif ($publicId) { } elseif ($publicId) {
$payment = Payment::scope($publicId)->firstOrFail(); $payment = Payment::scope($publicId)->firstOrFail();
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in payment repo save'); \Log::warning('Entity not set in payment repo save');
}
} else { } else {
$payment = Payment::createNew(); $payment = Payment::createNew();
} }
if ($payment->is_deleted) {
return $payment;
}
$paymentTypeId = false; $paymentTypeId = false;
if (isset($input['payment_type_id'])) { if (isset($input['payment_type_id'])) {
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null; $paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;

View File

@ -17,15 +17,14 @@ class ProductRepository extends BaseRepository
->get(); ->get();
} }
public function find($accountId) public function find($accountId, $filter = null)
{ {
return DB::table('products') $query = DB::table('products')
->leftJoin('tax_rates', function($join) { ->leftJoin('tax_rates', function($join) {
$join->on('tax_rates.id', '=', 'products.default_tax_rate_id') $join->on('tax_rates.id', '=', 'products.default_tax_rate_id')
->whereNull('tax_rates.deleted_at'); ->whereNull('tax_rates.deleted_at');
}) })
->where('products.account_id', '=', $accountId) ->where('products.account_id', '=', $accountId)
->where('products.deleted_at', '=', null)
->select( ->select(
'products.public_id', 'products.public_id',
'products.product_key', 'products.product_key',
@ -35,6 +34,19 @@ class ProductRepository extends BaseRepository
'tax_rates.rate as tax_rate', 'tax_rates.rate as tax_rate',
'products.deleted_at' '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) public function save($data, $product = null)

View File

@ -5,8 +5,13 @@ use Session;
use App\Models\Client; use App\Models\Client;
use App\Models\Task; use App\Models\Task;
class TaskRepository class TaskRepository extends BaseRepository
{ {
public function getClassName()
{
return 'App\Models\Task';
}
public function find($clientPublicId = null, $filter = null) public function find($clientPublicId = null, $filter = null)
{ {
$query = \DB::table('tasks') $query = \DB::table('tasks')
@ -67,12 +72,15 @@ class TaskRepository
if ($task) { if ($task) {
// do nothing // do nothing
} elseif ($publicId) { } elseif ($publicId) {
$task = Task::scope($publicId)->firstOrFail(); $task = Task::scope($publicId)->withTrashed()->firstOrFail();
\Log::warning('Entity not set in task repo save');
} else { } else {
$task = Task::createNew(); $task = Task::createNew();
} }
if ($task->is_deleted) {
return $task;
}
if (isset($data['client']) && $data['client']) { if (isset($data['client']) && $data['client']) {
$task->client_id = Client::getPrivateId($data['client']); $task->client_id = Client::getPrivateId($data['client']);
} }
@ -109,26 +117,4 @@ class TaskRepository
return $task; 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);
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Repositories; <?php namespace App\Ninja\Repositories;
use Utils;
use DB; use DB;
use App\Models\Vendor; use App\Models\Vendor;
@ -70,20 +71,36 @@ class VendorRepository extends BaseRepository
$vendor = Vendor::createNew(); $vendor = Vendor::createNew();
} else { } else {
$vendor = Vendor::scope($publicId)->with('vendor_contacts')->firstOrFail(); $vendor = Vendor::scope($publicId)->with('vendor_contacts')->firstOrFail();
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in vendor repo save'); \Log::warning('Entity not set in vendor repo save');
} }
}
if ($vendor->is_deleted) {
return $vendor;
}
$vendor->fill($data); $vendor->fill($data);
$vendor->save(); $vendor->save();
$first = true; $first = true;
$vendorcontacts = isset($data['vendor_contact']) ? [$data['vendor_contact']] : $data['vendor_contacts']; $vendorcontacts = isset($data['vendor_contact']) ? [$data['vendor_contact']] : $data['vendor_contacts'];
$vendorcontactIds = [];
foreach ($vendorcontacts as $vendorcontact) { foreach ($vendorcontacts as $vendorcontact) {
$vendorcontact = $vendor->addVendorContact($vendorcontact, $first); $vendorcontact = $vendor->addVendorContact($vendorcontact, $first);
$vendorcontactIds[] = $vendorcontact->public_id;
$first = false; $first = false;
} }
if ( ! $vendor->wasRecentlyCreated) {
foreach ($vendor->vendor_contacts as $contact) {
if (!in_array($contact->public_id, $vendorcontactIds)) {
$contact->delete();
}
}
}
return $vendor; return $vendor;
} }
} }

View File

@ -16,7 +16,7 @@ class ActivityTransformer extends EntityTransformer
protected $availableIncludes = [ ]; protected $availableIncludes = [ ];
/** /**
* @param Client $client * @param Activity $activity
* @return array * @return array
*/ */
public function transform(Activity $activity) public function transform(Activity $activity)
@ -29,7 +29,10 @@ class ActivityTransformer extends EntityTransformer
'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null, 'invoice_id' => $activity->invoice ? $activity->invoice->public_id : null,
'payment_id' => $activity->payment ? $activity->payment->public_id : null, 'payment_id' => $activity->payment ? $activity->payment->public_id : null,
'credit_id' => $activity->credit ? $activity->credit->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
]; ];
} }
} }

View File

@ -23,11 +23,16 @@ class ExpenseTransformer extends EntityTransformer
'transaction_id' => $expense->transaction_id, 'transaction_id' => $expense->transaction_id,
'bank_id' => $expense->bank_id, 'bank_id' => $expense->bank_id,
'expense_currency_id' => (int) $expense->expense_currency_id, 'expense_currency_id' => (int) $expense->expense_currency_id,
'expense_category_id' => (int) $expense->expense_category_id,
'amount' => (float) $expense->amount, 'amount' => (float) $expense->amount,
'expense_date' => $expense->expense_date, 'expense_date' => $expense->expense_date,
'exchange_rate' => (float) $expense->exchange_rate, 'exchange_rate' => (float) $expense->exchange_rate,
'invoice_currency_id' => (int) $expense->invoice_currency_id, 'invoice_currency_id' => (int) $expense->invoice_currency_id,
'is_deleted' => (bool) $expense->is_deleted, '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), '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, '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, 'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,

View File

@ -31,6 +31,7 @@ class UserAccountTransformer extends EntityTransformer
'name' => $user->account->present()->name, 'name' => $user->account->present()->name,
'token' => $user->account->getToken($user->id, $this->tokenName), 'token' => $user->account->getToken($user->id, $this->tokenName),
'default_url' => SITE_URL, 'default_url' => SITE_URL,
'plan' => $user->account->company->plan,
'logo' => $user->account->logo, 'logo' => $user->account->logo,
'logo_url' => $user->account->getLogoURL(), 'logo_url' => $user->account->getLogoURL(),
]; ];

View File

@ -23,7 +23,7 @@ class AccountGatewayPolicy extends EntityPolicy
* @param User $user * @param User $user
* @return bool * @return bool
*/ */
public static function create(User $user) { public static function create(User $user, $item) {
return $user->hasPermission('admin'); return $user->hasPermission('admin');
} }
} }

View File

@ -22,7 +22,7 @@ class BankAccountPolicy extends EntityPolicy
* @param User $user * @param User $user
* @return bool * @return bool
*/ */
public static function create(User $user) { public static function create(User $user, $item) {
return $user->hasPermission('admin'); return $user->hasPermission('admin');
} }
} }

View File

@ -13,7 +13,7 @@ class DocumentPolicy extends EntityPolicy
* @param User $user * @param User $user
* @return bool * @return bool
*/ */
public static function create(User $user) public static function create(User $user, $item)
{ {
return !empty($user); return !empty($user);
} }

Some files were not shown because too many files have changed in this diff Show More