Merge branch 'release-2.5.1'

Conflicts:
	app/Ninja/Mailers/ContactMailer.php
This commit is contained in:
Hillel Coren 2016-03-22 19:41:35 +02:00
commit a61f5cc98a
220 changed files with 8838 additions and 2853 deletions

View File

@ -20,11 +20,27 @@ MAIL_FROM_ADDRESS
MAIL_FROM_NAME MAIL_FROM_NAME
MAIL_PASSWORD MAIL_PASSWORD
#POSTMARK_API_TOKEN=
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single LOG=single
REQUIRE_HTTPS=false REQUIRE_HTTPS=false
API_SECRET=password API_SECRET=password
GOOGLE_CLIENT_ID #TRUSTED_PROXIES=
GOOGLE_CLIENT_SECRET
GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google #SESSION_DRIVER=
#SESSION_DOMAIN=
#SESSION_ENCRYPT=
#SESSION_SECURE=
#CACHE_DRIVER=
#CACHE_HOST=
#CACHE_PORT1=
#CACHE_PORT2=
#GOOGLE_CLIENT_ID=
#GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
#GOOGLE_MAPS_API_KEY=

View File

@ -3,7 +3,7 @@ language: php
sudo: true sudo: true
php: php:
- 5.5 - 5.5.9
# - 5.6 # - 5.6
# - 7.0 # - 7.0
# - hhvm # - hhvm
@ -34,7 +34,6 @@ install:
# these providers require referencing git commit's which cause Travis to fail # these providers require referencing git commit's which cause Travis to fail
- sed -i '/mollie/d' composer.json - sed -i '/mollie/d' composer.json
- sed -i '/2checkout/d' composer.json - sed -i '/2checkout/d' composer.json
- sed -i '/omnipay-neteller/d' composer.json
- travis_retry composer install --prefer-dist; - travis_retry composer install --prefer-dist;
before_script: before_script:
@ -65,16 +64,18 @@ before_script:
script: script:
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php
#- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance QuoteCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php - php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
#- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php

View File

@ -95,19 +95,19 @@ module.exports = function(grunt) {
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/typeahead.js/dist/typeahead.min.js', 'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
'public/vendor/accounting/accounting.min.js', 'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js', 'public/vendor/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js', 'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.min.js', 'public/vendor/moment/min/moment.min.js',
'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js', 'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js',
'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js', 'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js',
'public/vendor/fuse.js/src/fuse.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js', //'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js', //'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js', //'public/vendor/pdfmake/build/pdfmake.min.js',
//'public/vendor/pdfmake/build/vfs_fonts.js', //'public/vendor/pdfmake/build/vfs_fonts.js',
//'public/js/vfs_fonts.js', //'public/js/vfs_fonts.js',
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js', 'public/js/bootstrap-combobox.js',
'public/js/script.js', 'public/js/script.js',
'public/js/pdf.pdfmake.js', 'public/js/pdf.pdfmake.js',
@ -140,7 +140,6 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.css', 'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css', 'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css', 'public/css/typeahead.js-bootstrap.css',
'public/css/lightbox.css',
//'public/vendor/handsontable/dist/jquery.handsontable.full.css', //'public/vendor/handsontable/dist/jquery.handsontable.full.css',
'public/css/style.css', 'public/css/style.css',
], ],

View File

@ -13,7 +13,7 @@ open-source software.
1. Redistributions of source code, in whole or part and with or without 1. Redistributions of source code, in whole or part and with or without
modification requires the express permission of the author and must prominently modification requires the express permission of the author and must prominently
display "Powered by InvoiceNinja" or the Invoice Ninja logo in verifiable form display "Powered by InvoiceNinja" and the Invoice Ninja logo in verifiable form
with hyperlink to said site. with hyperlink to said site.
2. Neither the name nor any trademark of the Author may be used to 2. Neither the name nor any trademark of the Author may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific

View File

@ -0,0 +1,63 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\AccountRepository;
use App\Services\PaymentService;
use App\Models\Invoice;
class ChargeRenewalInvoices extends Command
{
protected $name = 'ninja:charge-renewals';
protected $description = 'Charge renewal invoices';
protected $mailer;
protected $accountRepo;
protected $paymentService;
public function __construct(Mailer $mailer, AccountRepository $repo, PaymentService $paymentService)
{
parent::__construct();
$this->mailer = $mailer;
$this->accountRepo = $repo;
$this->paymentService = $paymentService;
}
public function fire()
{
$this->info(date('Y-m-d').' ChargeRenewalInvoices...');
$account = $this->accountRepo->getNinjaAccount();
$invoices = Invoice::whereAccountId($account->id)
->whereDueDate(date('Y-m-d'))
->with('client')
->orderBy('id')
->get();
$this->info(count($invoices).' invoices found');
foreach ($invoices as $invoice) {
$this->info("Charging invoice {$invoice->invoice_number}");
$this->paymentService->autoBillInvoice($invoice);
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -47,7 +47,7 @@ class SendRenewalInvoices extends Command
} }
$client = $this->accountRepo->getNinjaClient($account); $client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client); $invitation = $this->accountRepo->createNinjaInvoice($client, $account);
// set the due date to 10 days from now // set the due date to 10 days from now
$invoice = $invitation->invoice; $invoice = $invitation->invoice;

View File

@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel
'App\Console\Commands\ResetData', 'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData', 'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendRenewalInvoices',
'App\Console\Commands\ChargeRenewalInvoices',
'App\Console\Commands\SendReminders', 'App\Console\Commands\SendReminders',
'App\Console\Commands\TestOFX', 'App\Console\Commands\TestOFX',
'App\Console\Commands\GenerateResources', 'App\Console\Commands\GenerateResources',

View File

@ -47,13 +47,16 @@ class Handler extends ExceptionHandler {
if ($e instanceof ModelNotFoundException) { if ($e instanceof ModelNotFoundException) {
return Redirect::to('/'); return Redirect::to('/');
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) { } elseif ($e instanceof \Illuminate\Session\TokenMismatchException) {
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e // prevent loop since the page auto-submits
return redirect() if ($request->path() != 'get_started') {
->back() // https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
->withInput($request->except('password', '_token')) return redirect()
->with([ ->back()
'warning' => trans('texts.token_expired') ->withInput($request->except('password', '_token'))
]); ->with([
'warning' => trans('texts.token_expired')
]);
}
} }
// 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

View File

@ -19,6 +19,8 @@ use App\Ninja\Transformers\UserAccountTransformer;
use App\Http\Controllers\BaseAPIController; use App\Http\Controllers\BaseAPIController;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
use App\Events\UserSignedUp;
use App\Http\Requests\RegisterRequest;
use App\Http\Requests\UpdateAccountRequest; use App\Http\Requests\UpdateAccountRequest;
class AccountApiController extends BaseAPIController class AccountApiController extends BaseAPIController
@ -32,13 +34,20 @@ class AccountApiController extends BaseAPIController
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
} }
public function register(RegisterRequest $request)
{
$account = $this->accountRepo->create($request->first_name, $request->last_name, $request->email, $request->password);
$user = $account->users()->first();
Auth::login($user, true);
event(new UserSignedUp());
return $this->processLogin($request);
}
public function login(Request $request) public function login(Request $request)
{ {
if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) {
sleep(ERROR_DELAY);
return $this->errorResponse(['message'=>'Invalid secret'],401);
}
if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) { if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
return $this->processLogin($request); return $this->processLogin($request);
} else { } else {
@ -52,7 +61,7 @@ class AccountApiController extends BaseAPIController
// Create a new token only if one does not already exist // Create a new token only if one does not already exist
$user = Auth::user(); $user = Auth::user();
$this->accountRepo->createTokens($user, $request->token_name); $this->accountRepo->createTokens($user, $request->token_name);
$users = $this->accountRepo->findUsers($user, 'account.account_tokens'); $users = $this->accountRepo->findUsers($user, 'account.account_tokens');
$transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name); $transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name);
$data = $this->createCollection($users, $transformer, 'user_account'); $data = $this->createCollection($users, $transformer, 'user_account');
@ -65,24 +74,7 @@ class AccountApiController extends BaseAPIController
$account = Auth::user()->account; $account = Auth::user()->account;
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false; $updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
$map = [ $account->loadAllData($updatedAt);
'users' => [],
'clients' => ['contacts'],
'invoices' => ['invoice_items', 'user', 'client', 'payments'],
'products' => [],
'tax_rates' => [],
'expenses' => ['client', 'invoice', 'vendor'],
'payments' => ['invoice'],
];
foreach ($map as $key => $values) {
$account->load([$key => function($query) use ($values, $updatedAt) {
$query->withTrashed()->with($values);
if ($updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
}
}]);
}
$transformer = new AccountTransformer(null, $request->serializer); $transformer = new AccountTransformer(null, $request->serializer);
$account = $this->createItem($account, $transformer, 'account'); $account = $this->createItem($account, $transformer, 'account');
@ -117,4 +109,82 @@ class AccountApiController extends BaseAPIController
return $this->response($account); return $this->response($account);
} }
public function addDeviceToken(Request $request)
{
$account = Auth::user()->account;
//scan if this user has a token already registered (tokens can change, so we need to use the users email as key)
$devices = json_decode($account->devices,TRUE);
for($x=0; $x<count($devices); $x++)
{
if ($devices[$x]['email'] == Auth::user()->username) {
$devices[$x]['token'] = $request->token; //update
$account->devices = json_encode($devices);
$account->save();
$devices[$x]['account_key'] = $account->account_key;
return $this->response($devices[$x]);
}
}
//User does not have a device, create new record
$newDevice = [
'token' => $request->token,
'email' => $request->email,
'device' => $request->device,
'account_key' => $account->account_key,
'notify_sent' => TRUE,
'notify_viewed' => TRUE,
'notify_approved' => TRUE,
'notify_paid' => TRUE,
];
$devices[] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($newDevice);
}
public function updatePushNotifications(Request $request)
{
$account = Auth::user()->account;
$devices = json_decode($account->devices, TRUE);
if(count($devices) < 1)
return $this->errorResponse(['message'=>'No registered devices.'], 400);
for($x=0; $x<count($devices); $x++)
{
if($devices[$x]['email'] == Auth::user()->username)
{
$newDevice = [
'token' => $devices[$x]['token'],
'email' => $devices[$x]['email'],
'device' => $devices[$x]['device'],
'account_key' => $account->account_key,
'notify_sent' => $request->notify_sent,
'notify_viewed' => $request->notify_viewed,
'notify_approved' => $request->notify_approved,
'notify_paid' => $request->notify_paid,
];
//unset($devices[$x]);
$devices[$x] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($newDevice);
}
}
}
} }

View File

@ -15,6 +15,7 @@ use Response;
use Request; use Request;
use App\Models\Affiliate; use App\Models\Affiliate;
use App\Models\License; use App\Models\License;
use App\Models\Invoice;
use App\Models\User; use App\Models\User;
use App\Models\Account; use App\Models\Account;
use App\Models\Gateway; use App\Models\Gateway;
@ -25,7 +26,7 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Repositories\ReferralRepository; use App\Ninja\Repositories\ReferralRepository;
use App\Ninja\Mailers\UserMailer; use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer; use App\Ninja\Mailers\ContactMailer;
use App\Events\UserLoggedIn; use App\Events\UserSignedUp;
use App\Events\UserSettingsChanged; use App\Events\UserSettingsChanged;
use App\Services\AuthService; use App\Services\AuthService;
@ -40,7 +41,7 @@ class AccountController extends BaseController
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository) public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository)
{ {
parent::__construct(); //parent::__construct();
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
$this->userMailer = $userMailer; $this->userMailer = $userMailer;
@ -99,7 +100,7 @@ class AccountController extends BaseController
} }
Auth::login($user, true); Auth::login($user, true);
event(new UserLoggedIn()); event(new UserSignedUp());
$redirectTo = Input::get('redirect_to') ?: 'invoices/create'; $redirectTo = Input::get('redirect_to') ?: 'invoices/create';
@ -122,7 +123,8 @@ class AccountController extends BaseController
public function getSearchData() public function getSearchData()
{ {
$data = $this->accountRepo->getSearchData(); $account = Auth::user()->account;
$data = $this->accountRepo->getSearchData($account);
return Response::json($data); return Response::json($data);
} }
@ -135,8 +137,6 @@ class AccountController extends BaseController
if ($section == ACCOUNT_COMPANY_DETAILS) { if ($section == ACCOUNT_COMPANY_DETAILS) {
return self::showCompanyDetails(); return self::showCompanyDetails();
} elseif ($section == ACCOUNT_USER_DETAILS) {
return self::showUserDetails();
} elseif ($section == ACCOUNT_LOCALIZATION) { } elseif ($section == ACCOUNT_LOCALIZATION) {
return self::showLocalization(); return self::showLocalization();
} elseif ($section == ACCOUNT_PAYMENTS) { } elseif ($section == ACCOUNT_PAYMENTS) {
@ -150,7 +150,7 @@ class AccountController extends BaseController
} elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) { } elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) {
return self::showInvoiceDesign($section); return self::showInvoiceDesign($section);
} elseif ($section == ACCOUNT_CLIENT_PORTAL) { } elseif ($section == ACCOUNT_CLIENT_PORTAL) {
return self::showClientViewStyling(); return self::showClientPortal();
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) { } elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
return self::showTemplates(); return self::showTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) { } elseif ($section === ACCOUNT_PRODUCTS) {
@ -230,7 +230,7 @@ class AccountController extends BaseController
return View::make('accounts.details', $data); return View::make('accounts.details', $data);
} }
private function showUserDetails() public function showUserDetails()
{ {
$oauthLoginUrls = []; $oauthLoginUrls = [];
foreach (AuthService::$providers as $provider) { foreach (AuthService::$providers as $provider) {
@ -393,12 +393,27 @@ class AccountController extends BaseController
if ($section == ACCOUNT_CUSTOMIZE_DESIGN) { if ($section == ACCOUNT_CUSTOMIZE_DESIGN) {
$data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design; $data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design;
// sample invoice to help determine variables
$invoice = Invoice::scope()
->with('client', 'account')
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->first();
if ($invoice) {
$invoice->hidePrivateFields();
unset($invoice->account);
unset($invoice->invoice_items);
unset($invoice->client->contacts);
$data['sampleInvoice'] = $invoice;
}
} }
return View::make("accounts.{$section}", $data); return View::make("accounts.{$section}", $data);
} }
private function showClientViewStyling() private function showClientPortal()
{ {
$account = Auth::user()->account->load('country'); $account = Auth::user()->account->load('country');
$css = $account->client_view_css ? $account->client_view_css : ''; $css = $account->client_view_css ? $account->client_view_css : '';
@ -414,8 +429,11 @@ class AccountController extends BaseController
$data = [ $data = [
'client_view_css' => $css, 'client_view_css' => $css,
'enable_portal_password' => $account->enable_portal_password,
'send_portal_password' => $account->send_portal_password,
'title' => trans("texts.client_portal"), 'title' => trans("texts.client_portal"),
'section' => ACCOUNT_CLIENT_PORTAL, 'section' => ACCOUNT_CLIENT_PORTAL,
'account' => $account,
]; ];
return View::make("accounts.client_portal", $data); return View::make("accounts.client_portal", $data);
@ -447,8 +465,6 @@ class AccountController extends BaseController
{ {
if ($section === ACCOUNT_COMPANY_DETAILS) { if ($section === ACCOUNT_COMPANY_DETAILS) {
return AccountController::saveDetails(); return AccountController::saveDetails();
} elseif ($section === ACCOUNT_USER_DETAILS) {
return AccountController::saveUserDetails();
} elseif ($section === ACCOUNT_LOCALIZATION) { } elseif ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveLocalization(); return AccountController::saveLocalization();
} elseif ($section === ACCOUNT_NOTIFICATIONS) { } elseif ($section === ACCOUNT_NOTIFICATIONS) {
@ -528,6 +544,11 @@ class AccountController extends BaseController
$account = Auth::user()->account; $account = Auth::user()->account;
$account->client_view_css = $sanitized_css; $account->client_view_css = $sanitized_css;
$account->enable_client_portal = !!Input::get('enable_client_portal');
$account->enable_portal_password = !!Input::get('enable_portal_password');
$account->send_portal_password = !!Input::get('send_portal_password');
$account->save(); $account->save();
Session::flash('message', trans('texts.updated_settings')); Session::flash('message', trans('texts.updated_settings'));
@ -668,6 +689,8 @@ class AccountController extends BaseController
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false; $account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
$account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1')); $account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1'));
$account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2')); $account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2'));
$account->custom_invoice_item_label1 = trim(Input::get('custom_invoice_item_label1'));
$account->custom_invoice_item_label2 = trim(Input::get('custom_invoice_item_label2'));
$account->invoice_number_counter = Input::get('invoice_number_counter'); $account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix'); $account->quote_number_prefix = Input::get('quote_number_prefix');
@ -676,6 +699,7 @@ class AccountController extends BaseController
$account->invoice_footer = Input::get('invoice_footer'); $account->invoice_footer = Input::get('invoice_footer');
$account->quote_terms = Input::get('quote_terms'); $account->quote_terms = Input::get('quote_terms');
$account->auto_convert_quote = Input::get('auto_convert_quote'); $account->auto_convert_quote = Input::get('auto_convert_quote');
$account->recurring_invoice_number_prefix = Input::get('recurring_invoice_number_prefix');
if (Input::has('recurring_hour')) { if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour'); $account->recurring_hour = Input::get('recurring_hour');
@ -736,7 +760,7 @@ class AccountController extends BaseController
} }
$labels = []; $labels = [];
foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms'] as $field) { foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due'] as $field) {
$labels[$field] = Input::get("labels_{$field}"); $labels[$field] = Input::get("labels_{$field}");
} }
$account->invoice_labels = json_encode($labels); $account->invoice_labels = json_encode($labels);
@ -811,7 +835,7 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS); return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS);
} }
private function saveUserDetails() public function saveUserDetails()
{ {
$user = Auth::user(); $user = Auth::user();
$rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id'];

View File

@ -24,7 +24,7 @@ class AccountGatewayController extends BaseController
public function __construct(AccountGatewayService $accountGatewayService) public function __construct(AccountGatewayService $accountGatewayService)
{ {
parent::__construct(); //parent::__construct();
$this->accountGatewayService = $accountGatewayService; $this->accountGatewayService = $accountGatewayService;
} }

View File

@ -15,7 +15,7 @@ class ActivityController extends BaseController
public function __construct(ActivityService $activityService) public function __construct(ActivityService $activityService)
{ {
parent::__construct(); //parent::__construct();
$this->activityService = $activityService; $this->activityService = $activityService;
} }

View File

@ -30,7 +30,7 @@ class AppController extends BaseController
public function __construct(AccountRepository $accountRepo, Mailer $mailer, EmailService $emailService) public function __construct(AccountRepository $accountRepo, Mailer $mailer, EmailService $emailService)
{ {
parent::__construct(); //parent::__construct();
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
$this->mailer = $mailer; $this->mailer = $mailer;
@ -78,7 +78,7 @@ class AppController extends BaseController
} elseif (!$valid) { } elseif (!$valid) {
return Redirect::to('/setup')->withInput(); return Redirect::to('/setup')->withInput();
} }
if (Utils::isDatabaseSetup() && Account::count() > 0) { if (Utils::isDatabaseSetup() && Account::count() > 0) {
return Redirect::to('/'); return Redirect::to('/');
} }
@ -114,7 +114,7 @@ class AppController extends BaseController
} }
Cache::flush(); Cache::flush();
Artisan::call('optimize', array('--force' => true)); Artisan::call('optimize', array('--force' => true));
$firstName = trim(Input::get('first_name')); $firstName = trim(Input::get('first_name'));
$lastName = trim(Input::get('last_name')); $lastName = trim(Input::get('last_name'));
$email = trim(strtolower(Input::get('email'))); $email = trim(strtolower(Input::get('email')));
@ -152,7 +152,7 @@ class AppController extends BaseController
$_ENV['DB_DATABASE'] = $db['type']['database']; $_ENV['DB_DATABASE'] = $db['type']['database'];
$_ENV['DB_USERNAME'] = $db['type']['username']; $_ENV['DB_USERNAME'] = $db['type']['username'];
$_ENV['DB_PASSWORD'] = $db['type']['password']; $_ENV['DB_PASSWORD'] = $db['type']['password'];
if ($mail) { if ($mail) {
$_ENV['MAIL_DRIVER'] = $mail['driver']; $_ENV['MAIL_DRIVER'] = $mail['driver'];
$_ENV['MAIL_PORT'] = $mail['port']; $_ENV['MAIL_PORT'] = $mail['port'];
@ -184,7 +184,7 @@ class AppController extends BaseController
foreach ($database['connections'][$dbType] as $key => $val) { foreach ($database['connections'][$dbType] as $key => $val) {
Config::set("database.connections.{$dbType}.{$key}", $val); Config::set("database.connections.{$dbType}.{$key}", $val);
} }
try { try {
DB::reconnect(); DB::reconnect();
$valid = DB::connection()->getDatabaseName() ? true : false; $valid = DB::connection()->getDatabaseName() ? true : false;
@ -206,7 +206,7 @@ class AppController extends BaseController
Config::set('mail.from.address', $email); Config::set('mail.from.address', $email);
Config::set('mail.from.name', $fromName); Config::set('mail.from.name', $fromName);
$data = [ $data = [
'text' => 'Test email', 'text' => 'Test email',
]; ];
@ -231,7 +231,8 @@ class AppController extends BaseController
} }
Artisan::call('optimize', array('--force' => true)); Artisan::call('optimize', array('--force' => true));
} catch (Exception $e) { } catch (Exception $e) {
Response::make($e->getMessage(), 500); Utils::logError($e);
return Response::make($e->getMessage(), 500);
} }
} }
@ -243,6 +244,7 @@ class AppController extends BaseController
if (!Utils::isNinjaProd()) { if (!Utils::isNinjaProd()) {
try { try {
set_time_limit(60 * 5); set_time_limit(60 * 5);
Artisan::call('optimize', array('--force' => true));
Cache::flush(); Cache::flush();
Session::flush(); Session::flush();
Artisan::call('migrate', array('--force' => true)); Artisan::call('migrate', array('--force' => true));
@ -250,15 +252,19 @@ class AppController extends BaseController
'PaymentLibraries', 'PaymentLibraries',
'Fonts', 'Fonts',
'Banks', 'Banks',
'InvoiceStatus' 'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) { ] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder")); Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
} }
Artisan::call('optimize', array('--force' => true));
Event::fire(new UserSettingsChanged()); Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates')); Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) { } catch (Exception $e) {
Response::make($e->getMessage(), 500); Utils::logError($e);
return Response::make($e->getMessage(), 500);
} }
} }
@ -276,7 +282,7 @@ class AppController extends BaseController
{ {
$messageId = Input::get('MessageID'); $messageId = Input::get('MessageID');
return $this->emailService->markOpened($messageId) ? RESULT_SUCCESS : RESULT_FAILURE; return $this->emailService->markOpened($messageId) ? RESULT_SUCCESS : RESULT_FAILURE;
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
@ -288,7 +294,7 @@ class AppController extends BaseController
} }
if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) { if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) {
$payments = DB::table('accounts') $data = DB::table('accounts')
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id') ->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id') ->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY) ->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
@ -300,15 +306,9 @@ class AppController extends BaseController
'payments.amount' 'payments.amount'
]); ]);
} else { } else {
$payments = DB::table('accounts') $data = DB::table('users')->count();
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
->where('payments.is_deleted', '=', false)
->groupBy('clients.id')
->count();
} }
return json_encode($payments); return json_encode($data);
} }
} }

View File

@ -10,8 +10,6 @@ use App\Events\UserLoggedIn;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
use App\Services\AuthService; use App\Services\AuthService;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Registrar;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller { class AuthController extends Controller {
@ -29,7 +27,6 @@ class AuthController extends Controller {
use AuthenticatesAndRegistersUsers; use AuthenticatesAndRegistersUsers;
protected $loginPath = '/login';
protected $redirectTo = '/dashboard'; protected $redirectTo = '/dashboard';
protected $authService; protected $authService;
protected $accountRepo; protected $accountRepo;
@ -41,16 +38,38 @@ class AuthController extends Controller {
* @param \Illuminate\Contracts\Auth\Registrar $registrar * @param \Illuminate\Contracts\Auth\Registrar $registrar
* @return void * @return void
*/ */
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo, AuthService $authService) public function __construct(AccountRepository $repo, AuthService $authService)
{ {
$this->auth = $auth;
$this->registrar = $registrar;
$this->accountRepo = $repo; $this->accountRepo = $repo;
$this->authService = $authService; $this->authService = $authService;
//$this->middleware('guest', ['except' => 'getLogout']); //$this->middleware('guest', ['except' => 'getLogout']);
} }
public function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
public function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
public function authLogin($provider, Request $request) public function authLogin($provider, Request $request)
{ {
return $this->authService->execute($provider, $request->has('code')); return $this->authService->execute($provider, $request->has('code'));

View File

@ -1,8 +1,6 @@
<?php namespace App\Http\Controllers\Auth; <?php namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller { class PasswordController extends Controller {
@ -29,11 +27,8 @@ class PasswordController extends Controller {
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwords * @param \Illuminate\Contracts\Auth\PasswordBroker $passwords
* @return void * @return void
*/ */
public function __construct(Guard $auth, PasswordBroker $passwords) public function __construct()
{ {
$this->auth = $auth;
$this->passwords = $passwords;
$this->middleware('guest'); $this->middleware('guest');
} }

View File

@ -27,7 +27,7 @@ class BankAccountController extends BaseController
public function __construct(BankAccountService $bankAccountService, BankAccountRepository $bankAccountRepo) public function __construct(BankAccountService $bankAccountService, BankAccountRepository $bankAccountRepo)
{ {
parent::__construct(); //parent::__construct();
$this->bankAccountService = $bankAccountService; $this->bankAccountService = $bankAccountService;
$this->bankAccountRepo = $bankAccountRepo; $this->bankAccountRepo = $bankAccountRepo;
@ -91,7 +91,7 @@ class BankAccountController extends BaseController
$publicId = Input::get('public_id'); $publicId = Input::get('public_id');
$username = trim(Input::get('bank_username')); $username = trim(Input::get('bank_username'));
$password = trim(Input::get('bank_password')); $password = trim(Input::get('bank_password'));
if ($publicId) { if ($publicId) {
$bankAccount = BankAccount::scope($publicId)->firstOrFail(); $bankAccount = BankAccount::scope($publicId)->firstOrFail();
if ($username != $bankAccount->username) { if ($username != $bankAccount->username) {

View File

@ -1,10 +1,14 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesCommands; use App\Http\Middleware\PermissionsRequired;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Auth;
class BaseController extends Controller class BaseController extends Controller
{ {
use DispatchesCommands; use DispatchesJobs;
protected $model = 'App\Models\EntityModel';
/** /**
* Setup the layout used by the controller. * Setup the layout used by the controller.
@ -17,9 +21,40 @@ class BaseController extends Controller
$this->layout = View::make($this->layout); $this->layout = View::make($this->layout);
} }
} }
public function __construct() protected function checkViewPermission($object, &$response = null){
{ if(!$object->canView()){
$this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put'))); $response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkEditPermission($object, &$response = null){
if(!$object->canEdit()){
$response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkCreatePermission(&$response = null){
if(!call_user_func(array($this->model, 'canCreate'))){
$response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkUpdatePermission($input, &$response = null){
$creating = empty($input['public_id']) || $input['public_id'] == '-1';
if($creating){
return $this->checkCreatePermission($response);
}
else{
$object = call_user_func(array($this->model, 'scope'), $input['public_id'])->firstOrFail();
return $this->checkEditPermission($object, $response);
}
} }
} }

View File

@ -174,7 +174,7 @@ class ClientApiController extends BaseAPIController
if(!$client) if(!$client)
return $this->errorResponse(['message'=>'Client not found.'],400); return $this->errorResponse(['message'=>'Client not found.'],400);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer')); $transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT); $data = $this->createItem($client, $transformer, ENTITY_CLIENT);
@ -203,7 +203,7 @@ class ClientApiController extends BaseAPIController
* ) * )
* ) * )
*/ */
public function destroy($publicId) public function destroy($publicId)
{ {

View File

@ -0,0 +1,79 @@
<?php namespace App\Http\Controllers\ClientAuth;
use Auth;
use Event;
use Utils;
use Session;
use Illuminate\Http\Request;
use App\Models\User;
use App\Events\UserLoggedIn;
use App\Http\Controllers\Controller;
use App\Ninja\Repositories\AccountRepository;
use App\Services\AuthService;
use App\Models\Invitation;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class AuthController extends Controller {
protected $guard = 'client';
protected $redirectTo = '/client/dashboard';
use AuthenticatesUsers;
public function showLoginForm()
{
$data = array(
);
$invitation_key = session('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}
}
return view('clientauth.login')->with($data);
}
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function getCredentials(Request $request)
{
$credentials = $request->only('password');
$credentials['id'] = null;
$invitation_key = session('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$credentials['id'] = $invitation->contact_id;
}
}
return $credentials;
}
/**
* Validate the user login request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function validateLogin(Request $request)
{
$this->validate($request, [
'password' => 'required',
]);
}
}

View File

@ -0,0 +1,197 @@
<?php namespace App\Http\Controllers\ClientAuth;
use Config;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Password;
use App\Models\Invitation;
class PasswordController extends Controller {
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
protected $redirectTo = '/client/dashboard';
/**
* Create a new password controller instance.
*
* @param \Illuminate\Contracts\Auth\Guard $auth
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwords
* @return void
*/
public function __construct()
{
$this->middleware('guest');
Config::set("auth.defaults.passwords","client");
}
public function showLinkRequestForm()
{
$data = array();
$invitation_key = session('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}
}
return view('clientauth.password')->with($data);
}
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function sendResetLinkEmail(Request $request)
{
$broker = $this->getBroker();
$contact_id = null;
$invitation_key = session('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$contact_id = $invitation->contact_id;
}
}
$response = Password::broker($broker)->sendResetLink(array('id'=>$contact_id), function (Message $message) {
$message->subject($this->getEmailSubject());
});
switch ($response) {
case Password::RESET_LINK_SENT:
return $this->getSendResetLinkEmailSuccessResponse($response);
case Password::INVALID_USER:
default:
return $this->getSendResetLinkEmailFailureResponse($response);
}
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @param string|null $invitation_key
* @param string|null $token
* @return \Illuminate\Http\Response
*/
public function showResetForm(Request $request, $invitation_key = null, $token = null)
{
if (is_null($token)) {
return $this->getEmail();
}
$data = compact('token', 'invitation_key');
$invitation_key = session('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}
}
return view('clientauth.reset')->with($data);
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @param string|null $invitation_key
* @param string|null $token
* @return \Illuminate\Http\Response
*/
public function getReset(Request $request, $invitation_key = null, $token = null)
{
return $this->showResetForm($request, $invitation_key, $token);
}
/**
* Reset the given user's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function reset(Request $request)
{
$this->validate($request, $this->getResetValidationRules());
$credentials = $request->only(
'password', 'password_confirmation', 'token'
);
$credentials['id'] = null;
$invitation_key = $request->input('invitation_key');
if($invitation_key){
$invitation = Invitation::where('invitation_key', '=', $invitation_key)->first();
if ($invitation && !$invitation->is_deleted) {
$credentials['id'] = $invitation->contact_id;
}
}
$broker = $this->getBroker();
$response = Password::broker($broker)->reset($credentials, function ($user, $password) {
$this->resetPassword($user, $password);
});
switch ($response) {
case Password::PASSWORD_RESET:
return $this->getResetSuccessResponse($response);
default:
return $this->getResetFailureResponse($request, $response);
}
}
/**
* Get the password reset validation rules.
*
* @return array
*/
protected function getResetValidationRules()
{
return [
'token' => 'required',
'password' => 'required|confirmed|min:6',
];
}
}

View File

@ -20,6 +20,9 @@ use App\Models\Size;
use App\Models\PaymentTerm; use App\Models\PaymentTerm;
use App\Models\Industry; use App\Models\Industry;
use App\Models\Currency; use App\Models\Currency;
use App\Models\Payment;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Country; use App\Models\Country;
use App\Models\Task; use App\Models\Task;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
@ -32,10 +35,11 @@ class ClientController extends BaseController
{ {
protected $clientService; protected $clientService;
protected $clientRepo; protected $clientRepo;
protected $model = 'App\Models\Client';
public function __construct(ClientRepository $clientRepo, ClientService $clientService) public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{ {
parent::__construct(); //parent::__construct();
$this->clientRepo = $clientRepo; $this->clientRepo = $clientRepo;
$this->clientService = $clientService; $this->clientService = $clientService;
@ -77,10 +81,16 @@ class ClientController extends BaseController
*/ */
public function store(CreateClientRequest $request) public function store(CreateClientRequest $request)
{ {
$client = $this->clientService->save($request->input()); $data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
Session::flash('message', trans('texts.created_client')); Session::flash('message', trans('texts.created_client'));
return redirect()->to($client->getRoute()); return redirect()->to($client->getRoute());
} }
@ -93,23 +103,37 @@ class ClientController extends BaseController
public function show($publicId) public function show($publicId)
{ {
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail(); $client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail();
if(!$this->checkViewPermission($client, $response)){
return $response;
}
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [ $actionLinks = [];
['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id] if(Task::canCreate()){
]; $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id];
}
if (Utils::isPro()) { if (Utils::isPro() && Invoice::canCreate()) {
array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]); $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id];
}
if(!empty($actionLinks)){
$actionLinks[] = \DropdownButton::DIVIDER;
}
if(Payment::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id];
}
if(Credit::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id];
}
if(Expense::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id];
} }
array_push($actionLinks,
\DropdownButton::DIVIDER,
['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id],
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id],
['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]
);
$data = array( $data = array(
'actionLinks' => $actionLinks, 'actionLinks' => $actionLinks,
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
@ -132,7 +156,11 @@ class ClientController extends BaseController
*/ */
public function create() public function create()
{ {
if (Client::scope()->count() > Auth::user()->getMaxNumClients()) { if(!$this->checkCreatePermission($response)){
return $response;
}
if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]); return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]);
} }
@ -157,6 +185,11 @@ class ClientController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = Client::scope($publicId)->with('contacts')->firstOrFail();
if(!$this->checkEditPermission($client, $response)){
return $response;
}
$data = [ $data = [
'client' => $client, 'client' => $client,
'method' => 'PUT', 'method' => 'PUT',
@ -199,10 +232,16 @@ class ClientController extends BaseController
*/ */
public function update(UpdateClientRequest $request) public function update(UpdateClientRequest $request)
{ {
$client = $this->clientService->save($request->input()); $data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
Session::flash('message', trans('texts.updated_client')); Session::flash('message', trans('texts.updated_client'));
return redirect()->to($client->getRoute()); return redirect()->to($client->getRoute());
} }

View File

@ -1,11 +1,11 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesCommands; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller extends BaseController { abstract class Controller extends BaseController {
use DispatchesCommands, ValidatesRequests; use DispatchesJobs, ValidatesRequests;
} }

View File

@ -17,10 +17,11 @@ class CreditController extends BaseController
{ {
protected $creditRepo; protected $creditRepo;
protected $creditService; protected $creditService;
protected $model = 'App\Models\Credit';
public function __construct(CreditRepository $creditRepo, CreditService $creditService) public function __construct(CreditRepository $creditRepo, CreditService $creditService)
{ {
parent::__construct(); // parent::__construct();
$this->creditRepo = $creditRepo; $this->creditRepo = $creditRepo;
$this->creditService = $creditService; $this->creditService = $creditService;
@ -56,6 +57,10 @@ class CreditController extends BaseController
public function create($clientPublicId = 0) public function create($clientPublicId = 0)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$data = array( $data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, 'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId, //'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
@ -72,6 +77,11 @@ class CreditController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$credit = Credit::scope($publicId)->firstOrFail(); $credit = Credit::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($credit, $response)){
return $response;
}
$credit->credit_date = Utils::fromSqlDate($credit->credit_date); $credit->credit_date = Utils::fromSqlDate($credit->credit_date);
$data = array( $data = array(
@ -88,9 +98,9 @@ class CreditController extends BaseController
public function store(CreateCreditRequest $request) public function store(CreateCreditRequest $request)
{ {
$credit = $this->creditRepo->save($request->input()); $credit = $this->creditRepo->save($request->input());
Session::flash('message', trans('texts.created_credit')); Session::flash('message', trans('texts.created_credit'));
return redirect()->to($credit->client->getRoute()); return redirect()->to($credit->client->getRoute());
} }

View File

@ -0,0 +1,179 @@
<?php namespace App\Http\Controllers;
use Auth;
use DB;
use View;
use App\Models\Activity;
class DashboardApiController extends BaseAPIController
{
public function index()
{
$view_all = !Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients
$select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients,
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
COUNT(DISTINCT clients.id) active_clients');
$metrics = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false);
if(!$view_all){
$metrics = $metrics->where(function($query) use($user_id){
$query->where('invoices.user_id', '=', $user_id);
$query->orwhere(function($query) use($user_id){
$query->where('invoices.user_id', '=', null);
$query->where('clients.user_id', '=', $user_id);
});
});
}
$metrics = $metrics->groupBy('accounts.id')
->first();
$select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id');
$paidToDate = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false);
if(!$view_all){
$paidToDate = $paidToDate->where('clients.user_id', '=', $user_id);
}
$paidToDate = $paidToDate->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$select = DB::raw('AVG(invoices.amount) as invoice_avg, clients.currency_id as currency_id');
$averageInvoice = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false);
if(!$view_all){
$averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id);
}
$averageInvoice = $averageInvoice->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$select = DB::raw('SUM(clients.balance) as value, clients.currency_id as currency_id');
$balances = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$pastDue = DB::table('invoices')
->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '<', date('Y-m-d'));
if(!$view_all){
$pastDue = $pastDue->where('invoices.user_id', '=', $user_id);
}
$pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->orderBy('invoices.due_date', 'asc')
->take(50)
->get();
$upcoming = DB::table('invoices')
->leftJoin('clients', 'clients.id', '=', 'invoices.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '>=', date('Y-m-d'))
->orderBy('invoices.due_date', 'asc');
if(!$view_all){
$upcoming = $upcoming->where('invoices.user_id', '=', $user_id);
}
$upcoming = $upcoming->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->get();
$payments = DB::table('payments')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->where('payments.account_id', '=', Auth::user()->account_id)
->where('payments.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true);
if(!$view_all){
$payments = $payments->where('payments.user_id', '=', $user_id);
}
$payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id'])
->orderBy('payments.payment_date', 'desc')
->take(50)
->get();
$hasQuotes = false;
foreach ([$upcoming, $pastDue] as $data) {
foreach ($data as $invoice) {
if ($invoice->is_quote) {
$hasQuotes = true;
}
}
}
$data = [
'id' => 1,
'paidToDate' => $paidToDate[0]->value,
'paidToDateCurrency' => $paidToDate[0]->currency_id,
'balances' => $balances[0]->value,
'balancesCurrency' => $balances[0]->currency_id,
'averageInvoice' => $averageInvoice[0]->invoice_avg,
'averageInvoiceCurrency' => $averageInvoice[0]->currency_id,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
];
return $this->response($data);
}
}

View File

@ -11,7 +11,9 @@ class DashboardController extends BaseController
{ {
public function index() public function index()
{ {
$view_all = !Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients // total_income, billed_clients, invoice_sent and active_clients
$select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients,
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
@ -24,8 +26,19 @@ class DashboardController extends BaseController
->where('clients.is_deleted', '=', false) ->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false) ->where('invoices.is_quote', '=', false);
->groupBy('accounts.id')
if(!$view_all){
$metrics = $metrics->where(function($query) use($user_id){
$query->where('invoices.user_id', '=', $user_id);
$query->orwhere(function($query) use($user_id){
$query->where('invoices.user_id', '=', null);
$query->where('clients.user_id', '=', $user_id);
});
});
}
$metrics = $metrics->groupBy('accounts.id')
->first(); ->first();
$select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id'); $select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id');
@ -33,8 +46,13 @@ class DashboardController extends BaseController
->select($select) ->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id) ->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false) ->where('clients.is_deleted', '=', false);
->groupBy('accounts.id')
if(!$view_all){
$paidToDate = $paidToDate->where('clients.user_id', '=', $user_id);
}
$paidToDate = $paidToDate->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get(); ->get();
@ -47,8 +65,13 @@ class DashboardController extends BaseController
->where('clients.is_deleted', '=', false) ->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false) ->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false);
->groupBy('accounts.id')
if(!$view_all){
$averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id);
}
$averageInvoice = $averageInvoice->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get(); ->get();
@ -63,9 +86,14 @@ class DashboardController extends BaseController
->get(); ->get();
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
->where('activities.activity_type_id', '>', 0);
if(!$view_all){
$activities = $activities->where('activities.user_id', '=', $user_id);
}
$activities = $activities->orderBy('activities.created_at', 'desc')
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account')
->where('activity_type_id', '>', 0)
->orderBy('created_at', 'desc')
->take(50) ->take(50)
->get(); ->get();
@ -81,8 +109,13 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '<', date('Y-m-d')) ->where('invoices.due_date', '<', date('Y-m-d'));
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote'])
if(!$view_all){
$pastDue = $pastDue->where('invoices.user_id', '=', $user_id);
}
$pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->orderBy('invoices.due_date', 'asc') ->orderBy('invoices.due_date', 'asc')
->take(50) ->take(50)
->get(); ->get();
@ -100,9 +133,14 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '>=', date('Y-m-d')) ->where('invoices.due_date', '>=', date('Y-m-d'))
->orderBy('invoices.due_date', 'asc') ->orderBy('invoices.due_date', 'asc');
->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) if(!$view_all){
$upcoming = $upcoming->where('invoices.user_id', '=', $user_id);
}
$upcoming = $upcoming->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote'])
->get(); ->get();
$payments = DB::table('payments') $payments = DB::table('payments')
@ -114,8 +152,13 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('clients.is_deleted', '=', false) ->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true);
->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
if(!$view_all){
$payments = $payments->where('payments.user_id', '=', $user_id);
}
$payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id'])
->orderBy('payments.payment_date', 'desc') ->orderBy('payments.payment_date', 'desc')
->take(50) ->take(50)
->get(); ->get();

View File

@ -25,10 +25,11 @@ class ExpenseController extends BaseController
// Expenses // Expenses
protected $expenseRepo; protected $expenseRepo;
protected $expenseService; protected $expenseService;
protected $model = 'App\Models\Expense';
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{ {
parent::__construct(); // parent::__construct();
$this->expenseRepo = $expenseRepo; $this->expenseRepo = $expenseRepo;
$this->expenseService = $expenseService; $this->expenseService = $expenseService;
@ -44,7 +45,7 @@ class ExpenseController extends BaseController
return View::make('list', array( return View::make('list', array(
'entityType' => ENTITY_EXPENSE, 'entityType' => ENTITY_EXPENSE,
'title' => trans('texts.expenses'), 'title' => trans('texts.expenses'),
'sortCol' => '1', 'sortCol' => '3',
'columns' => Utils::trans([ 'columns' => Utils::trans([
'checkbox', 'checkbox',
'vendor', 'vendor',
@ -70,6 +71,10 @@ class ExpenseController extends BaseController
public function create($vendorPublicId = null, $clientPublicId = null) public function create($vendorPublicId = null, $clientPublicId = null)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
if($vendorPublicId != 0) { if($vendorPublicId != 0) {
$vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail();
} else { } else {
@ -95,6 +100,11 @@ class ExpenseController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$expense = Expense::scope($publicId)->firstOrFail(); $expense = Expense::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($expense, $response)){
return $response;
}
$expense->expense_date = Utils::fromSqlDate($expense->expense_date); $expense->expense_date = Utils::fromSqlDate($expense->expense_date);
$actions = []; $actions = [];

View File

@ -89,6 +89,8 @@ class ExportController extends BaseController
if ($key === 'quotes') { if ($key === 'quotes') {
$key = 'invoices'; $key = 'invoices';
$data['entityType'] = ENTITY_QUOTE; $data['entityType'] = ENTITY_QUOTE;
} elseif ($key === 'recurringInvoices') {
$key = 'recurring_invoices';
} }
$sheet->loadView("export.{$key}", $data); $sheet->loadView("export.{$key}", $data);
}); });
@ -109,8 +111,7 @@ class ExportController extends BaseController
if ($request->input(ENTITY_CLIENT)) { if ($request->input(ENTITY_CLIENT)) {
$data['clients'] = Client::scope() $data['clients'] = Client::scope()
->with('user', 'contacts', 'country') ->with('user', 'contacts', 'country')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
$data['contacts'] = Contact::scope() $data['contacts'] = Contact::scope()
@ -126,33 +127,36 @@ class ExportController extends BaseController
if ($request->input(ENTITY_TASK)) { if ($request->input(ENTITY_TASK)) {
$data['tasks'] = Task::scope() $data['tasks'] = Task::scope()
->with('user', 'client.contacts') ->with('user', 'client.contacts')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
} }
if ($request->input(ENTITY_INVOICE)) { if ($request->input(ENTITY_INVOICE)) {
$data['invoices'] = Invoice::scope() $data['invoices'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status') ->with('user', 'client.contacts', 'invoice_status')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->where('is_quote', '=', false) ->where('is_quote', '=', false)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->get(); ->get();
$data['quotes'] = Invoice::scope() $data['quotes'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status') ->with('user', 'client.contacts', 'invoice_status')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->where('is_quote', '=', true) ->where('is_quote', '=', true)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->get(); ->get();
$data['recurringInvoices'] = Invoice::scope()
->with('user', 'client.contacts', 'invoice_status', 'frequency')
->withArchived()
->where('is_quote', '=', false)
->where('is_recurring', '=', true)
->get();
} }
if ($request->input(ENTITY_PAYMENT)) { if ($request->input(ENTITY_PAYMENT)) {
$data['payments'] = Payment::scope() $data['payments'] = Payment::scope()
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway') ->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway')
->get(); ->get();
} }
@ -161,14 +165,14 @@ class ExportController extends BaseController
if ($request->input(ENTITY_VENDOR)) { if ($request->input(ENTITY_VENDOR)) {
$data['clients'] = Vendor::scope() $data['clients'] = Vendor::scope()
->with('user', 'vendorcontacts', 'country') ->with('user', 'vendorcontacts', 'country')
->withTrashed() ->withArchived()
->where('is_deleted', '=', false)
->get(); ->get();
$data['vendor_contacts'] = VendorContact::scope() $data['vendor_contacts'] = VendorContact::scope()
->with('user', 'vendor.contacts') ->with('user', 'vendor.contacts')
->withTrashed() ->withTrashed()
->get(); ->get();
/* /*
$data['expenses'] = Credit::scope() $data['expenses'] = Credit::scope()
->with('user', 'client.contacts') ->with('user', 'client.contacts')

View File

@ -17,7 +17,7 @@ class HomeController extends BaseController
public function __construct(Mailer $mailer) public function __construct(Mailer $mailer)
{ {
parent::__construct(); //parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
} }

View File

@ -13,7 +13,7 @@ class ImportController extends BaseController
{ {
public function __construct(ImportService $importService) public function __construct(ImportService $importService)
{ {
parent::__construct(); //parent::__construct();
$this->importService = $importService; $this->importService = $importService;
} }

View File

@ -149,7 +149,7 @@ class InvoiceApiController extends BaseAPIController
$client = Client::scope()->whereHas('contacts', function($query) use ($email) { $client = Client::scope()->whereHas('contacts', function($query) use ($email) {
$query->where('email', '=', $email); $query->where('email', '=', $email);
})->first(); })->first();
if (!$client) { if (!$client) {
$validator = Validator::make(['email'=>$email], ['email' => 'email']); $validator = Validator::make(['email'=>$email], ['email' => 'email']);
if ($validator->fails()) { if ($validator->fails()) {
@ -220,7 +220,7 @@ class InvoiceApiController extends BaseAPIController
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$account->loadLocalizationSettings($client); $account->loadLocalizationSettings($client);
// set defaults for optional fields // set defaults for optional fields
$fields = [ $fields = [
'discount' => 0, 'discount' => 0,

View File

@ -34,10 +34,11 @@ class InvoiceController extends BaseController
protected $clientRepo; protected $clientRepo;
protected $invoiceService; protected $invoiceService;
protected $recurringInvoiceService; protected $recurringInvoiceService;
protected $model = 'App\Models\Invoice';
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService)
{ {
parent::__construct(); // parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
@ -51,6 +52,7 @@ class InvoiceController extends BaseController
$data = [ $data = [
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE, 'entityType' => ENTITY_INVOICE,
'sortCol' => '3',
'columns' => Utils::trans([ 'columns' => Utils::trans([
'checkbox', 'checkbox',
'invoice_number', 'invoice_number',
@ -87,9 +89,14 @@ class InvoiceController extends BaseController
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$invoice = Invoice::scope($publicId) $invoice = Invoice::scope($publicId)
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments')
->withTrashed() ->withTrashed()
->firstOrFail(); ->firstOrFail();
if(!$this->checkEditPermission($invoice, $response)){
return $response;
}
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$contactIds = DB::table('invitations') $contactIds = DB::table('invitations')
@ -99,6 +106,8 @@ class InvoiceController extends BaseController
->where('invitations.deleted_at', '=', null) ->where('invitations.deleted_at', '=', null)
->select('contacts.public_id')->lists('public_id'); ->select('contacts.public_id')->lists('public_id');
$clients = Client::scope()->withTrashed()->with('contacts', 'country');
if ($clone) { if ($clone) {
$invoice->id = $invoice->public_id = null; $invoice->id = $invoice->public_id = null;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice); $invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
@ -111,6 +120,7 @@ class InvoiceController extends BaseController
Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT'; $method = 'PUT';
$url = "{$entityType}s/{$publicId}"; $url = "{$entityType}s/{$publicId}";
$clients->whereId($invoice->client_id);
} }
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
@ -145,6 +155,14 @@ class InvoiceController extends BaseController
if (!$invoice->is_recurring && $invoice->balance > 0) { if (!$invoice->is_recurring && $invoice->balance > 0) {
$actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')];
} }
foreach ($invoice->payments as $payment) {
$label = trans("texts.view_payment");
if (count($invoice->payments) > 1) {
$label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client);
}
$actions[] = ['url' => $payment->present()->url, 'label' => $label];
}
} }
if (count($actions) > 3) { if (count($actions) > 3) {
@ -156,8 +174,12 @@ class InvoiceController extends BaseController
$lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null; $lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null;
if(!Auth::user()->hasPermission('view_all')){
$clients = $clients->where('clients.user_id', '=', Auth::user()->id);
}
$data = array( $data = array(
'clients' => Client::scope()->withTrashed()->with('contacts', 'country')->whereId($invoice->client_id)->get(), 'clients' => $clients->get(),
'entityType' => $entityType, 'entityType' => $entityType,
'showBreadcrumbs' => $clone, 'showBreadcrumbs' => $clone,
'invoice' => $invoice, 'invoice' => $invoice,
@ -203,7 +225,11 @@ class InvoiceController extends BaseController
public function create($clientPublicId = 0, $isRecurring = false) public function create($clientPublicId = 0, $isRecurring = false)
{ {
$account = Auth::user()->account; if(!$this->checkCreatePermission($response)){
return $response;
}
$account = Auth::user()->account;
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
$clientId = null; $clientId = null;
@ -214,8 +240,13 @@ class InvoiceController extends BaseController
$invoice = $account->createInvoice($entityType, $clientId); $invoice = $account->createInvoice($entityType, $clientId);
$invoice->public_id = 0; $invoice->public_id = 0;
$clients = Client::scope()->with('contacts', 'country')->orderBy('name');
if(!Auth::user()->hasPermission('view_all')){
$clients = $clients->where('clients.user_id', '=', Auth::user()->id);
}
$data = [ $data = [
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'clients' => $clients->get(),
'entityType' => $invoice->getEntityType(), 'entityType' => $invoice->getEntityType(),
'invoice' => $invoice, 'invoice' => $invoice,
'method' => 'POST', 'method' => 'POST',
@ -332,10 +363,16 @@ class InvoiceController extends BaseController
*/ */
public function store(SaveInvoiceWithClientRequest $request) public function store(SaveInvoiceWithClientRequest $request)
{ {
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$action = Input::get('action'); $action = Input::get('action');
$entityType = Input::get('entityType'); $entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($request->input()); $invoice = $this->invoiceService->save($data, true);
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$message = trans("texts.created_{$entityType}"); $message = trans("texts.created_{$entityType}");
@ -366,10 +403,16 @@ class InvoiceController extends BaseController
*/ */
public function update(SaveInvoiceWithClientRequest $request) public function update(SaveInvoiceWithClientRequest $request)
{ {
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$action = Input::get('action'); $action = Input::get('action');
$entityType = Input::get('entityType'); $entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($request->input()); $invoice = $this->invoiceService->save($data, true);
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$message = trans("texts.updated_{$entityType}"); $message = trans("texts.updated_{$entityType}");
Session::flash('message', $message); Session::flash('message', $message);

View File

@ -30,9 +30,11 @@ use App\Http\Requests\UpdatePaymentRequest;
class PaymentController extends BaseController class PaymentController extends BaseController
{ {
protected $model = 'App\Models\Payment';
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService)
{ {
parent::__construct(); // parent::__construct();
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
@ -46,6 +48,7 @@ class PaymentController extends BaseController
return View::make('list', array( return View::make('list', array(
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'), 'title' => trans('texts.payments'),
'sortCol' => '6',
'columns' => Utils::trans([ 'columns' => Utils::trans([
'checkbox', 'checkbox',
'invoice', 'invoice',
@ -66,6 +69,10 @@ class PaymentController extends BaseController
public function create($clientPublicId = 0, $invoicePublicId = 0) public function create($clientPublicId = 0, $invoicePublicId = 0)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$invoices = Invoice::scope() $invoices = Invoice::scope()
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->where('is_quote', '=', false) ->where('is_quote', '=', false)
@ -92,6 +99,11 @@ class PaymentController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$payment = Payment::scope($publicId)->firstOrFail(); $payment = Payment::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($payment, $response)){
return $response;
}
$payment->payment_date = Utils::fromSqlDate($payment->payment_date); $payment->payment_date = Utils::fromSqlDate($payment->payment_date);
$data = array( $data = array(
@ -573,6 +585,11 @@ class PaymentController extends BaseController
public function store(CreatePaymentRequest $request) public function store(CreatePaymentRequest $request)
{ {
$input = $request->input(); $input = $request->input();
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['invoice_id'] = Invoice::getPrivateId($input['invoice']);
$input['client_id'] = Client::getPrivateId($input['client']); $input['client_id'] = Client::getPrivateId($input['client']);
$payment = $this->paymentRepo->save($input); $payment = $this->paymentRepo->save($input);
@ -590,6 +607,11 @@ class PaymentController extends BaseController
public function update(UpdatePaymentRequest $request) public function update(UpdatePaymentRequest $request)
{ {
$input = $request->input(); $input = $request->input();
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$payment = $this->paymentRepo->save($input); $payment = $this->paymentRepo->save($input);
Session::flash('message', trans('texts.updated_payment')); Session::flash('message', trans('texts.updated_payment'));
@ -620,6 +642,6 @@ class PaymentController extends BaseController
$message .= $error ?: trans('texts.payment_error'); $message .= $error ?: trans('texts.payment_error');
Session::flash('error', $message); Session::flash('error', $message);
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message)); Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
} }
} }

View File

@ -22,8 +22,8 @@ class PaymentTermController extends BaseController
public function __construct(PaymentTermService $paymentTermService) public function __construct(PaymentTermService $paymentTermService)
{ {
parent::__construct(); //parent::__construct();
$this->paymentTermService = $paymentTermService; $this->paymentTermService = $paymentTermService;
} }
@ -99,5 +99,5 @@ class PaymentTermController extends BaseController
return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS); return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS);
} }
} }

View File

@ -21,7 +21,7 @@ class ProductController extends BaseController
public function __construct(ProductService $productService) public function __construct(ProductService $productService)
{ {
parent::__construct(); //parent::__construct();
$this->productService = $productService; $this->productService = $productService;
} }

View File

@ -34,10 +34,7 @@ class PublicClientController extends BaseController
public function view($invitationKey) public function view($invitationKey)
{ {
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [ return $this->returnError();
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
} }
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
@ -53,7 +50,8 @@ class PublicClientController extends BaseController
]); ]);
} }
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey)
&& (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
if ($invoice->is_quote) { if ($invoice->is_quote) {
event(new QuoteInvitationWasViewed($invoice, $invitation)); event(new QuoteInvitationWasViewed($invoice, $invitation));
} else { } else {
@ -104,7 +102,9 @@ class PublicClientController extends BaseController
// Checkout.com requires first getting a payment token // Checkout.com requires first getting a payment token
$checkoutComToken = false; $checkoutComToken = false;
$checkoutComKey = false; $checkoutComKey = false;
$checkoutComDebug = false;
if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) { if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) {
$checkoutComDebug = $accountGateway->getConfigField('testMode');
if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) { if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) {
$checkoutComKey = $accountGateway->getConfigField('publicApiKey'); $checkoutComKey = $accountGateway->getConfigField('publicApiKey');
$invitation->transaction_reference = $checkoutComToken; $invitation->transaction_reference = $checkoutComToken;
@ -118,6 +118,7 @@ class PublicClientController extends BaseController
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(), 'hideHeader' => $account->isNinjaAccount(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(), 'invoice' => $invoice->hidePrivateFields(),
@ -128,6 +129,7 @@ class PublicClientController extends BaseController
'paymentURL' => $paymentURL, 'paymentURL' => $paymentURL,
'checkoutComToken' => $checkoutComToken, 'checkoutComToken' => $checkoutComToken,
'checkoutComKey' => $checkoutComKey, 'checkoutComKey' => $checkoutComKey,
'checkoutComDebug' => $checkoutComDebug,
'phantomjs' => Input::has('phantomjs'), 'phantomjs' => Input::has('phantomjs'),
); );
@ -188,11 +190,16 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$client = $invoice->client; $client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$data = [ $data = [
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,
@ -244,6 +251,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
@ -275,6 +283,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
@ -293,7 +302,7 @@ class PublicClientController extends BaseController
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch')); $payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments) return Datatable::query($payments)
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; }) ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })->toHtml()
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; }) ->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); }) ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
@ -312,6 +321,7 @@ class PublicClientController extends BaseController
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
@ -332,13 +342,11 @@ class PublicClientController extends BaseController
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch')); return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
} }
private function returnError() private function returnError($error = false)
{ {
return response()->view('error', [ return response()->view('error', [
'error' => trans('texts.invoice_not_found'), 'error' => $error ?: trans('texts.invoice_not_found'),
'hideHeader' => true, 'hideHeader' => true,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
]); ]);
} }

View File

@ -52,7 +52,7 @@ class QuoteApiController extends BaseAPIController
} }
$invoices = $invoices->orderBy('created_at', 'desc')->paginate(); $invoices = $invoices->orderBy('created_at', 'desc')->paginate();
$transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer')); $transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate(); $paginator = $paginator->paginate();

View File

@ -33,10 +33,11 @@ class QuoteController extends BaseController
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; protected $clientRepo;
protected $invoiceService; protected $invoiceService;
protected $model = 'App\Models\Invoice';
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{ {
parent::__construct(); // parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
@ -53,6 +54,7 @@ class QuoteController extends BaseController
$data = [ $data = [
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE, 'entityType' => ENTITY_QUOTE,
'sortCol' => '3',
'columns' => Utils::trans([ 'columns' => Utils::trans([
'checkbox', 'checkbox',
'quote_number', 'quote_number',
@ -78,6 +80,10 @@ class QuoteController extends BaseController
public function create($clientPublicId = 0) public function create($clientPublicId = 0)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
if (!Utils::isPro()) { if (!Utils::isPro()) {
return Redirect::to('/invoices/create'); return Redirect::to('/invoices/create');
} }
@ -99,7 +105,7 @@ class QuoteController extends BaseController
'title' => trans('texts.new_quote'), 'title' => trans('texts.new_quote'),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
@ -136,7 +142,7 @@ class QuoteController 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 Redirect::to('invoices/'.$clone->public_id);
} }
$count = $this->invoiceService->bulk($ids, $action); $count = $this->invoiceService->bulk($ids, $action);
if ($count > 0) { if ($count > 0) {

View File

@ -9,7 +9,7 @@ class RecurringInvoiceController extends BaseController
public function __construct(InvoiceRepository $invoiceRepo) public function __construct(InvoiceRepository $invoiceRepo)
{ {
parent::__construct(); //parent::__construct();
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
} }
@ -29,7 +29,7 @@ class RecurringInvoiceController extends BaseController
'action' 'action'
]) ])
]; ];
return response()->view('list', $data); return response()->view('list', $data);
} }

View File

@ -10,6 +10,9 @@ use DatePeriod;
use Session; use Session;
use View; use View;
use App\Models\Account; use App\Models\Account;
use App\Models\Client;
use App\Models\Payment;
use App\Models\Expense;
class ReportController extends BaseController class ReportController extends BaseController
{ {
@ -47,6 +50,7 @@ class ReportController extends BaseController
$groupBy = Input::get('group_by'); $groupBy = Input::get('group_by');
$chartType = Input::get('chart_type'); $chartType = Input::get('chart_type');
$reportType = Input::get('report_type'); $reportType = Input::get('report_type');
$dateField = Input::get('date_field');
$startDate = Utils::toSqlDate(Input::get('start_date'), false); $startDate = Utils::toSqlDate(Input::get('start_date'), false);
$endDate = Utils::toSqlDate(Input::get('end_date'), false); $endDate = Utils::toSqlDate(Input::get('end_date'), false);
$enableReport = Input::get('enable_report') ? true : false; $enableReport = Input::get('enable_report') ? true : false;
@ -55,6 +59,7 @@ class ReportController extends BaseController
$groupBy = 'MONTH'; $groupBy = 'MONTH';
$chartType = 'Bar'; $chartType = 'Bar';
$reportType = ENTITY_INVOICE; $reportType = ENTITY_INVOICE;
$dateField = FILTER_INVOICE_DATE;
$startDate = Utils::today(false)->modify('-3 month'); $startDate = Utils::today(false)->modify('-3 month');
$endDate = Utils::today(false); $endDate = Utils::today(false);
$enableReport = true; $enableReport = true;
@ -76,6 +81,8 @@ class ReportController extends BaseController
ENTITY_CLIENT => trans('texts.client'), ENTITY_CLIENT => trans('texts.client'),
ENTITY_INVOICE => trans('texts.invoice'), ENTITY_INVOICE => trans('texts.invoice'),
ENTITY_PAYMENT => trans('texts.payment'), ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expenses'),
ENTITY_TAX_RATE => trans('texts.taxes'),
]; ];
$params = [ $params = [
@ -94,10 +101,11 @@ class ReportController extends BaseController
if (Auth::user()->account->isPro()) { if (Auth::user()->account->isPro()) {
if ($enableReport) { if ($enableReport) {
$params = array_merge($params, self::generateReport($reportType, $groupBy, $startDate, $endDate)); $isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
if ($action == 'export') { if ($isExport) {
self::export($params['exportData'], $params['reportTotals']); self::export($params['displayData'], $params['columns'], $params['reportTotals']);
} }
} }
if ($enableChart) { if ($enableChart) {
@ -106,11 +114,7 @@ class ReportController extends BaseController
} else { } else {
$params['columns'] = []; $params['columns'] = [];
$params['displayData'] = []; $params['displayData'] = [];
$params['reportTotals'] = [ $params['reportTotals'] = [];
'amount' => [],
'balance' => [],
'paid' => [],
];
$params['labels'] = []; $params['labels'] = [];
$params['datasets'] = []; $params['datasets'] = [];
$params['scaleStepWidth'] = 100; $params['scaleStepWidth'] = 100;
@ -212,165 +216,320 @@ class ReportController extends BaseController
]; ];
} }
private function generateReport($reportType, $groupBy, $startDate, $endDate) private function generateReport($reportType, $startDate, $endDate, $dateField, $isExport)
{ {
if ($reportType == ENTITY_CLIENT) { if ($reportType == ENTITY_CLIENT) {
$columns = ['client', 'amount', 'paid', 'balance']; return $this->generateClientReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_INVOICE) { } elseif ($reportType == ENTITY_INVOICE) {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance']; return $this->generateInvoiceReport($startDate, $endDate, $isExport);
} else { } elseif ($reportType == ENTITY_PAYMENT) {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; return $this->generatePaymentReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_TAX_RATE) {
return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport);
} elseif ($reportType == ENTITY_EXPENSE) {
return $this->generateExpenseReport($startDate, $endDate, $isExport);
} }
}
$query = DB::table('invoices') private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport)
->join('accounts', 'accounts.id', '=', 'invoices.account_id') {
->join('clients', 'clients.id', '=', 'invoices.client_id') $columns = ['tax_name', 'tax_rate', 'amount', 'paid'];
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('invoices.is_deleted', '=', false)
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->where('invoices.invoice_date', '>=', $startDate->format('Y-m-d'))
->where('invoices.invoice_date', '<=', $endDate->format('Y-m-d'))
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true);
$select = [ $account = Auth::user()->account;
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
'accounts.country_id',
'contacts.first_name',
'contacts.last_name',
'contacts.email',
'clients.name as client_name',
'clients.public_id as client_public_id',
'invoices.public_id as invoice_public_id'
];
if ($reportType == ENTITY_CLIENT) {
$query->groupBy('clients.id');
array_push($select, DB::raw('sum(invoices.amount) amount'), DB::raw('sum(invoices.balance) balance'), DB::raw('sum(invoices.amount - invoices.balance) paid'));
} else {
$query->orderBy('invoices.id');
array_push($select, 'invoices.invoice_number', 'invoices.amount', 'invoices.balance', 'invoices.invoice_date');
if ($reportType == ENTITY_INVOICE) {
array_push($select, DB::raw('(invoices.amount - invoices.balance) paid'));
} else {
$query->join('payments', 'payments.invoice_id', '=', 'invoices.id')
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id');
array_push($select, 'payments.payment_date', 'payments.amount as paid', 'payment_types.name as payment_type', 'gateways.name as gateway');
}
}
$query->select($select);
$data = $query->get();
$lastInvoiceId = null;
$sameAsLast = false;
$displayData = []; $displayData = [];
$reportTotals = [];
$exportData = []; $clients = Client::scope()
$reportTotals = [ ->withArchived()
'amount' => [], ->with('contacts')
'balance' => [], ->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) {
'paid' => [], $query->withArchived();
]; if ($dateField == FILTER_PAYMENT_DATE) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->whereHas('payments', function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived();
})
->with(['payments' => function($query) use ($startDate, $endDate) {
$query->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate)
->withArchived()
->with('payment_type', 'account_gateway.gateway');
}, 'invoice_items']);
}
}]);
foreach ($data as $record) { foreach ($clients->get() as $client) {
$sameAsLast = ($lastInvoiceId == $record->invoice_public_id); $currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
$lastInvoiceId = $record->invoice_public_id; $amount = 0;
$paid = 0;
$taxTotals = [];
$displayRow = []; foreach ($client->invoices as $invoice) {
if ($sameAsLast) { foreach ($invoice->getTaxes(true) as $key => $tax) {
array_push($displayRow, '', '', '', ''); if ( ! isset($taxTotals[$currencyId])) {
} else { $taxTotals[$currencyId] = [];
array_push($displayRow, link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record))); }
if ($reportType != ENTITY_CLIENT) { if (isset($taxTotals[$currencyId][$key])) {
array_push($displayRow, $taxTotals[$currencyId][$key]['amount'] += $tax['amount'];
link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number), $taxTotals[$currencyId][$key]['paid'] += $tax['paid'];
Utils::fromSqlDate($record->invoice_date, true) } else {
); $taxTotals[$currencyId][$key] = $tax;
}
} }
array_push($displayRow, Utils::formatMoney($record->amount, $record->currency_id, $record->country_id));
} $amount += $invoice->amount;
if ($reportType != ENTITY_PAYMENT) { $paid += $invoice->getAmountPaid();
array_push($displayRow, Utils::formatMoney($record->paid, $record->currency_id, $record->country_id));
}
if ($reportType == ENTITY_PAYMENT) {
array_push($displayRow,
Utils::fromSqlDate($record->payment_date, true),
Utils::formatMoney($record->paid, $record->currency_id, $record->country_id),
$record->gateway ?: $record->payment_type
);
} else {
array_push($displayRow, Utils::formatMoney($record->balance, $record->currency_id, $record->country_id));
} }
// export data foreach ($taxTotals as $currencyId => $taxes) {
$exportRow = []; foreach ($taxes as $tax) {
if ($sameAsLast) { $displayData[] = [
$exportRow[trans('texts.client')] = ' '; $tax['name'],
$exportRow[trans('texts.invoice_number')] = ' '; $tax['rate'] . '%',
$exportRow[trans('texts.invoice_date')] = ' '; $account->formatMoney($tax['amount'], $client),
$exportRow[trans('texts.amount')] = ' '; $account->formatMoney($tax['paid'], $client)
} else { ];
$exportRow[trans('texts.client')] = Utils::getClientDisplayName($record);
if ($reportType != ENTITY_CLIENT) {
$exportRow[trans('texts.invoice_number')] = $record->invoice_number;
$exportRow[trans('texts.invoice_date')] = Utils::fromSqlDate($record->invoice_date, true);
} }
$exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id, $record->country_id);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']);
} }
if ($reportType != ENTITY_PAYMENT) {
$exportRow[trans('texts.paid')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
}
if ($reportType == ENTITY_PAYMENT) {
$exportRow[trans('texts.payment_date')] = Utils::fromSqlDate($record->payment_date, true);
$exportRow[trans('texts.payment_amount')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
$exportRow[trans('texts.method')] = $record->gateway ?: $record->payment_type;
} else {
$exportRow[trans('texts.balance')] = Utils::formatMoney($record->balance, $record->currency_id, $record->country_id);
}
$displayData[] = $displayRow;
$exportData[] = $exportRow;
$accountCurrencyId = Auth::user()->account->currency_id;
$currencyId = $record->currency_id ? $record->currency_id : ($accountCurrencyId ? $accountCurrencyId : DEFAULT_CURRENCY);
if (!isset($reportTotals['amount'][$currencyId])) {
$reportTotals['amount'][$currencyId] = 0;
$reportTotals['balance'][$currencyId] = 0;
$reportTotals['paid'][$currencyId] = 0;
}
if (!$sameAsLast) {
$reportTotals['amount'][$currencyId] += $record->amount;
$reportTotals['balance'][$currencyId] += $record->balance;
}
$reportTotals['paid'][$currencyId] += $record->paid;
} }
return [ return [
'columns' => $columns, 'columns' => $columns,
'displayData' => $displayData, 'displayData' => $displayData,
'reportTotals' => $reportTotals, 'reportTotals' => $reportTotals,
'exportData' => $exportData ];
}
private function generatePaymentReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$payments = Payment::scope()
->withTrashed()
->where('is_deleted', '=', false)
->whereHas('client', function($query) {
$query->where('is_deleted', '=', false);
})
->whereHas('invoice', function($query) {
$query->where('is_deleted', '=', false);
})
->with('client.contacts', 'invoice', 'payment_type', 'account_gateway.gateway')
->where('payment_date', '>=', $startDate)
->where('payment_date', '<=', $endDate);
foreach ($payments->get() as $payment) {
$invoice = $payment->invoice;
$client = $payment->client;
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment->present()->payment_date,
$account->formatMoney($payment->amount, $client),
$payment->present()->method,
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
]; ];
} }
private function export($data, $totals) private function generateInvoiceReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withTrashed()
->with('contacts')
->where('is_deleted', '=', false)
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('is_deleted', '=', false)
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->with(['payments' => function($query) {
$query->withTrashed()
->with('payment_type', 'account_gateway.gateway')
->where('is_deleted', '=', false);
}, 'invoice_items'])
->withTrashed();
}]);
foreach ($clients->get() as $client) {
foreach ($client->invoices as $invoice) {
$payments = count($invoice->payments) ? $invoice->payments : [false];
foreach ($payments as $payment) {
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$account->formatMoney($invoice->amount, $client),
$payment ? $payment->present()->payment_date : '',
$payment ? $account->formatMoney($payment->amount, $client) : '',
$payment ? $payment->present()->method : '',
];
if ($payment) {
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount);
}
}
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance);
}
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function generateClientReport($startDate, $endDate, $isExport)
{
$columns = ['client', 'amount', 'paid', 'balance'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$clients = Client::scope()
->withArchived()
->with('contacts')
->with(['invoices' => function($query) use ($startDate, $endDate) {
$query->where('invoice_date', '>=', $startDate)
->where('invoice_date', '<=', $endDate)
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->withArchived();
}]);
foreach ($clients->get() as $client) {
$amount = 0;
$paid = 0;
foreach ($client->invoices as $invoice) {
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
$displayData[] = [
$isExport ? $client->getDisplayName() : $client->present()->link,
$account->formatMoney($amount, $client),
$account->formatMoney($paid, $client),
$account->formatMoney($amount - $paid, $client)
];
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $paid);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $amount - $paid);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function generateExpenseReport($startDate, $endDate, $isExport)
{
$columns = ['vendor', 'client', 'date', 'expense_amount', 'invoiced_amount'];
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$expenses = Expense::scope()
->withTrashed()
->with('client.contacts', 'vendor')
->where('expense_date', '>=', $startDate)
->where('expense_date', '<=', $endDate);
foreach ($expenses->get() as $expense) {
$amount = $expense->amount;
$invoiced = $expense->present()->invoiced_amount;
$displayData[] = [
$expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '',
$expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '',
$expense->present()->expense_date,
Utils::formatMoney($amount, $expense->currency_id),
Utils::formatMoney($invoiced, $expense->invoice_currency_id),
];
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0);
$reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'invoiced', $invoiced);
$reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'invoiced', 0);
}
return [
'columns' => $columns,
'displayData' => $displayData,
'reportTotals' => $reportTotals,
];
}
private function addToTotals($data, $currencyId, $field, $value) {
$currencyId = $currencyId ?: Auth::user()->account->getCurrencyId();
if (!isset($data[$currencyId][$field])) {
$data[$currencyId][$field] = 0;
}
$data[$currencyId][$field] += $value;
return $data;
}
private function export($data, $columns, $totals)
{ {
$output = fopen('php://output', 'w') or Utils::fatalError(); $output = fopen('php://output', 'w') or Utils::fatalError();
header('Content-Type:application/csv'); header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv'); header('Content-Disposition:attachment;filename=ninja-report.csv');
Utils::exportData($output, $data); Utils::exportData($output, $data, Utils::trans($columns));
fwrite($output, trans('texts.totals'));
foreach ($totals as $currencyId => $fields) {
foreach ($fields as $key => $value) {
fwrite($output, ',' . trans("texts.{$key}"));
}
fwrite($output, "\n");
break;
}
foreach (['amount', 'paid', 'balance'] as $type) { foreach ($totals as $currencyId => $fields) {
$csv = trans("texts.{$type}").','; $csv = Utils::getFromCache($currencyId, 'currencies')->name . ',';
foreach ($totals[$type] as $currencyId => $amount) { foreach ($fields as $key => $value) {
$csv .= Utils::formatMoney($amount, $currencyId).','; $csv .= '"' . Utils::formatMoney($value, $currencyId).'",';
} }
fwrite($output, $csv."\n"); fwrite($output, $csv."\n");
} }

View File

@ -49,7 +49,7 @@ class TaskApiController extends BaseAPIController
$tasks->whereHas('client', $filter); $tasks->whereHas('client', $filter);
$paginator->whereHas('client', $filter); $paginator->whereHas('client', $filter);
} }
$tasks = $tasks->orderBy('created_at', 'desc')->paginate(); $tasks = $tasks->orderBy('created_at', 'desc')->paginate();
$paginator = $paginator->paginate(); $paginator = $paginator->paginate();
$transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer')); $transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer'));
@ -84,11 +84,11 @@ class TaskApiController extends BaseAPIController
{ {
$data = Input::all(); $data = Input::all();
$taskId = isset($data['id']) ? $data['id'] : false; $taskId = isset($data['id']) ? $data['id'] : false;
if (isset($data['client_id']) && $data['client_id']) { if (isset($data['client_id']) && $data['client_id']) {
$data['client'] = $data['client_id']; $data['client'] = $data['client_id'];
} }
$task = $this->taskRepo->save($taskId, $data); $task = $this->taskRepo->save($taskId, $data);
$task = Task::scope($task->public_id)->with('client')->first(); $task = Task::scope($task->public_id)->with('client')->first();

View File

@ -22,10 +22,11 @@ class TaskController extends BaseController
{ {
protected $taskRepo; protected $taskRepo;
protected $taskService; protected $taskService;
protected $model = 'App\Models\Task';
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService) public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService)
{ {
parent::__construct(); // parent::__construct();
$this->taskRepo = $taskRepo; $this->taskRepo = $taskRepo;
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
@ -84,6 +85,9 @@ class TaskController extends BaseController
*/ */
public function create($clientPublicId = 0) public function create($clientPublicId = 0)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$this->checkTimezone(); $this->checkTimezone();
$data = [ $data = [
@ -113,6 +117,10 @@ class TaskController extends BaseController
$task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail(); $task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail();
if(!$this->checkEditPermission($task, $response)){
return $response;
}
$actions = []; $actions = [];
if ($task->invoice) { if ($task->invoice) {
$actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; $actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
@ -175,6 +183,10 @@ class TaskController extends BaseController
private function save($publicId = null) private function save($publicId = null)
{ {
$action = Input::get('action'); $action = Input::get('action');
if(!$this->checkUpdatePermission(array('public_id'=>$publicId)/* Hacky, but works */, $response)){
return $response;
}
if (in_array($action, ['archive', 'delete', 'restore'])) { if (in_array($action, ['archive', 'delete', 'restore'])) {
return self::bulk(); return self::bulk();

View File

@ -25,7 +25,7 @@ class TaxRateController extends BaseController
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo) public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo)
{ {
parent::__construct(); //parent::__construct();
$this->taxRateService = $taxRateService; $this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo; $this->taxRateRepo = $taxRateRepo;
@ -76,9 +76,9 @@ class TaxRateController extends BaseController
public function update(UpdateTaxRateRequest $request, $publicId) public function update(UpdateTaxRateRequest $request, $publicId)
{ {
$taxRate = TaxRate::scope($publicId)->firstOrFail(); $taxRate = TaxRate::scope($publicId)->firstOrFail();
$this->taxRateRepo->save($request->input(), $taxRate); $this->taxRateRepo->save($request->input(), $taxRate);
Session::flash('message', trans('texts.updated_tax_rate')); Session::flash('message', trans('texts.updated_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES); return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
} }

View File

@ -20,7 +20,7 @@ class TokenController extends BaseController
public function __construct(TokenService $tokenService) public function __construct(TokenService $tokenService)
{ {
parent::__construct(); //parent::__construct();
$this->tokenService = $tokenService; $this->tokenService = $tokenService;
} }

View File

@ -42,7 +42,7 @@ class UserApiController extends BaseAPIController
return $this->save($request); return $this->save($request);
} }
*/ */
public function update(UpdateUserRequest $request, $userPublicId) public function update(UpdateUserRequest $request, $userPublicId)
{ {
/* /*

View File

@ -11,9 +11,9 @@ use Request;
use Redirect; use Redirect;
use Session; use Session;
use URL; use URL;
use Password;
use Utils; use Utils;
use Validator; use Validator;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Models\User; use App\Models\User;
use App\Http\Requests; use App\Http\Requests;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
@ -30,7 +30,7 @@ class UserController extends BaseController
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer, UserService $userService) public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer, UserService $userService)
{ {
parent::__construct(); //parent::__construct();
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer; $this->contactMailer = $contactMailer;
@ -77,7 +77,6 @@ class UserController extends BaseController
'user' => $user, 'user' => $user,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'users/'.$publicId, 'url' => 'users/'.$publicId,
'title' => trans('texts.edit_user'),
]; ];
return View::make('users.edit', $data); return View::make('users.edit', $data);
@ -120,7 +119,6 @@ class UserController extends BaseController
'user' => null, 'user' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'users', 'url' => 'users',
'title' => trans('texts.add_user'),
]; ];
return View::make('users.edit', $data); return View::make('users.edit', $data);
@ -130,7 +128,7 @@ class UserController extends BaseController
{ {
$action = Input::get('bulk_action'); $action = Input::get('bulk_action');
$id = Input::get('bulk_public_id'); $id = Input::get('bulk_public_id');
$user = User::where('account_id', '=', Auth::user()->account_id) $user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $id) ->where('public_id', '=', $id)
->withTrashed() ->withTrashed()
@ -192,6 +190,8 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name')); $user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email')); $user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email')); $user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
} else { } else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id) $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first(); ->orderBy('public_id', 'DESC')->first();
@ -202,10 +202,12 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name')); $user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email')); $user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email')); $user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->registered = true; $user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH); $user->password = str_random(RANDOM_KEY_LENGTH);
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
$user->public_id = $lastUser->public_id + 1; $user->public_id = $lastUser->public_id + 1;
$user->permissions = Input::get('permissions');
} }
$user->save(); $user->save();
@ -219,7 +221,7 @@ class UserController extends BaseController
Session::flash('message', $message); Session::flash('message', $message);
} }
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT); return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
} }
@ -240,10 +242,10 @@ class UserController extends BaseController
* *
* @param string $code * @param string $code
*/ */
public function confirm($code, TokenRepositoryInterface $tokenRepo) public function confirm($code)
{ {
$user = User::where('confirmation_code', '=', $code)->get()->first(); $user = User::where('confirmation_code', '=', $code)->get()->first();
if ($user) { if ($user) {
$notice_msg = trans('texts.security.confirmation'); $notice_msg = trans('texts.security.confirmation');
@ -253,7 +255,7 @@ class UserController extends BaseController
if ($user->public_id) { if ($user->public_id) {
//Auth::login($user); //Auth::login($user);
$token = $tokenRepo->create($user); $token = Password::getRepository()->create($user);
return Redirect::to("/password/reset/{$token}"); return Redirect::to("/password/reset/{$token}");
} else { } else {
@ -294,7 +296,7 @@ class UserController extends BaseController
return Redirect::to('/')->with('clearGuestKey', true); return Redirect::to('/')->with('clearGuestKey', true);
} }
*/ */
public function changePassword() public function changePassword()
{ {
// check the current password is correct // check the current password is correct
@ -326,7 +328,7 @@ class UserController extends BaseController
$oldUserId = Auth::user()->id; $oldUserId = Auth::user()->id;
$referer = Request::header('referer'); $referer = Request::header('referer');
$account = $this->accountRepo->findUserAccounts($newUserId, $oldUserId); $account = $this->accountRepo->findUserAccounts($newUserId, $oldUserId);
if ($account) { if ($account) {
if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) { if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) {
Auth::loginUsingId($newUserId); Auth::loginUsingId($newUserId);
@ -337,7 +339,7 @@ class UserController extends BaseController
Session::put('_token', str_random(40)); Session::put('_token', str_random(40));
} }
} }
return Redirect::to($referer); return Redirect::to($referer);
} }

View File

@ -83,7 +83,7 @@ class VendorApiController extends BaseAPIController
public function store(CreateVendorRequest $request) public function store(CreateVendorRequest $request)
{ {
$vendor = $this->vendorRepo->save($request->input()); $vendor = $this->vendorRepo->save($request->input());
$vendor = Vendor::scope($vendor->public_id) $vendor = Vendor::scope($vendor->public_id)
->with('country', 'vendorcontacts', 'industry', 'size', 'currency') ->with('country', 'vendorcontacts', 'industry', 'size', 'currency')
->first(); ->first();

View File

@ -30,10 +30,11 @@ class VendorController extends BaseController
{ {
protected $vendorService; protected $vendorService;
protected $vendorRepo; protected $vendorRepo;
protected $model = 'App\Models\Vendor';
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService) public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
{ {
parent::__construct(); //parent::__construct();
$this->vendorRepo = $vendorRepo; $this->vendorRepo = $vendorRepo;
$this->vendorService = $vendorService; $this->vendorService = $vendorService;
@ -76,7 +77,13 @@ class VendorController extends BaseController
*/ */
public function store(CreateVendorRequest $request) public function store(CreateVendorRequest $request)
{ {
$vendor = $this->vendorService->save($request->input()); $data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
Session::flash('message', trans('texts.created_vendor')); Session::flash('message', trans('texts.created_vendor'));
@ -92,6 +99,11 @@ class VendorController extends BaseController
public function show($publicId) public function show($publicId)
{ {
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail(); $vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail();
if(!$this->checkViewPermission($vendor, $response)){
return $response;
}
Utils::trackViewed($vendor->getDisplayName(), 'vendor'); Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [ $actionLinks = [
@ -119,6 +131,10 @@ class VendorController extends BaseController
*/ */
public function create() public function create()
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) { if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]); return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]);
} }
@ -144,6 +160,11 @@ class VendorController extends BaseController
public function edit($publicId) public function edit($publicId)
{ {
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail(); $vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
if(!$this->checkEditPermission($vendor, $response)){
return $response;
}
$data = [ $data = [
'vendor' => $vendor, 'vendor' => $vendor,
'method' => 'PUT', 'method' => 'PUT',
@ -180,7 +201,13 @@ class VendorController extends BaseController
*/ */
public function update(UpdateVendorRequest $request) public function update(UpdateVendorRequest $request)
{ {
$vendor = $this->vendorService->save($request->input()); $data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
Session::flash('message', trans('texts.updated_vendor')); Session::flash('message', trans('texts.updated_vendor'));

View File

@ -28,6 +28,7 @@ class Kernel extends HttpKernel {
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate', 'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'permissions.required' => 'App\Http\Middleware\PermissionsRequired',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'api' => 'App\Http\Middleware\ApiCheck', 'api' => 'App\Http\Middleware\ApiCheck',
]; ];

View File

@ -21,11 +21,15 @@ class ApiCheck {
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
$loggingIn = $request->is('api/v1/login'); $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register');
$headers = Utils::getApiHeaders(); $headers = Utils::getApiHeaders();
if ($loggingIn) { if ($loggingIn) {
// do nothing // check API secret
if ( ! $request->api_secret || ! env(API_SECRET) || ! hash_equals($request->api_secret, env(API_SECRET))) {
sleep(ERROR_DELAY);
return Response::json('Invalid secret', 403, $headers);
}
} else { } else {
// check for a valid token // check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']); $token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
@ -34,7 +38,7 @@ class ApiCheck {
Auth::loginUsingId($token->user_id); Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id); Session::set('token_id', $token->id);
} else { } else {
sleep(3); sleep(ERROR_DELAY);
return Response::json('Invalid token', 403, $headers); return Response::json('Invalid token', 403, $headers);
} }
} }

View File

@ -1,28 +1,13 @@
<?php namespace App\Http\Middleware; <?php namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Contracts\Auth\Guard; use Auth;
use Session;
use App\Models\Invitation;
use App\Models\Contact;
use App\Models\Account;
class Authenticate { class Authenticate {
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
@ -30,9 +15,46 @@ class Authenticate {
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next, $guard = 'user')
{ {
if ($this->auth->guest()) $authenticated = Auth::guard($guard)->check();
if($guard == 'client' && !empty($request->invitation_key)){
$old_key = session('invitation_key');
if($old_key && $old_key != $request->invitation_key){
if($this->getInvitationContactId($old_key) != $this->getInvitationContactId($request->invitation_key)){
// This is a different client; reauthenticate
$authenticated = false;
Auth::guard($guard)->logout();
}
}
Session::put('invitation_key', $request->invitation_key);
}
if($guard=='client'){
$invitation_key = session('invitation_key');
$account_id = $this->getInvitationAccountId($invitation_key);
if(Auth::guard('user')->check() && Auth::user('user')->account_id === $account_id){
// This is an admin; let them pretend to be a client
$authenticated = true;
}
// Does this account require portal passwords?
$account = Account::whereId($account_id)->first();
if(!$account->enable_portal_password || !$account->isPro()){
$authenticated = true;
}
if(!$authenticated){
$contact = Contact::whereId($this->getInvitationContactId($invitation_key))->first();
if($contact && !$contact->password){
$authenticated = true;
}
}
}
if (!$authenticated)
{ {
if ($request->ajax()) if ($request->ajax())
{ {
@ -40,11 +62,30 @@ class Authenticate {
} }
else else
{ {
return redirect()->guest('/login'); return redirect()->guest($guard=='client'?'/client/login':'/login');
} }
} }
return $next($request); return $next($request);
} }
protected function getInvitation($key){
$invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first();
if ($invitation && !$invitation->is_deleted) {
return $invitation;
}
else return null;
}
protected function getInvitationContactId($key){
$invitation = $this->getInvitation($key);
return $invitation?$invitation->contact_id:null;
}
protected function getInvitationAccountId($key){
$invitation = $this->getInvitation($key);
return $invitation?$invitation->account_id:null;
}
} }

View File

@ -0,0 +1,57 @@
<?php namespace App\Http\Middleware;
use Closure;
use Auth;
class PermissionsRequired {
/**
* @var array of controller => [action => permission]
*/
static protected $actions = [];
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next, $guard = 'user')
{
// Get the current route.
$route = $request->route();
// Get the current route actions.
$actions = $route->getAction();
// Check if we have any permissions to check the user has.
if ($permissions = !empty($actions['permissions']) ? $actions['permissions'] : null)
{
if(!Auth::user($guard)->hasPermission($permissions, !empty($actions['permissions_require_all']))){
return response('Unauthorized.', 401);
}
}
// Check controller permissions
$action = explode('@', $request->route()->getActionName());
if(isset(static::$actions[$action[0]]) && isset(static::$actions[$action[0]][$action[1]])) {
$controller_permissions = static::$actions[$action[0]][$action[1]];
if(!Auth::user($guard)->hasPermission($controller_permissions)){
return response('Unauthorized.', 401);
}
}
return $next($request);
}
/**
* add a controller's action permission
*
* @param \App\Http\Controllers\Controller $controller
* @param array $permissions
*/
public static function addPermission(\App\Http\Controllers\Controller $controller, $permissions)
{
static::$actions[get_class($controller)] = $permissions;
}
}

View File

@ -48,6 +48,9 @@ class StartupCheck
$file = storage_path() . '/version.txt'; $file = storage_path() . '/version.txt';
$version = @file_get_contents($file); $version = @file_get_contents($file);
if ($version != NINJA_VERSION) { if ($version != NINJA_VERSION) {
if (version_compare(phpversion(), '5.5.9', '<')) {
dd('Please update PHP to >= 5.5.9');
}
$handle = fopen($file, 'w'); $handle = fopen($file, 'w');
fwrite($handle, NINJA_VERSION); fwrite($handle, NINJA_VERSION);
fclose($handle); fclose($handle);

View File

@ -24,7 +24,7 @@ class CreateExpenseRequest extends Request
public function rules() public function rules()
{ {
return [ return [
'amount' => 'positive', 'amount' => 'numeric',
]; ];
} }
} }

View File

@ -0,0 +1,67 @@
<?php namespace app\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Http\Request as InputRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Factory;
use App\Libraries\Utils;
use Response;
class RegisterRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function __construct(InputRequest $req)
{
$this->req = $req;
}
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'email' => 'required|unique:users',
'first_name' => 'required',
'last_name' => 'required',
'password' => 'required',
];
return $rules;
}
public function response(array $errors)
{
/* If the user is not validating from a mobile app - pass through parent::response */
if(!isset($this->req->api_secret))
return parent::response($errors);
/* If the user is validating from a mobile app - pass through first error string and return error */
foreach($errors as $error) {
foreach ($error as $key => $value) {
$message['error'] = ['message'=>$value];
$message = json_encode($message, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($message, 400, $headers);
}
}
}
}

View File

@ -24,7 +24,7 @@ class UpdateExpenseRequest extends Request
public function rules() public function rules()
{ {
return [ return [
'amount' => 'positive', 'amount' => 'numeric',
'expense_date' => 'required', 'expense_date' => 'required',
]; ];
} }

View File

@ -35,17 +35,20 @@ Route::get('/keep_alive', 'HomeController@keepAlive');
Route::post('/get_started', 'AccountController@getStarted'); Route::post('/get_started', 'AccountController@getStarted');
// Client visible pages // Client visible pages
Route::get('view/{invitation_key}', 'PublicClientController@view'); Route::group(['middleware' => 'auth:client'], function() {
Route::get('download/{invitation_key}', 'PublicClientController@download'); Route::get('view/{invitation_key}', 'PublicClientController@view');
Route::get('view', 'HomeController@viewLogo'); Route::get('download/{invitation_key}', 'PublicClientController@download');
Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('view', 'HomeController@viewLogo');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::get('complete', 'PaymentController@offsite_payment'); Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex'); Route::get('complete', 'PaymentController@offsite_payment');
Route::get('client/invoices', 'PublicClientController@invoiceIndex'); Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex'); Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard'); Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard');
});
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable')); Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable')); Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
@ -65,16 +68,25 @@ Route::post('/hook/email_bounced', 'AppController@emailBounced');
Route::post('/hook/email_opened', 'AppController@emailOpened'); Route::post('/hook/email_opened', 'AppController@emailOpened');
// Laravel auth routes // Laravel auth routes
get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister')); Route::get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister'));
post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister')); Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister'));
get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
get('/user/confirm/{code}', 'UserController@confirm'); Route::get('/user/confirm/{code}', 'UserController@confirm');
// Client auth
Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin'));
Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin'));
Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout'));
Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset'));
Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset'));
if (Utils::isNinja()) { if (Utils::isNinja()) {
@ -87,12 +99,80 @@ if (Utils::isReseller()) {
Route::post('/reseller_stats', 'AppController@stats'); Route::post('/reseller_stats', 'AppController@stats');
} }
Route::group(['middleware' => 'auth'], function() { Route::group(['middleware' => 'auth:user'], function() {
Route::get('dashboard', 'DashboardController@index'); Route::get('dashboard', 'DashboardController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
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('settings/user_details', 'AccountController@showUserDetails');
Route::post('settings/user_details', 'AccountController@saveUserDetails');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable'));
Route::post('clients/bulk', 'ClientController@bulk');
Route::resource('tasks', 'TaskController');
Route::get('api/tasks/{client_id?}', array('as'=>'api.tasks', 'uses'=>'TaskController@getDatatable'));
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable'));
Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('recurring_invoices', 'RecurringInvoiceController@index');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit');
Route::put('quotes/{public_id}', 'InvoiceController@update');
Route::get('quotes/{public_id}', 'InvoiceController@edit');
Route::post('quotes', 'InvoiceController@store');
Route::get('quotes', 'QuoteController@index');
Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable'));
Route::post('quotes/bulk', 'QuoteController@bulk');
Route::resource('payments', 'PaymentController');
Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create');
Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable'));
Route::post('payments/bulk', 'PaymentController@bulk');
Route::resource('credits', 'CreditController');
Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create');
Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable'));
Route::post('credits/bulk', 'CreditController@bulk');
Route::get('/resend_confirmation', 'AccountController@resendConfirmation');
Route::post('/update_setup', 'AppController@updateSetup');
// vendor
Route::resource('vendors', 'VendorController');
Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable'));
Route::post('vendors/bulk', 'VendorController@bulk');
// Expense
Route::resource('expenses', 'ExpenseController');
Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create');
Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable'));
Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor'));
Route::post('expenses/bulk', 'ExpenseController@bulk');
});
Route::group([
'middleware' => ['auth:user', 'permissions.required'],
'permissions' => 'admin',
], function() {
Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable'));
Route::resource('users', 'UserController'); Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk'); Route::post('users/bulk', 'UserController@bulk');
@ -148,66 +228,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); Route::post('bank_accounts/bulk', 'BankAccountController@bulk');
Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); Route::post('bank_accounts/validate', 'BankAccountController@validateAccount');
Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable'));
Route::post('clients/bulk', 'ClientController@bulk');
Route::resource('tasks', 'TaskController');
Route::get('api/tasks/{client_id?}', array('as'=>'api.tasks', 'uses'=>'TaskController@getDatatable'));
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable'));
Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable'));
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('recurring_invoices', 'RecurringInvoiceController@index');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit');
Route::put('quotes/{public_id}', 'InvoiceController@update');
Route::get('quotes/{public_id}', 'InvoiceController@edit');
Route::post('quotes', 'InvoiceController@store');
Route::get('quotes', 'QuoteController@index');
Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable'));
Route::post('quotes/bulk', 'QuoteController@bulk');
Route::resource('payments', 'PaymentController');
Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create');
Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable'));
Route::post('payments/bulk', 'PaymentController@bulk');
Route::resource('credits', 'CreditController');
Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create');
Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable'));
Route::post('credits/bulk', 'CreditController@bulk');
get('/resend_confirmation', 'AccountController@resendConfirmation');
post('/update_setup', 'AppController@updateSetup');
// vendor
Route::resource('vendors', 'VendorController');
Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable'));
Route::post('vendors/bulk', 'VendorController@bulk');
// Expense
Route::resource('expenses', 'ExpenseController');
Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create');
Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable'));
Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor'));
Route::post('expenses/bulk', 'ExpenseController@bulk');
}); });
// Route groups for API // Route groups for API
@ -215,6 +235,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{ {
Route::get('ping', 'ClientApiController@ping'); Route::get('ping', 'ClientApiController@ping');
Route::post('login', 'AccountApiController@login'); Route::post('login', 'AccountApiController@login');
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');
Route::put('accounts', 'AccountApiController@update'); Route::put('accounts', 'AccountApiController@update');
@ -234,6 +255,9 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('tax_rates', 'TaxRateApiController'); Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController'); Route::resource('users', 'UserApiController');
Route::resource('expenses','ExpenseApiController'); Route::resource('expenses','ExpenseApiController');
Route::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
Route::get('dashboard', 'DashboardApiController@index');
// Vendor // Vendor
Route::resource('vendors', 'VendorApiController'); Route::resource('vendors', 'VendorApiController');
@ -485,6 +509,8 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_PAYFAST', 13); define('GATEWAY_PAYFAST', 13);
define('GATEWAY_PAYPAL_EXPRESS', 17); define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18); define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_SAGE_PAY_DIRECT', 20);
define('GATEWAY_SAGE_PAY_SERVER', 21);
define('GATEWAY_STRIPE', 23); define('GATEWAY_STRIPE', 23);
define('GATEWAY_GOCARDLESS', 6); define('GATEWAY_GOCARDLESS', 6);
define('GATEWAY_TWO_CHECKOUT', 27); define('GATEWAY_TWO_CHECKOUT', 27);
@ -509,7 +535,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.5.0.4'); define('NINJA_VERSION', '2.5.1');
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');
@ -527,6 +553,8 @@ if (!defined('CONTACT_EMAIL')) {
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'); define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup');
define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'); define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all');
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
define('COUNT_FREE_DESIGNS', 4); define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1); define('PRODUCT_ONE_CLICK_INSTALL', 1);
@ -549,6 +577,9 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_PASSWORD', 'password'); define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET'); define('API_SECRET', 'API_SECRET');
define('IOS_PRODUCTION_PUSH','ninjaIOS');
define('IOS_DEV_PUSH','devNinjaIOS');
define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3); define('TOKEN_BILLING_OPT_OUT', 3);
@ -572,6 +603,9 @@ if (!defined('CONTACT_EMAIL')) {
define('REMINDER_FIELD_DUE_DATE', 1); define('REMINDER_FIELD_DUE_DATE', 1);
define('REMINDER_FIELD_INVOICE_DATE', 2); define('REMINDER_FIELD_INVOICE_DATE', 2);
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
define('SOCIAL_GOOGLE', 'Google'); define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook'); define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub'); define('SOCIAL_GITHUB', 'GitHub');
@ -581,6 +615,7 @@ if (!defined('CONTACT_EMAIL')) {
define('USER_STATE_PENDING', 'pending'); define('USER_STATE_PENDING', 'pending');
define('USER_STATE_DISABLED', 'disabled'); define('USER_STATE_DISABLED', 'disabled');
define('USER_STATE_ADMIN', 'admin'); define('USER_STATE_ADMIN', 'admin');
define('USER_STATE_OWNER', 'owner');
define('API_SERIALIZER_ARRAY', 'array'); define('API_SERIALIZER_ARRAY', 'array');
define('API_SERIALIZER_JSON', 'json'); define('API_SERIALIZER_JSON', 'json');
@ -668,8 +703,9 @@ if (Utils::isNinjaDev()) {
*/ */
/* /*
if (Utils::isNinjaDev() && Auth::check() && Auth::user()->id === 1) if (Utils::isNinjaDev())
{ {
Auth::loginUsingId(1); //ini_set('memory_limit','1024M');
//Auth::loginUsingId(1);
} }
*/ */

View File

@ -104,8 +104,8 @@ class Login
"<ORG>".$this->bank->org."\n". "<ORG>".$this->bank->org."\n".
"<FID>".$this->bank->fid."\n". "<FID>".$this->bank->fid."\n".
"</FI>\n". "</FI>\n".
"<APPID>QMOFX\n". "<APPID>QWIN\n".
"<APPVER>1900\n". "<APPVER>2500\n".
"</SONRQ>\n". "</SONRQ>\n".
"</SIGNONMSGSRQV1>\n". "</SIGNONMSGSRQV1>\n".
"<SIGNUPMSGSRQV1>\n". "<SIGNUPMSGSRQV1>\n".
@ -171,8 +171,8 @@ class Account
"<ORG>".$this->login->bank->org."\n". "<ORG>".$this->login->bank->org."\n".
"<FID>".$this->login->bank->fid."\n". "<FID>".$this->login->bank->fid."\n".
"</FI>\n". "</FI>\n".
"<APPID>QMOFX\n". "<APPID>QWIN\n".
"<APPVER>1900\n". "<APPVER>2500\n".
"</SONRQ>\n". "</SONRQ>\n".
"</SIGNONMSGSRQV1>\n"; "</SIGNONMSGSRQV1>\n";
if ($this->type == 'BANK') { if ($this->type == 'BANK') {

View File

@ -72,7 +72,7 @@ class Utils
public static function requireHTTPS() public static function requireHTTPS()
{ {
if (Request::root() === 'http://ninja.dev:8000') { if (Request::root() === 'http://ninja.dev' || Request::root() === 'http://ninja.dev:8000') {
return false; return false;
} }
@ -118,6 +118,21 @@ class Utils
return Auth::check() && Auth::user()->isPro(); return Auth::check() && Auth::user()->isPro();
} }
public static function isAdmin()
{
return Auth::check() && Auth::user()->is_admin;
}
public static function hasPermission($permission, $requireAll = false)
{
return Auth::check() && Auth::user()->hasPermission($permission, $requireAll);
}
public static function hasAllPermissions($permission)
{
return Auth::check() && Auth::user()->hasPermissions($permission);
}
public static function isTrial() public static function isTrial()
{ {
return Auth::check() && Auth::user()->isTrial(); return Auth::check() && Auth::user()->isTrial();
@ -127,6 +142,13 @@ class Utils
{ {
return App::getLocale() == 'en'; return App::getLocale() == 'en';
} }
public static function getLocaleRegion()
{
$parts = explode('_', App::getLocale());
return count($parts) ? $parts[0] : 'en';
}
public static function getUserType() public static function getUserType()
{ {
@ -225,7 +247,7 @@ class Utils
return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}"; return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
} }
public static function logError($error, $context = 'PHP') public static function logError($error, $context = 'PHP', $info = false)
{ {
if ($error instanceof Exception) { if ($error instanceof Exception) {
$error = self::getErrorString($error); $error = self::getErrorString($error);
@ -249,7 +271,11 @@ class Utils
'count' => Session::get('error_count', 0), 'count' => Session::get('error_count', 0),
]; ];
Log::error($error."\n", $data); if ($info) {
Log::info($error."\n", $data);
} else {
Log::error($error."\n", $data);
}
/* /*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message) Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
@ -598,8 +624,8 @@ class Utils
private static function getMonth($offset) private static function getMonth($offset)
{ {
$months = [ "January", "February", "March", "April", "May", "June", $months = [ "january", "february", "march", "april", "may", "june",
"July", "August", "September", "October", "November", "December", ]; "july", "august", "september", "october", "november", "december", ];
$month = intval(date('n')) - 1; $month = intval(date('n')) - 1;
@ -610,7 +636,7 @@ class Utils
$month += 12; $month += 12;
} }
return $months[$month]; return trans('texts.' . $months[$month]);
} }
private static function getQuarter($offset) private static function getQuarter($offset)
@ -774,9 +800,11 @@ class Utils
return $str; return $str;
} }
public static function exportData($output, $data) public static function exportData($output, $data, $headers = false)
{ {
if (count($data) > 0) { if ($headers) {
fputcsv($output, $headers);
} elseif (count($data) > 0) {
fputcsv($output, array_keys($data[0])); fputcsv($output, array_keys($data[0]));
} }

View File

@ -9,16 +9,20 @@ use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Events\QuoteInvitationWasApproved; use App\Events\QuoteInvitationWasApproved;
use App\Events\PaymentWasCreated; use App\Events\PaymentWasCreated;
use App\Ninja\Notifications;
use App\Services\PushService;
class NotificationListener class NotificationListener
{ {
protected $userMailer; protected $userMailer;
protected $contactMailer; protected $contactMailer;
protected $pushService;
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer) public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, PushService $pushService)
{ {
$this->userMailer = $userMailer; $this->userMailer = $userMailer;
$this->contactMailer = $contactMailer; $this->contactMailer = $contactMailer;
$this->pushService = $pushService;
} }
private function sendEmails($invoice, $type, $payment = null) private function sendEmails($invoice, $type, $payment = null)
@ -35,26 +39,31 @@ class NotificationListener
public function emailedInvoice(InvoiceWasEmailed $event) public function emailedInvoice(InvoiceWasEmailed $event)
{ {
$this->sendEmails($event->invoice, 'sent'); $this->sendEmails($event->invoice, 'sent');
$this->pushService->sendNotification($event->invoice, 'sent');
} }
public function emailedQuote(QuoteWasEmailed $event) public function emailedQuote(QuoteWasEmailed $event)
{ {
$this->sendEmails($event->quote, 'sent'); $this->sendEmails($event->quote, 'sent');
$this->pushService->sendNotification($event->quote, 'sent');
} }
public function viewedInvoice(InvoiceInvitationWasViewed $event) public function viewedInvoice(InvoiceInvitationWasViewed $event)
{ {
$this->sendEmails($event->invoice, 'viewed'); $this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
} }
public function viewedQuote(QuoteInvitationWasViewed $event) public function viewedQuote(QuoteInvitationWasViewed $event)
{ {
$this->sendEmails($event->quote, 'viewed'); $this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
} }
public function approvedQuote(QuoteInvitationWasApproved $event) public function approvedQuote(QuoteInvitationWasApproved $event)
{ {
$this->sendEmails($event->quote, 'approved'); $this->sendEmails($event->quote, 'approved');
$this->pushService->sendNotification($event->quote, 'approved');
} }
public function createdPayment(PaymentWasCreated $event) public function createdPayment(PaymentWasCreated $event)
@ -66,6 +75,8 @@ class NotificationListener
$this->contactMailer->sendPaymentConfirmation($event->payment); $this->contactMailer->sendPaymentConfirmation($event->payment);
$this->sendEmails($event->payment->invoice, 'paid', $event->payment); $this->sendEmails($event->payment->invoice, 'paid', $event->payment);
$this->pushService->sendNotification($event->payment->invoice, 'paid');
} }
} }

View File

@ -24,66 +24,46 @@ class SubscriptionListener
{ {
public function createdClient(ClientWasCreated $event) public function createdClient(ClientWasCreated $event)
{ {
if ( ! Auth::check()) { $transformer = new ClientTransformer($event->client->account);
return; $this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer);
}
$transformer = new ClientTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer);
} }
public function createdQuote(QuoteWasCreated $event) public function createdQuote(QuoteWasCreated $event)
{ {
if ( ! Auth::check()) { $transformer = new InvoiceTransformer($event->quote->account);
return; $this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
}
$transformer = new InvoiceTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
} }
public function createdPayment(PaymentWasCreated $event) public function createdPayment(PaymentWasCreated $event)
{ {
if ( ! Auth::check()) { $transformer = new PaymentTransformer($event->payment->account);
return; $this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
}
$transformer = new PaymentTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
}
public function createdCredit(CreditWasCreated $event)
{
if ( ! Auth::check()) {
return;
}
//$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit);
} }
public function createdInvoice(InvoiceWasCreated $event) public function createdInvoice(InvoiceWasCreated $event)
{ {
if ( ! Auth::check()) { $transformer = new InvoiceTransformer($event->invoice->account);
return; $this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
} }
$transformer = new InvoiceTransformer(Auth::user()->account); public function createdCredit(CreditWasCreated $event)
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); {
} }
public function createdVendor(VendorWasCreated $event) public function createdVendor(VendorWasCreated $event)
{ {
//$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor);
} }
public function createdExpense(ExpenseWasCreated $event) public function createdExpense(ExpenseWasCreated $event)
{ {
//$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_EXPENSE, $event->expense);
} }
private function checkSubscriptions($activityTypeId, $entity, $transformer, $include = '') private function checkSubscriptions($eventId, $entity, $transformer, $include = '')
{ {
$subscription = $entity->account->getSubscription($activityTypeId); $subscription = $entity->account->getSubscription($eventId);
if ($subscription) { if ($subscription) {
$manager = new Manager(); $manager = new Manager();
@ -93,7 +73,12 @@ class SubscriptionListener
$resource = new Item($entity, $transformer, $entity->getEntityType()); $resource = new Item($entity, $transformer, $entity->getEntityType());
$data = $manager->createData($resource)->toArray(); $data = $manager->createData($resource)->toArray();
// For legacy Zapier support
if (isset($data['client_id'])) {
$data['client_name'] = $entity->client->getDisplayName();
}
Utils::notifyZapier($subscription, $data); Utils::notifyZapier($subscription, $data);
} }
} }
} }

View File

@ -439,7 +439,7 @@ class Account extends Eloquent
return $height; return $height;
} }
public function createInvoice($entityType, $clientId = null) public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null)
{ {
$invoice = Invoice::createNew(); $invoice = Invoice::createNew();
@ -614,6 +614,28 @@ class Account extends Eloquent
$this->save(); $this->save();
} }
public function loadAllData($updatedAt = null)
{
$map = [
'users' => [],
'clients' => ['contacts'],
'invoices' => ['invoice_items', 'user', 'client', 'payments'],
'products' => [],
'tax_rates' => [],
'expenses' => ['client', 'invoice', 'vendor'],
'payments' => ['invoice'],
];
foreach ($map as $key => $values) {
$this->load([$key => function($query) use ($values, $updatedAt) {
$query->withTrashed()->with($values);
if ($updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
}
}]);
}
}
public function loadLocalizationSettings($client = false) public function loadLocalizationSettings($client = false)
{ {
$this->load('timezone', 'date_format', 'datetime_format', 'language'); $this->load('timezone', 'date_format', 'datetime_format', 'language');
@ -661,7 +683,7 @@ class Account extends Eloquent
'subtotal', 'subtotal',
'paid_to_date', 'paid_to_date',
'balance_due', 'balance_due',
'amount_due', 'partial_due',
'terms', 'terms',
'your_invoice', 'your_invoice',
'quote', 'quote',
@ -1001,7 +1023,7 @@ class Account extends Eloquent
return true; return true;
} }
public function showCustomField($field, $entity) public function showCustomField($field, $entity = false)
{ {
if ($this->isPro()) { if ($this->isPro()) {
return $this->$field ? true : false; return $this->$field ? true : false;
@ -1050,7 +1072,13 @@ class Account extends Eloquent
public function hasLargeFont() public function hasLargeFont()
{ {
return stripos($this->getBodyFontName(), 'chinese') || stripos($this->getHeaderFontName(), 'chinese'); foreach (['chinese', 'japanese'] as $language) {
if (stripos($this->getBodyFontName(), $language) || stripos($this->getHeaderFontName(), $language)) {
return true;
}
}
return false;
} }
public function getFontsUrl($protocol = ''){ public function getFontsUrl($protocol = ''){

View File

@ -154,7 +154,15 @@ class Client extends EntityModel
$contact = Contact::createNew(); $contact = Contact::createNew();
$contact->send_invoice = true; $contact->send_invoice = true;
} }
if (!Utils::isPro() || $this->account->enable_portal_password){
if(!empty($data['password']) && $data['password']!='-%unchanged%-'){
$contact->password = bcrypt($data['password']);
} else if(empty($data['password'])){
$contact->password = null;
}
}
$contact->fill($data); $contact->fill($data);
$contact->is_primary = $isPrimary; $contact->is_primary = $isPrimary;
@ -272,6 +280,11 @@ class Client extends EntityModel
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false; return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
} }
public function getAmount()
{
return $this->balance + $this->paid_to_date;
}
public function getCurrencyId() public function getCurrencyId()
{ {
if ($this->currency_id) { if ($this->currency_id) {

View File

@ -3,10 +3,14 @@
use HTML; use HTML;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class Contact extends EntityModel class Contact extends EntityModel implements AuthenticatableContract, CanResetPasswordContract
{ {
use SoftDeletes; use SoftDeletes, Authenticatable, CanResetPassword;
protected $dates = ['deleted_at']; protected $dates = ['deleted_at'];
protected $fillable = [ protected $fillable = [

View File

@ -42,7 +42,7 @@ class EntityModel extends Eloquent
{ {
$className = get_called_class(); $className = get_called_class();
return $className::scope($publicId)->withTrashed()->pluck('id'); return $className::scope($publicId)->withTrashed()->value('id');
} }
public function getActivityKey() public function getActivityKey()
@ -81,6 +81,11 @@ class EntityModel extends Eloquent
return $query; return $query;
} }
public function scopeWithArchived($query)
{
return $query->withTrashed()->where('is_deleted', '=', false);
}
public function getName() public function getName()
{ {
return $this->public_id; return $this->public_id;
@ -108,4 +113,56 @@ class EntityModel extends Eloquent
$name = $parts[count($parts)-1]; $name = $parts[count($parts)-1];
return strtolower($name) . '_id'; return strtolower($name) . '_id';
} }
public static function canCreate() {
return Auth::user()->hasPermission('create_all');
}
public function canEdit() {
return static::canEditItem($this);
}
public static function canEditItem($item) {
return Auth::user()->hasPermission('edit_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id);
}
public static function canEditItemById($item_id) {
if(Auth::user()->hasPermission('edit_all')) {
return true;
}
return static::whereId($item_id)->first()->user_id == Auth::user()->id;
}
public static function canEditItemByOwner($user_id) {
if(Auth::user()->hasPermission('edit_all')) {
return true;
}
return Auth::user()->id == $user_id;
}
public function canView() {
return static::canViewItem($this);
}
public static function canViewItem($item) {
return Auth::user()->hasPermission('view_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id);
}
public static function canViewItemById($item_id) {
if(Auth::user()->hasPermission('view_all')) {
return true;
}
return static::whereId($item_id)->first()->user_id == Auth::user()->id;
}
public static function canViewItemByOwner($user_id) {
if(Auth::user()->hasPermission('view_all')) {
return true;
}
return Auth::user()->id == $user_id;
}
} }

View File

@ -75,6 +75,8 @@ class Gateway extends Eloquent
$link = 'https://bitpay.com/dashboard/signup'; $link = 'https://bitpay.com/dashboard/signup';
} elseif ($this->id == GATEWAY_DWOLLA) { } elseif ($this->id == GATEWAY_DWOLLA) {
$link = 'https://www.dwolla.com/register'; $link = 'https://www.dwolla.com/register';
} elseif ($this->id == GATEWAY_SAGE_PAY_DIRECT || $this->id == GATEWAY_SAGE_PAY_SERVER) {
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
} }
$key = 'texts.gateway_help_'.$this->id; $key = 'texts.gateway_help_'.$this->id;

View File

@ -129,13 +129,21 @@ class Invoice extends EntityModel implements BalanceAffecting
return false; return false;
} }
public function getAmountPaid() public function getAmountPaid($calculate = false)
{ {
if ($this->is_quote || $this->is_recurring) { if ($this->is_quote || $this->is_recurring) {
return 0; return 0;
} }
return ($this->amount - $this->balance); if ($calculate) {
$amount = 0;
foreach ($this->payments as $payment) {
$amount += $payment->amount;
}
return $amount;
} else {
return ($this->amount - $this->balance);
}
} }
public function trashed() public function trashed()
@ -192,6 +200,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id'); return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
} }
public function frequency()
{
return $this->belongsTo('App\Models\Frequency');
}
public function invitations() public function invitations()
{ {
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -442,12 +455,16 @@ class Invoice extends EntityModel implements BalanceAffecting
'show_item_taxes', 'show_item_taxes',
'custom_invoice_text_label1', 'custom_invoice_text_label1',
'custom_invoice_text_label2', 'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
]); ]);
foreach ($this->invoice_items as $invoiceItem) { foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([ $invoiceItem->setVisible([
'product_key', 'product_key',
'notes', 'notes',
'custom_value1',
'custom_value2',
'cost', 'cost',
'qty', 'qty',
'tax_name', 'tax_name',
@ -752,6 +769,98 @@ class Invoice extends EntityModel implements BalanceAffecting
return Utils::decodePDF($pdfString); return Utils::decodePDF($pdfString);
} }
public function getItemTaxable($invoiceItem, $invoiceTotal)
{
$total = $invoiceItem->qty * $invoiceItem->cost;
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $invoiceTotal ? ($total / $invoiceTotal * $this->discount) : 0;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
return $total;
}
public function getTaxable()
{
$total = 0;
foreach ($this->invoice_items as $invoiceItem) {
$total += $invoiceItem->qty * $invoiceItem->cost;
}
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $this->discount;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
if ($this->custom_value1 && $this->custom_taxes1) {
$total += $this->custom_value1;
}
if ($this->custom_value2 && $this->custom_taxes2) {
$total += $this->custom_value2;
}
return $total;
}
public function getTaxes($calculatePaid = false)
{
$taxes = [];
$taxable = $this->getTaxable();
if ($this->tax_rate && $this->tax_name) {
$taxAmount = $taxable * ($this->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($taxAmount) {
$taxes[$this->tax_name.$this->tax_rate] = [
'name' => $this->tax_name,
'rate' => $this->tax_rate,
'amount' => $taxAmount,
'paid' => round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2)
];
}
}
foreach ($this->invoice_items as $invoiceItem) {
if ( ! $invoiceItem->tax_rate || ! $invoiceItem->tax_name) {
continue;
}
$taxAmount = $this->getItemTaxable($invoiceItem, $taxable);
$taxAmount = $taxable * ($invoiceItem->tax_rate / 100);
$taxAmount = round($taxAmount, 2);
if ($taxAmount) {
$key = $invoiceItem->tax_name.$invoiceItem->tax_rate;
if ( ! isset($taxes[$key])) {
$taxes[$key] = [
'amount' => 0,
'paid' => 0
];
}
$taxes[$key]['amount'] += $taxAmount;
$taxes[$key]['paid'] += $this->amount && $taxAmount ? round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) : 0;
$taxes[$key]['name'] = $invoiceItem->tax_name;
$taxes[$key]['rate'] = $invoiceItem->tax_rate;
}
}
return $taxes;
}
} }
Invoice::creating(function ($invoice) { Invoice::creating(function ($invoice) {

View File

@ -1,5 +1,6 @@
<?php namespace App\Models; <?php namespace App\Models;
use Auth;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends EntityModel class Product extends EntityModel
@ -21,4 +22,8 @@ class Product extends EntityModel
{ {
return $this->belongsTo('App\Models\TaxRate'); return $this->belongsTo('App\Models\TaxRate');
} }
public function canEdit() {
return Auth::user()->hasPermission('admin');
}
} }

View File

@ -16,4 +16,8 @@ class TaxRate extends EntityModel
{ {
return ENTITY_TAX_RATE; return ENTITY_TAX_RATE;
} }
public function canEdit() {
return Auth::user()->hasPermission('admin');
}
} }

View File

@ -14,7 +14,12 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract { class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
public static $all_permissions = array(
'create_all' => 0b0001,
'view_all' => 0b0010,
'edit_all' => 0b0100,
);
use Authenticatable, CanResetPassword; use Authenticatable, CanResetPassword;
/** /**
@ -253,7 +258,69 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
&& $this->email != $this->getOriginal('email') && $this->email != $this->getOriginal('email')
&& $this->getOriginal('confirmed'); && $this->getOriginal('confirmed');
} }
/**
* Set the permissions attribute on the model.
*
* @param mixed $value
* @return $this
*/
protected function setPermissionsAttribute($value){
if(empty($value)) {
$this->attributes['permissions'] = 0;
} else {
$bitmask = 0;
foreach($value as $permission){
$bitmask = $bitmask | static::$all_permissions[$permission];
}
$this->attributes['permissions'] = $bitmask;
}
return $this;
}
/**
* Expands the value of the permissions attribute
*
* @param mixed $value
* @return mixed
*/
protected function getPermissionsAttribute($value){
$permissions = array();
foreach(static::$all_permissions as $permission => $bitmask){
if(($value & $bitmask) == $bitmask) {
$permissions[$permission] = $permission;
}
}
return $permissions;
}
/**
* Checks to see if the user has the required permission
*
* @param mixed $permission Either a single permission or an array of possible permissions
* @param boolean True to require all permissions, false to require only one
* @return boolean
*/
public function hasPermission($permission, $requireAll = false){
if ($this->is_admin) {
return true;
} else if(is_string($permission)){
return !empty($this->permissions[$permission]);
} else if(is_array($permission)) {
if($requireAll){
return count(array_diff($permission, $this->permissions)) == 0;
} else {
return count(array_intersect($permission, $this->permissions)) > 0;
}
}
return false;
}
} }
User::updating(function ($user) { User::updating(function ($user) {

View File

@ -5,6 +5,7 @@ use DB;
use Carbon; use Carbon;
use App\Events\VendorWasCreated; use App\Events\VendorWasCreated;
use App\Events\VendorWasUpdated; use App\Events\VendorWasUpdated;
use App\Events\VendorWasDeleted;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;

View File

@ -1,6 +1,6 @@
<?php namespace App\Ninja\Mailers; <?php namespace App\Ninja\Mailers;
use HTML; use Form;
use Utils; use Utils;
use Event; use Event;
use URL; use URL;
@ -27,6 +27,7 @@ class ContactMailer extends Mailer
'firstName', 'firstName',
'invoice', 'invoice',
'quote', 'quote',
'password',
'viewLink', 'viewLink',
'viewButton', 'viewButton',
'paymentLink', 'paymentLink',
@ -109,6 +110,13 @@ class ContactMailer extends Mailer
'invitation' => $invitation, 'invitation' => $invitation,
'amount' => $invoice->getRequestedAmount() 'amount' => $invoice->getRequestedAmount()
]; ];
if (empty($invitation->contact->password) && $account->isPro() && $account->enable_portal_password && $account->send_portal_password) {
// The contact needs a password
$variables['password'] = $password = $this->generatePassword();
$invitation->contact->password = bcrypt($password);
$invitation->contact->save();
}
$data = [ $data = [
'body' => $this->processVariables($body, $variables), 'body' => $this->processVariables($body, $variables),
@ -143,6 +151,28 @@ class ContactMailer extends Mailer
return $response; return $response;
} }
} }
protected function generatePassword($length = 9)
{
$sets = array(
'abcdefghjkmnpqrstuvwxyz',
'ABCDEFGHJKMNPQRSTUVWXYZ',
'23456789',
);
$all = '';
$password = '';
foreach($sets as $set)
{
$password .= $set[array_rand(str_split($set))];
$all .= $set;
}
$all = str_split($all);
for($i = 0; $i < $length - count($sets); $i++)
$password .= $all[array_rand($all)];
$password = str_shuffle($password);
return $password;
}
public function sendPaymentConfirmation(Payment $payment) public function sendPaymentConfirmation(Payment $payment)
{ {
@ -232,6 +262,7 @@ class ContactMailer extends Mailer
$client = $data['client']; $client = $data['client'];
$invitation = $data['invitation']; $invitation = $data['invitation'];
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$variables = [ $variables = [
'$footer' => $account->getEmailFooter(), '$footer' => $account->getEmailFooter(),
@ -245,10 +276,11 @@ class ContactMailer extends Mailer
'$invoice' => $invoice->invoice_number, '$invoice' => $invoice->invoice_number,
'$quote' => $invoice->invoice_number, '$quote' => $invoice->invoice_number,
'$link' => $invitation->getLink(), '$link' => $invitation->getLink(),
'$viewLink' => $invitation->getLink(), '$password' => $passwordHTML,
'$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()), '$viewLink' => $invitation->getLink().'$password',
'$paymentLink' => $invitation->getLink('payment'), '$viewButton' => Form::emailViewButton($invitation->getLink(), $invoice->getEntityType()).'$password',
'$paymentButton' => HTML::emailPaymentButton($invitation->getLink('payment')), '$paymentLink' => $invitation->getLink('payment').'$password',
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
'$customClient1' => $account->custom_client_label1, '$customClient1' => $account->custom_client_label1,
'$customClient2' => $account->custom_client_label2, '$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1, '$customInvoice1' => $account->custom_invoice_text_label1,
@ -260,10 +292,21 @@ class ContactMailer extends Mailer
$camelType = Gateway::getPaymentTypeName($type); $camelType = Gateway::getPaymentTypeName($type);
$type = Utils::toSnakeCase($camelType); $type = Utils::toSnakeCase($camelType);
$variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}"; $variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}";
$variables["\${$camelType}Button"] = HTML::emailPaymentButton($invitation->getLink('payment') . "/{$type}"); $variables["\${$camelType}Button"] = Form::emailPaymentButton($invitation->getLink('payment') . "/{$type}");
} }
$includesPasswordPlaceholder = strpos($template, '$password') !== false;
$str = str_replace(array_keys($variables), array_values($variables), $template); $str = str_replace(array_keys($variables), array_values($variables), $template);
if(!$includesPasswordPlaceholder && $passwordHTML){
$pos = strrpos($str, '$password');
if($pos !== false)
{
$str = substr_replace($str, $passwordHTML, $pos, 9/* length of "$password" */);
}
}
$str = str_replace('$password', '', $str);
$str = autolink($str, 100); $str = autolink($str, 100);
return $str; return $str;

View File

@ -81,7 +81,7 @@ class Mailer
$emailError = $exception->getMessage(); $emailError = $exception->getMessage();
} }
Utils::logError("Email Error: $emailError"); //Utils::logError("Email Error: $emailError");
if (isset($data['invitation'])) { if (isset($data['invitation'])) {
$invitation = $data['invitation']; $invitation = $data['invitation'];

View File

@ -0,0 +1,96 @@
<?php
namespace App\Ninja\Notifications;
use Davibennun\LaravelPushNotification\Facades\PushNotification;
use Illuminate\Http\Request;
/**
* Class PushFactory
* @package App\Ninja\Notifications
*/
class PushFactory
{
/**
* PushFactory constructor.
*
* @param $this->certificate - Development or production.
*
* Static variables defined in routes.php
*
* IOS_PRODUCTION_PUSH
* IOS_DEV_PUSH
*/
public function __construct()
{
$this->certificate = IOS_DEV_PUSH;
}
/**
* customMessage function
*
* Send a message with a nested custom payload to perform additional trickery within application
*
* @access public
*
* @param $token
* @param $message
* @param $messageArray
*
* @return void
*/
public function customMessage($token, $message, $messageArray)
{
$customMessage = PushNotification::Message($message, $messageArray);
$this->message($token, $customMessage);
}
/**
* message function
*
* Send a plain text only message to a single device.
*
* @access public
*
* @param $token - device token
* @param $message - user specific message
*
* @return void
*
*/
public function message($token, $message)
{
PushNotification::app($this->certificate)
->to($token)
->send($message);
}
/**
* getFeedback function
*
* Returns an array of expired/invalid tokens to be removed from iOS PUSH notifications.
*
* We need to run this once ~ 24hrs
*
* @access public
*
* @param string $token - A valid token (can be any valid token)
* @param string $message - Nil value for message
*
* @return array
*/
public function getFeedback($token, $message = '')
{
$feedback = PushNotification::app($this->certificate)
->to($token)
->send($message);
return $feedback->getFeedback();
}
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters; <?php namespace App\Ninja\Presenters;
use URL;
use Utils; use Utils;
use Laracasts\Presenter\Presenter; use Laracasts\Presenter\Presenter;
@ -26,6 +27,15 @@ class ClientPresenter extends Presenter {
} }
return "<span class=\"label label-{$class}\">{$text}</span>"; return "<span class=\"label label-{$class}\">{$text}</span>";
}
public function url()
{
return URL::to('/clients/' . $this->entity->public_id);
}
public function link()
{
return link_to('/clients/' . $this->entity->public_id, $this->entity->getDisplayName());
} }
} }

View File

@ -20,4 +20,14 @@ class ExpensePresenter extends Presenter {
{ {
return round($this->entity->amount * $this->entity->exchange_rate, 2); return round($this->entity->amount * $this->entity->exchange_rate, 2);
} }
public function invoiced_amount()
{
return $this->entity->invoice_id ? $this->converted_amount() : 0;
}
public function link()
{
return link_to('/expenses/' . $this->entity->public_id, $this->entity->name);
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters; <?php namespace App\Ninja\Presenters;
use URL;
use Utils; use Utils;
use Laracasts\Presenter\Presenter; use Laracasts\Presenter\Presenter;
@ -18,7 +19,7 @@ class InvoicePresenter extends Presenter {
public function balanceDueLabel() public function balanceDueLabel()
{ {
if ($this->entity->partial) { if ($this->entity->partial) {
return 'amount_due'; return 'partial_due';
} elseif ($this->entity->is_quote) { } elseif ($this->entity->is_quote) {
return 'total'; return 'total';
} else { } else {
@ -40,9 +41,15 @@ class InvoicePresenter extends Presenter {
public function status() public function status()
{ {
$status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft'; if ($this->entity->is_deleted) {
$status = strtolower($status); return trans('texts.deleted');
return trans("texts.status_{$status}"); } elseif ($this->entity->trashed()) {
return trans('texts.archived');
} else {
$status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft';
$status = strtolower($status);
return trans("texts.status_{$status}");
}
} }
public function invoice_date() public function invoice_date()
@ -55,4 +62,24 @@ class InvoicePresenter extends Presenter {
return Utils::fromSqlDate($this->entity->due_date); return Utils::fromSqlDate($this->entity->due_date);
} }
public function frequency()
{
return $this->entity->frequency ? $this->entity->frequency->name : '';
}
public function url()
{
return URL::to('/invoices/' . $this->entity->public_id);
}
public function link()
{
return link_to('/invoices/' . $this->entity->public_id, $this->entity->invoice_number);
}
public function email()
{
$client = $this->entity->client;
return count($client->contacts) ? $client->contacts[0]->email : '';
}
} }

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Presenters; <?php namespace App\Ninja\Presenters;
use URL;
use Utils; use Utils;
use Laracasts\Presenter\Presenter; use Laracasts\Presenter\Presenter;
@ -24,4 +25,14 @@ class PaymentPresenter extends Presenter {
} }
} }
public function url()
{
return URL::to('/payments/' . $this->entity->public_id . '/edit');
}
public function link()
{
return link_to('/payments/' . $this->entity->public_id . '/edit', $this->entity->getDisplayName());
}
} }

View File

@ -9,4 +9,9 @@ class VendorPresenter extends Presenter {
{ {
return $this->entity->country ? $this->entity->country->name : ''; return $this->entity->country ? $this->entity->country->name : '';
} }
public function link()
{
return link_to('/vendors/' . $this->entity->public_id, $this->entity->name);
}
} }

View File

@ -5,6 +5,7 @@ use Request;
use Session; use Session;
use Utils; use Utils;
use DB; use DB;
use URL;
use stdClass; use stdClass;
use Validator; use Validator;
use Schema; use Schema;
@ -58,7 +59,7 @@ class AccountRepository
} }
$user->confirmed = !Utils::isNinja(); $user->confirmed = !Utils::isNinja();
$user->registered = !Utils::isNinja() && $user->email; $user->registered = !Utils::isNinja() || $email;
if (!$user->confirmed) { if (!$user->confirmed) {
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
@ -69,49 +70,135 @@ class AccountRepository
return $account; return $account;
} }
public function getSearchData() public function getSearchData($account)
{ {
$clients = \DB::table('clients') $data = $this->getAccountSearchData($account);
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("clients.name <> ''")
->select(\DB::raw("'clients' as type, '" . trans('texts.clients') . "' as trans_type, clients.public_id, clients.name, '' as token"));
$contacts = \DB::table('clients') $data['navigation'] = $this->getNavigationSearchData();
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''")
->select(\DB::raw("'clients' as type, '" . trans('texts.contacts') . "' as trans_type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token"));
$invoices = \DB::table('clients') return $data;
->join('invoices', 'invoices.client_id', '=', 'clients.id') }
->where('clients.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null)
->select(\DB::raw("'invoices' as type, '" . trans('texts.invoices') . "' as trans_type, invoices.public_id, CONCAT(invoices.invoice_number, ': ', clients.name) as name, invoices.invoice_number as token"));
$data = []; private function getAccountSearchData($account)
{
$data = [
'clients' => [],
'contacts' => [],
'invoices' => [],
'quotes' => [],
];
foreach ($clients->union($contacts)->union($invoices)->get() as $row) { // include custom client fields in search
$type = $row->trans_type; if ($account->custom_client_label1) {
$data[$account->custom_client_label1] = [];
}
if ($account->custom_client_label2) {
$data[$account->custom_client_label2] = [];
}
if (!isset($data[$type])) { $clients = Client::scope()
$data[$type] = []; ->with('contacts', 'invoices')
->get();
foreach ($clients as $client) {
if ($client->name) {
$data['clients'][] = [
'value' => $client->name,
'tokens' => $client->name,
'url' => $client->present()->url,
];
}
if ($client->custom_value1) {
$data[$account->custom_client_label1][] = [
'value' => "{$client->custom_value1}: " . $client->getDisplayName(),
'tokens' => $client->custom_value1,
'url' => $client->present()->url,
];
}
if ($client->custom_value2) {
$data[$account->custom_client_label2][] = [
'value' => "{$client->custom_value2}: " . $client->getDisplayName(),
'tokens' => $client->custom_value2,
'url' => $client->present()->url,
];
} }
$tokens = explode(' ', $row->name); foreach ($client->contacts as $contact) {
$tokens[] = $type; if ($contact->getFullName()) {
$data['contacts'][] = [
if ($type == 'Invoices') { 'value' => $contact->getDisplayName(),
$tokens[] = intVal($row->token).''; 'tokens' => $contact->getDisplayName(),
'url' => $client->present()->url,
];
}
if ($contact->email) {
$data['contacts'][] = [
'value' => $contact->email,
'tokens' => $contact->email,
'url' => $client->present()->url,
];
}
} }
$data[$type][] = [ foreach ($client->invoices as $invoice) {
'value' => $row->name, $entityType = $invoice->getEntityType();
'public_id' => $row->public_id, $data["{$entityType}s"][] = [
'tokens' => $tokens, 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(),
'entity_type' => $row->type, 'tokens' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(),
'url' => $invoice->present()->url,
];
}
}
return $data;
}
private function getNavigationSearchData()
{
$entityTypes = [
ENTITY_INVOICE,
ENTITY_CLIENT,
ENTITY_QUOTE,
ENTITY_TASK,
ENTITY_EXPENSE,
ENTITY_RECURRING_INVOICE,
ENTITY_PAYMENT,
ENTITY_CREDIT
];
foreach ($entityTypes as $entityType) {
$features[] = [
"new_{$entityType}",
"/{$entityType}s/create",
];
$features[] = [
"list_{$entityType}s",
"/{$entityType}s",
];
}
$features[] = ['dashboard', '/dashboard'];
$features[] = ['customize_design', '/settings/customize_design'];
$features[] = ['new_tax_rate', '/tax_rates/create'];
$features[] = ['new_product', '/products/create'];
$features[] = ['new_user', '/users/create'];
$features[] = ['custom_fields', '/settings/invoice_settings'];
$settings = array_merge(Account::$basicSettings, Account::$advancedSettings);
foreach ($settings as $setting) {
$features[] = [
$setting,
"/settings/{$setting}",
];
}
foreach ($features as $feature) {
$data[] = [
'value' => trans('texts.' . $feature[0]),
'tokens' => trans('texts.' . $feature[0]),
'url' => URL::to($feature[1])
]; ];
} }
@ -127,8 +214,10 @@ class AccountRepository
$account = Auth::user()->account; $account = Auth::user()->account;
$client = $this->getNinjaClient($account); $client = $this->getNinjaClient($account);
$invitation = $this->createNinjaInvoice($client, $account); $invitation = $this->createNinjaInvoice($client, $account);
return $invitation; return $invitation;
} }
public function createNinjaInvoice($client, $clientAccount) public function createNinjaInvoice($client, $clientAccount)
{ {
$account = $this->getNinjaAccount(); $account = $this->getNinjaAccount();

View File

@ -1,6 +1,7 @@
<?php namespace App\Ninja\Repositories; <?php namespace App\Ninja\Repositories;
use DB; use DB;
use Cache;
use App\Ninja\Repositories\BaseRepository; use App\Ninja\Repositories\BaseRepository;
use App\Models\Client; use App\Models\Client;
use App\Models\Contact; use App\Models\Contact;
@ -45,7 +46,8 @@ class ClientRepository extends BaseRepository
'clients.work_phone', 'clients.work_phone',
'contacts.email', 'contacts.email',
'clients.deleted_at', 'clients.deleted_at',
'clients.is_deleted' 'clients.is_deleted',
'clients.user_id'
); );
if (!\Session::get('show_trash:client')) { if (!\Session::get('show_trash:client')) {
@ -74,6 +76,17 @@ class ClientRepository extends BaseRepository
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = Client::scope($publicId)->with('contacts')->firstOrFail();
} }
// convert currency code to id
if (isset($data['currency_code'])) {
$currencyCode = strtolower($data['currency_code']);
$currency = Cache::get('currencies')->filter(function($item) use ($currencyCode) {
return strtolower($item->code) == $currencyCode;
})->first();
if ($currency) {
$data['currency_id'] = $currency->id;
}
}
$client->fill($data); $client->fill($data);
$client->save(); $client->save();

View File

@ -29,6 +29,7 @@ class CreditRepository extends BaseRepository
'credits.public_id', 'credits.public_id',
'clients.name as client_name', 'clients.name as client_name',
'clients.public_id as client_public_id', 'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'credits.amount', 'credits.amount',
'credits.balance', 'credits.balance',
'credits.credit_date', 'credits.credit_date',
@ -37,7 +38,8 @@ class CreditRepository extends BaseRepository
'contacts.email', 'contacts.email',
'credits.private_notes', 'credits.private_notes',
'credits.deleted_at', 'credits.deleted_at',
'credits.is_deleted' 'credits.is_deleted',
'credits.user_id'
); );
if ($clientPublicId) { if ($clientPublicId) {

View File

@ -40,7 +40,8 @@ class ExpenseRepository extends BaseRepository
'expenses.public_id', 'expenses.public_id',
'expenses.deleted_at', 'expenses.deleted_at',
'expenses.should_be_invoiced', 'expenses.should_be_invoiced',
'expenses.created_at' 'expenses.created_at',
'expenses.user_id'
); );
return $query; return $query;
@ -80,11 +81,15 @@ class ExpenseRepository extends BaseRepository
'expenses.vendor_id', 'expenses.vendor_id',
'expenses.expense_currency_id', 'expenses.expense_currency_id',
'expenses.invoice_currency_id', 'expenses.invoice_currency_id',
'expenses.user_id',
'invoices.public_id as invoice_public_id', 'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'vendors.name as vendor_name', 'vendors.name as vendor_name',
'vendors.public_id as vendor_public_id', 'vendors.public_id as vendor_public_id',
'vendors.user_id as vendor_user_id',
'clients.name as client_name', 'clients.name as client_name',
'clients.public_id as client_public_id', 'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'contacts.first_name', 'contacts.first_name',
'contacts.email', 'contacts.email',
'contacts.last_name', 'contacts.last_name',

View File

@ -49,6 +49,7 @@ class InvoiceRepository extends BaseRepository
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'),
'clients.public_id as client_public_id', 'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'invoice_number', 'invoice_number',
'invoice_status_id', 'invoice_status_id',
'clients.name as client_name', 'clients.name as client_name',
@ -65,7 +66,8 @@ class InvoiceRepository extends BaseRepository
'invoices.quote_invoice_id', 'invoices.quote_invoice_id',
'invoices.deleted_at', 'invoices.deleted_at',
'invoices.is_deleted', 'invoices.is_deleted',
'invoices.partial' 'invoices.partial',
'invoices.user_id'
); );
if (!\Session::get('show_trash:'.$entityType)) { if (!\Session::get('show_trash:'.$entityType)) {
@ -170,7 +172,7 @@ class InvoiceRepository extends BaseRepository
); );
$table = \Datatable::query($query) $table = \Datatable::query($query)
->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); }) ->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml(); })
->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); }) ->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }); ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); });
@ -189,7 +191,7 @@ class InvoiceRepository extends BaseRepository
->make(); ->make();
} }
public function save($data) public function save($data, $checkSubPermissions = false)
{ {
$account = \Auth::user()->account; $account = \Auth::user()->account;
$publicId = isset($data['public_id']) ? $data['public_id'] : false; $publicId = isset($data['public_id']) ? $data['public_id'] : false;
@ -398,36 +400,47 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $item) { foreach ($data['invoice_items'] as $item) {
$item = (array) $item; $item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) { if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) {
continue; continue;
} }
$task = false; $task = false;
if (isset($item['task_public_id']) && $item['task_public_id']) { if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail(); $task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$task->invoice_id = $invoice->id; if(!$checkSubPermissions || $task->canEdit()){
$task->client_id = $invoice->client_id; $task->invoice_id = $invoice->id;
$task->save(); $task->client_id = $invoice->client_id;
$task->save();
}
} }
$expense = false; $expense = false;
if (isset($item['expense_public_id']) && $item['expense_public_id']) { if (isset($item['expense_public_id']) && $item['expense_public_id']) {
$expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail(); $expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$expense->invoice_id = $invoice->id; if(!$checkSubPermissions || $expense->canEdit()){
$expense->client_id = $invoice->client_id; $expense->invoice_id = $invoice->id;
$expense->save(); $expense->client_id = $invoice->client_id;
$expense->save();
}
} }
if ($productKey = trim($item['product_key'])) { if ($productKey = trim($item['product_key'])) {
if (\Auth::user()->account->update_products && ! strtotime($productKey)) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) {
$product = Product::findProductByKey($productKey); $product = Product::findProductByKey($productKey);
if (!$product) { if (!$product) {
$product = Product::createNew(); if(!$checkSubPermissions || Product::canCreate()){
$product->product_key = trim($item['product_key']); $product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
else{
$product = null;
}
}
if($product && (!$checkSubPermissions || $product->canEdit())){
$product->notes = ($task || $expense) ? '' : $item['notes'];
$product->cost = $expense ? 0 : $item['cost'];
$product->save();
} }
$product->notes = ($task || $expense) ? '' : $item['notes'];
$product->cost = $expense ? 0 : $item['cost'];
$product->save();
} }
} }
@ -439,6 +452,13 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->qty = Utils::parseFloat($item['qty']); $invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0; $invoiceItem->tax_rate = 0;
if (isset($item['custom_value1'])) {
$invoiceItem->custom_value1 = $item['custom_value1'];
}
if (isset($item['custom_value2'])) {
$invoiceItem->custom_value2 = $item['custom_value2'];
}
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) { if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']); $invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']); $invoiceItem['tax_name'] = trim($item['tax_name']);
@ -603,7 +623,7 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::createNew($recurInvoice); $invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id; $invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id; $invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber($recurInvoice); $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice);
$invoice->amount = $recurInvoice->amount; $invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d'); $invoice->invoice_date = date_create()->format('Y-m-d');

View File

@ -36,9 +36,11 @@ class PaymentRepository extends BaseRepository
'payments.transaction_reference', 'payments.transaction_reference',
'clients.name as client_name', 'clients.name as client_name',
'clients.public_id as client_public_id', 'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'payments.amount', 'payments.amount',
'payments.payment_date', 'payments.payment_date',
'invoices.public_id as invoice_public_id', 'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'invoices.invoice_number', 'invoices.invoice_number',
'contacts.first_name', 'contacts.first_name',
'contacts.last_name', 'contacts.last_name',
@ -47,6 +49,7 @@ class PaymentRepository extends BaseRepository
'payments.account_gateway_id', 'payments.account_gateway_id',
'payments.deleted_at', 'payments.deleted_at',
'payments.is_deleted', 'payments.is_deleted',
'payments.user_id',
'invoices.is_deleted as invoice_is_deleted', 'invoices.is_deleted as invoice_is_deleted',
'gateways.name as gateway_name' 'gateways.name as gateway_name'
); );
@ -116,7 +119,7 @@ class PaymentRepository extends BaseRepository
public function save($input) public function save($input)
{ {
$publicId = isset($input['public_id']) ? $input['public_id'] : false; $publicId = isset($input['public_id']) ? $input['public_id'] : false;
if ($publicId) { if ($publicId) {
$payment = Payment::scope($publicId)->firstOrFail(); $payment = Payment::scope($publicId)->firstOrFail();
} else { } else {
@ -136,7 +139,7 @@ class PaymentRepository extends BaseRepository
} else { } else {
$payment->payment_date = date('Y-m-d'); $payment->payment_date = date('Y-m-d');
} }
if (isset($input['transaction_reference'])) { if (isset($input['transaction_reference'])) {
$payment->transaction_reference = trim($input['transaction_reference']); $payment->transaction_reference = trim($input['transaction_reference']);
} }

View File

@ -27,6 +27,7 @@ class TaskRepository
'tasks.public_id', 'tasks.public_id',
'clients.name as client_name', 'clients.name as client_name',
'clients.public_id as client_public_id', 'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'contacts.first_name', 'contacts.first_name',
'contacts.email', 'contacts.email',
'contacts.last_name', 'contacts.last_name',
@ -36,9 +37,11 @@ class TaskRepository
'tasks.deleted_at', 'tasks.deleted_at',
'invoices.invoice_number', 'invoices.invoice_number',
'invoices.public_id as invoice_public_id', 'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'tasks.is_running', 'tasks.is_running',
'tasks.time_log', 'tasks.time_log',
'tasks.created_at' 'tasks.created_at',
'tasks.user_id'
); );
if ($clientPublicId) { if ($clientPublicId) {

View File

@ -22,7 +22,7 @@ class UserRepository extends BaseRepository
$query->where('users.deleted_at', '=', null); $query->where('users.deleted_at', '=', null);
} }
$query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at'); $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at', 'users.is_admin', 'users.permissions');
return $query; return $query;
} }
@ -34,5 +34,4 @@ class UserRepository extends BaseRepository
return $user; return $user;
} }
} }

View File

@ -42,7 +42,8 @@ class VendorRepository extends BaseRepository
'vendors.city', 'vendors.city',
'vendor_contacts.email', 'vendor_contacts.email',
'vendors.deleted_at', 'vendors.deleted_at',
'vendors.is_deleted' 'vendors.is_deleted',
'vendors.user_id'
); );
if (!\Session::get('show_trash:vendor')) { if (!\Session::get('show_trash:vendor')) {

View File

@ -21,7 +21,11 @@ class UserTransformer extends EntityTransformer
'registered' => (bool) $user->registered, 'registered' => (bool) $user->registered,
'confirmed' => (bool) $user->confirmed, 'confirmed' => (bool) $user->confirmed,
'oauth_user_id' => $user->oauth_user_id, 'oauth_user_id' => $user->oauth_user_id,
'oauth_provider_id' => $user->oauth_provider_id 'oauth_provider_id' => $user->oauth_provider_id,
'notify_sent' => (bool) $user->notify_sent,
'notify_viewed' => (bool) $user->notify_viewed,
'notify_paid' => (bool) $user->notify_paid,
'notify_approved' => (bool) $user->notify_approved,
]; ];
} }
} }

View File

@ -4,9 +4,13 @@ use Session;
use Auth; use Auth;
use Utils; use Utils;
use HTML; use HTML;
use Form;
use URL; use URL;
use Request; use Request;
use Validator; use Validator;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Vendor;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider { class AppServiceProvider extends ServiceProvider {
@ -18,62 +22,74 @@ class AppServiceProvider extends ServiceProvider {
*/ */
public function boot() public function boot()
{ {
HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { Form::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
});
Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$capitalize = config('former.capitalize_translations');
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2.'/*') ) ? ' class="active"' : ''; $class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2.'/*') ) ? ' class="active"' : '';
$title = ucwords(trans("texts.$text")) . Utils::getProLabel($text); if ($capitalize) {
$title = ucwords(trans("texts.$text")) . Utils::getProLabel($text);
} else {
$title = trans("texts.$text") . Utils::getProLabel($text);
}
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$title.'</a></li>'; return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$title.'</a></li>';
}); });
HTML::macro('tab_link', function($url, $text, $active = false) { Form::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : ''; $class = $active ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>'; return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>';
}); });
HTML::macro('menu_link', function($type) { Form::macro('menu_link', function($type) {
$types = $type.'s'; $types = $type.'s';
$Type = ucfirst($type); $Type = ucfirst($type);
$Types = ucfirst($types); $Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : ''; $class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : '';
$str = '<li class="dropdown '.$class.'"> $str = '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a> <a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>';
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>'; $items = [];
if(Auth::user()->hasPermission('create_all')){
$items[] = '<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
}
if ($type == ENTITY_INVOICE) { if ($type == ENTITY_INVOICE) {
$str .= '<li class="divider"></li> if(!empty($items))$items[] = '<li class="divider"></li>';
<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</a></li> $items[] = '<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</a></li>';
<li><a href="'.URL::to('recurring_invoices/create').'">'.trans("texts.new_recurring_invoice").'</a></li>'; if(Invoice::canCreate())$items[] = '<li><a href="'.URL::to('recurring_invoices/create').'">'.trans("texts.new_recurring_invoice").'</a></li>';
if (Auth::user()->isPro()) { if (Auth::user()->isPro()) {
$str .= '<li class="divider"></li> $items[] = '<li class="divider"></li>';
<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li> $items[] = '<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>';
<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>'; if(Invoice::canCreate())$items[] = '<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
} }
} else if ($type == ENTITY_CLIENT) { } else if ($type == ENTITY_CLIENT) {
$str .= '<li class="divider"></li> if(!empty($items))$items[] = '<li class="divider"></li>';
<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li> $items[] = '<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li>';
<li><a href="'.URL::to('credits/create').'">'.trans("texts.new_credit").'</a></li>'; if(Credit::canCreate())$items[] = '<li><a href="'.URL::to('credits/create').'">'.trans("texts.new_credit").'</a></li>';
} else if ($type == ENTITY_EXPENSE) { } else if ($type == ENTITY_EXPENSE) {
$str .= '<li class="divider"></li> if(!empty($items))$items[] = '<li class="divider"></li>';
<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li> $items[] = '<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li>';
<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>'; if(Vendor::canCreate())$items[] = '<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>';
} }
if(!empty($items)){
$str.= '<ul class="dropdown-menu" id="menu1">'.implode($items).'</ul>';
}
$str .= '</ul> $str .= '</li>';
</li>';
return $str; return $str;
}); });
HTML::macro('image_data', function($imagePath) { Form::macro('flatButton', function($label, $color) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
});
HTML::macro('flatButton', function($label, $color) {
return '<input type="button" value="' . trans("texts.{$label}") . '" style="background-color:' . $color . ';border:0 none;border-radius:5px;padding:12px 40px;margin:0 6px;cursor:hand;display:inline-block;font-size:14px;color:#fff;text-transform:none;font-weight:bold;"/>'; return '<input type="button" value="' . trans("texts.{$label}") . '" style="background-color:' . $color . ';border:0 none;border-radius:5px;padding:12px 40px;margin:0 6px;cursor:hand;display:inline-block;font-size:14px;color:#fff;text-transform:none;font-weight:bold;"/>';
}); });
HTML::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) { Form::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) {
return view('partials.email_button') return view('partials.email_button')
->with([ ->with([
'link' => $link, 'link' => $link,
@ -83,7 +99,7 @@ class AppServiceProvider extends ServiceProvider {
->render(); ->render();
}); });
HTML::macro('emailPaymentButton', function($link = '#') { Form::macro('emailPaymentButton', function($link = '#') {
return view('partials.email_button') return view('partials.email_button')
->with([ ->with([
'link' => $link, 'link' => $link,
@ -93,7 +109,7 @@ class AppServiceProvider extends ServiceProvider {
->render(); ->render();
}); });
HTML::macro('breadcrumbs', function($status = false) { Form::macro('breadcrumbs', function($status = false) {
$str = '<ol class="breadcrumb">'; $str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path. // Get the breadcrumbs by exploding the current path.
@ -135,7 +151,7 @@ class AppServiceProvider extends ServiceProvider {
return $str . '</ol>'; return $str . '</ol>';
}); });
Validator::extend('positive', function($attribute, $value, $parameters) { Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0; return Utils::parseFloat($value) >= 0;
}); });
@ -223,11 +239,6 @@ class AppServiceProvider extends ServiceProvider {
'Illuminate\Contracts\Auth\Registrar', 'Illuminate\Contracts\Auth\Registrar',
'App\Services\Registrar' 'App\Services\Registrar'
); );
$this->app->bind(
'App\Ninja\Import\DataImporterServiceInterface',
'App\Ninja\Import\FreshBooks\FreshBooksDataImporterService'
);
} }
} }

View File

@ -41,7 +41,7 @@ class AccountGatewayService extends BaseService
[ [
'name', 'name',
function ($model) { function ($model) {
return link_to("gateways/{$model->public_id}/edit", $model->name); return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml();
} }
], ],
[ [

View File

@ -38,11 +38,11 @@ class ActivityService extends BaseService
'activity_type_id', 'activity_type_id',
function ($model) { function ($model) {
$data = [ $data = [
'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)), 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml(),
'user' => $model->is_system ? '<i>' . trans('texts.system') . '</i>' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), 'user' => $model->is_system ? '<i>' . trans('texts.system') . '</i>' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice) : null, 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice)->toHtml() : null,
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice) : null, 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null,
'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)) : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
'payment' => $model->payment ?: '', 'payment' => $model->payment ?: '',
'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) 'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id)
]; ];

View File

@ -4,7 +4,6 @@ use stdClass;
use Utils; use Utils;
use URL; use URL;
use Hash; use Hash;
use App\Models\Gateway;
use App\Models\BankSubaccount; use App\Models\BankSubaccount;
use App\Models\Vendor; use App\Models\Vendor;
use App\Models\Expense; use App\Models\Expense;
@ -37,7 +36,7 @@ class BankAccountService extends BaseService
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true) public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
{ {
if ( ! $bankId || ! $username || ! $password) { if (! $bankId || ! $username || ! $password) {
return false; return false;
} }
@ -47,12 +46,13 @@ class BankAccountService extends BaseService
->withTrashed() ->withTrashed()
->get(['transaction_id']) ->get(['transaction_id'])
->toArray(); ->toArray();
$expenses = array_flip(array_map(function($val) { $expenses = array_flip(array_map(function ($val) {
return $val['transaction_id']; return $val['transaction_id'];
}, $expenses)); }, $expenses));
$vendorMap = $this->createVendorMap();
$bankAccounts = BankSubaccount::scope() $bankAccounts = BankSubaccount::scope()
->whereHas('bank_account', function($query) use ($bankId) { ->whereHas('bank_account', function ($query) use ($bankId) {
$query->where('bank_id', '=', $bankId); $query->where('bank_id', '=', $bankId);
}) })
->get(); ->get();
@ -64,13 +64,13 @@ class BankAccountService extends BaseService
$finance = new Finance(); $finance = new Finance();
$finance->banks[$bankId] = $bank->getOFXBank($finance); $finance->banks[$bankId] = $bank->getOFXBank($finance);
$finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password); $finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password);
foreach ($finance->banks as $bank) { foreach ($finance->banks as $bank) {
foreach ($bank->logins as $login) { foreach ($bank->logins as $login) {
$login->setup(); $login->setup();
foreach ($login->accounts as $account) { foreach ($login->accounts as $account) {
$account->setup($includeTransactions); $account->setup($includeTransactions);
if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)) { if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)) {
$data[] = $account; $data[] = $account;
} }
} }
@ -83,9 +83,9 @@ class BankAccountService extends BaseService
} }
} }
private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions) private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)
{ {
$obj = new stdClass; $obj = new stdClass();
$obj->account_name = ''; $obj->account_name = '';
// look up bank account name // look up bank account name
@ -106,7 +106,7 @@ class BankAccountService extends BaseService
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR); $obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
if ($includeTransactions) { if ($includeTransactions) {
$ofxParser = new \OfxParser\Parser; $ofxParser = new \OfxParser\Parser();
$ofx = $ofxParser->loadFromString($account->response); $ofx = $ofxParser->loadFromString($account->response);
$obj->start_date = $ofx->BankAccount->Statement->startDate; $obj->start_date = $ofx->BankAccount->Statement->startDate;
@ -121,7 +121,13 @@ class BankAccountService extends BaseService
if ($transaction->amount >= 0) { if ($transaction->amount >= 0) {
continue; continue;
} }
$transaction->vendor = $this->prepareValue(substr($transaction->name, 0, 20));
// if vendor has already been imported use current name
$vendorName = trim(substr($transaction->name, 0, 20));
$key = strtolower($vendorName);
$vendor = isset($vendorMap[$key]) ? $vendorMap[$key] : null;
$transaction->vendor = $vendor ? $vendor->name : $this->prepareValue($vendorName);
$transaction->info = $this->prepareValue(substr($transaction->name, 20)); $transaction->info = $this->prepareValue(substr($transaction->name, 20));
$transaction->memo = $this->prepareValue($transaction->memo); $transaction->memo = $this->prepareValue($transaction->memo);
$transaction->date = \Auth::user()->account->formatDate($transaction->date); $transaction->date = \Auth::user()->account->formatDate($transaction->date);
@ -133,15 +139,13 @@ class BankAccountService extends BaseService
return $obj; return $obj;
} }
private function prepareValue($value) { private function prepareValue($value)
{
return ucwords(strtolower(trim($value))); return ucwords(strtolower(trim($value)));
} }
public function importExpenses($bankId, $input) { private function createVendorMap()
$countVendors = 0; {
$countExpenses = 0;
// create a vendor map
$vendorMap = []; $vendorMap = [];
$vendors = Vendor::scope() $vendors = Vendor::scope()
->withTrashed() ->withTrashed()
@ -151,6 +155,15 @@ class BankAccountService extends BaseService
$vendorMap[strtolower($vendor->transaction_name)] = $vendor; $vendorMap[strtolower($vendor->transaction_name)] = $vendor;
} }
return $vendorMap;
}
public function importExpenses($bankId, $input)
{
$vendorMap = $this->createVendorMap();
$countVendors = 0;
$countExpenses = 0;
foreach ($input as $transaction) { foreach ($input as $transaction) {
$vendorName = $transaction['vendor']; $vendorName = $transaction['vendor'];
$key = strtolower($vendorName); $key = strtolower($vendorName);
@ -165,7 +178,7 @@ class BankAccountService extends BaseService
$field => $info, $field => $info,
'name' => $vendorName, 'name' => $vendorName,
'transaction_name' => $transaction['vendor_orig'], 'transaction_name' => $transaction['vendor_orig'],
'vendorcontact' => [] 'vendorcontact' => [],
]); ]);
$vendorMap[$key] = $vendor; $vendorMap[$key] = $vendor;
$vendorMap[$transaction['vendor_orig']] = $vendor; $vendorMap[$transaction['vendor_orig']] = $vendor;
@ -191,7 +204,8 @@ class BankAccountService extends BaseService
]); ]);
} }
private function determineInfoField($value) { private function determineInfoField($value)
{
if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) { if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) {
return 'work_phone'; return 'work_phone';
} elseif (strpos($value, '.') !== false) { } elseif (strpos($value, '.') !== false) {
@ -214,8 +228,8 @@ class BankAccountService extends BaseService
[ [
'bank_name', 'bank_name',
function ($model) { function ($model) {
return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name); return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name)->toHtml();
} },
], ],
[ [
'bank_library_id', 'bank_library_id',
@ -233,9 +247,8 @@ class BankAccountService extends BaseService
uctrans('texts.edit_bank_account'), uctrans('texts.edit_bank_account'),
function ($model) { function ($model) {
return URL::to("bank_accounts/{$model->public_id}/edit"); return URL::to("bank_accounts/{$model->public_id}/edit");
} },
] ]
]; ];
} }
}
}

View File

@ -1,11 +1,11 @@
<?php namespace App\Services; <?php namespace App\Services;
use Illuminate\Foundation\Bus\DispatchesCommands; use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Services\DatatableService; use App\Services\DatatableService;
class BaseService class BaseService
{ {
use DispatchesCommands; use DispatchesJobs;
protected function getRepo() protected function getRepo()
{ {
@ -14,14 +14,16 @@ class BaseService
public function bulk($ids, $action) public function bulk($ids, $action)
{ {
if ( ! $ids) { if ( ! $ids ) {
return 0; return 0;
} }
$entities = $this->getRepo()->findByPublicIdsWithTrashed($ids); $entities = $this->getRepo()->findByPublicIdsWithTrashed($ids);
foreach ($entities as $entity) { foreach ($entities as $entity) {
$this->getRepo()->$action($entity); if($entity->canEdit()){
$this->getRepo()->$action($entity);
}
} }
return count($entities); return count($entities);

View File

@ -4,6 +4,12 @@ use Utils;
use URL; use URL;
use Auth; use Auth;
use App\Services\BaseService; use App\Services\BaseService;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Payment;
use App\Models\Task;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\NinjaRepository; use App\Ninja\Repositories\NinjaRepository;
@ -37,6 +43,10 @@ class ClientService extends BaseService
{ {
$query = $this->clientRepo->find($search); $query = $this->clientRepo->find($search);
if(!Utils::hasPermission('view_all')){
$query->where('clients.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_CLIENT, $query); return $this->createDatatable(ENTITY_CLIENT, $query);
} }
@ -46,19 +56,19 @@ class ClientService extends BaseService
[ [
'name', 'name',
function ($model) { function ($model) {
return link_to("clients/{$model->public_id}", $model->name ?: ''); return link_to("clients/{$model->public_id}", $model->name ?: '')->toHtml();
} }
], ],
[ [
'first_name', 'first_name',
function ($model) { function ($model) {
return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name); return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml();
} }
], ],
[ [
'email', 'email',
function ($model) { function ($model) {
return link_to("clients/{$model->public_id}", $model->email ?: ''); return link_to("clients/{$model->public_id}", $model->email ?: '')->toHtml();
} }
], ],
[ [
@ -89,19 +99,33 @@ class ClientService extends BaseService
trans('texts.edit_client'), trans('texts.edit_client'),
function ($model) { function ($model) {
return URL::to("clients/{$model->public_id}/edit"); return URL::to("clients/{$model->public_id}/edit");
},
function ($model) {
return Client::canEditItem($model);
}
],
[
'--divider--', function(){return false;},
function ($model) {
return Client::canEditItem($model) && (Task::canCreate() || Invoice::canCreate());
} }
], ],
[],
[ [
trans('texts.new_task'), trans('texts.new_task'),
function ($model) { function ($model) {
return URL::to("tasks/create/{$model->public_id}"); return URL::to("tasks/create/{$model->public_id}");
},
function ($model) {
return Task::canCreate();
} }
], ],
[ [
trans('texts.new_invoice'), trans('texts.new_invoice'),
function ($model) { function ($model) {
return URL::to("invoices/create/{$model->public_id}"); return URL::to("invoices/create/{$model->public_id}");
},
function ($model) {
return Invoice::canCreate();
} }
], ],
[ [
@ -110,26 +134,40 @@ class ClientService extends BaseService
return URL::to("quotes/create/{$model->public_id}"); return URL::to("quotes/create/{$model->public_id}");
}, },
function ($model) { function ($model) {
return Auth::user()->isPro(); return Auth::user()->isPro() && Invoice::canCreate();
}
],
[
'--divider--', function(){return false;},
function ($model) {
return (Task::canCreate() || Invoice::canCreate()) && (Payment::canCreate() || Credit::canCreate() || Expense::canCreate());
} }
], ],
[],
[ [
trans('texts.enter_payment'), trans('texts.enter_payment'),
function ($model) { function ($model) {
return URL::to("payments/create/{$model->public_id}"); return URL::to("payments/create/{$model->public_id}");
},
function ($model) {
return Payment::canCreate();
} }
], ],
[ [
trans('texts.enter_credit'), trans('texts.enter_credit'),
function ($model) { function ($model) {
return URL::to("credits/create/{$model->public_id}"); return URL::to("credits/create/{$model->public_id}");
},
function ($model) {
return Credit::canCreate();
} }
], ],
[ [
trans('texts.enter_expense'), trans('texts.enter_expense'),
function ($model) { function ($model) {
return URL::to("expenses/create/0/{$model->public_id}"); return URL::to("expenses/create/0/{$model->public_id}");
},
function ($model) {
return Expense::canCreate();
} }
] ]
]; ];

View File

@ -2,7 +2,10 @@
use Utils; use Utils;
use URL; use URL;
use Auth;
use App\Services\BaseService; use App\Services\BaseService;
use App\Models\Client;
use App\Models\Payment;
use App\Ninja\Repositories\CreditRepository; use App\Ninja\Repositories\CreditRepository;
@ -30,6 +33,10 @@ class CreditService extends BaseService
public function getDatatable($clientPublicId, $search) public function getDatatable($clientPublicId, $search)
{ {
$query = $this->creditRepo->find($clientPublicId, $search); $query = $this->creditRepo->find($clientPublicId, $search);
if(!Utils::hasPermission('view_all')){
$query->where('credits.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId); return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId);
} }
@ -40,7 +47,11 @@ class CreditService extends BaseService
[ [
'client_name', 'client_name',
function ($model) { function ($model) {
return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)) : ''; if(!Client::canViewItemByOwner($model->client_user_id)){
return Utils::getClientDisplayName($model);
}
return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : '';
}, },
! $hideClient ! $hideClient
], ],
@ -78,6 +89,9 @@ class CreditService extends BaseService
trans('texts.apply_credit'), trans('texts.apply_credit'),
function ($model) { function ($model) {
return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1';
},
function ($model) {
return Payment::canCreate();
} }
] ]
]; ];

View File

@ -1,7 +1,9 @@
<?php namespace App\Services; <?php namespace App\Services;
use HtmlString;
use Utils; use Utils;
use Datatable; use Datatable;
use Auth;
class DatatableService class DatatableService
{ {
@ -12,7 +14,9 @@ class DatatableService
if ($actions && $showCheckbox) { if ($actions && $showCheckbox) {
$table->addColumn('checkbox', function ($model) { $table->addColumn('checkbox', function ($model) {
return '<input type="checkbox" name="ids[]" value="' . $model->public_id $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id);
return !$can_edit?'':'<input type="checkbox" name="ids[]" value="' . $model->public_id
. '" ' . Utils::getEntityRowClass($model) . '>'; . '" ' . Utils::getEntityRowClass($model) . '>';
}); });
} }
@ -44,6 +48,8 @@ class DatatableService
$hasAction = false; $hasAction = false;
$str = '<center style="min-width:100px">'; $str = '<center style="min-width:100px">';
$can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id);
if (property_exists($model, 'is_deleted') && $model->is_deleted) { if (property_exists($model, 'is_deleted') && $model->is_deleted) {
$str .= '<button type="button" class="btn btn-sm btn-danger tr-status">'.trans('texts.deleted').'</button>'; $str .= '<button type="button" class="btn btn-sm btn-danger tr-status">'.trans('texts.deleted').'</button>';
} elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') { } elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') {
@ -69,9 +75,15 @@ class DatatableService
} }
list($value, $url, $visible) = $action; list($value, $url, $visible) = $action;
if ($visible($model)) { if ($visible($model)) {
$str .= "<li><a href=\"{$url($model)}\">{$value}</a></li>"; if($value == '--divider--'){
$lastIsDivider = false; $str .= "<li class=\"divider\"></li>";
$hasAction = true; $lastIsDivider = true;
}
else {
$str .= "<li><a href=\"{$url($model)}\">{$value}</a></li>";
$hasAction = true;
$lastIsDivider = false;
}
} }
} elseif ( ! $lastIsDivider) { } elseif ( ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>"; $str .= "<li class=\"divider\"></li>";
@ -83,20 +95,20 @@ class DatatableService
return ''; return '';
} }
if ( ! $lastIsDivider) { if ( $can_edit && ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>"; $str .= "<li class=\"divider\"></li>";
} }
if ($entityType != ENTITY_USER || $model->public_id) { if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) {
$str .= "<li><a href=\"javascript:archiveEntity({$model->public_id})\">" $str .= "<li><a href=\"javascript:archiveEntity({$model->public_id})\">"
. trans("texts.archive_{$entityType}") . "</a></li>"; . trans("texts.archive_{$entityType}") . "</a></li>";
} }
} else { } else if($can_edit) {
$str .= "<li><a href=\"javascript:restoreEntity({$model->public_id})\">" $str .= "<li><a href=\"javascript:restoreEntity({$model->public_id})\">"
. trans("texts.restore_{$entityType}") . "</a></li>"; . trans("texts.restore_{$entityType}") . "</a></li>";
} }
if (property_exists($model, 'is_deleted') && !$model->is_deleted) { if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) {
$str .= "<li><a href=\"javascript:deleteEntity({$model->public_id})\">" $str .= "<li><a href=\"javascript:deleteEntity({$model->public_id})\">"
. trans("texts.delete_{$entityType}") . "</a></li>"; . trans("texts.delete_{$entityType}") . "</a></li>";
} }

View File

@ -1,10 +1,13 @@
<?php namespace App\Services; <?php namespace App\Services;
use Auth;
use DB; use DB;
use Utils; use Utils;
use URL; use URL;
use App\Services\BaseService; use App\Services\BaseService;
use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\ExpenseRepository;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Client; use App\Models\Client;
use App\Models\Vendor; use App\Models\Vendor;
@ -30,11 +33,11 @@ class ExpenseService extends BaseService
if (isset($data['client_id']) && $data['client_id']) { if (isset($data['client_id']) && $data['client_id']) {
$data['client_id'] = Client::getPrivateId($data['client_id']); $data['client_id'] = Client::getPrivateId($data['client_id']);
} }
if (isset($data['vendor_id']) && $data['vendor_id']) { if (isset($data['vendor_id']) && $data['vendor_id']) {
$data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']); $data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']);
} }
return $this->expenseRepo->save($data); return $this->expenseRepo->save($data);
} }
@ -42,6 +45,10 @@ class ExpenseService extends BaseService
{ {
$query = $this->expenseRepo->find($search); $query = $this->expenseRepo->find($search);
if(!Utils::hasPermission('view_all')){
$query->where('expenses.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_EXPENSE, $query); return $this->createDatatable(ENTITY_EXPENSE, $query);
} }
@ -63,7 +70,11 @@ class ExpenseService extends BaseService
function ($model) function ($model)
{ {
if ($model->vendor_public_id) { if ($model->vendor_public_id) {
return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name); if(!Vendor::canViewItemByOwner($model->vendor_user_id)){
return $model->vendor_name;
}
return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml();
} else { } else {
return ''; return '';
} }
@ -74,7 +85,11 @@ class ExpenseService extends BaseService
function ($model) function ($model)
{ {
if ($model->client_public_id) { if ($model->client_public_id) {
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)); if(!Client::canViewItemByOwner($model->client_user_id)){
return Utils::getClientDisplayName($model);
}
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
} else { } else {
return ''; return '';
} }
@ -83,7 +98,11 @@ class ExpenseService extends BaseService
[ [
'expense_date', 'expense_date',
function ($model) { function ($model) {
return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date)); if(!Expense::canEditItemByOwner($model->user_id)){
return Utils::fromSqlDate($model->expense_date);
}
return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml();
} }
], ],
[ [
@ -92,7 +111,7 @@ class ExpenseService extends BaseService
// 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($model->amount * $model->exchange_rate, 2);
return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' .
Utils::formatMoney($converted, $model->invoice_currency_id); Utils::formatMoney($converted, $model->invoice_currency_id);
} else { } else {
return Utils::formatMoney($model->amount, $model->expense_currency_id); return Utils::formatMoney($model->amount, $model->expense_currency_id);
@ -151,6 +170,9 @@ class ExpenseService extends BaseService
trans('texts.edit_expense'), trans('texts.edit_expense'),
function ($model) { function ($model) {
return URL::to("expenses/{$model->public_id}/edit") ; return URL::to("expenses/{$model->public_id}/edit") ;
},
function ($model) {
return Expense::canEditItem($model);
} }
], ],
[ [
@ -159,7 +181,7 @@ class ExpenseService extends BaseService
return URL::to("/invoices/{$model->invoice_public_id}/edit"); return URL::to("/invoices/{$model->invoice_public_id}/edit");
}, },
function ($model) { function ($model) {
return $model->invoice_public_id; return $model->invoice_public_id && Invoice::canEditItemByOwner($model->invoice_user_id);
} }
], ],
[ [
@ -168,7 +190,7 @@ class ExpenseService extends BaseService
return "javascript:invoiceEntity({$model->public_id})"; return "javascript:invoiceEntity({$model->public_id})";
}, },
function ($model) { function ($model) {
return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate();
} }
], ],
]; ];
@ -178,7 +200,7 @@ class ExpenseService extends BaseService
{ {
return []; return [];
} }
private function getStatusLabel($invoiceId, $shouldBeInvoiced) private function getStatusLabel($invoiceId, $shouldBeInvoiced)
{ {
if ($invoiceId) { if ($invoiceId) {

View File

@ -8,6 +8,9 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Events\QuoteInvitationWasApproved; use App\Events\QuoteInvitationWasApproved;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Invoice;
use App\Models\Client;
use App\Models\Payment;
class InvoiceService extends BaseService class InvoiceService extends BaseService
{ {
@ -27,15 +30,27 @@ class InvoiceService extends BaseService
return $this->invoiceRepo; return $this->invoiceRepo;
} }
public function save($data) public function save($data, $checkSubPermissions = false)
{ {
if (isset($data['client'])) { if (isset($data['client'])) {
$client = $this->clientRepo->save($data['client']); $can_save_client = !$checkSubPermissions;
$data['client_id'] = $client->id; if(!$can_save_client){
if(empty($data['client']['public_id']) || $data['client']['public_id']=='-1'){
$can_save_client = Client::canCreate();
}
else{
$can_save_client = Client::wherePublicId($data['client']['public_id'])->first()->canEdit();
}
}
if($can_save_client){
$client = $this->clientRepo->save($data['client']);
$data['client_id'] = $client->id;
}
} }
$invoice = $this->invoiceRepo->save($data); $invoice = $this->invoiceRepo->save($data, $checkSubPermissions);
$client = $invoice->client; $client = $invoice->client;
$client->load('contacts'); $client->load('contacts');
$sendInvoiceIds = []; $sendInvoiceIds = [];
@ -45,7 +60,7 @@ class InvoiceService extends BaseService
$sendInvoiceIds[] = $contact->id; $sendInvoiceIds[] = $contact->id;
} }
} }
foreach ($client->contacts as $contact) { foreach ($client->contacts as $contact) {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first(); $invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
@ -69,7 +84,7 @@ class InvoiceService extends BaseService
if (!$invitation) { if (!$invitation) {
return $invoice; return $invoice;
} }
foreach ($invoice->invitations as $invoiceInvitation) { foreach ($invoice->invitations as $invoiceInvitation) {
if ($invitation->contact_id == $invoiceInvitation->contact_id) { if ($invitation->contact_id == $invoiceInvitation->contact_id) {
return $invoiceInvitation->invitation_key; return $invoiceInvitation->invitation_key;
@ -79,11 +94,12 @@ class InvoiceService extends BaseService
public function approveQuote($quote, $invitation = null) public function approveQuote($quote, $invitation = null)
{ {
$account = Auth::user()->account; $account = $quote->account;
if (!$quote->is_quote || $quote->quote_invoice_id) { if (!$quote->is_quote || $quote->quote_invoice_id) {
return null; return null;
} }
if ($account->auto_convert_quote || ! $account->isPro()) { if ($account->auto_convert_quote || ! $account->isPro()) {
$invoice = $this->convertQuote($quote, $invitation); $invoice = $this->convertQuote($quote, $invitation);
@ -94,7 +110,7 @@ class InvoiceService extends BaseService
$quote->markApproved(); $quote->markApproved();
event(new QuoteInvitationWasApproved($quote, null, $invitation)); event(new QuoteInvitationWasApproved($quote, null, $invitation));
foreach ($quote->invitations as $invoiceInvitation) { foreach ($quote->invitations as $invoiceInvitation) {
if ($invitation->contact_id == $invoiceInvitation->contact_id) { if ($invitation->contact_id == $invoiceInvitation->contact_id) {
return $invoiceInvitation->invitation_key; return $invoiceInvitation->invitation_key;
@ -108,6 +124,10 @@ class InvoiceService extends BaseService
$query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false);
if(!Utils::hasPermission('view_all')){
$query->where('invoices.user_id', '=', Auth::user()->id);
}
return $this->createDatatable($entityType, $query, !$clientPublicId); return $this->createDatatable($entityType, $query, !$clientPublicId);
} }
@ -117,13 +137,20 @@ class InvoiceService extends BaseService
[ [
'invoice_number', 'invoice_number',
function ($model) use ($entityType) { function ($model) use ($entityType) {
return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); if(!Invoice::canEditItem($model)){
return $model->invoice_number;
}
return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml();
} }
], ],
[ [
'client_name', 'client_name',
function ($model) { function ($model) {
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)); if(!Client::canViewItemByOwner($model->client_user_id)){
return Utils::getClientDisplayName($model);
}
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
}, },
! $hideClient ! $hideClient
], ],
@ -159,8 +186,8 @@ class InvoiceService extends BaseService
], ],
[ [
'invoice_status_name', 'invoice_status_name',
function ($model) { function ($model) use ($entityType) {
return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : self::getStatusLabel($model); return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($entityType, $model);
} }
] ]
]; ];
@ -173,12 +200,18 @@ class InvoiceService extends BaseService
trans("texts.edit_{$entityType}"), trans("texts.edit_{$entityType}"),
function ($model) use ($entityType) { function ($model) use ($entityType) {
return URL::to("{$entityType}s/{$model->public_id}/edit"); return URL::to("{$entityType}s/{$model->public_id}/edit");
},
function ($model) {
return Invoice::canEditItem($model);
} }
], ],
[ [
trans("texts.clone_{$entityType}"), trans("texts.clone_{$entityType}"),
function ($model) use ($entityType) { function ($model) use ($entityType) {
return URL::to("{$entityType}s/{$model->public_id}/clone"); return URL::to("{$entityType}s/{$model->public_id}/clone");
},
function ($model) {
return Invoice::canCreate();
} }
], ],
[ [
@ -187,14 +220,19 @@ class InvoiceService extends BaseService
return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}");
} }
], ],
[], [
'--divider--', function(){return false;},
function ($model) {
return Invoice::canEditItem($model) || Payment::canCreate();
}
],
[ [
trans("texts.mark_sent"), trans("texts.mark_sent"),
function ($model) { function ($model) {
return "javascript:markEntity({$model->public_id})"; return "javascript:markEntity({$model->public_id})";
}, },
function ($model) { function ($model) {
return $model->invoice_status_id < INVOICE_STATUS_SENT; return $model->invoice_status_id < INVOICE_STATUS_SENT && Invoice::canEditItem($model);
} }
], ],
[ [
@ -203,7 +241,7 @@ class InvoiceService extends BaseService
return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}");
}, },
function ($model) use ($entityType) { function ($model) use ($entityType) {
return $entityType == ENTITY_INVOICE && $model->balance > 0; return $entityType == ENTITY_INVOICE && $model->balance > 0 && Payment::canCreate();
} }
], ],
[ [
@ -212,7 +250,7 @@ class InvoiceService extends BaseService
return URL::to("quotes/{$model->quote_id}/edit"); return URL::to("quotes/{$model->quote_id}/edit");
}, },
function ($model) use ($entityType) { function ($model) use ($entityType) {
return $entityType == ENTITY_INVOICE && $model->quote_id; return $entityType == ENTITY_INVOICE && $model->quote_id && Invoice::canEditItem($model);
} }
], ],
[ [
@ -221,7 +259,7 @@ class InvoiceService extends BaseService
return URL::to("invoices/{$model->quote_invoice_id}/edit"); return URL::to("invoices/{$model->quote_invoice_id}/edit");
}, },
function ($model) use ($entityType) { function ($model) use ($entityType) {
return $entityType == ENTITY_QUOTE && $model->quote_invoice_id; return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Invoice::canEditItem($model);
} }
], ],
[ [
@ -230,18 +268,19 @@ class InvoiceService extends BaseService
return "javascript:convertEntity({$model->public_id})"; return "javascript:convertEntity({$model->public_id})";
}, },
function ($model) use ($entityType) { function ($model) use ($entityType) {
return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id; return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Invoice::canEditItem($model);
} }
] ]
]; ];
} }
private function getStatusLabel($model) private function getStatusLabel($entityType, $model)
{ {
// check if invoice is overdue // check if invoice is overdue
if (Utils::parseFloat($model->balance) && $model->due_date && $model->due_date != '0000-00-00') { if (Utils::parseFloat($model->balance) && $model->due_date && $model->due_date != '0000-00-00') {
if (\DateTime::createFromFormat('Y-m-d', $model->due_date) < new \DateTime("now")) { if (\DateTime::createFromFormat('Y-m-d', $model->due_date) < new \DateTime("now")) {
return "<h4><div class=\"label label-danger\">".trans('texts.overdue')."</div></h4>"; $label = $entityType == ENTITY_INVOICE ? trans('texts.overdue') : trans('texts.expired');
return "<h4><div class=\"label label-danger\">" . $label . "</div></h4>";
} }
} }
@ -266,5 +305,5 @@ class InvoiceService extends BaseService
} }
return "<h4><div class=\"label label-{$class}\">$label</div></h4>"; return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
} }
} }

View File

@ -1,6 +1,7 @@
<?php namespace App\Services; <?php namespace App\Services;
use Utils; use Utils;
use Auth;
use URL; use URL;
use DateTime; use DateTime;
use Event; use Event;
@ -10,6 +11,8 @@ use CreditCard;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Account; use App\Models\Account;
use App\Models\Country; use App\Models\Country;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\AccountGatewayToken; use App\Models\AccountGatewayToken;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
@ -202,7 +205,7 @@ class PaymentService extends BaseService
if ($response->isRedirect()) { if ($response->isRedirect()) {
$token = $response->getTransactionReference(); $token = $response->getTransactionReference();
} }
Session::set($invitation->id . 'payment_type', PAYMENT_TYPE_CREDIT_CARD); Session::set($invitation->id . 'payment_type', PAYMENT_TYPE_CREDIT_CARD);
return $token; return $token;
@ -273,7 +276,7 @@ class PaymentService extends BaseService
// submit purchase/get response // submit purchase/get response
$response = $gateway->purchase($details)->send(); $response = $gateway->purchase($details)->send();
if ($response->isSuccessful()) { if ($response->isSuccessful()) {
$ref = $response->getTransactionReference(); $ref = $response->getTransactionReference();
return $this->createPayment($invitation, $accountGateway, $ref); return $this->createPayment($invitation, $accountGateway, $ref);
@ -286,6 +289,10 @@ class PaymentService extends BaseService
{ {
$query = $this->paymentRepo->find($clientPublicId, $search); $query = $this->paymentRepo->find($clientPublicId, $search);
if(!Utils::hasPermission('view_all')){
$query->where('payments.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId); return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId);
} }
@ -295,13 +302,21 @@ class PaymentService extends BaseService
[ [
'invoice_number', 'invoice_number',
function ($model) { function ($model) {
return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); if(!Invoice::canEditItemByOwner($model->invoice_user_id)){
return $model->invoice_number;
}
return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml();
} }
], ],
[ [
'client_name', 'client_name',
function ($model) { function ($model) {
return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)) : ''; if(!Client::canViewItemByOwner($model->client_user_id)){
return Utils::getClientDisplayName($model);
}
return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : '';
}, },
! $hideClient ! $hideClient
], ],
@ -339,6 +354,9 @@ class PaymentService extends BaseService
trans('texts.edit_payment'), trans('texts.edit_payment'),
function ($model) { function ($model) {
return URL::to("payments/{$model->public_id}/edit"); return URL::to("payments/{$model->public_id}/edit");
},
function ($model) {
return Payment::canEditItem($model);
} }
] ]
]; ];

View File

@ -34,7 +34,7 @@ class PaymentTermService extends BaseService
[ [
'name', 'name',
function ($model) { function ($model) {
return link_to("payment_terms/{$model->public_id}/edit", $model->name); return link_to("payment_terms/{$model->public_id}/edit", $model->name)->toHtml();
} }
], ],
[ [

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