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_PASSWORD
#POSTMARK_API_TOKEN=
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single
REQUIRE_HTTPS=false
API_SECRET=password
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
#TRUSTED_PROXIES=
#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
php:
- 5.5
- 5.5.9
# - 5.6
# - 7.0
# - hhvm
@ -34,7 +34,6 @@ install:
# these providers require referencing git commit's which cause Travis to fail
- sed -i '/mollie/d' composer.json
- sed -i '/2checkout/d' composer.json
- sed -i '/omnipay-neteller/d' composer.json
- travis_retry composer install --prefer-dist;
before_script:
@ -65,16 +64,18 @@ before_script:
script:
- 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 CheckBalanceCest.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 InvoiceCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php
#- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.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
- 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 ClientCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance QuoteCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php
- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.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
#- 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.es.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/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.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/fuse.js/src/fuse.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js',
//'public/vendor/pdfmake/build/vfs_fonts.js',
//'public/js/vfs_fonts.js',
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js',
'public/js/script.js',
'public/js/pdf.pdfmake.js',
@ -140,7 +140,6 @@ module.exports = function(grunt) {
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',
'public/css/lightbox.css',
//'public/vendor/handsontable/dist/jquery.handsontable.full.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
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.
2. Neither the name nor any trademark of the Author may be used to
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);
$invitation = $this->accountRepo->createNinjaInvoice($client);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account);
// set the due date to 10 days from now
$invoice = $invitation->invoice;

View File

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

View File

@ -47,6 +47,8 @@ class Handler extends ExceptionHandler {
if ($e instanceof ModelNotFoundException) {
return Redirect::to('/');
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) {
// prevent loop since the page auto-submits
if ($request->path() != 'get_started') {
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
return redirect()
->back()
@ -55,6 +57,7 @@ class Handler extends ExceptionHandler {
'warning' => trans('texts.token_expired')
]);
}
}
// In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {

View File

@ -19,6 +19,8 @@ use App\Ninja\Transformers\UserAccountTransformer;
use App\Http\Controllers\BaseAPIController;
use Swagger\Annotations as SWG;
use App\Events\UserSignedUp;
use App\Http\Requests\RegisterRequest;
use App\Http\Requests\UpdateAccountRequest;
class AccountApiController extends BaseAPIController
@ -32,13 +34,20 @@ class AccountApiController extends BaseAPIController
$this->accountRepo = $accountRepo;
}
public function login(Request $request)
public function register(RegisterRequest $request)
{
if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) {
sleep(ERROR_DELAY);
return $this->errorResponse(['message'=>'Invalid secret'],401);
$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)
{
if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
return $this->processLogin($request);
} else {
@ -65,24 +74,7 @@ class AccountApiController extends BaseAPIController
$account = Auth::user()->account;
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
$map = [
'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);
}
}]);
}
$account->loadAllData($updatedAt);
$transformer = new AccountTransformer(null, $request->serializer);
$account = $this->createItem($account, $transformer, 'account');
@ -117,4 +109,82 @@ class AccountApiController extends BaseAPIController
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 App\Models\Affiliate;
use App\Models\License;
use App\Models\Invoice;
use App\Models\User;
use App\Models\Account;
use App\Models\Gateway;
@ -25,7 +26,7 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Repositories\ReferralRepository;
use App\Ninja\Mailers\UserMailer;
use App\Ninja\Mailers\ContactMailer;
use App\Events\UserLoggedIn;
use App\Events\UserSignedUp;
use App\Events\UserSettingsChanged;
use App\Services\AuthService;
@ -40,7 +41,7 @@ class AccountController extends BaseController
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository)
{
parent::__construct();
//parent::__construct();
$this->accountRepo = $accountRepo;
$this->userMailer = $userMailer;
@ -99,7 +100,7 @@ class AccountController extends BaseController
}
Auth::login($user, true);
event(new UserLoggedIn());
event(new UserSignedUp());
$redirectTo = Input::get('redirect_to') ?: 'invoices/create';
@ -122,7 +123,8 @@ class AccountController extends BaseController
public function getSearchData()
{
$data = $this->accountRepo->getSearchData();
$account = Auth::user()->account;
$data = $this->accountRepo->getSearchData($account);
return Response::json($data);
}
@ -135,8 +137,6 @@ class AccountController extends BaseController
if ($section == ACCOUNT_COMPANY_DETAILS) {
return self::showCompanyDetails();
} elseif ($section == ACCOUNT_USER_DETAILS) {
return self::showUserDetails();
} elseif ($section == ACCOUNT_LOCALIZATION) {
return self::showLocalization();
} elseif ($section == ACCOUNT_PAYMENTS) {
@ -150,7 +150,7 @@ class AccountController extends BaseController
} elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) {
return self::showInvoiceDesign($section);
} elseif ($section == ACCOUNT_CLIENT_PORTAL) {
return self::showClientViewStyling();
return self::showClientPortal();
} elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) {
return self::showTemplates();
} elseif ($section === ACCOUNT_PRODUCTS) {
@ -230,7 +230,7 @@ class AccountController extends BaseController
return View::make('accounts.details', $data);
}
private function showUserDetails()
public function showUserDetails()
{
$oauthLoginUrls = [];
foreach (AuthService::$providers as $provider) {
@ -393,12 +393,27 @@ class AccountController extends BaseController
if ($section == ACCOUNT_CUSTOMIZE_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);
}
private function showClientViewStyling()
private function showClientPortal()
{
$account = Auth::user()->account->load('country');
$css = $account->client_view_css ? $account->client_view_css : '';
@ -414,8 +429,11 @@ class AccountController extends BaseController
$data = [
'client_view_css' => $css,
'enable_portal_password' => $account->enable_portal_password,
'send_portal_password' => $account->send_portal_password,
'title' => trans("texts.client_portal"),
'section' => ACCOUNT_CLIENT_PORTAL,
'account' => $account,
];
return View::make("accounts.client_portal", $data);
@ -447,8 +465,6 @@ class AccountController extends BaseController
{
if ($section === ACCOUNT_COMPANY_DETAILS) {
return AccountController::saveDetails();
} elseif ($section === ACCOUNT_USER_DETAILS) {
return AccountController::saveUserDetails();
} elseif ($section === ACCOUNT_LOCALIZATION) {
return AccountController::saveLocalization();
} elseif ($section === ACCOUNT_NOTIFICATIONS) {
@ -528,6 +544,11 @@ class AccountController extends BaseController
$account = Auth::user()->account;
$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();
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_text_label1 = trim(Input::get('custom_invoice_text_label1'));
$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->quote_number_prefix = Input::get('quote_number_prefix');
@ -676,6 +699,7 @@ class AccountController extends BaseController
$account->invoice_footer = Input::get('invoice_footer');
$account->quote_terms = Input::get('quote_terms');
$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')) {
$account->recurring_hour = Input::get('recurring_hour');
@ -736,7 +760,7 @@ class AccountController extends BaseController
}
$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}");
}
$account->invoice_labels = json_encode($labels);
@ -811,7 +835,7 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS);
}
private function saveUserDetails()
public function saveUserDetails()
{
$user = Auth::user();
$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)
{
parent::__construct();
//parent::__construct();
$this->accountGatewayService = $accountGatewayService;
}

View File

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

View File

@ -30,7 +30,7 @@ class AppController extends BaseController
public function __construct(AccountRepository $accountRepo, Mailer $mailer, EmailService $emailService)
{
parent::__construct();
//parent::__construct();
$this->accountRepo = $accountRepo;
$this->mailer = $mailer;
@ -231,7 +231,8 @@ class AppController extends BaseController
}
Artisan::call('optimize', array('--force' => true));
} 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()) {
try {
set_time_limit(60 * 5);
Artisan::call('optimize', array('--force' => true));
Cache::flush();
Session::flush();
Artisan::call('migrate', array('--force' => true));
@ -250,15 +252,19 @@ class AppController extends BaseController
'PaymentLibraries',
'Fonts',
'Banks',
'InvoiceStatus'
'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
}
Artisan::call('optimize', array('--force' => true));
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) {
Response::make($e->getMessage(), 500);
Utils::logError($e);
return Response::make($e->getMessage(), 500);
}
}
@ -288,7 +294,7 @@ class AppController extends BaseController
}
if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) {
$payments = DB::table('accounts')
$data = DB::table('accounts')
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
@ -300,15 +306,9 @@ class AppController extends BaseController
'payments.amount'
]);
} else {
$payments = DB::table('accounts')
->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();
$data = DB::table('users')->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\Ninja\Repositories\AccountRepository;
use App\Services\AuthService;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Registrar;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller {
@ -29,7 +27,6 @@ class AuthController extends Controller {
use AuthenticatesAndRegistersUsers;
protected $loginPath = '/login';
protected $redirectTo = '/dashboard';
protected $authService;
protected $accountRepo;
@ -41,16 +38,38 @@ class AuthController extends Controller {
* @param \Illuminate\Contracts\Auth\Registrar $registrar
* @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->authService = $authService;
//$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)
{
return $this->authService->execute($provider, $request->has('code'));

View File

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

View File

@ -27,7 +27,7 @@ class BankAccountController extends BaseController
public function __construct(BankAccountService $bankAccountService, BankAccountRepository $bankAccountRepo)
{
parent::__construct();
//parent::__construct();
$this->bankAccountService = $bankAccountService;
$this->bankAccountRepo = $bankAccountRepo;

View File

@ -1,10 +1,14 @@
<?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
{
use DispatchesCommands;
use DispatchesJobs;
protected $model = 'App\Models\EntityModel';
/**
* Setup the layout used by the controller.
@ -18,8 +22,39 @@ class BaseController extends Controller
}
}
public function __construct()
{
$this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put')));
protected function checkViewPermission($object, &$response = null){
if(!$object->canView()){
$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

@ -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\Industry;
use App\Models\Currency;
use App\Models\Payment;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Country;
use App\Models\Task;
use App\Ninja\Repositories\ClientRepository;
@ -32,10 +35,11 @@ class ClientController extends BaseController
{
protected $clientService;
protected $clientRepo;
protected $model = 'App\Models\Client';
public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{
parent::__construct();
//parent::__construct();
$this->clientRepo = $clientRepo;
$this->clientService = $clientService;
@ -77,7 +81,13 @@ class ClientController extends BaseController
*/
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'));
@ -93,22 +103,36 @@ class ClientController extends BaseController
public function show($publicId)
{
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail();
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [
['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]
];
if (Utils::isPro()) {
array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]);
if(!$this->checkViewPermission($client, $response)){
return $response;
}
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]
);
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [];
if(Task::canCreate()){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id];
}
if (Utils::isPro() && Invoice::canCreate()) {
$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];
}
$data = array(
'actionLinks' => $actionLinks,
@ -132,7 +156,11 @@ class ClientController extends BaseController
*/
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"]);
}
@ -157,6 +185,11 @@ class ClientController extends BaseController
public function edit($publicId)
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
if(!$this->checkEditPermission($client, $response)){
return $response;
}
$data = [
'client' => $client,
'method' => 'PUT',
@ -199,7 +232,13 @@ class ClientController extends BaseController
*/
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'));

View File

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

View File

@ -17,10 +17,11 @@ class CreditController extends BaseController
{
protected $creditRepo;
protected $creditService;
protected $model = 'App\Models\Credit';
public function __construct(CreditRepository $creditRepo, CreditService $creditService)
{
parent::__construct();
// parent::__construct();
$this->creditRepo = $creditRepo;
$this->creditService = $creditService;
@ -56,6 +57,10 @@ class CreditController extends BaseController
public function create($clientPublicId = 0)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
$data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
@ -72,6 +77,11 @@ class CreditController extends BaseController
public function edit($publicId)
{
$credit = Credit::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($credit, $response)){
return $response;
}
$credit->credit_date = Utils::fromSqlDate($credit->credit_date);
$data = array(

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,6 +11,8 @@ class DashboardController extends BaseController
{
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,
@ -24,8 +26,19 @@ class DashboardController extends BaseController
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false)
->groupBy('accounts.id')
->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');
@ -33,8 +46,13 @@ class DashboardController extends BaseController
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->groupBy('accounts.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();
@ -47,8 +65,13 @@ class DashboardController extends BaseController
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_quote', '=', false)
->where('invoices.is_recurring', '=', false)
->groupBy('accounts.id')
->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();
@ -63,9 +86,14 @@ class DashboardController extends BaseController
->get();
$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')
->where('activity_type_id', '>', 0)
->orderBy('created_at', 'desc')
->take(50)
->get();
@ -81,8 +109,13 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->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'])
->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();
@ -100,9 +133,14 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '>=', date('Y-m-d'))
->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'])
->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')
@ -114,8 +152,13 @@ class DashboardController extends BaseController
->where('invoices.is_deleted', '=', false)
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->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'])
->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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,10 +33,11 @@ class QuoteController extends BaseController
protected $invoiceRepo;
protected $clientRepo;
protected $invoiceService;
protected $model = 'App\Models\Invoice';
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{
parent::__construct();
// parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
@ -53,6 +54,7 @@ class QuoteController extends BaseController
$data = [
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'sortCol' => '3',
'columns' => Utils::trans([
'checkbox',
'quote_number',
@ -78,6 +80,10 @@ class QuoteController extends BaseController
public function create($clientPublicId = 0)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
if (!Utils::isPro()) {
return Redirect::to('/invoices/create');
}

View File

@ -9,7 +9,7 @@ class RecurringInvoiceController extends BaseController
public function __construct(InvoiceRepository $invoiceRepo)
{
parent::__construct();
//parent::__construct();
$this->invoiceRepo = $invoiceRepo;
}

View File

@ -10,6 +10,9 @@ use DatePeriod;
use Session;
use View;
use App\Models\Account;
use App\Models\Client;
use App\Models\Payment;
use App\Models\Expense;
class ReportController extends BaseController
{
@ -47,6 +50,7 @@ class ReportController extends BaseController
$groupBy = Input::get('group_by');
$chartType = Input::get('chart_type');
$reportType = Input::get('report_type');
$dateField = Input::get('date_field');
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
$enableReport = Input::get('enable_report') ? true : false;
@ -55,6 +59,7 @@ class ReportController extends BaseController
$groupBy = 'MONTH';
$chartType = 'Bar';
$reportType = ENTITY_INVOICE;
$dateField = FILTER_INVOICE_DATE;
$startDate = Utils::today(false)->modify('-3 month');
$endDate = Utils::today(false);
$enableReport = true;
@ -76,6 +81,8 @@ class ReportController extends BaseController
ENTITY_CLIENT => trans('texts.client'),
ENTITY_INVOICE => trans('texts.invoice'),
ENTITY_PAYMENT => trans('texts.payment'),
ENTITY_EXPENSE => trans('texts.expenses'),
ENTITY_TAX_RATE => trans('texts.taxes'),
];
$params = [
@ -94,10 +101,11 @@ class ReportController extends BaseController
if (Auth::user()->account->isPro()) {
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') {
self::export($params['exportData'], $params['reportTotals']);
if ($isExport) {
self::export($params['displayData'], $params['columns'], $params['reportTotals']);
}
}
if ($enableChart) {
@ -106,11 +114,7 @@ class ReportController extends BaseController
} else {
$params['columns'] = [];
$params['displayData'] = [];
$params['reportTotals'] = [
'amount' => [],
'balance' => [],
'paid' => [],
];
$params['reportTotals'] = [];
$params['labels'] = [];
$params['datasets'] = [];
$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) {
$columns = ['client', 'amount', 'paid', 'balance'];
return $this->generateClientReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_INVOICE) {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance'];
} else {
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
}
$query = DB::table('invoices')
->join('accounts', 'accounts.id', '=', 'invoices.account_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->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 = [
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');
return $this->generateInvoiceReport($startDate, $endDate, $isExport);
} elseif ($reportType == ENTITY_PAYMENT) {
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->select($select);
$data = $query->get();
private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport)
{
$columns = ['tax_name', 'tax_rate', 'amount', 'paid'];
$lastInvoiceId = null;
$sameAsLast = false;
$account = Auth::user()->account;
$displayData = [];
$reportTotals = [];
$exportData = [];
$reportTotals = [
'amount' => [],
'balance' => [],
'paid' => [],
$clients = Client::scope()
->withArchived()
->with('contacts')
->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) {
$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 ($clients->get() as $client) {
$currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId();
$amount = 0;
$paid = 0;
$taxTotals = [];
foreach ($client->invoices as $invoice) {
foreach ($invoice->getTaxes(true) as $key => $tax) {
if ( ! isset($taxTotals[$currencyId])) {
$taxTotals[$currencyId] = [];
}
if (isset($taxTotals[$currencyId][$key])) {
$taxTotals[$currencyId][$key]['amount'] += $tax['amount'];
$taxTotals[$currencyId][$key]['paid'] += $tax['paid'];
} else {
$taxTotals[$currencyId][$key] = $tax;
}
}
$amount += $invoice->amount;
$paid += $invoice->getAmountPaid();
}
foreach ($taxTotals as $currencyId => $taxes) {
foreach ($taxes as $tax) {
$displayData[] = [
$tax['name'],
$tax['rate'] . '%',
$account->formatMoney($tax['amount'], $client),
$account->formatMoney($tax['paid'], $client)
];
foreach ($data as $record) {
$sameAsLast = ($lastInvoiceId == $record->invoice_public_id);
$lastInvoiceId = $record->invoice_public_id;
$displayRow = [];
if ($sameAsLast) {
array_push($displayRow, '', '', '', '');
} else {
array_push($displayRow, link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record)));
if ($reportType != ENTITY_CLIENT) {
array_push($displayRow,
link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number),
Utils::fromSqlDate($record->invoice_date, true)
);
}
array_push($displayRow, Utils::formatMoney($record->amount, $record->currency_id, $record->country_id));
}
if ($reportType != ENTITY_PAYMENT) {
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
$exportRow = [];
if ($sameAsLast) {
$exportRow[trans('texts.client')] = ' ';
$exportRow[trans('texts.invoice_number')] = ' ';
$exportRow[trans('texts.invoice_date')] = ' ';
$exportRow[trans('texts.amount')] = ' ';
} 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);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']);
$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']);
}
$exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id, $record->country_id);
}
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 [
'columns' => $columns,
'displayData' => $displayData,
'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();
header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv');
Utils::exportData($output, $data);
Utils::exportData($output, $data, Utils::trans($columns));
foreach (['amount', 'paid', 'balance'] as $type) {
$csv = trans("texts.{$type}").',';
foreach ($totals[$type] as $currencyId => $amount) {
$csv .= Utils::formatMoney($amount, $currencyId).',';
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 ($totals as $currencyId => $fields) {
$csv = Utils::getFromCache($currencyId, 'currencies')->name . ',';
foreach ($fields as $key => $value) {
$csv .= '"' . Utils::formatMoney($value, $currencyId).'",';
}
fwrite($output, $csv."\n");
}

View File

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

View File

@ -25,7 +25,7 @@ class TaxRateController extends BaseController
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo)
{
parent::__construct();
//parent::__construct();
$this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo;

View File

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

View File

@ -11,9 +11,9 @@ use Request;
use Redirect;
use Session;
use URL;
use Password;
use Utils;
use Validator;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Models\User;
use App\Http\Requests;
use App\Ninja\Repositories\AccountRepository;
@ -30,7 +30,7 @@ class UserController extends BaseController
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer, UserService $userService)
{
parent::__construct();
//parent::__construct();
$this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
@ -77,7 +77,6 @@ class UserController extends BaseController
'user' => $user,
'method' => 'PUT',
'url' => 'users/'.$publicId,
'title' => trans('texts.edit_user'),
];
return View::make('users.edit', $data);
@ -120,7 +119,6 @@ class UserController extends BaseController
'user' => null,
'method' => 'POST',
'url' => 'users',
'title' => trans('texts.add_user'),
];
return View::make('users.edit', $data);
@ -192,6 +190,8 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
} else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
@ -202,10 +202,12 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH);
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
$user->public_id = $lastUser->public_id + 1;
$user->permissions = Input::get('permissions');
}
$user->save();
@ -240,7 +242,7 @@ class UserController extends BaseController
*
* @param string $code
*/
public function confirm($code, TokenRepositoryInterface $tokenRepo)
public function confirm($code)
{
$user = User::where('confirmation_code', '=', $code)->get()->first();
@ -253,7 +255,7 @@ class UserController extends BaseController
if ($user->public_id) {
//Auth::login($user);
$token = $tokenRepo->create($user);
$token = Password::getRepository()->create($user);
return Redirect::to("/password/reset/{$token}");
} else {

View File

@ -30,10 +30,11 @@ class VendorController extends BaseController
{
protected $vendorService;
protected $vendorRepo;
protected $model = 'App\Models\Vendor';
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
{
parent::__construct();
//parent::__construct();
$this->vendorRepo = $vendorRepo;
$this->vendorService = $vendorService;
@ -76,7 +77,13 @@ class VendorController extends BaseController
*/
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'));
@ -92,6 +99,11 @@ class VendorController extends BaseController
public function show($publicId)
{
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail();
if(!$this->checkViewPermission($vendor, $response)){
return $response;
}
Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [
@ -119,6 +131,10 @@ class VendorController extends BaseController
*/
public function create()
{
if(!$this->checkCreatePermission($response)){
return $response;
}
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"]);
}
@ -144,6 +160,11 @@ class VendorController extends BaseController
public function edit($publicId)
{
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
if(!$this->checkEditPermission($vendor, $response)){
return $response;
}
$data = [
'vendor' => $vendor,
'method' => 'PUT',
@ -180,7 +201,13 @@ class VendorController extends BaseController
*/
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'));

View File

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

View File

@ -21,11 +21,15 @@ class ApiCheck {
*/
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();
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 {
// check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
@ -34,7 +38,7 @@ class ApiCheck {
Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id);
} else {
sleep(3);
sleep(ERROR_DELAY);
return Response::json('Invalid token', 403, $headers);
}
}

View File

@ -1,28 +1,13 @@
<?php namespace App\Http\Middleware;
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 {
/**
* 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.
*
@ -30,9 +15,46 @@ class Authenticate {
* @param \Closure $next
* @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())
{
@ -40,11 +62,30 @@ class Authenticate {
}
else
{
return redirect()->guest('/login');
return redirect()->guest($guard=='client'?'/client/login':'/login');
}
}
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';
$version = @file_get_contents($file);
if ($version != NINJA_VERSION) {
if (version_compare(phpversion(), '5.5.9', '<')) {
dd('Please update PHP to >= 5.5.9');
}
$handle = fopen($file, 'w');
fwrite($handle, NINJA_VERSION);
fclose($handle);

View File

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

View File

@ -35,17 +35,20 @@ Route::get('/keep_alive', 'HomeController@keepAlive');
Route::post('/get_started', 'AccountController@getStarted');
// Client visible pages
Route::get('view/{invitation_key}', 'PublicClientController@view');
Route::get('download/{invitation_key}', 'PublicClientController@download');
Route::get('view', 'HomeController@viewLogo');
Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard');
Route::group(['middleware' => 'auth:client'], function() {
Route::get('view/{invitation_key}', 'PublicClientController@view');
Route::get('download/{invitation_key}', 'PublicClientController@download');
Route::get('view', 'HomeController@viewLogo');
Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
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.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
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');
// Laravel auth routes
get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister'));
post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister'));
get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
get('/user/confirm/{code}', 'UserController@confirm');
Route::get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister'));
Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister'));
Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
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()) {
@ -87,12 +99,80 @@ if (Utils::isReseller()) {
Route::post('/reseller_stats', 'AppController@stats');
}
Route::group(['middleware' => 'auth'], function() {
Route::group(['middleware' => 'auth:user'], function() {
Route::get('dashboard', 'DashboardController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage');
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::resource('users', 'UserController');
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/validate', 'BankAccountController@validateAccount');
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
@ -215,6 +235,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{
Route::get('ping', 'ClientApiController@ping');
Route::post('login', 'AccountApiController@login');
Route::post('register', 'AccountApiController@register');
Route::get('static', 'AccountApiController@getStaticData');
Route::get('accounts', 'AccountApiController@show');
Route::put('accounts', 'AccountApiController@update');
@ -234,6 +255,9 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController');
Route::resource('expenses','ExpenseApiController');
Route::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
Route::get('dashboard', 'DashboardApiController@index');
// Vendor
Route::resource('vendors', 'VendorApiController');
@ -485,6 +509,8 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_PAYFAST', 13);
define('GATEWAY_PAYPAL_EXPRESS', 17);
define('GATEWAY_PAYPAL_PRO', 18);
define('GATEWAY_SAGE_PAY_DIRECT', 20);
define('GATEWAY_SAGE_PAY_SERVER', 21);
define('GATEWAY_STRIPE', 23);
define('GATEWAY_GOCARDLESS', 6);
define('GATEWAY_TWO_CHECKOUT', 27);
@ -509,7 +535,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.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('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('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_SELF_HOST', 5); // include the custom design
define('PRODUCT_ONE_CLICK_INSTALL', 1);
@ -549,6 +577,9 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET');
define('IOS_PRODUCTION_PUSH','ninjaIOS');
define('IOS_DEV_PUSH','devNinjaIOS');
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);
@ -572,6 +603,9 @@ if (!defined('CONTACT_EMAIL')) {
define('REMINDER_FIELD_DUE_DATE', 1);
define('REMINDER_FIELD_INVOICE_DATE', 2);
define('FILTER_INVOICE_DATE', 'invoice_date');
define('FILTER_PAYMENT_DATE', 'payment_date');
define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub');
@ -581,6 +615,7 @@ if (!defined('CONTACT_EMAIL')) {
define('USER_STATE_PENDING', 'pending');
define('USER_STATE_DISABLED', 'disabled');
define('USER_STATE_ADMIN', 'admin');
define('USER_STATE_OWNER', 'owner');
define('API_SERIALIZER_ARRAY', 'array');
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".
"<FID>".$this->bank->fid."\n".
"</FI>\n".
"<APPID>QMOFX\n".
"<APPVER>1900\n".
"<APPID>QWIN\n".
"<APPVER>2500\n".
"</SONRQ>\n".
"</SIGNONMSGSRQV1>\n".
"<SIGNUPMSGSRQV1>\n".
@ -171,8 +171,8 @@ class Account
"<ORG>".$this->login->bank->org."\n".
"<FID>".$this->login->bank->fid."\n".
"</FI>\n".
"<APPID>QMOFX\n".
"<APPVER>1900\n".
"<APPID>QWIN\n".
"<APPVER>2500\n".
"</SONRQ>\n".
"</SIGNONMSGSRQV1>\n";
if ($this->type == 'BANK') {

View File

@ -72,7 +72,7 @@ class Utils
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;
}
@ -118,6 +118,21 @@ class Utils
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()
{
return Auth::check() && Auth::user()->isTrial();
@ -128,6 +143,13 @@ class Utils
return App::getLocale() == 'en';
}
public static function getLocaleRegion()
{
$parts = explode('_', App::getLocale());
return count($parts) ? $parts[0] : 'en';
}
public static function getUserType()
{
if (Utils::isNinja()) {
@ -225,7 +247,7 @@ class Utils
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) {
$error = self::getErrorString($error);
@ -249,7 +271,11 @@ class Utils
'count' => Session::get('error_count', 0),
];
if ($info) {
Log::info($error."\n", $data);
} else {
Log::error($error."\n", $data);
}
/*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
@ -598,8 +624,8 @@ class Utils
private static function getMonth($offset)
{
$months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December", ];
$months = [ "january", "february", "march", "april", "may", "june",
"july", "august", "september", "october", "november", "december", ];
$month = intval(date('n')) - 1;
@ -610,7 +636,7 @@ class Utils
$month += 12;
}
return $months[$month];
return trans('texts.' . $months[$month]);
}
private static function getQuarter($offset)
@ -774,9 +800,11 @@ class Utils
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]));
}

View File

@ -9,16 +9,20 @@ use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Events\QuoteInvitationWasApproved;
use App\Events\PaymentWasCreated;
use App\Ninja\Notifications;
use App\Services\PushService;
class NotificationListener
{
protected $userMailer;
protected $contactMailer;
protected $pushService;
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer)
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, PushService $pushService)
{
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
$this->pushService = $pushService;
}
private function sendEmails($invoice, $type, $payment = null)
@ -35,26 +39,31 @@ class NotificationListener
public function emailedInvoice(InvoiceWasEmailed $event)
{
$this->sendEmails($event->invoice, 'sent');
$this->pushService->sendNotification($event->invoice, 'sent');
}
public function emailedQuote(QuoteWasEmailed $event)
{
$this->sendEmails($event->quote, 'sent');
$this->pushService->sendNotification($event->quote, 'sent');
}
public function viewedInvoice(InvoiceInvitationWasViewed $event)
{
$this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
}
public function viewedQuote(QuoteInvitationWasViewed $event)
{
$this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
}
public function approvedQuote(QuoteInvitationWasApproved $event)
{
$this->sendEmails($event->quote, 'approved');
$this->pushService->sendNotification($event->quote, 'approved');
}
public function createdPayment(PaymentWasCreated $event)
@ -66,6 +75,8 @@ class NotificationListener
$this->contactMailer->sendPaymentConfirmation($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)
{
if ( ! Auth::check()) {
return;
}
$transformer = new ClientTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer);
$transformer = new ClientTransformer($event->client->account);
$this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer);
}
public function createdQuote(QuoteWasCreated $event)
{
if ( ! Auth::check()) {
return;
}
$transformer = new InvoiceTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
$transformer = new InvoiceTransformer($event->quote->account);
$this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
}
public function createdPayment(PaymentWasCreated $event)
{
if ( ! Auth::check()) {
return;
}
$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);
$transformer = new PaymentTransformer($event->payment->account);
$this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
}
public function createdInvoice(InvoiceWasCreated $event)
{
if ( ! Auth::check()) {
return;
$transformer = new InvoiceTransformer($event->invoice->account);
$this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
}
$transformer = new InvoiceTransformer(Auth::user()->account);
$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
public function createdCredit(CreditWasCreated $event)
{
}
public function createdVendor(VendorWasCreated $event)
{
//$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor);
}
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) {
$manager = new Manager();
@ -93,6 +73,11 @@ class SubscriptionListener
$resource = new Item($entity, $transformer, $entity->getEntityType());
$data = $manager->createData($resource)->toArray();
// For legacy Zapier support
if (isset($data['client_id'])) {
$data['client_name'] = $entity->client->getDisplayName();
}
Utils::notifyZapier($subscription, $data);
}
}

View File

@ -439,7 +439,7 @@ class Account extends Eloquent
return $height;
}
public function createInvoice($entityType, $clientId = null)
public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null)
{
$invoice = Invoice::createNew();
@ -614,6 +614,28 @@ class Account extends Eloquent
$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)
{
$this->load('timezone', 'date_format', 'datetime_format', 'language');
@ -661,7 +683,7 @@ class Account extends Eloquent
'subtotal',
'paid_to_date',
'balance_due',
'amount_due',
'partial_due',
'terms',
'your_invoice',
'quote',
@ -1001,7 +1023,7 @@ class Account extends Eloquent
return true;
}
public function showCustomField($field, $entity)
public function showCustomField($field, $entity = false)
{
if ($this->isPro()) {
return $this->$field ? true : false;
@ -1050,7 +1072,13 @@ class Account extends Eloquent
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 = ''){

View File

@ -155,6 +155,14 @@ class Client extends EntityModel
$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->is_primary = $isPrimary;
@ -272,6 +280,11 @@ class Client extends EntityModel
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
}
public function getAmount()
{
return $this->balance + $this->paid_to_date;
}
public function getCurrencyId()
{
if ($this->currency_id) {

View File

@ -3,10 +3,14 @@
use HTML;
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 $fillable = [

View File

@ -42,7 +42,7 @@ class EntityModel extends Eloquent
{
$className = get_called_class();
return $className::scope($publicId)->withTrashed()->pluck('id');
return $className::scope($publicId)->withTrashed()->value('id');
}
public function getActivityKey()
@ -81,6 +81,11 @@ class EntityModel extends Eloquent
return $query;
}
public function scopeWithArchived($query)
{
return $query->withTrashed()->where('is_deleted', '=', false);
}
public function getName()
{
return $this->public_id;
@ -108,4 +113,56 @@ class EntityModel extends Eloquent
$name = $parts[count($parts)-1];
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';
} elseif ($this->id == GATEWAY_DWOLLA) {
$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;

View File

@ -129,14 +129,22 @@ class Invoice extends EntityModel implements BalanceAffecting
return false;
}
public function getAmountPaid()
public function getAmountPaid($calculate = false)
{
if ($this->is_quote || $this->is_recurring) {
return 0;
}
if ($calculate) {
$amount = 0;
foreach ($this->payments as $payment) {
$amount += $payment->amount;
}
return $amount;
} else {
return ($this->amount - $this->balance);
}
}
public function trashed()
{
@ -192,6 +200,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
}
public function frequency()
{
return $this->belongsTo('App\Models\Frequency');
}
public function invitations()
{
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
@ -442,12 +455,16 @@ class Invoice extends EntityModel implements BalanceAffecting
'show_item_taxes',
'custom_invoice_text_label1',
'custom_invoice_text_label2',
'custom_invoice_item_label1',
'custom_invoice_item_label2',
]);
foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([
'product_key',
'notes',
'custom_value1',
'custom_value2',
'cost',
'qty',
'tax_name',
@ -752,6 +769,98 @@ class Invoice extends EntityModel implements BalanceAffecting
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) {

View File

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

View File

@ -14,6 +14,11 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
public static $all_permissions = array(
'create_all' => 0b0001,
'view_all' => 0b0010,
'edit_all' => 0b0100,
);
use Authenticatable, CanResetPassword;
@ -254,6 +259,68 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
&& $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) {

View File

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

View File

@ -1,6 +1,6 @@
<?php namespace App\Ninja\Mailers;
use HTML;
use Form;
use Utils;
use Event;
use URL;
@ -27,6 +27,7 @@ class ContactMailer extends Mailer
'firstName',
'invoice',
'quote',
'password',
'viewLink',
'viewButton',
'paymentLink',
@ -110,6 +111,13 @@ class ContactMailer extends Mailer
'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 = [
'body' => $this->processVariables($body, $variables),
'link' => $invitation->getLink(),
@ -144,6 +152,28 @@ class ContactMailer extends Mailer
}
}
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)
{
$account = $payment->account;
@ -232,6 +262,7 @@ class ContactMailer extends Mailer
$client = $data['client'];
$invitation = $data['invitation'];
$invoice = $invitation->invoice;
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
$variables = [
'$footer' => $account->getEmailFooter(),
@ -245,10 +276,11 @@ class ContactMailer extends Mailer
'$invoice' => $invoice->invoice_number,
'$quote' => $invoice->invoice_number,
'$link' => $invitation->getLink(),
'$viewLink' => $invitation->getLink(),
'$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()),
'$paymentLink' => $invitation->getLink('payment'),
'$paymentButton' => HTML::emailPaymentButton($invitation->getLink('payment')),
'$password' => $passwordHTML,
'$viewLink' => $invitation->getLink().'$password',
'$viewButton' => Form::emailViewButton($invitation->getLink(), $invoice->getEntityType()).'$password',
'$paymentLink' => $invitation->getLink('payment').'$password',
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
'$customClient1' => $account->custom_client_label1,
'$customClient2' => $account->custom_client_label2,
'$customInvoice1' => $account->custom_invoice_text_label1,
@ -260,10 +292,21 @@ class ContactMailer extends Mailer
$camelType = Gateway::getPaymentTypeName($type);
$type = Utils::toSnakeCase($camelType);
$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);
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);
return $str;

View File

@ -81,7 +81,7 @@ class Mailer
$emailError = $exception->getMessage();
}
Utils::logError("Email Error: $emailError");
//Utils::logError("Email Error: $emailError");
if (isset($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;
use URL;
use Utils;
use Laracasts\Presenter\Presenter;
@ -26,6 +27,15 @@ class ClientPresenter extends Presenter {
}
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);
}
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;
use URL;
use Utils;
use Laracasts\Presenter\Presenter;
@ -18,7 +19,7 @@ class InvoicePresenter extends Presenter {
public function balanceDueLabel()
{
if ($this->entity->partial) {
return 'amount_due';
return 'partial_due';
} elseif ($this->entity->is_quote) {
return 'total';
} else {
@ -40,10 +41,16 @@ class InvoicePresenter extends Presenter {
public function status()
{
if ($this->entity->is_deleted) {
return trans('texts.deleted');
} 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()
{
@ -55,4 +62,24 @@ class InvoicePresenter extends Presenter {
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;
use URL;
use Utils;
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 : '';
}
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 Utils;
use DB;
use URL;
use stdClass;
use Validator;
use Schema;
@ -58,7 +59,7 @@ class AccountRepository
}
$user->confirmed = !Utils::isNinja();
$user->registered = !Utils::isNinja() && $user->email;
$user->registered = !Utils::isNinja() || $email;
if (!$user->confirmed) {
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
@ -69,49 +70,135 @@ class AccountRepository
return $account;
}
public function getSearchData()
public function getSearchData($account)
{
$clients = \DB::table('clients')
->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"));
$data = $this->getAccountSearchData($account);
$contacts = \DB::table('clients')
->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"));
$data['navigation'] = $this->getNavigationSearchData();
$invoices = \DB::table('clients')
->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 = [];
foreach ($clients->union($contacts)->union($invoices)->get() as $row) {
$type = $row->trans_type;
if (!isset($data[$type])) {
$data[$type] = [];
return $data;
}
$tokens = explode(' ', $row->name);
$tokens[] = $type;
private function getAccountSearchData($account)
{
$data = [
'clients' => [],
'contacts' => [],
'invoices' => [],
'quotes' => [],
];
if ($type == 'Invoices') {
$tokens[] = intVal($row->token).'';
// include custom client fields in search
if ($account->custom_client_label1) {
$data[$account->custom_client_label1] = [];
}
if ($account->custom_client_label2) {
$data[$account->custom_client_label2] = [];
}
$data[$type][] = [
'value' => $row->name,
'public_id' => $row->public_id,
'tokens' => $tokens,
'entity_type' => $row->type,
$clients = Client::scope()
->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,
];
}
foreach ($client->contacts as $contact) {
if ($contact->getFullName()) {
$data['contacts'][] = [
'value' => $contact->getDisplayName(),
'tokens' => $contact->getDisplayName(),
'url' => $client->present()->url,
];
}
if ($contact->email) {
$data['contacts'][] = [
'value' => $contact->email,
'tokens' => $contact->email,
'url' => $client->present()->url,
];
}
}
foreach ($client->invoices as $invoice) {
$entityType = $invoice->getEntityType();
$data["{$entityType}s"][] = [
'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(),
'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;
$client = $this->getNinjaClient($account);
$invitation = $this->createNinjaInvoice($client, $account);
return $invitation;
}
public function createNinjaInvoice($client, $clientAccount)
{
$account = $this->getNinjaAccount();

View File

@ -1,6 +1,7 @@
<?php namespace App\Ninja\Repositories;
use DB;
use Cache;
use App\Ninja\Repositories\BaseRepository;
use App\Models\Client;
use App\Models\Contact;
@ -45,7 +46,8 @@ class ClientRepository extends BaseRepository
'clients.work_phone',
'contacts.email',
'clients.deleted_at',
'clients.is_deleted'
'clients.is_deleted',
'clients.user_id'
);
if (!\Session::get('show_trash:client')) {
@ -74,6 +76,17 @@ class ClientRepository extends BaseRepository
$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->save();

View File

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

View File

@ -40,7 +40,8 @@ class ExpenseRepository extends BaseRepository
'expenses.public_id',
'expenses.deleted_at',
'expenses.should_be_invoiced',
'expenses.created_at'
'expenses.created_at',
'expenses.user_id'
);
return $query;
@ -80,11 +81,15 @@ class ExpenseRepository extends BaseRepository
'expenses.vendor_id',
'expenses.expense_currency_id',
'expenses.invoice_currency_id',
'expenses.user_id',
'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'vendors.name as vendor_name',
'vendors.public_id as vendor_public_id',
'vendors.user_id as vendor_user_id',
'clients.name as client_name',
'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'contacts.first_name',
'contacts.email',
'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.country_id, accounts.country_id) country_id'),
'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'invoice_number',
'invoice_status_id',
'clients.name as client_name',
@ -65,7 +66,8 @@ class InvoiceRepository extends BaseRepository
'invoices.quote_invoice_id',
'invoices.deleted_at',
'invoices.is_deleted',
'invoices.partial'
'invoices.partial',
'invoices.user_id'
);
if (!\Session::get('show_trash:'.$entityType)) {
@ -170,7 +172,7 @@ class InvoiceRepository extends BaseRepository
);
$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('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); });
@ -189,7 +191,7 @@ class InvoiceRepository extends BaseRepository
->make();
}
public function save($data)
public function save($data, $checkSubPermissions = false)
{
$account = \Auth::user()->account;
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
@ -398,38 +400,49 @@ class InvoiceRepository extends BaseRepository
foreach ($data['invoice_items'] as $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;
}
$task = false;
if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
if(!$checkSubPermissions || $task->canEdit()){
$task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id;
$task->save();
}
}
$expense = false;
if (isset($item['expense_public_id']) && $item['expense_public_id']) {
$expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail();
if(!$checkSubPermissions || $expense->canEdit()){
$expense->invoice_id = $invoice->id;
$expense->client_id = $invoice->client_id;
$expense->save();
}
}
if ($productKey = trim($item['product_key'])) {
if (\Auth::user()->account->update_products && ! strtotime($productKey)) {
$product = Product::findProductByKey($productKey);
if (!$product) {
if(!$checkSubPermissions || Product::canCreate()){
$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();
}
}
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
@ -439,6 +452,13 @@ class InvoiceRepository extends BaseRepository
$invoiceItem->qty = Utils::parseFloat($item['qty']);
$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']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']);
@ -603,7 +623,7 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_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->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');

View File

@ -36,9 +36,11 @@ class PaymentRepository extends BaseRepository
'payments.transaction_reference',
'clients.name as client_name',
'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'payments.amount',
'payments.payment_date',
'invoices.public_id as invoice_public_id',
'invoices.user_id as invoice_user_id',
'invoices.invoice_number',
'contacts.first_name',
'contacts.last_name',
@ -47,6 +49,7 @@ class PaymentRepository extends BaseRepository
'payments.account_gateway_id',
'payments.deleted_at',
'payments.is_deleted',
'payments.user_id',
'invoices.is_deleted as invoice_is_deleted',
'gateways.name as gateway_name'
);

View File

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

View File

@ -22,7 +22,7 @@ class UserRepository extends BaseRepository
$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;
}
@ -34,5 +34,4 @@ class UserRepository extends BaseRepository
return $user;
}
}

View File

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

View File

@ -21,7 +21,11 @@ class UserTransformer extends EntityTransformer
'registered' => (bool) $user->registered,
'confirmed' => (bool) $user->confirmed,
'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 Utils;
use HTML;
use Form;
use URL;
use Request;
use Validator;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Vendor;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
@ -18,62 +22,74 @@ class AppServiceProvider extends ServiceProvider {
*/
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"' : '';
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>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
Form::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : '';
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';
$Type = ucfirst($type);
$Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : '';
$str = '<li class="dropdown '.$class.'">
<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>';
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>';
$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) {
$str .= '<li class="divider"></li>
<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(!empty($items))$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</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()) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>
<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>';
if(Invoice::canCreate())$items[] = '<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
}
} else if ($type == ENTITY_CLIENT) {
$str .= '<li class="divider"></li>
<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(!empty($items))$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('credits').'">'.trans("texts.credits").'</a></li>';
if(Credit::canCreate())$items[] = '<li><a href="'.URL::to('credits/create').'">'.trans("texts.new_credit").'</a></li>';
} else if ($type == ENTITY_EXPENSE) {
$str .= '<li class="divider"></li>
<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(!empty($items))$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li>';
if(Vendor::canCreate())$items[] = '<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>';
}
$str .= '</ul>
</li>';
if(!empty($items)){
$str.= '<ul class="dropdown-menu" id="menu1">'.implode($items).'</ul>';
}
$str .= '</li>';
return $str;
});
HTML::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
});
HTML::macro('flatButton', function($label, $color) {
Form::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;"/>';
});
HTML::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) {
Form::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) {
return view('partials.email_button')
->with([
'link' => $link,
@ -83,7 +99,7 @@ class AppServiceProvider extends ServiceProvider {
->render();
});
HTML::macro('emailPaymentButton', function($link = '#') {
Form::macro('emailPaymentButton', function($link = '#') {
return view('partials.email_button')
->with([
'link' => $link,
@ -93,7 +109,7 @@ class AppServiceProvider extends ServiceProvider {
->render();
});
HTML::macro('breadcrumbs', function($status = false) {
Form::macro('breadcrumbs', function($status = false) {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
@ -223,11 +239,6 @@ class AppServiceProvider extends ServiceProvider {
'Illuminate\Contracts\Auth\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',
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',
function ($model) {
$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),
'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice) : null,
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice) : 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),
'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)->toHtml() : null,
'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 ?: '',
'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id)
];

View File

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

View File

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

View File

@ -4,6 +4,12 @@ use Utils;
use URL;
use Auth;
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\NinjaRepository;
@ -37,6 +43,10 @@ class ClientService extends BaseService
{
$query = $this->clientRepo->find($search);
if(!Utils::hasPermission('view_all')){
$query->where('clients.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_CLIENT, $query);
}
@ -46,19 +56,19 @@ class ClientService extends BaseService
[
'name',
function ($model) {
return link_to("clients/{$model->public_id}", $model->name ?: '');
return link_to("clients/{$model->public_id}", $model->name ?: '')->toHtml();
}
],
[
'first_name',
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',
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'),
function ($model) {
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'),
function ($model) {
return URL::to("tasks/create/{$model->public_id}");
},
function ($model) {
return Task::canCreate();
}
],
[
trans('texts.new_invoice'),
function ($model) {
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}");
},
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'),
function ($model) {
return URL::to("payments/create/{$model->public_id}");
},
function ($model) {
return Payment::canCreate();
}
],
[
trans('texts.enter_credit'),
function ($model) {
return URL::to("credits/create/{$model->public_id}");
},
function ($model) {
return Credit::canCreate();
}
],
[
trans('texts.enter_expense'),
function ($model) {
return URL::to("expenses/create/0/{$model->public_id}");
},
function ($model) {
return Expense::canCreate();
}
]
];

View File

@ -2,7 +2,10 @@
use Utils;
use URL;
use Auth;
use App\Services\BaseService;
use App\Models\Client;
use App\Models\Payment;
use App\Ninja\Repositories\CreditRepository;
@ -31,6 +34,10 @@ class CreditService extends BaseService
{
$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);
}
@ -40,7 +47,11 @@ class CreditService extends BaseService
[
'client_name',
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
],
@ -78,6 +89,9 @@ class CreditService extends BaseService
trans('texts.apply_credit'),
function ($model) {
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;
use HtmlString;
use Utils;
use Datatable;
use Auth;
class DatatableService
{
@ -12,7 +14,9 @@ class DatatableService
if ($actions && $showCheckbox) {
$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) . '>';
});
}
@ -44,6 +48,8 @@ class DatatableService
$hasAction = false;
$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) {
$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') {
@ -69,9 +75,15 @@ class DatatableService
}
list($value, $url, $visible) = $action;
if ($visible($model)) {
if($value == '--divider--'){
$str .= "<li class=\"divider\"></li>";
$lastIsDivider = true;
}
else {
$str .= "<li><a href=\"{$url($model)}\">{$value}</a></li>";
$lastIsDivider = false;
$hasAction = true;
$lastIsDivider = false;
}
}
} elseif ( ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>";
@ -83,20 +95,20 @@ class DatatableService
return '';
}
if ( ! $lastIsDivider) {
if ( $can_edit && ! $lastIsDivider) {
$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})\">"
. trans("texts.archive_{$entityType}") . "</a></li>";
}
} else {
} else if($can_edit) {
$str .= "<li><a href=\"javascript:restoreEntity({$model->public_id})\">"
. 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})\">"
. trans("texts.delete_{$entityType}") . "</a></li>";
}

View File

@ -1,10 +1,13 @@
<?php namespace App\Services;
use Auth;
use DB;
use Utils;
use URL;
use App\Services\BaseService;
use App\Ninja\Repositories\ExpenseRepository;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Client;
use App\Models\Vendor;
@ -42,6 +45,10 @@ class ExpenseService extends BaseService
{
$query = $this->expenseRepo->find($search);
if(!Utils::hasPermission('view_all')){
$query->where('expenses.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_EXPENSE, $query);
}
@ -63,7 +70,11 @@ class ExpenseService extends BaseService
function ($model)
{
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 {
return '';
}
@ -74,7 +85,11 @@ class ExpenseService extends BaseService
function ($model)
{
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 {
return '';
}
@ -83,7 +98,11 @@ class ExpenseService extends BaseService
[
'expense_date',
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();
}
],
[
@ -151,6 +170,9 @@ class ExpenseService extends BaseService
trans('texts.edit_expense'),
function ($model) {
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");
},
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})";
},
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();
}
],
];

View File

@ -8,6 +8,9 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Events\QuoteInvitationWasApproved;
use App\Models\Invitation;
use App\Models\Invoice;
use App\Models\Client;
use App\Models\Payment;
class InvoiceService extends BaseService
{
@ -27,14 +30,26 @@ class InvoiceService extends BaseService
return $this->invoiceRepo;
}
public function save($data)
public function save($data, $checkSubPermissions = false)
{
if (isset($data['client'])) {
$can_save_client = !$checkSubPermissions;
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->load('contacts');
@ -79,7 +94,8 @@ class InvoiceService extends BaseService
public function approveQuote($quote, $invitation = null)
{
$account = Auth::user()->account;
$account = $quote->account;
if (!$quote->is_quote || $quote->quote_invoice_id) {
return null;
}
@ -108,6 +124,10 @@ class InvoiceService extends BaseService
$query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search)
->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);
}
@ -117,13 +137,20 @@ class InvoiceService extends BaseService
[
'invoice_number',
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',
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
],
@ -159,8 +186,8 @@ class InvoiceService extends BaseService
],
[
'invoice_status_name',
function ($model) {
return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : self::getStatusLabel($model);
function ($model) use ($entityType) {
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}"),
function ($model) use ($entityType) {
return URL::to("{$entityType}s/{$model->public_id}/edit");
},
function ($model) {
return Invoice::canEditItem($model);
}
],
[
trans("texts.clone_{$entityType}"),
function ($model) use ($entityType) {
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}");
}
],
[],
[
'--divider--', function(){return false;},
function ($model) {
return Invoice::canEditItem($model) || Payment::canCreate();
}
],
[
trans("texts.mark_sent"),
function ($model) {
return "javascript:markEntity({$model->public_id})";
},
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}");
},
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");
},
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");
},
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})";
},
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
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")) {
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>";
}
}

View File

@ -1,6 +1,7 @@
<?php namespace App\Services;
use Utils;
use Auth;
use URL;
use DateTime;
use Event;
@ -10,6 +11,8 @@ use CreditCard;
use App\Models\Payment;
use App\Models\Account;
use App\Models\Country;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\AccountGatewayToken;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\AccountRepository;
@ -286,6 +289,10 @@ class PaymentService extends BaseService
{
$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);
}
@ -295,13 +302,21 @@ class PaymentService extends BaseService
[
'invoice_number',
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',
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
],
@ -339,6 +354,9 @@ class PaymentService extends BaseService
trans('texts.edit_payment'),
function ($model) {
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',
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();
}
],
[

View File

@ -44,7 +44,7 @@ class ProductService extends BaseService
[
'product_key',
function ($model) {
return link_to('products/'.$model->public_id.'/edit', $model->product_key);
return link_to('products/'.$model->public_id.'/edit', $model->product_key)->toHtml();
}
],
[

View File

@ -0,0 +1,171 @@
<?php
namespace App\Services;
use Illuminate\Http\Request;
use App\Ninja\Notifications\PushFactory;
/**
* Class PushService
* @package App\Ninja\Notifications
*/
/**
* $account->devices Definition
*
* @param string token (push notification device token)
* @param string email (user email address - required for use as key)
* @param string device (ios, gcm etc etc)
* @param bool notify_sent
* @param bool notify_paid
* @param bool notify_approved
* @param bool notify_viewed
*/
class PushService
{
protected $pushFactory;
/**
* @param PushFactory $pushFactory
*/
public function __construct(PushFactory $pushFactory)
{
$this->pushFactory = $pushFactory;
}
/**
* @param $invoice - Invoice object
* @param $type - Type of notification, ie. Quote APPROVED, Invoice PAID, Invoice/Quote SENT, Invoice/Quote VIEWED
*/
public function sendNotification($invoice, $type)
{
//check user has registered for push notifications
if(!$this->checkDeviceExists($invoice->account))
return;
//Harvest an array of devices that are registered for this notification type
$devices = json_decode($invoice->account->devices, TRUE);
foreach($devices as $device)
{
if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios'))
$this->pushMessage($invoice, $device['token'], $type);
}
}
/**
* pushMessage function
*
* method to dispatch iOS notifications
*
* @param $invoice
* @param $token
* @param $type
*/
private function pushMessage($invoice, $token, $type)
{
$this->pushFactory->message($token, $this->messageType($invoice, $type));
}
/**
* checkDeviceExists function
*
* Returns a boolean if this account has devices registered for PUSH notifications
*
* @param $account
* @return bool
*/
private function checkDeviceExists($account)
{
$devices = json_decode($account->devices, TRUE);
if(count($devices) >= 1)
return TRUE;
else
return FALSE;
}
/**
* messageType function
*
* method which formats an appropriate message depending on message type
*
* @param $invoice
* @param $type
* @return string
*/
private function messageType($invoice, $type)
{
switch($type)
{
case 'sent':
return $this->entitySentMessage($invoice);
break;
case 'paid':
return $this->invoicePaidMessage($invoice);
break;
case 'approved':
return $this->quoteApprovedMessage($invoice);
break;
case 'viewed':
return $this->entityViewedMessage($invoice);
break;
}
}
/**
* @param $invoice
* @return string
*/
private function entitySentMessage($invoice)
{
if($invoice->is_quote)
return trans("texts.notification_quote_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
else
return trans("texts.notification_invoice_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
}
/**
* @param $invoice
* @return string
*/
private function invoicePaidMessage($invoice)
{
return trans("texts.notification_invoice_paid_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
}
/**
* @param $invoice
* @return string
*/
private function quoteApprovedMessage($invoice)
{
return trans("texts.notification_quote_approved_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
}
/**
* @param $invoice
* @return string
*/
private function entityViewedMessage($invoice)
{
if($invoice->is_quote)
return trans("texts.notification_quote_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
else
return trans("texts.notification_invoice_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]);
}
}

View File

@ -1,7 +1,9 @@
<?php namespace App\Services;
use URL;
use Auth;
use Utils;
use App\Models\Invoice;
use App\Ninja\Repositories\InvoiceRepository;
class RecurringInvoiceService extends BaseService
@ -19,6 +21,10 @@ class RecurringInvoiceService extends BaseService
{
$query = $this->invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search);
if(!Utils::hasPermission('view_all')){
$query->where('invoices.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_RECURRING_INVOICE, $query, !$clientPublicId);
}
@ -28,13 +34,13 @@ class RecurringInvoiceService extends BaseService
[
'frequency',
function ($model) {
return link_to("invoices/{$model->public_id}", $model->frequency);
return link_to("invoices/{$model->public_id}", $model->frequency)->toHtml();
}
],
[
'client_name',
function ($model) {
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model));
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
},
! $hideClient
],
@ -66,6 +72,9 @@ class RecurringInvoiceService extends BaseService
trans('texts.edit_invoice'),
function ($model) {
return URL::to("invoices/{$model->public_id}/edit");
},
function ($model) {
return Invoice::canEditItem($model);
}
]
];

View File

@ -1,39 +0,0 @@
<?php namespace App\Services;
use App\Model\User;
use Validator;
use Illuminate\Contracts\Auth\Registrar as RegistrarContract;
class Registrar implements RegistrarContract {
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
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']),
]);
}
}

View File

@ -1,8 +1,11 @@
<?php namespace App\Services;
use Auth;
use URL;
use Utils;
use App\Models\Task;
use App\Models\Invoice;
use App\Models\Client;
use App\Ninja\Repositories\TaskRepository;
use App\Services\BaseService;
@ -33,6 +36,10 @@ class TaskService extends BaseService
{
$query = $this->taskRepo->find($clientPublicId, $search);
if(!Utils::hasPermission('view_all')){
$query->where('tasks.user_id', '=', Auth::user()->id);
}
return $this->createDatatable(ENTITY_TASK, $query, !$clientPublicId);
}
@ -42,14 +49,18 @@ class TaskService extends BaseService
[
'client_name',
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
],
[
'created_at',
function ($model) {
return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model));
return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml();
}
],
[
@ -82,7 +93,7 @@ class TaskService extends BaseService
return URL::to('tasks/'.$model->public_id.'/edit');
},
function ($model) {
return !$model->deleted_at || $model->deleted_at == '0000-00-00';
return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Task::canEditItem($model);
}
],
[
@ -91,7 +102,7 @@ class TaskService extends BaseService
return URL::to("/invoices/{$model->invoice_public_id}/edit");
},
function ($model) {
return $model->invoice_number;
return $model->invoice_number && Invoice::canEditItemByOwner($model->invoice_user_id);
}
],
[
@ -100,7 +111,7 @@ class TaskService extends BaseService
return "javascript:stopTask({$model->public_id})";
},
function ($model) {
return $model->is_running;
return $model->is_running && Task::canEditItem($model);
}
],
[
@ -109,7 +120,7 @@ class TaskService extends BaseService
return "javascript:invoiceEntity({$model->public_id})";
},
function ($model) {
return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00');
return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate();
}
]
];

View File

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

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