Merge branch 'release-2.5.2'

Conflicts:
	app/Http/Controllers/AppController.php
	app/Http/routes.php
	app/Models/Account.php
	app/Models/Invitation.php
	composer.json
	composer.lock
	resources/lang/de/texts.php
	resources/lang/fr/texts.php
	resources/views/header.blade.php
This commit is contained in:
Hillel Coren 2016-05-10 21:41:13 +03:00
commit da911d090e
299 changed files with 13312 additions and 4155 deletions

View File

@ -43,4 +43,28 @@ API_SECRET=password
#GOOGLE_CLIENT_SECRET= #GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google #GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
#GOOGLE_MAPS_API_KEY= #GOOGLE_MAPS_API_KEY=
#S3_KEY=
#S3_SECRET=
#S3_REGION=
#S3_BUCKET=
#RACKSPACE_USERNAME=
#RACKSPACE_KEY=
#RACKSPACE_CONTAINER=
#RACKSPACE_REGION=
#RACKSPACE_TEMP_URL_SECRET=
# If this is set to anything, the URL secret will be set the next
# time a file is downloaded through the client portal.
# Only set this temporarily, as it slows things down.
#RACKSPACE_TEMP_URL_SECRET_SET=
#DOCUMENT_FILESYSTEM=
#MAX_DOCUMENT_SIZE # KB
#MAX_EMAIL_DOCUMENTS_SIZE # Total KB
#MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed)
#DOCUMENT_PREVIEW_SIZE # Pixels

138
.htaccess
View File

@ -6,3 +6,141 @@
# https://coderwall.com/p/erbaig/laravel-s-htaccess-to-remove-public-from-url # https://coderwall.com/p/erbaig/laravel-s-htaccess-to-remove-public-from-url
# RewriteRule ^(.*)$ public/$1 [L] # RewriteRule ^(.*)$ public/$1 [L]
</IfModule> </IfModule>
# https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess
# ######################################################################
# # INTERNET EXPLORER #
# ######################################################################
# ----------------------------------------------------------------------
# | Iframes cookies |
# ----------------------------------------------------------------------
# Allow cookies to be set from iframes in Internet Explorer.
#
# https://msdn.microsoft.com/en-us/library/ms537343.aspx
# http://www.w3.org/TR/2000/CR-P3P-20001215/
<IfModule mod_headers.c>
Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
</IfModule>
# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS #
# ######################################################################
# ----------------------------------------------------------------------
# | Character encodings |
# ----------------------------------------------------------------------
# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
AddDefaultCharset utf-8
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Serve the following file types with the media type `charset`
# parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
<IfModule mod_mime.c>
AddCharset utf-8 .atom \
.bbaw \
.css \
.geojson \
.js \
.json \
.jsonld \
.manifest \
.rdf \
.rss \
.topojson \
.vtt \
.webapp \
.webmanifest \
.xloc \
.xml
</IfModule>
# ######################################################################
# # WEB PERFORMANCE #
# ######################################################################
# ----------------------------------------------------------------------
# | Compression |
# ----------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled headers.
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Map certain file types to the specified encoding type in order to
# make Apache serve them with the appropriate `Content-Encoding` HTTP
# response header (this will NOT make Apache compress them!).
# If the following file types wouldn't be served without the appropriate
# `Content-Enable` HTTP response header, client applications (e.g.:
# browsers) wouldn't know that they first need to uncompress the response,
# and thus, wouldn't be able to understand the content.
# http://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
<IfModule mod_mime.c>
AddEncoding gzip svgz
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Compress all output labeled with one of the following media types.
# IMPORTANT: For Apache versions below 2.3.7 you don't need to enable
# `mod_filter` and can remove the `<IfModule mod_filter.c>` & `</IfModule>`
# lines as `AddOutputFilterByType` is still in the core directives.
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE "application/atom+xml" \
"application/javascript" \
"application/json" \
"application/ld+json" \
"application/manifest+json" \
"application/rdf+xml" \
"application/rss+xml" \
"application/schema+json" \
"application/vnd.geo+json" \
"application/vnd.ms-fontobject" \
"application/x-font-ttf" \
"application/x-web-app-manifest+json" \
"application/xhtml+xml" \
"application/xml" \
"font/opentype" \
"image/svg+xml" \
"image/x-icon" \
"text/cache-manifest" \
"text/css" \
"text/html" \
"text/javascript" \
"text/plain" \
"text/vtt" \
"text/x-component" \
"text/xml"
</IfModule>
</IfModule>

View File

@ -45,7 +45,8 @@ before_script:
- php artisan key:generate --no-interaction - php artisan key:generate --no-interaction
- sed -i 's/APP_ENV=production/APP_ENV=development/g' .env - sed -i 's/APP_ENV=production/APP_ENV=development/g' .env
- sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env - sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env
- sed -i 's/REQUIRE_HTTPS=false/NINJA_DEV=true/g' .env - sed -i '$a NINJA_DEV=true' .env
- sed -i '$a TRAVIS=true' .env
# create the database and user # create the database and user
- mysql -u root -e "create database IF NOT EXISTS ninja;" - mysql -u root -e "create database IF NOT EXISTS ninja;"
- mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;" - mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;"
@ -87,7 +88,10 @@ after_script:
- mysql -u root -e 'select * from clients;' ninja - mysql -u root -e 'select * from clients;' ninja
- mysql -u root -e 'select * from invoices;' ninja - mysql -u root -e 'select * from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja - mysql -u root -e 'select * from invoice_items;' ninja
- cat storage/logs/laravel.log - mysql -u root -e 'select * from payments;' ninja
- mysql -u root -e 'select * from credits;' ninja
- cat storage/logs/laravel-error.log
- cat storage/logs/laravel-info.log
notifications: notifications:
email: email:

View File

@ -95,6 +95,7 @@ module.exports = function(grunt) {
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/dropzone/dist/min/dropzone.min.js',
'public/vendor/typeahead.js/dist/typeahead.jquery.min.js', 'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
'public/vendor/accounting/accounting.min.js', 'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js', 'public/vendor/spectrum/spectrum.js',
@ -137,6 +138,7 @@ module.exports = function(grunt) {
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css', 'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css', 'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css', 'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/dropzone/dist/min/dropzone.min.css',
'public/vendor/spectrum/spectrum.css', 'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css', 'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css', 'public/css/typeahead.js-bootstrap.css',
@ -169,7 +171,7 @@ module.exports = function(grunt) {
'public/js/pdf_viewer.js', 'public/js/pdf_viewer.js',
'public/js/compatibility.js', 'public/js/compatibility.js',
'public/js/pdfmake.min.js', 'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js', 'public/js/vfs.js',
], ],
dest: 'public/pdf.built.js', dest: 'public/pdf.built.js',
nonull: true nonull: true

View File

@ -1,4 +1,4 @@
<?php namespace app\Commands; <?php namespace App\Commands;
abstract class Command abstract class Command
{ {

View File

@ -0,0 +1,172 @@
<?php namespace App\Console\Commands;
use stdClass;
use Auth;
use DB;
use Utils;
use Artisan;
use Illuminate\Console\Command;
use Faker\Factory;
use App\Models\User;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\VendorRepository;
use App\Ninja\Repositories\ExpenseRepository;
class CreateTestData extends Command
{
//protected $name = 'ninja:create-test-data';
protected $description = 'Create Test Data';
protected $signature = 'ninja:create-test-data {count=1}';
protected $token;
public function __construct(
ClientRepository $clientRepo,
InvoiceRepository $invoiceRepo,
PaymentRepository $paymentRepo,
VendorRepository $vendorRepo,
ExpenseRepository $expenseRepo)
{
parent::__construct();
$this->faker = Factory::create();
$this->clientRepo = $clientRepo;
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
$this->vendorRepo = $vendorRepo;
$this->expenseRepo = $expenseRepo;
}
public function fire()
{
if (Utils::isNinjaProd()) {
return false;
}
$this->info(date('Y-m-d').' Running CreateTestData...');
Auth::loginUsingId(1);
$this->count = $this->argument('count');
$this->createClients();
$this->createVendors();
$this->info('Done');
}
private function createClients()
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'name' => $this->faker->name,
'address1' => $this->faker->streetAddress,
'address2' => $this->faker->secondaryAddress,
'city' => $this->faker->city,
'state' => $this->faker->state,
'postal_code' => $this->faker->postcode,
'contacts' => [[
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->safeEmail,
'phone' => $this->faker->phoneNumber,
]]
];
$client = $this->clientRepo->save($data);
$this->info('Client: ' . $client->name);
$this->createInvoices($client);
}
}
private function createInvoices($client)
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'client_id' => $client->id,
'invoice_items' => [[
'product_key' => $this->faker->word,
'qty' => $this->faker->randomDigit + 1,
'cost' => $this->faker->randomFloat(2, 1, 10),
'notes' => $this->faker->text($this->faker->numberBetween(50, 300))
]]
];
$invoice = $this->invoiceRepo->save($data);
$this->info('Invoice: ' . $invoice->invoice_number);
$this->createPayment($client, $invoice);
}
}
private function createPayment($client, $invoice)
{
$data = [
'invoice_id' => $invoice->id,
'client_id' => $client->id,
'amount' => $this->faker->randomFloat(2, 0, $invoice->amount)
];
$payment = $this->paymentRepo->save($data);
$this->info('Payment: ' . $payment->amount);
}
private function createVendors()
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'name' => $this->faker->name,
'address1' => $this->faker->streetAddress,
'address2' => $this->faker->secondaryAddress,
'city' => $this->faker->city,
'state' => $this->faker->state,
'postal_code' => $this->faker->postcode,
'vendor_contacts' => [[
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->safeEmail,
'phone' => $this->faker->phoneNumber,
]]
];
$vendor = $this->vendorRepo->save($data);
$this->info('Vendor: ' . $vendor->name);
$this->createExpense($vendor);
}
}
private function createExpense($vendor)
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'vendor_id' => $vendor->id,
'amount' => $this->faker->randomFloat(2, 1, 10),
'expense_date' => null,
'public_notes' => null,
];
$expense = $this->expenseRepo->save($data);
$this->info('Expense: ' . $expense->amount);
}
}
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

@ -1,4 +1,4 @@
<?php namespace app\Console\Commands; <?php namespace App\Console\Commands;
use File; use File;
use Illuminate\Console\Command; use Illuminate\Console\Command;

View File

@ -0,0 +1,57 @@
<?php namespace App\Console\Commands;
use DB;
use Illuminate\Console\Command;
class PruneData extends Command
{
protected $name = 'ninja:prune-data';
protected $description = 'Delete inactive accounts';
public function fire()
{
$this->info(date('Y-m-d').' Running PruneData...');
// delete accounts who never registered, didn't create any invoices,
// hansn't logged in within the past 6 months and isn't linked to another account
$sql = 'select a.id
from (select id, last_login from accounts) a
left join users u on u.account_id = a.id and u.public_id = 0
left join invoices i on i.account_id = a.id
left join user_accounts ua1 on ua1.user_id1 = u.id
left join user_accounts ua2 on ua2.user_id2 = u.id
left join user_accounts ua3 on ua3.user_id3 = u.id
left join user_accounts ua4 on ua4.user_id4 = u.id
left join user_accounts ua5 on ua5.user_id5 = u.id
where u.registered = 0
and a.last_login < DATE_SUB(now(), INTERVAL 6 MONTH)
and (ua1.id is null and ua2.id is null and ua3.id is null and ua4.id is null and ua5.id is null)
group by a.id
having count(i.id) = 0';
$results = DB::select($sql);
foreach ($results as $result) {
$this->info("Deleting {$result->id}");
DB::table('accounts')
->where('id', '=', $result->id)
->delete();
}
$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

@ -0,0 +1,41 @@
<?php namespace App\Console\Commands;
use DateTime;
use App\Models\Document;
use Illuminate\Console\Command;
class RemoveOrphanedDocuments extends Command
{
protected $name = 'ninja:remove-orphaned-documents';
protected $description = 'Removes old documents not associated with an expense or invoice';
public function fire()
{
$this->info(date('Y-m-d').' Running RemoveOrphanedDocuments...');
$documents = Document::whereRaw('invoice_id IS NULL AND expense_id IS NULL AND updated_at <= ?', array(new DateTime('-1 hour')))
->get();
$this->info(count($documents).' orphaned document(s) found');
foreach ($documents as $document) {
$document->delete();
}
$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

@ -36,7 +36,7 @@ class SendReminders extends Command
$this->info(count($accounts).' accounts found'); $this->info(count($accounts).' accounts found');
foreach ($accounts as $account) { foreach ($accounts as $account) {
if (!$account->isPro()) { if (!$account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
continue; continue;
} }

View File

@ -5,7 +5,7 @@ use DateTime;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use App\Models\Account; use App\Models\Company;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
@ -30,32 +30,44 @@ class SendRenewalInvoices extends Command
$today = new DateTime(); $today = new DateTime();
$sentTo = []; $sentTo = [];
// get all accounts with pro plans expiring in 10 days // get all accounts with plans expiring in 10 days
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355') $companies = Company::whereRaw('datediff(plan_expires, curdate()) = 10')
->orderBy('id') ->orderBy('id')
->get(); ->get();
$this->info(count($accounts).' accounts found'); $this->info(count($companies).' companies found');
foreach ($accounts as $account) { foreach ($companies as $company) {
// don't send multiple invoices to multi-company users if (!count($company->accounts)) {
if ($userAccountId = $this->accountRepo->getUserAccountId($account)) { continue;
if (isset($sentTo[$userAccountId])) {
continue;
} else {
$sentTo[$userAccountId] = true;
}
} }
$account = $company->accounts->sortBy('id')->first();
$plan = $company->plan;
$term = $company->plan_term;
if ($company->pending_plan) {
$plan = $company->pending_plan;
$term = $company->pending_term;
}
if ($plan == PLAN_FREE || !$plan || !$term ){
continue;
}
$client = $this->accountRepo->getNinjaClient($account); $client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account); $invitation = $this->accountRepo->createNinjaInvoice($client, $account, $plan, $term);
// set the due date to 10 days from now // set the due date to 10 days from now
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$invoice->due_date = date('Y-m-d', strtotime('+ 10 days')); $invoice->due_date = date('Y-m-d', strtotime('+ 10 days'));
$invoice->save(); $invoice->save();
$this->mailer->sendInvoice($invoice); if ($term == PLAN_TERM_YEARLY) {
$this->info("Sent invoice to {$client->getDisplayName()}"); $this->mailer->sendInvoice($invoice);
$this->info("Sent {$term}ly {$plan} invoice to {$client->getDisplayName()}");
} else {
$this->info("Created {$term}ly {$plan} invoice for {$client->getDisplayName()}");
}
} }
$this->info('Done'); $this->info('Done');

View File

@ -1,4 +1,4 @@
<?php namespace app\Console\Commands; <?php namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Services\BankAccountService; use App\Services\BankAccountService;

View File

@ -1,4 +1,4 @@
<?php namespace app\Console; <?php namespace App\Console;
use Utils; use Utils;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
@ -13,8 +13,11 @@ class Kernel extends ConsoleKernel
*/ */
protected $commands = [ protected $commands = [
'App\Console\Commands\SendRecurringInvoices', 'App\Console\Commands\SendRecurringInvoices',
'App\Console\Commands\RemoveOrphanedDocuments',
'App\Console\Commands\ResetData', 'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData', 'App\Console\Commands\CheckData',
'App\Console\Commands\PruneData',
'App\Console\Commands\CreateTestData',
'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendRenewalInvoices',
'App\Console\Commands\ChargeRenewalInvoices', 'App\Console\Commands\ChargeRenewalInvoices',
'App\Console\Commands\SendReminders', 'App\Console\Commands\SendReminders',

View File

@ -4,7 +4,11 @@ use Redirect;
use Utils; use Utils;
use Exception; use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
class Handler extends ExceptionHandler { class Handler extends ExceptionHandler {
@ -14,7 +18,10 @@ class Handler extends ExceptionHandler {
* @var array * @var array
*/ */
protected $dontReport = [ protected $dontReport = [
'Symfony\Component\HttpKernel\Exception\HttpException' AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
]; ];
/** /**
@ -27,7 +34,12 @@ class Handler extends ExceptionHandler {
*/ */
public function report(Exception $e) public function report(Exception $e)
{ {
if (Utils::isNinja()) { // don't show these errors in the logs
if ($e instanceof HttpResponseException) {
return false;
}
if (Utils::isNinja() && ! Utils::isTravis()) {
Utils::logError(Utils::getErrorString($e)); Utils::logError(Utils::getErrorString($e));
return false; return false;
} else { } else {
@ -60,7 +72,9 @@ class Handler extends ExceptionHandler {
} }
// In production, except for maintenance mode, we'll show a custom error screen // In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) { if (Utils::isNinjaProd()
&& !Utils::isDownForMaintenance()
&& !($e instanceof HttpResponseException)) {
$data = [ $data = [
'error' => get_class($e), 'error' => get_class($e),
'hideHeader' => true, 'hideHeader' => true,

View File

@ -34,6 +34,13 @@ class AccountApiController extends BaseAPIController
$this->accountRepo = $accountRepo; $this->accountRepo = $accountRepo;
} }
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make(RESULT_SUCCESS, 200, $headers);
}
public function register(RegisterRequest $request) public function register(RegisterRequest $request)
{ {

File diff suppressed because one or more lines are too long

View File

@ -83,20 +83,23 @@ class AppController extends BaseController
return Redirect::to('/'); return Redirect::to('/');
} }
$_ENV['APP_ENV']='production'; $_ENV['APP_ENV'] = 'production';
$_ENV['APP_DEBUG']=$app['debug']; $_ENV['APP_DEBUG'] = $app['debug'];
$_ENV['APP_URL']=$app['url']; $_ENV['APP_URL'] = $app['url'];
$_ENV['APP_KEY']=$app['key']; $_ENV['APP_KEY'] = $app['key'];
$_ENV['DB_TYPE']=$dbType; $_ENV['DB_TYPE'] = $dbType;
$_ENV['DB_HOST']=$database['type']['host']; $_ENV['DB_HOST'] = $database['type']['host'];
$_ENV['DB_DATABASE']=$database['type']['database']; $_ENV['DB_DATABASE'] = $database['type']['database'];
$_ENV['DB_USERNAME']=$database['type']['username']; $_ENV['DB_USERNAME'] = $database['type']['username'];
$_ENV['DB_PASSWORD']=$database['type']['password']; $_ENV['DB_PASSWORD'] = $database['type']['password'];
$_ENV['MAIL_DRIVER']=$mail['driver']; $_ENV['MAIL_DRIVER'] = $mail['driver'];
$_ENV['MAIL_PORT']=$mail['port']; $_ENV['MAIL_PORT'] = $mail['port'];
$_ENV['MAIL_ENCRYPTION']=$mail['encryption']; $_ENV['MAIL_ENCRYPTION'] = $mail['encryption'];
$_ENV['MAIL_HOST']=$mail['host']; $_ENV['MAIL_HOST'] = $mail['host'];
$_ENV['MAIL_USERNAME']=$mail['username'];; $_ENV['MAIL_USERNAME'] = $mail['username'];
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address';
$config = ''; $config = '';
foreach ($_ENV as $key => $val) { foreach ($_ENV as $key => $val) {
@ -175,9 +178,12 @@ class AppController extends BaseController
$config = ''; $config = '';
foreach ($_ENV as $key => $val) { foreach ($_ENV as $key => $val) {
if (preg_match('/\s/',$val)) { if (is_array($val)) {
$val = "'{$val}'"; continue;
} }
if (preg_match('/\s/', $val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n"; $config .= "{$key}={$val}\n";
} }
@ -260,18 +266,7 @@ class AppController extends BaseController
Cache::flush(); Cache::flush();
Session::flush(); Session::flush();
Artisan::call('migrate', array('--force' => true)); Artisan::call('migrate', array('--force' => true));
foreach ([ Artisan::call('db:seed', array('--force' => true, '--class' => "UpdateSeeder"));
'PaymentLibraries',
'Fonts',
'Banks',
'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
}
Event::fire(new UserSettingsChanged()); Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates')); Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) { } catch (Exception $e) {
@ -300,7 +295,7 @@ class AppController extends BaseController
public function stats() public function stats()
{ {
if (Input::get('password') != env('RESELLER_PASSWORD')) { if ( ! hash_equals(Input::get('password'), env('RESELLER_PASSWORD'))) {
sleep(3); sleep(3);
return ''; return '';
} }
@ -323,4 +318,4 @@ class AppController extends BaseController
return json_encode($data); return json_encode($data);
} }
} }

View File

@ -133,6 +133,9 @@ class AuthController extends Controller {
if (Auth::check() && !Auth::user()->registered) { if (Auth::check() && !Auth::user()->registered) {
$account = Auth::user()->account; $account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account); $this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete(); $account->forceDelete();
} }

View File

@ -2,6 +2,9 @@
use Session; use Session;
use Utils; use Utils;
use Auth;
use Log;
use Input;
use Response; use Response;
use Request; use Request;
use League\Fractal; use League\Fractal;
@ -9,8 +12,10 @@ use League\Fractal\Manager;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Models\EntityModel;
use App\Ninja\Serializers\ArraySerializer; use App\Ninja\Serializers\ArraySerializer;
use League\Fractal\Serializer\JsonApiSerializer; use League\Fractal\Serializer\JsonApiSerializer;
use Illuminate\Pagination\LengthAwarePaginator;
/** /**
* @SWG\Swagger( * @SWG\Swagger(
@ -62,6 +67,74 @@ class BaseAPIController extends Controller
} else { } else {
$this->manager->setSerializer(new ArraySerializer()); $this->manager->setSerializer(new ArraySerializer());
} }
if (Utils::isNinjaDev()) {
\DB::enableQueryLog();
}
}
protected function handleAction($request)
{
$entity = $request->entity();
$action = $request->action;
$repo = Utils::toCamelCase($this->entityType) . 'Repo';
$this->$repo->$action($entity);
return $this->itemResponse($entity);
}
protected function listResponse($query)
{
$transformerClass = EntityModel::getTransformerName($this->entityType);
$transformer = new $transformerClass(Auth::user()->account, Input::get('serializer'));
$includes = $transformer->getDefaultIncludes();
$includes = $this->getRequestIncludes($includes);
$query->with($includes);
if ($updatedAt = Input::get('updated_at')) {
$updatedAt = date('Y-m-d H:i:s', $updatedAt);
$query->where(function($query) use ($includes, $updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
foreach ($includes as $include) {
$query->orWhereHas($include, function($query) use ($updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
});
}
});
}
if ($clientPublicId = Input::get('client_id')) {
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$query->whereHas('client', $filter);
}
if ( ! Utils::hasPermission('view_all')){
if ($this->entityType == ENTITY_USER) {
$query->where('id', '=', Auth::user()->id);
} else {
$query->where('user_id', '=', Auth::user()->id);
}
}
$data = $this->createCollection($query, $transformer, $this->entityType);
return $this->response($data);
}
protected function itemResponse($item)
{
$transformerClass = EntityModel::getTransformerName($this->entityType);
$transformer = new $transformerClass(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($item, $transformer, $this->entityType);
return $this->response($data);
} }
protected function createItem($data, $transformer, $entityType) protected function createItem($data, $transformer, $entityType)
@ -74,23 +147,31 @@ class BaseAPIController extends Controller
return $this->manager->createData($resource)->toArray(); return $this->manager->createData($resource)->toArray();
} }
protected function createCollection($data, $transformer, $entityType, $paginator = false) protected function createCollection($query, $transformer, $entityType)
{ {
if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) { if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) {
$entityType = null; $entityType = null;
} }
$resource = new Collection($data, $transformer, $entityType); if (is_a($query, "Illuminate\Database\Eloquent\Builder")) {
$limit = min(MAX_API_PAGE_SIZE, Input::get('per_page', DEFAULT_API_PAGE_SIZE));
if ($paginator) { $resource = new Collection($query->get(), $transformer, $entityType);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); $resource->setPaginator(new IlluminatePaginatorAdapter($query->paginate($limit)));
} else {
$resource = new Collection($query, $transformer, $entityType);
} }
return $this->manager->createData($resource)->toArray(); return $this->manager->createData($resource)->toArray();
} }
protected function response($response) protected function response($response)
{ {
if (Utils::isNinjaDev()) {
$count = count(\DB::getQueryLog());
Log::info(Request::method() . ' - ' . Request::url() . ": $count queries");
Log::info(json_encode(\DB::getQueryLog()));
}
$index = Request::get('index') ?: 'data'; $index = Request::get('index') ?: 'data';
if ($index == 'none') { if ($index == 'none') {
@ -123,26 +204,21 @@ class BaseAPIController extends Controller
} }
protected function getRequestIncludes($data)
protected function getIncluded()
{ {
$data = ['user'];
$included = Request::get('include'); $included = Request::get('include');
$included = explode(',', $included); $included = explode(',', $included);
foreach ($included as $include) { foreach ($included as $include) {
if ($include == 'invoices') { if ($include == 'invoices') {
$data[] = 'invoices.invoice_items'; $data[] = 'invoices.invoice_items';
$data[] = 'invoices.user'; } elseif ($include == 'client') {
$data[] = 'client.contacts';
} elseif ($include == 'clients') { } elseif ($include == 'clients') {
$data[] = 'clients.contacts'; $data[] = 'clients.contacts';
$data[] = 'clients.user';
} elseif ($include == 'vendors') { } elseif ($include == 'vendors') {
$data[] = 'vendors.vendorcontacts'; $data[] = 'vendors.vendor_contacts';
$data[] = 'vendors.user'; } elseif ($include) {
}
elseif ($include) {
$data[] = $include; $data[] = $include;
} }
} }

View File

@ -2,13 +2,16 @@
use App\Http\Middleware\PermissionsRequired; use App\Http\Middleware\PermissionsRequired;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Input;
use Auth; use Auth;
use Utils;
class BaseController extends Controller class BaseController extends Controller
{ {
use DispatchesJobs; use DispatchesJobs, AuthorizesRequests;
protected $model = 'App\Models\EntityModel'; protected $entityType;
/** /**
* Setup the layout used by the controller. * Setup the layout used by the controller.
@ -21,40 +24,4 @@ class BaseController extends Controller
$this->layout = View::make($this->layout); $this->layout = View::make($this->layout);
} }
} }
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

@ -10,27 +10,19 @@ use App\Ninja\Repositories\ClientRepository;
use App\Http\Requests\CreateClientRequest; use App\Http\Requests\CreateClientRequest;
use App\Http\Controllers\BaseAPIController; use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\ClientTransformer; use App\Ninja\Transformers\ClientTransformer;
use App\Services\ClientService;
use App\Http\Requests\UpdateClientRequest; use App\Http\Requests\UpdateClientRequest;
class ClientApiController extends BaseAPIController class ClientApiController extends BaseAPIController
{ {
protected $clientRepo; protected $clientRepo;
protected $clientService;
public function __construct(ClientRepository $clientRepo, ClientService $clientService) protected $entityType = ENTITY_CLIENT;
public function __construct(ClientRepository $clientRepo)
{ {
parent::__construct(); parent::__construct();
$this->clientRepo = $clientRepo; $this->clientRepo = $clientRepo;
$this->clientService = $clientService;
}
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make('', 200, $headers);
} }
/** /**
@ -52,27 +44,17 @@ class ClientApiController extends BaseAPIController
public function index() public function index()
{ {
$clients = Client::scope() $clients = Client::scope()
->with($this->getIncluded()) ->orderBy('created_at', 'desc')
->orderBy('created_at', 'desc')->withTrashed(); ->withTrashed();
// Filter by email // Filter by email
if (Input::has('email')) { if ($email = Input::get('email')) {
$email = Input::get('email');
$clients = $clients->whereHas('contacts', function ($query) use ($email) { $clients = $clients->whereHas('contacts', function ($query) use ($email) {
$query->where('email', $email); $query->where('email', $email);
}); });
} }
$clients = $clients->paginate(); return $this->listResponse($clients);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Client::scope()->withTrashed()->paginate();
$data = $this->createCollection($clients, $transformer, ENTITY_CLIENT, $paginator);
return $this->response($data);
} }
/** /**
@ -100,14 +82,7 @@ class ClientApiController extends BaseAPIController
{ {
$client = $this->clientRepo->save($request->input()); $client = $this->clientRepo->save($request->input());
$client = Client::scope($client->public_id) return $this->itemResponse($client);
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
} }
/** /**
@ -134,51 +109,15 @@ class ClientApiController extends BaseAPIController
public function update(UpdateClientRequest $request, $publicId) public function update(UpdateClientRequest $request, $publicId)
{ {
if ($request->action == ACTION_ARCHIVE) { if ($request->action) {
return $this->handleAction($request);
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Record not found'], 400);
$this->clientRepo->archive($client);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
} }
else if ($request->action == ACTION_RESTORE){
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.'], 400);
$this->clientRepo->restore($client);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
$data = $request->input(); $data = $request->input();
$data['public_id'] = $publicId; $data['public_id'] = $publicId;
$this->clientRepo->save($data); $client = $this->clientRepo->save($data, $request->entity());
$client = Client::scope($publicId) return $this->itemResponse($client);
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.'],400);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
} }
@ -204,23 +143,13 @@ class ClientApiController extends BaseAPIController
* ) * )
*/ */
public function destroy($publicId) public function destroy(UpdateClientRequest $request)
{ {
$client = $request->entity();
$client = Client::scope($publicId)->withTrashed()->first();
$this->clientRepo->delete($client); $this->clientRepo->delete($client);
$client = Client::scope($publicId) return $this->itemResponse($client);
->with('country', 'contacts', 'industry', 'size', 'currency')
->withTrashed()
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
} }
}
}

View File

@ -33,7 +33,7 @@ class AuthController extends Controller {
$client = $invoice->client; $client = $invoice->client;
$account = $client->account; $account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel(); $data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS(); $data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl(); $data['clientFontUrl'] = $account->getFontsUrl();
} }

View File

@ -50,7 +50,7 @@ class PasswordController extends Controller {
$client = $invoice->client; $client = $invoice->client;
$account = $client->account; $account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel(); $data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS(); $data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl(); $data['clientFontUrl'] = $account->getFontsUrl();
} }
@ -117,7 +117,7 @@ class PasswordController extends Controller {
$client = $invoice->client; $client = $invoice->client;
$account = $client->account; $account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel(); $data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS(); $data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl(); $data['clientFontUrl'] = $account->getFontsUrl();
} }

View File

@ -28,6 +28,7 @@ use App\Models\Task;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Services\ClientService; use App\Services\ClientService;
use App\Http\Requests\ClientRequest;
use App\Http\Requests\CreateClientRequest; use App\Http\Requests\CreateClientRequest;
use App\Http\Requests\UpdateClientRequest; use App\Http\Requests\UpdateClientRequest;
@ -35,7 +36,7 @@ class ClientController extends BaseController
{ {
protected $clientService; protected $clientService;
protected $clientRepo; protected $clientRepo;
protected $model = 'App\Models\Client'; protected $entityType = ENTITY_CLIENT;
public function __construct(ClientRepository $clientRepo, ClientService $clientService) public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{ {
@ -81,13 +82,7 @@ class ClientController extends BaseController
*/ */
public function store(CreateClientRequest $request) public function store(CreateClientRequest $request)
{ {
$data = $request->input(); $client = $this->clientService->save($request->input());
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
Session::flash('message', trans('texts.created_client')); Session::flash('message', trans('texts.created_client'));
@ -100,38 +95,35 @@ class ClientController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function show($publicId) public function show(ClientRequest $request)
{ {
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail(); $client = $request->entity();
if(!$this->checkViewPermission($client, $response)){
return $response;
}
$user = Auth::user();
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = []; $actionLinks = [];
if(Task::canCreate()){ if($user->can('create', ENTITY_TASK)){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
} }
if (Utils::isPro() && Invoice::canCreate()) { if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_INVOICE)) {
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
} }
if(!empty($actionLinks)){ if(!empty($actionLinks)){
$actionLinks[] = \DropdownButton::DIVIDER; $actionLinks[] = \DropdownButton::DIVIDER;
} }
if(Payment::canCreate()){ if($user->can('create', ENTITY_PAYMENT)){
$actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => URL::to('/payments/create/'.$client->public_id)];
} }
if(Credit::canCreate()){ if($user->can('create', ENTITY_CREDIT)){
$actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => URL::to('/credits/create/'.$client->public_id)];
} }
if(Expense::canCreate()){ if($user->can('create', ENTITY_EXPENSE)){
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]; $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => URL::to('/expenses/create/0/'.$client->public_id)];
} }
$data = array( $data = array(
@ -154,12 +146,8 @@ class ClientController extends BaseController
* *
* @return Response * @return Response
*/ */
public function create() public function create(ClientRequest $request)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) { if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]); return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]);
} }
@ -182,18 +170,14 @@ class ClientController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function edit($publicId) public function edit(ClientRequest $request)
{ {
$client = Client::scope($publicId)->with('contacts')->firstOrFail(); $client = $request->entity();
if(!$this->checkEditPermission($client, $response)){
return $response;
}
$data = [ $data = [
'client' => $client, 'client' => $client,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'clients/'.$publicId, 'url' => 'clients/'.$client->public_id,
'title' => trans('texts.edit_client'), 'title' => trans('texts.edit_client'),
]; ];
@ -201,7 +185,7 @@ class ClientController extends BaseController
if (Auth::user()->account->isNinjaAccount()) { if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) { if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid']; $data['planDetails'] = $account->getPlanDetails(false, false);
} }
} }
@ -232,13 +216,7 @@ class ClientController extends BaseController
*/ */
public function update(UpdateClientRequest $request) public function update(UpdateClientRequest $request)
{ {
$data = $request->input(); $client = $this->clientService->save($request->input(), $request->entity());
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
Session::flash('message', trans('texts.updated_client')); Session::flash('message', trans('texts.updated_client'));

View File

@ -12,12 +12,13 @@ use App\Models\Client;
use App\Services\CreditService; use App\Services\CreditService;
use App\Ninja\Repositories\CreditRepository; use App\Ninja\Repositories\CreditRepository;
use App\Http\Requests\CreateCreditRequest; use App\Http\Requests\CreateCreditRequest;
use App\Http\Requests\CreditRequest;
class CreditController extends BaseController class CreditController extends BaseController
{ {
protected $creditRepo; protected $creditRepo;
protected $creditService; protected $creditService;
protected $model = 'App\Models\Credit'; protected $entityType = ENTITY_CREDIT;
public function __construct(CreditRepository $creditRepo, CreditService $creditService) public function __construct(CreditRepository $creditRepo, CreditService $creditService)
{ {
@ -55,32 +56,26 @@ class CreditController extends BaseController
return $this->creditService->getDatatable($clientPublicId, Input::get('sSearch')); return $this->creditService->getDatatable($clientPublicId, Input::get('sSearch'));
} }
public function create($clientPublicId = 0) public function create(CreditRequest $request)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$data = array( $data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, 'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
'credit' => null, 'credit' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'credits', 'url' => 'credits',
'title' => trans('texts.new_credit'), 'title' => trans('texts.new_credit'),
//'invoices' => Invoice::scope()->with('client', 'invoice_status')->orderBy('invoice_number')->get(), 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ); );
return View::make('credits.edit', $data); return View::make('credits.edit', $data);
} }
/*
public function edit($publicId) public function edit($publicId)
{ {
$credit = Credit::scope($publicId)->firstOrFail(); $credit = Credit::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($credit, $response)){ $this->authorize('edit', $credit);
return $response;
}
$credit->credit_date = Utils::fromSqlDate($credit->credit_date); $credit->credit_date = Utils::fromSqlDate($credit->credit_date);
@ -94,7 +89,8 @@ class CreditController extends BaseController
return View::make('credit.edit', $data); return View::make('credit.edit', $data);
} }
*/
public function store(CreateCreditRequest $request) public function store(CreateCreditRequest $request)
{ {
$credit = $this->creditRepo->save($request->input()); $credit = $this->creditRepo->save($request->input());

View File

@ -11,7 +11,7 @@ class DashboardController extends BaseController
{ {
public function index() public function index()
{ {
$view_all = !Auth::user()->hasPermission('view_all'); $view_all = Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id; $user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients // total_income, billed_clients, invoice_sent and active_clients
@ -105,6 +105,7 @@ class DashboardController extends BaseController
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false) //->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0) ->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
@ -129,6 +130,7 @@ class DashboardController extends BaseController
->where('invoices.deleted_at', '=', null) ->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false) ->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false) //->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0) ->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)

View File

@ -0,0 +1,123 @@
<?php namespace App\Http\Controllers;
use Datatable;
use Input;
use Redirect;
use Session;
use URL;
use Utils;
use View;
use Validator;
use Response;
use App\Models\Document;
use App\Ninja\Repositories\DocumentRepository;
use App\Http\Requests\DocumentRequest;
use App\Http\Requests\CreateDocumentRequest;
class DocumentController extends BaseController
{
protected $documentRepo;
protected $entityType = ENTITY_DOCUMENT;
public function __construct(DocumentRepository $documentRepo)
{
// parent::__construct();
$this->documentRepo = $documentRepo;
}
public function get(DocumentRequest $request)
{
return static::getDownloadResponse($request->entity());
}
public static function getDownloadResponse($document){
$direct_url = $document->getDirectUrl();
if($direct_url){
return redirect($direct_url);
}
$stream = $document->getStream();
if($stream){
$headers = [
'Content-Type' => Document::$types[$document->type]['mime'],
'Content-Length' => $document->size,
];
$response = Response::stream(function() use ($stream) {
fpassthru($stream);
}, 200, $headers);
}
else{
$response = Response::make($document->getRaw(), 200);
$response->header('content-type', Document::$types[$document->type]['mime']);
}
return $response;
}
public function getPreview(DocumentRequest $request)
{
$document = $request->entity();
if(empty($document->preview)){
return Response::view('error', array('error'=>'Preview does not exist!'), 404);
}
$direct_url = $document->getDirectPreviewUrl();
if($direct_url){
return redirect($direct_url);
}
$previewType = pathinfo($document->preview, PATHINFO_EXTENSION);
$response = Response::make($document->getRawPreview(), 200);
$response->header('content-type', Document::$types[$previewType]['mime']);
return $response;
}
public function getVFSJS(DocumentRequest $request, $publicId, $name)
{
$document = $request->entity();
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
public function postUpload(CreateDocumentRequest $request)
{
if (!Utils::hasFeature(FEATURE_DOCUMENTS)) {
return;
}
$result = $this->documentRepo->upload(Input::all()['file'], $doc_array);
if(is_string($result)){
return Response::json([
'error' => $result,
'code' => 400
], 400);
} else {
return Response::json([
'error' => false,
'document' => $doc_array,
'code' => 200
], 200);
}
}
}

View File

@ -1,5 +1,5 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
// vendor
use App\Models\Expense; use App\Models\Expense;
use app\Ninja\Repositories\ExpenseRepository; use app\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Transformers\ExpenseTransformer; use App\Ninja\Transformers\ExpenseTransformer;
@ -16,6 +16,8 @@ class ExpenseApiController extends BaseAPIController
protected $expenseRepo; protected $expenseRepo;
protected $expenseService; protected $expenseService;
protected $entityType = ENTITY_EXPENSE;
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{ {
parent::__construct(); parent::__construct();
@ -26,20 +28,12 @@ class ExpenseApiController extends BaseAPIController
public function index() public function index()
{ {
$expenses = Expense::scope() $expenses = Expense::scope()
->withTrashed() ->withTrashed()
->with('client', 'invoice', 'vendor')
->orderBy('created_at','desc'); ->orderBy('created_at','desc');
$expenses = $expenses->paginate(); return $this->listResponse($expenses);
$transformer = new ExpenseTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Expense::scope()->withTrashed()->paginate();
$data = $this->createCollection($expenses, $transformer, ENTITY_EXPENSE, $paginator);
return $this->response($data);
} }
public function update() public function update()

View File

@ -17,6 +17,8 @@ use App\Models\Expense;
use App\Models\Client; use App\Models\Client;
use App\Services\ExpenseService; use App\Services\ExpenseService;
use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\ExpenseRepository;
use App\Http\Requests\ExpenseRequest;
use App\Http\Requests\CreateExpenseRequest; use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\UpdateExpenseRequest; use App\Http\Requests\UpdateExpenseRequest;
@ -25,7 +27,7 @@ class ExpenseController extends BaseController
// Expenses // Expenses
protected $expenseRepo; protected $expenseRepo;
protected $expenseService; protected $expenseService;
protected $model = 'App\Models\Expense'; protected $entityType = ENTITY_EXPENSE;
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{ {
@ -69,42 +71,35 @@ class ExpenseController extends BaseController
return $this->expenseService->getDatatableVendor($vendorPublicId); return $this->expenseService->getDatatableVendor($vendorPublicId);
} }
public function create($vendorPublicId = null, $clientPublicId = null) public function create(ExpenseRequest $request)
{ {
if(!$this->checkCreatePermission($response)){ if ($request->vendor_id != 0) {
return $response; $vendor = Vendor::scope($request->vendor_id)->with('vendor_contacts')->firstOrFail();
}
if($vendorPublicId != 0) {
$vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail();
} else { } else {
$vendor = null; $vendor = null;
} }
$data = array( $data = array(
'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId, 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $request->vendor_id,
'expense' => null, 'expense' => null,
'method' => 'POST', 'method' => 'POST',
'url' => 'expenses', 'url' => 'expenses',
'title' => trans('texts.new_expense'), 'title' => trans('texts.new_expense'),
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), 'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendor' => $vendor, 'vendor' => $vendor,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $clientPublicId, 'clientPublicId' => $request->client_id,
); );
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
return View::make('expenses.edit', $data); return View::make('expenses.edit', $data);
} }
public function edit($publicId) public function edit(ExpenseRequest $request)
{ {
$expense = Expense::scope($publicId)->firstOrFail(); $expense = $request->entity();
if(!$this->checkEditPermission($expense, $response)){
return $response;
}
$expense->expense_date = Utils::fromSqlDate($expense->expense_date); $expense->expense_date = Utils::fromSqlDate($expense->expense_date);
$actions = []; $actions = [];
@ -112,15 +107,6 @@ class ExpenseController extends BaseController
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; $actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
} else { } else {
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_expense")]; $actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_expense")];
/*
// check for any open invoices
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
foreach ($invoices as $invoice) {
$actions[] = ['url' => 'javascript:submitAction("add_to_invoice", '.$invoice->public_id.')', 'label' => trans("texts.add_to_invoice", ["invoice" => $invoice->invoice_number])];
}
*/
} }
$actions[] = \DropdownButton::DIVIDER; $actions[] = \DropdownButton::DIVIDER;
@ -135,10 +121,10 @@ class ExpenseController extends BaseController
'vendor' => null, 'vendor' => null,
'expense' => $expense, 'expense' => $expense,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'expenses/'.$publicId, 'url' => 'expenses/'.$expense->public_id,
'title' => 'Edit Expense', 'title' => 'Edit Expense',
'actions' => $actions, 'actions' => $actions,
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), 'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null, 'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $expense->client ? $expense->client->public_id : null, 'clientPublicId' => $expense->client ? $expense->client->public_id : null,
@ -146,12 +132,6 @@ class ExpenseController extends BaseController
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
}
}
return View::make('expenses.edit', $data); return View::make('expenses.edit', $data);
} }
@ -163,7 +143,10 @@ class ExpenseController extends BaseController
*/ */
public function update(UpdateExpenseRequest $request) public function update(UpdateExpenseRequest $request)
{ {
$expense = $this->expenseService->save($request->input()); $data = $request->input();
$data['documents'] = $request->file('documents');
$expense = $this->expenseService->save($data, $request->entity());
Session::flash('message', trans('texts.updated_expense')); Session::flash('message', trans('texts.updated_expense'));
@ -177,7 +160,10 @@ class ExpenseController extends BaseController
public function store(CreateExpenseRequest $request) public function store(CreateExpenseRequest $request)
{ {
$expense = $this->expenseService->save($request->input()); $data = $request->input();
$data['documents'] = $request->file('documents');
$expense = $this->expenseService->save($data);
Session::flash('message', trans('texts.created_expense')); Session::flash('message', trans('texts.created_expense'));
@ -195,8 +181,7 @@ class ExpenseController extends BaseController
$expenses = Expense::scope($ids)->with('client')->get(); $expenses = Expense::scope($ids)->with('client')->get();
$clientPublicId = null; $clientPublicId = null;
$currencyId = null; $currencyId = null;
$data = [];
// Validate that either all expenses do not have a client or if there is a client, it is the same client // Validate that either all expenses do not have a client or if there is a client, it is the same client
foreach ($expenses as $expense) foreach ($expenses as $expense)
{ {
@ -220,19 +205,11 @@ class ExpenseController extends BaseController
Session::flash('error', trans('texts.expense_error_invoiced')); Session::flash('error', trans('texts.expense_error_invoiced'));
return Redirect::to('expenses'); return Redirect::to('expenses');
} }
$account = Auth::user()->account;
$data[] = [
'publicId' => $expense->public_id,
'description' => $expense->public_notes,
'qty' => 1,
'cost' => $expense->present()->converted_amount,
];
} }
return Redirect::to("invoices/create/{$clientPublicId}") return Redirect::to("invoices/create/{$clientPublicId}")
->with('expenseCurrencyId', $currencyId) ->with('expenseCurrencyId', $currencyId)
->with('expenses', $data); ->with('expenses', $ids);
break; break;
default: default:

View File

@ -164,12 +164,12 @@ class ExportController extends BaseController
if ($request->input(ENTITY_VENDOR)) { if ($request->input(ENTITY_VENDOR)) {
$data['clients'] = Vendor::scope() $data['clients'] = Vendor::scope()
->with('user', 'vendorcontacts', 'country') ->with('user', 'vendor_contacts', 'country')
->withArchived() ->withArchived()
->get(); ->get();
$data['vendor_contacts'] = VendorContact::scope() $data['vendor_contacts'] = VendorContact::scope()
->with('user', 'vendor.contacts') ->with('user', 'vendor.vendor_contacts')
->withTrashed() ->withTrashed()
->get(); ->get();

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Controllers; <?php namespace App\Http\Controllers;
use Utils; use Utils;
use View; use View;

View File

@ -18,14 +18,17 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Http\Controllers\BaseAPIController; use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\InvoiceTransformer; use App\Ninja\Transformers\InvoiceTransformer;
use App\Http\Requests\CreateInvoiceRequest; use App\Http\Requests\InvoiceRequest;
use App\Http\Requests\UpdateInvoiceRequest; use App\Http\Requests\CreateInvoiceAPIRequest;
use App\Http\Requests\UpdateInvoiceAPIRequest;
use App\Services\InvoiceService; use App\Services\InvoiceService;
class InvoiceApiController extends BaseAPIController class InvoiceApiController extends BaseAPIController
{ {
protected $invoiceRepo; protected $invoiceRepo;
protected $entityType = ENTITY_INVOICE;
public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer) public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer)
{ {
parent::__construct(); parent::__construct();
@ -55,36 +58,12 @@ class InvoiceApiController extends BaseAPIController
*/ */
public function index() public function index()
{ {
$paginator = Invoice::scope()->withTrashed(); $invoices = Invoice::scope()
$invoices = Invoice::scope()->withTrashed() ->withTrashed()
->with(array_merge(['invoice_items'], $this->getIncluded())); ->with('invoice_items', 'client')
->orderBy('created_at', 'desc');
if ($clientPublicId = Input::get('client_id')) { return $this->listResponse($invoices);
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$invoices->whereHas('client', $filter);
$paginator->whereHas('client', $filter);
}
$invoices = $invoices->orderBy('created_at', 'desc')->paginate();
/*
// Add the first invitation link to the data
foreach ($invoices as $key => $invoice) {
foreach ($invoice->invitations as $subKey => $invitation) {
$invoices[$key]['link'] = $invitation->getLink();
}
unset($invoice['invitations']);
}
*/
$transformer = new InvoiceTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate();
$data = $this->createCollection($invoices, $transformer, 'invoices', $paginator);
return $this->response($data);
} }
/** /**
@ -104,18 +83,9 @@ class InvoiceApiController extends BaseAPIController
* ) * )
*/ */
public function show($publicId) public function show(InvoiceRequest $request)
{ {
return $this->itemResponse($request->entity());
$invoice = Invoice::scope($publicId)->withTrashed()->first();
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist!'], 404);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
} }
/** /**
@ -139,7 +109,7 @@ class InvoiceApiController extends BaseAPIController
* ) * )
* ) * )
*/ */
public function store(CreateInvoiceRequest $request) public function store(CreateInvoiceAPIRequest $request)
{ {
$data = Input::all(); $data = Input::all();
$error = null; $error = null;
@ -166,6 +136,7 @@ class InvoiceApiController extends BaseAPIController
'state', 'state',
'postal_code', 'postal_code',
'private_notes', 'private_notes',
'currency_code',
] as $field) { ] as $field) {
if (isset($data[$field])) { if (isset($data[$field])) {
$clientData[$field] = $data[$field]; $clientData[$field] = $data[$field];
@ -209,11 +180,11 @@ class InvoiceApiController extends BaseAPIController
} }
} }
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first(); $invoice = Invoice::scope($invoice->public_id)
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); ->with('client', 'invoice_items', 'invitations')
$data = $this->createItem($invoice, $transformer, 'invoice'); ->first();
return $this->response($data); return $this->itemResponse($invoice);
} }
private function prepareData($data, $client) private function prepareData($data, $client)
@ -258,10 +229,11 @@ class InvoiceApiController extends BaseAPIController
// initialize the line items // initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) { if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)]; $data['invoice_items'] = [self::prepareItem($data)];
// make sure the tax isn't applied twice (for the invoice and the line item) // make sure the tax isn't applied twice (for the invoice and the line item)
unset($data['invoice_items'][0]['tax_name']); unset($data['invoice_items'][0]['tax_name1']);
unset($data['invoice_items'][0]['tax_rate']); unset($data['invoice_items'][0]['tax_rate1']);
unset($data['invoice_items'][0]['tax_name2']);
unset($data['invoice_items'][0]['tax_rate2']);
} else { } else {
foreach ($data['invoice_items'] as $index => $item) { foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item); $data['invoice_items'][$index] = self::prepareItem($item);
@ -298,36 +270,21 @@ class InvoiceApiController extends BaseAPIController
$item[$key] = $val; $item[$key] = $val;
} }
} }
return $item; return $item;
} }
public function emailInvoice() public function emailInvoice(InvoiceRequest $request)
{ {
$data = Input::all(); $invoice = $request->entity();
$error = null;
$invoice = Invoice::scope($data['id'])->withTrashed()->first(); $this->mailer->sendInvoice($invoice);
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist.'], 400);
$this->mailer->sendInvoice($invoice, false, false);
if($error) {
return $this->errorResponse(['message'=>'There was an error sending the invoice'], 400);
}
else {
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
}
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(); $headers = Utils::getApiHeaders();
return Response::make($response, $error ? 400 : 200, $headers); return Response::make($response, 200, $headers);
} }
/** /**
* @SWG\Put( * @SWG\Put(
* path="/invoices", * path="/invoices",
@ -349,45 +306,25 @@ class InvoiceApiController extends BaseAPIController
* ) * )
* ) * )
*/ */
public function update(UpdateInvoiceRequest $request, $publicId) public function update(UpdateInvoiceAPIRequest $request, $publicId)
{ {
if ($request->action == ACTION_ARCHIVE) { if ($request->action == ACTION_CONVERT) {
$invoice = Invoice::scope($publicId)->firstOrFail(); $quote = $request->entity();
$this->invoiceRepo->archive($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
else if ($request->action == ACTION_CONVERT) {
$quote = Invoice::scope($publicId)->firstOrFail();
$invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id); $invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id);
return $this->itemResponse($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); } elseif ($request->action) {
$data = $this->createItem($invoice, $transformer, 'invoice'); return $this->handleAction($request);
return $this->response($data);
}
else if ($request->action == ACTION_RESTORE) {
$invoice = Invoice::scope($publicId)->withTrashed()->firstOrFail();
$this->invoiceRepo->restore($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
} }
$data = $request->input(); $data = $request->input();
$data['public_id'] = $publicId; $data['public_id'] = $publicId;
$this->invoiceService->save($data); $this->invoiceService->save($data, $request->entity());
$invoice = Invoice::scope($publicId)->with('client', 'invoice_items', 'invitations')->firstOrFail(); $invoice = Invoice::scope($publicId)
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); ->with('client', 'invoice_items', 'invitations')
$data = $this->createItem($invoice, $transformer, 'invoice'); ->firstOrFail();
return $this->response($data); return $this->itemResponse($invoice);
} }
/** /**
@ -412,18 +349,13 @@ class InvoiceApiController extends BaseAPIController
* ) * )
*/ */
public function destroy($publicId) public function destroy(UpdateInvoiceAPIRequest $request)
{ {
$data['public_id'] = $publicId; $invoice = $request->entity();
$invoice = Invoice::scope($publicId)->firstOrFail();
$this->invoiceRepo->delete($invoice); $this->invoiceRepo->delete($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); return $this->itemResponse($invoice);
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
} }
} }

View File

@ -17,26 +17,32 @@ use App\Models\Invoice;
use App\Models\Client; use App\Models\Client;
use App\Models\Account; use App\Models\Account;
use App\Models\Product; use App\Models\Product;
use App\Models\Expense;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Models\Activity; use App\Models\Activity;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Services\InvoiceService; use App\Services\InvoiceService;
use App\Services\RecurringInvoiceService; use App\Services\RecurringInvoiceService;
use App\Http\Requests\SaveInvoiceWithClientRequest;
use App\Http\Requests\InvoiceRequest;
use App\Http\Requests\CreateInvoiceRequest;
use App\Http\Requests\UpdateInvoiceRequest;
class InvoiceController extends BaseController class InvoiceController extends BaseController
{ {
protected $mailer; protected $mailer;
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; protected $clientRepo;
protected $documentRepo;
protected $invoiceService; protected $invoiceService;
protected $recurringInvoiceService; protected $recurringInvoiceService;
protected $model = 'App\Models\Invoice'; protected $entityType = ENTITY_INVOICE;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService)
{ {
// parent::__construct(); // parent::__construct();
@ -85,20 +91,13 @@ class InvoiceController extends BaseController
return $this->recurringInvoiceService->getDatatable($accountId, $clientPublicId, ENTITY_RECURRING_INVOICE, $search); return $this->recurringInvoiceService->getDatatable($accountId, $clientPublicId, ENTITY_RECURRING_INVOICE, $search);
} }
public function edit($publicId, $clone = false) public function edit(InvoiceRequest $request, $publicId, $clone = false)
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
$invoice = Invoice::scope($publicId) $invoice = $request->entity()->load('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'payments');
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments')
->withTrashed()
->firstOrFail();
if(!$this->checkEditPermission($invoice, $response)){
return $response;
}
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$contactIds = DB::table('invitations') $contactIds = DB::table('invitations')
->join('contacts', 'contacts.id', '=', 'invitations.contact_id') ->join('contacts', 'contacts.id', '=', 'invitations.contact_id')
->where('invitations.invoice_id', '=', $invoice->id) ->where('invitations.invoice_id', '=', $invoice->id)
@ -119,7 +118,7 @@ class InvoiceController extends BaseController
} else { } else {
Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT'; $method = 'PUT';
$url = "{$entityType}s/{$publicId}"; $url = "{$entityType}s/{$invoice->public_id}";
$clients->whereId($invoice->client_id); $clients->whereId($invoice->client_id);
} }
@ -129,7 +128,11 @@ class InvoiceController extends BaseController
$invoice->start_date = Utils::fromSqlDate($invoice->start_date); $invoice->start_date = Utils::fromSqlDate($invoice->start_date);
$invoice->end_date = Utils::fromSqlDate($invoice->end_date); $invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date); $invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date);
$invoice->is_pro = Auth::user()->isPro(); $invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$actions = [ $actions = [
['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")], ['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")],
@ -191,7 +194,7 @@ class InvoiceController extends BaseController
'isRecurring' => $invoice->is_recurring, 'isRecurring' => $invoice->is_recurring,
'actions' => $actions, 'actions' => $actions,
'lastSent' => $lastSent); 'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel($invoice));
if ($clone) { if ($clone) {
$data['formIsChanged'] = true; $data['formIsChanged'] = true;
@ -211,6 +214,7 @@ class InvoiceController extends BaseController
$contact->email_error = $invitation->email_error; $contact->email_error = $invitation->email_error;
$contact->invitation_link = $invitation->getLink(); $contact->invitation_link = $invitation->getLink();
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false; $contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus(); $contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
} }
} }
@ -223,25 +227,27 @@ class InvoiceController extends BaseController
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
public function create($clientPublicId = 0, $isRecurring = false) public function create(InvoiceRequest $request, $clientPublicId = 0, $isRecurring = false)
{ {
if(!$this->checkCreatePermission($response)){ $account = Auth::user()->account;
return $response;
}
$account = Auth::user()->account;
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
$clientId = null; $clientId = null;
if ($clientPublicId) { if ($request->client_id) {
$clientId = Client::getPrivateId($clientPublicId); $clientId = Client::getPrivateId($request->client_id);
} }
$invoice = $account->createInvoice($entityType, $clientId); $invoice = $account->createInvoice($entityType, $clientId);
$invoice->public_id = 0; $invoice->public_id = 0;
if (Session::get('expenses')) {
$invoice->expenses = Expense::scope(Session::get('expenses'))->with('documents')->get();
}
$clients = Client::scope()->with('contacts', 'country')->orderBy('name'); $clients = Client::scope()->with('contacts', 'country')->orderBy('name');
if(!Auth::user()->hasPermission('view_all')){ if (!Auth::user()->hasPermission('view_all')) {
$clients = $clients->where('clients.user_id', '=', Auth::user()->id); $clients = $clients->where('clients.user_id', '=', Auth::user()->id);
} }
@ -253,17 +259,17 @@ class InvoiceController extends BaseController
'url' => 'invoices', 'url' => 'invoices',
'title' => trans('texts.new_invoice'), 'title' => trans('texts.new_invoice'),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel($invoice));
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
} }
public function createRecurring($clientPublicId = 0) public function createRecurring(InvoiceRequest $request, $clientPublicId = 0)
{ {
return self::create($clientPublicId, true); return self::create($request, $clientPublicId, true);
} }
private static function getViewModel() private static function getViewModel($invoice)
{ {
$recurringHelp = ''; $recurringHelp = '';
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) { foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
@ -324,11 +330,37 @@ class InvoiceController extends BaseController
} }
} }
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
// Check for any taxes which have been deleted
if ($invoice->exists) {
foreach ($invoice->getTaxes() as $key => $rate) {
if (isset($options[$key])) {
continue;
}
$options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%';
}
}
return [ return [
'data' => Input::old('data'), 'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'), 'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(), 'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'currencies' => Cache::get('currencies'), 'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'), 'languages' => Cache::get('languages'),
'sizes' => Cache::get('sizes'), 'sizes' => Cache::get('sizes'),
@ -350,7 +382,6 @@ class InvoiceController extends BaseController
'recurringDueDateHelp' => $recurringDueDateHelp, 'recurringDueDateHelp' => $recurringDueDateHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(), 'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null, 'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null, 'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
]; ];
@ -361,18 +392,15 @@ class InvoiceController extends BaseController
* *
* @return Response * @return Response
*/ */
public function store(SaveInvoiceWithClientRequest $request) public function store(CreateInvoiceRequest $request)
{ {
$data = $request->input(); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$action = Input::get('action'); $action = Input::get('action');
$entityType = Input::get('entityType'); $entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($data, true); $invoice = $this->invoiceService->save($data);
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$message = trans("texts.created_{$entityType}"); $message = trans("texts.created_{$entityType}");
@ -401,26 +429,23 @@ class InvoiceController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function update(SaveInvoiceWithClientRequest $request) public function update(UpdateInvoiceRequest $request)
{ {
$data = $request->input(); $data = $request->input();
$data['documents'] = $request->file('documents');
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$action = Input::get('action'); $action = Input::get('action');
$entityType = Input::get('entityType'); $entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($data, true); $invoice = $this->invoiceService->save($data, $request->entity());
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$message = trans("texts.updated_{$entityType}"); $message = trans("texts.updated_{$entityType}");
Session::flash('message', $message); Session::flash('message', $message);
if ($action == 'clone') { if ($action == 'clone') {
return $this->cloneInvoice($invoice->public_id); return $this->cloneInvoice($request, $invoice->public_id);
} elseif ($action == 'convert') { } elseif ($action == 'convert') {
return $this->convertQuote($invoice->public_id); return $this->convertQuote($request, $invoice->public_id);
} elseif ($action == 'email') { } elseif ($action == 'email') {
return $this->emailInvoice($invoice, Input::get('pdfupload')); return $this->emailInvoice($invoice, Input::get('pdfupload'));
} }
@ -489,7 +514,7 @@ class InvoiceController extends BaseController
{ {
Session::reflash(); Session::reflash();
return Redirect::to("invoices/{$publicId}/edit"); return Redirect::to("invoices/$publicId/edit");
} }
/** /**
@ -517,27 +542,31 @@ class InvoiceController extends BaseController
} }
} }
public function convertQuote($publicId) public function convertQuote(InvoiceRequest $request)
{ {
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail(); $clone = $this->invoiceService->convertQuote($request->entity());
$clone = $this->invoiceService->convertQuote($invoice);
Session::flash('message', trans('texts.converted_to_invoice')); Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to('invoices/'.$clone->public_id);
return Redirect::to('invoices/' . $clone->public_id);
} }
public function cloneInvoice($publicId) public function cloneInvoice(InvoiceRequest $request, $publicId)
{ {
return self::edit($publicId, true); return self::edit($request, $publicId, true);
} }
public function invoiceHistory($publicId) public function invoiceHistory(InvoiceRequest $request)
{ {
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail(); $invoice = $request->entity();
$invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country'); $invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro(); $invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->is_quote = intval($invoice->is_quote); $invoice->is_quote = intval($invoice->is_quote);
$activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE; $activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
@ -555,7 +584,11 @@ class InvoiceController extends BaseController
$backup = json_decode($activity->json_backup); $backup = json_decode($activity->json_backup);
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date); $backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
$backup->due_date = Utils::fromSqlDate($backup->due_date); $backup->due_date = Utils::fromSqlDate($backup->due_date);
$backup->is_pro = Auth::user()->isPro(); $backup->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote); $backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote);
$backup->account = $invoice->account->toArray(); $backup->account = $invoice->account->toArray();

View File

@ -12,18 +12,21 @@ use App\Ninja\Repositories\PaymentRepository;
use App\Http\Controllers\BaseAPIController; use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\PaymentTransformer; use App\Ninja\Transformers\PaymentTransformer;
use App\Ninja\Transformers\InvoiceTransformer; use App\Ninja\Transformers\InvoiceTransformer;
use App\Http\Requests\UpdatePaymentRequest;
use App\Http\Requests\CreatePaymentAPIRequest;
class PaymentApiController extends BaseAPIController class PaymentApiController extends BaseAPIController
{ {
protected $paymentRepo; protected $paymentRepo;
protected $entityType = ENTITY_PAYMENT;
public function __construct(PaymentRepository $paymentRepo, ContactMailer $contactMailer) public function __construct(PaymentRepository $paymentRepo, ContactMailer $contactMailer)
{ {
parent::__construct(); parent::__construct();
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->contactMailer = $contactMailer; $this->contactMailer = $contactMailer;
} }
/** /**
@ -44,85 +47,49 @@ class PaymentApiController extends BaseAPIController
*/ */
public function index() public function index()
{ {
$paginator = Payment::scope();
$payments = Payment::scope() $payments = Payment::scope()
->with('client.contacts', 'invitation', 'user', 'invoice')->withTrashed(); ->withTrashed()
->with(['invoice'])
->orderBy('created_at', 'desc');
if ($clientPublicId = Input::get('client_id')) { return $this->listResponse($payments);
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$payments->whereHas('client', $filter);
$paginator->whereHas('client', $filter);
}
$payments = $payments->orderBy('created_at', 'desc')->paginate();
$paginator = $paginator->paginate();
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createCollection($payments, $transformer, 'payments', $paginator);
return $this->response($data);
} }
/**
* @SWG\Put(
* path="/payments/{payment_id",
* summary="Update a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Update payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
/** public function update(UpdatePaymentRequest $request, $publicId)
* @SWG\Put( {
* path="/payments/{payment_id", if ($request->action) {
* summary="Update a payment", return $this->handleAction($request);
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Update payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(Request $request, $publicId)
{
$data = Input::all();
$data['public_id'] = $publicId;
$error = false;
if ($request->action == ACTION_ARCHIVE) {
$payment = Payment::scope($publicId)->withTrashed()->firstOrFail();
$this->paymentRepo->archive($payment);
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
$payment = $this->paymentRepo->save($data);
if ($error) {
return $error;
}
/*
$invoice = Invoice::scope($data['invoice_id'])->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->withTrashed()->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
} }
$data = $request->input();
$data['public_id'] = $publicId;
$payment = $this->paymentRepo->save($data, $request->entity());
return $this->itemResponse($payment);
}
/** /**
* @SWG\Post( * @SWG\Post(
@ -145,89 +112,46 @@ class PaymentApiController extends BaseAPIController
* ) * )
* ) * )
*/ */
public function store() public function store(CreatePaymentAPIRequest $request)
{ {
$data = Input::all(); $payment = $this->paymentRepo->save($request->input());
$error = false;
if (isset($data['invoice_id'])) {
$invoice = Invoice::scope($data['invoice_id'])->with('client')->first();
if ($invoice) {
$data['invoice_id'] = $invoice->id;
$data['client_id'] = $invoice->client->id;
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
if (!isset($data['transaction_reference'])) {
$data['transaction_reference'] = '';
}
if ($error) {
return $error;
}
$payment = $this->paymentRepo->save($data);
if (Input::get('email_receipt')) { if (Input::get('email_receipt')) {
$this->contactMailer->sendPaymentConfirmation($payment); $this->contactMailer->sendPaymentConfirmation($payment);
} }
/* return $this->itemResponse($payment);
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
} }
/** /**
* @SWG\Delete( * @SWG\Delete(
* path="/payments/{payment_id}", * path="/payments/{payment_id}",
* summary="Delete a payment", * summary="Delete a payment",
* tags={"payment"}, * tags={"payment"},
* @SWG\Parameter( * @SWG\Parameter(
* in="body", * in="body",
* name="body", * name="body",
* @SWG\Schema(ref="#/definitions/Payment") * @SWG\Schema(ref="#/definitions/Payment")
* ), * ),
* @SWG\Response( * @SWG\Response(
* response=200, * response=200,
* description="Delete payment", * description="Delete payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment")) * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ), * ),
* @SWG\Response( * @SWG\Response(
* response="default", * response="default",
* description="an ""unexpected"" error" * description="an ""unexpected"" error"
* ) * )
* ) * )
*/ */
public function destroy($publicId) public function destroy(UpdatePaymentRequest $request)
{ {
$payment = $request->entity();
$this->clientRepo->delete($payment);
$payment = Payment::scope($publicId)->withTrashed()->first(); return $this->itemResponse($payment);
$invoiceId = $payment->invoice->public_id; }
$this->paymentRepo->delete($payment);
/*
$invoice = Invoice::scope($invoiceId)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
} }

View File

@ -25,12 +25,13 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\ContactMailer; use App\Ninja\Mailers\ContactMailer;
use App\Services\PaymentService; use App\Services\PaymentService;
use App\Http\Requests\PaymentRequest;
use App\Http\Requests\CreatePaymentRequest; use App\Http\Requests\CreatePaymentRequest;
use App\Http\Requests\UpdatePaymentRequest; use App\Http\Requests\UpdatePaymentRequest;
class PaymentController extends BaseController class PaymentController extends BaseController
{ {
protected $model = 'App\Models\Payment'; protected $entityType = ENTITY_PAYMENT;
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService)
{ {
@ -67,12 +68,8 @@ class PaymentController extends BaseController
return $this->paymentService->getDatatable($clientPublicId, Input::get('sSearch')); return $this->paymentService->getDatatable($clientPublicId, Input::get('sSearch'));
} }
public function create($clientPublicId = 0, $invoicePublicId = 0) public function create(PaymentRequest $request)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$invoices = Invoice::scope() $invoices = Invoice::scope()
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
->where('is_quote', '=', false) ->where('is_quote', '=', false)
@ -81,8 +78,8 @@ class PaymentController extends BaseController
->orderBy('invoice_number')->get(); ->orderBy('invoice_number')->get();
$data = array( $data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, 'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId, 'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : ($request->invoice_id ?: 0),
'invoice' => null, 'invoice' => null,
'invoices' => $invoices, 'invoices' => $invoices,
'payment' => null, 'payment' => null,
@ -96,14 +93,10 @@ class PaymentController extends BaseController
return View::make('payments.edit', $data); return View::make('payments.edit', $data);
} }
public function edit($publicId) public function edit(PaymentRequest $request)
{ {
$payment = Payment::scope($publicId)->firstOrFail(); $payment = $request->entity();
if(!$this->checkEditPermission($payment, $response)){
return $response;
}
$payment->payment_date = Utils::fromSqlDate($payment->payment_date); $payment->payment_date = Utils::fromSqlDate($payment->payment_date);
$data = array( $data = array(
@ -113,7 +106,7 @@ class PaymentController extends BaseController
->with('client', 'invoice_status')->orderBy('invoice_number')->get(), ->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'payment' => $payment, 'payment' => $payment,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'payments/'.$publicId, 'url' => 'payments/'.$payment->public_id,
'title' => trans('texts.edit_payment'), 'title' => trans('texts.edit_payment'),
'paymentTypes' => Cache::get('paymentTypes'), 'paymentTypes' => Cache::get('paymentTypes'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ); 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
@ -191,7 +184,7 @@ class PaymentController extends BaseController
'currencyId' => $client->getCurrencyId(), 'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'), 'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account, 'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount(), 'hideHeader' => $account->isNinjaAccount(),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
@ -355,9 +348,18 @@ class PaymentController extends BaseController
$license->save(); $license->save();
} }
return $productId == PRODUCT_INVOICE_DESIGNS ? file_get_contents(storage_path() . '/invoice_designs.txt') : 'valid'; if ($productId == PRODUCT_INVOICE_DESIGNS) {
return file_get_contents(storage_path() . '/invoice_designs.txt');
} else {
// temporary fix to enable previous version to work
if (Input::get('get_date')) {
return $license->created_at->format('Y-m-d');
} else {
return 'valid';
}
}
} else { } else {
return 'invalid'; return RESULT_FAILURE;
} }
} }
@ -460,6 +462,8 @@ class PaymentController extends BaseController
$ref = $response->getData()['m_payment_id']; $ref = $response->getData()['m_payment_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) { } elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
$ref = $response->getData()['signature']; $ref = $response->getData()['signature'];
} elseif ($accountGateway->gateway_id == GATEWAY_CYBERSOURCE) {
$ref = $response->getData()['transaction_uuid'];
} else { } else {
$ref = $response->getTransactionReference(); $ref = $response->getTransactionReference();
} }
@ -482,6 +486,7 @@ class PaymentController extends BaseController
if ($account->account_key == NINJA_ACCOUNT_KEY) { if ($account->account_key == NINJA_ACCOUNT_KEY) {
Session::flash('trackEventCategory', '/account'); Session::flash('trackEventCategory', '/account');
Session::flash('trackEventAction', '/buy_pro_plan'); Session::flash('trackEventAction', '/buy_pro_plan');
Session::flash('trackEventAmount', $payment->amount);
} }
return Redirect::to('view/'.$payment->invitation->invitation_key); return Redirect::to('view/'.$payment->invitation->invitation_key);
@ -551,7 +556,16 @@ class PaymentController extends BaseController
} }
try { try {
if (method_exists($gateway, 'completePurchase') if ($accountGateway->isGateway(GATEWAY_CYBERSOURCE)) {
if (Input::get('decision') == 'ACCEPT') {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
} else {
$message = Input::get('message') . ': ' . Input::get('invalid_fields');
Session::flash('error', $message);
}
return Redirect::to($invitation->getLink());
} elseif (method_exists($gateway, 'completePurchase')
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT) && !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) { && !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway); $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
@ -572,11 +586,9 @@ class PaymentController extends BaseController
} else { } else {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId); $payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment')); Session::flash('message', trans('texts.applied_payment'));
return Redirect::to($invitation->getLink()); return Redirect::to($invitation->getLink());
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e); $this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to($invitation->getLink()); return Redirect::to($invitation->getLink());
} }
@ -585,11 +597,7 @@ class PaymentController extends BaseController
public function store(CreatePaymentRequest $request) public function store(CreatePaymentRequest $request)
{ {
$input = $request->input(); $input = $request->input();
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['invoice_id'] = Invoice::getPrivateId($input['invoice']);
$input['client_id'] = Client::getPrivateId($input['client']); $input['client_id'] = Client::getPrivateId($input['client']);
$payment = $this->paymentRepo->save($input); $payment = $this->paymentRepo->save($input);
@ -606,13 +614,7 @@ class PaymentController extends BaseController
public function update(UpdatePaymentRequest $request) public function update(UpdatePaymentRequest $request)
{ {
$input = $request->input(); $payment = $this->paymentRepo->save($request->input(), $request->entity());
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$payment = $this->paymentRepo->save($input);
Session::flash('message', trans('texts.updated_payment')); Session::flash('message', trans('texts.updated_payment'));

View File

@ -1,103 +1,54 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Ninja\Repositories\ProductRepository;
use App\Ninja\Transformers\ProductTransformer;
use Auth;
use Str;
use DB;
use Datatable;
use Utils;
use URL;
use View;
use Input;
use Session;
use Redirect;
use App\Models\Product; use App\Models\Product;
use App\Models\TaxRate; use App\Ninja\Repositories\ProductRepository;
use App\Services\ProductService; use App\Http\Requests\CreateProductRequest;
use App\Http\Requests\UpdateProductRequest;
class ProductApiController extends BaseAPIController class ProductApiController extends BaseAPIController
{ {
protected $productService; protected $productRepo;
protected $entityType = ENTITY_PRODUCT;
protected $productRepo; public function __construct(ProductRepository $productRepo)
public function __construct(ProductService $productService, ProductRepository $productRepo)
{ {
parent::__construct(); parent::__construct();
$this->productService = $productService;
$this->productRepo = $productRepo; $this->productRepo = $productRepo;
} }
public function index() public function index()
{ {
$products = Product::scope()
->withTrashed()
->orderBy('created_at', 'desc');
$products = Product::scope()->withTrashed(); return $this->listResponse($products);
$products = $products->paginate();
$paginator = Product::scope()->withTrashed()->paginate();
$transformer = new ProductTransformer(\Auth::user()->account, $this->serializer);
$data = $this->createCollection($products, $transformer, 'products', $paginator);
return $this->response($data);
} }
public function getDatatable() public function store(CreateProductRequest $request)
{ {
return $this->productService->getDatatable(Auth::user()->account_id); $product = $this->productRepo->save($request->input());
return $this->itemResponse($product);
} }
public function store() public function update(UpdateProductRequest $request, $publicId)
{ {
return $this->save(); if ($request->action) {
} return $this->handleAction($request);
public function update(\Illuminate\Http\Request $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$product = Product::scope($publicId)->withTrashed()->firstOrFail();
$this->productRepo->archive($product);
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
} }
else
return $this->save($publicId); $data = $request->input();
$data['public_id'] = $publicId;
$product = $this->productRepo->save($data, $request->entity());
return $this->itemResponse($product);
} }
public function destroy($publicId) public function destroy($publicId)
{ {
//stub //stub
} }
private function save($productPublicId = false)
{
if ($productPublicId) {
$product = Product::scope($productPublicId)->firstOrFail();
} else {
$product = Product::createNew();
}
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
//$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save();
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
}
} }

View File

@ -7,27 +7,33 @@ use URL;
use Input; use Input;
use Utils; use Utils;
use Request; use Request;
use Response;
use Session; use Session;
use Datatable; use Datatable;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Document;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository; use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Events\InvoiceInvitationWasViewed; use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService; use App\Services\PaymentService;
use Barracuda\ArchiveStream\ZipArchive;
class PublicClientController extends BaseController class PublicClientController extends BaseController
{ {
private $invoiceRepo; private $invoiceRepo;
private $paymentRepo; private $paymentRepo;
private $documentRepo;
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService) public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService)
{ {
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->activityRepo = $activityRepo; $this->activityRepo = $activityRepo;
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService; $this->paymentService = $paymentService;
} }
@ -66,7 +72,11 @@ class PublicClientController extends BaseController
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $account->isPro(); $invoice->features = [
'customize_invoice_design' => $account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => $account->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => $account->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->invoice_fonts = $account->getFontsData(); $invoice->invoice_fonts = $account->getFontsData();
if ($invoice->invoice_design_id == CUSTOM_DESIGN) { if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
@ -116,9 +126,10 @@ class PublicClientController extends BaseController
'account' => $account, 'account' => $account,
'showApprove' => $showApprove, 'showApprove' => $showApprove,
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount(), 'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(), 'invoice' => $invoice->hidePrivateFields(),
@ -132,6 +143,15 @@ class PublicClientController extends BaseController
'checkoutComDebug' => $checkoutComDebug, 'checkoutComDebug' => $checkoutComDebug,
'phantomjs' => Input::has('phantomjs'), 'phantomjs' => Input::has('phantomjs'),
); );
if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
if(count($zipDocs) > 1){
$data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}");
$data['documentsZipSize'] = $size;
}
}
return View::make('invoices.view', $data); return View::make('invoices.view', $data);
} }
@ -196,7 +216,7 @@ class PublicClientController extends BaseController
$client = $invoice->client; $client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) { if (!$account->enable_client_portal || !$account->enable_client_portal_dashboard) {
return $this->returnError(); return $this->returnError();
} }
@ -204,7 +224,8 @@ class PublicClientController extends BaseController
'color' => $color, 'color' => $color,
'account' => $account, 'account' => $account,
'client' => $client, 'client' => $client,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
]; ];
@ -245,13 +266,20 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'), 'title' => trans('texts.invoices'),
@ -278,12 +306,17 @@ class PublicClientController extends BaseController
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT, 'entityType' => ENTITY_PAYMENT,
@ -315,13 +348,19 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) { if (!$invitation = $this->getInvitation()) {
return $this->returnError(); return $this->returnError();
} }
$account = $invitation->account; $account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78'; $color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [ $data = [
'color' => $color, 'color' => $color,
'hideLogo' => $account->isWhiteLabel(), 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(), 'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(), 'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'), 'title' => trans('texts.quotes'),
@ -342,6 +381,44 @@ class PublicClientController extends BaseController
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch')); return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
} }
public function documentIndex()
{
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.documents'),
'entityType' => ENTITY_DOCUMENT,
'columns' => Utils::trans(['invoice_number', 'name', 'document_date', 'document_size']),
];
return response()->view('public_list', $data);
}
public function documentDatatable()
{
if (!$invitation = $this->getInvitation()) {
return false;
}
return $this->documentRepo->getClientDatatable($invitation->contact_id, ENTITY_DOCUMENT, Input::get('sSearch'));
}
private function returnError($error = false) private function returnError($error = false)
{ {
return response()->view('error', [ return response()->view('error', [
@ -372,5 +449,148 @@ class PublicClientController extends BaseController
return $invitation; return $invitation;
} }
public function getDocumentVFSJS($publicId, $name){
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->first();
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
protected function canCreateZip(){
return function_exists('gmp_init');
}
protected function getInvoiceZipDocuments($invoice, &$size=0){
$documents = $invoice->documents;
foreach($invoice->expenses as $expense){
$documents = $documents->merge($expense->documents);
}
$documents = $documents->sortBy('size');
$size = 0;
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
$toZip = array();
foreach($documents as $document){
if($size + $document->size > $maxSize)break;
if(!empty($toZip[$document->name])){
// This name is taken
if($toZip[$document->name]->hash != $document->hash){
// 2 different files with the same name
$nameInfo = pathinfo($document->name);
for($i = 1;; $i++){
$name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];
if(empty($toZip[$name])){
$toZip[$name] = $document;
$size += $document->size;
break;
} else if ($toZip[$name]->hash == $document->hash){
// We're not adding this after all
break;
}
}
}
}
else{
$toZip[$document->name] = $document;
$size += $document->size;
}
}
return $toZip;
}
public function getInvoiceDocumentsZip($invitationKey){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$invoice = $invitation->invoice;
$toZip = $this->getInvoiceZipDocuments($invoice);
if(!count($toZip)){
return Response::view('error', array('error'=>'No documents small enough'), 404);
}
$zip = new ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip');
return Response::stream(function() use ($toZip, $zip) {
foreach($toZip as $name=>$document){
$fileStream = $document->getStream();
if($fileStream){
$zip->init_file_stream_transfer($name, $document->size, array('time'=>$document->created_at->timestamp));
while ($buffer = fread($fileStream, 256000))$zip->stream_file_part($buffer);
fclose($fileStream);
$zip->complete_file_stream();
}
else{
$zip->add_file($name, $document->getRaw());
}
}
$zip->finish();
}, 200);
}
public function getDocument($invitationKey, $publicId){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->firstOrFail();
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
return DocumentController::getDownloadResponse($document);
}
} }

View File

@ -1,75 +0,0 @@
<?php namespace App\Http\Controllers;
use Auth;
use Input;
use Utils;
use Response;
use App\Models\Invoice;
use App\Ninja\Repositories\InvoiceRepository;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\QuoteTransformer;
class QuoteApiController extends BaseAPIController
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->invoiceRepo = $invoiceRepo;
}
/**
* @SWG\Get(
* path="/quotes",
* tags={"quote"},
* summary="List of quotes",
* @SWG\Response(
* response=200,
* description="A list with quotes",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
$paginator = Invoice::scope();
$invoices = Invoice::scope()
->with('client', 'invitations', 'user', 'invoice_items')
->where('invoices.is_quote', '=', true);
if ($clientPublicId = Input::get('client_id')) {
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$invoices->whereHas('client', $filter);
$paginator->whereHas('client', $filter);
}
$invoices = $invoices->orderBy('created_at', 'desc')->paginate();
$transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate();
$data = $this->createCollection($invoices, $transformer, 'quotes', $paginator);
return $this->response($data);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
}

View File

@ -26,6 +26,7 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Events\QuoteInvitationWasApproved; use App\Events\QuoteInvitationWasApproved;
use App\Services\InvoiceService; use App\Services\InvoiceService;
use App\Http\Requests\InvoiceRequest;
class QuoteController extends BaseController class QuoteController extends BaseController
{ {
@ -33,7 +34,7 @@ class QuoteController extends BaseController
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; protected $clientRepo;
protected $invoiceService; protected $invoiceService;
protected $model = 'App\Models\Invoice'; protected $entityType = ENTITY_INVOICE;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{ {
@ -47,7 +48,7 @@ class QuoteController extends BaseController
public function index() public function index()
{ {
if (!Utils::isPro()) { if (!Utils::hasFeature(FEATURE_QUOTES)) {
return Redirect::to('/invoices/create'); return Redirect::to('/invoices/create');
} }
@ -78,13 +79,9 @@ class QuoteController extends BaseController
return $this->invoiceService->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search); return $this->invoiceService->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
} }
public function create($clientPublicId = 0) public function create(InvoiceRequest $request, $clientPublicId = 0)
{ {
if(!$this->checkCreatePermission($response)){ if (!Utils::hasFeature(FEATURE_QUOTES)) {
return $response;
}
if (!Utils::isPro()) {
return Redirect::to('/invoices/create'); return Redirect::to('/invoices/create');
} }
@ -111,10 +108,27 @@ class QuoteController extends BaseController
private static function getViewModel() private static function getViewModel()
{ {
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
return [ return [
'entityType' => ENTITY_QUOTE, 'entityType' => ENTITY_QUOTE,
'account' => Auth::user()->account, 'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')), 'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(),

View File

@ -21,7 +21,7 @@ class ReportController extends BaseController
$message = ''; $message = '';
$fileName = storage_path().'/dataviz_sample.txt'; $fileName = storage_path().'/dataviz_sample.txt';
if (Auth::user()->account->isPro()) { if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
$account = Account::where('id', '=', Auth::user()->account->id) $account = Account::where('id', '=', Auth::user()->account->id)
->with(['clients.invoices.invoice_items', 'clients.contacts']) ->with(['clients.invoices.invoice_items', 'clients.contacts'])
->first(); ->first();
@ -99,13 +99,13 @@ class ReportController extends BaseController
'title' => trans('texts.charts_and_reports'), 'title' => trans('texts.charts_and_reports'),
]; ];
if (Auth::user()->account->isPro()) { if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
if ($enableReport) { if ($enableReport) {
$isExport = $action == 'export'; $isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport)); $params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
if ($isExport) { if ($isExport) {
self::export($params['displayData'], $params['columns'], $params['reportTotals']); self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
} }
} }
if ($enableChart) { if ($enableChart) {
@ -514,11 +514,14 @@ class ReportController extends BaseController
return $data; return $data;
} }
private function export($data, $columns, $totals) private function export($reportType, $data, $columns, $totals)
{ {
$output = fopen('php://output', 'w') or Utils::fatalError(); $output = fopen('php://output', 'w') or Utils::fatalError();
$reportType = trans("texts.{$reportType}s");
$date = date('Y-m-d');
header('Content-Type:application/csv'); header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv'); header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv");
Utils::exportData($output, $data, Utils::trans($columns)); Utils::exportData($output, $data, Utils::trans($columns));

View File

@ -13,6 +13,8 @@ class TaskApiController extends BaseAPIController
{ {
protected $taskRepo; protected $taskRepo;
protected $entityType = ENTITY_TASK;
public function __construct(TaskRepository $taskRepo) public function __construct(TaskRepository $taskRepo)
{ {
parent::__construct(); parent::__construct();
@ -38,25 +40,11 @@ class TaskApiController extends BaseAPIController
*/ */
public function index() public function index()
{ {
$paginator = Task::scope(); $payments = Task::scope()
$tasks = Task::scope() ->withTrashed()
->with($this->getIncluded()); ->orderBy('created_at', 'desc');
if ($clientPublicId = Input::get('client_id')) { return $this->listResponse($payments);
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$tasks->whereHas('client', $filter);
$paginator->whereHas('client', $filter);
}
$tasks = $tasks->orderBy('created_at', 'desc')->paginate();
$paginator = $paginator->paginate();
$transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createCollection($tasks, $transformer, 'tasks', $paginator);
return $this->response($data);
} }
/** /**

View File

@ -18,11 +18,15 @@ use App\Ninja\Repositories\TaskRepository;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Services\TaskService; use App\Services\TaskService;
use App\Http\Requests\TaskRequest;
use App\Http\Requests\CreateTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
class TaskController extends BaseController class TaskController extends BaseController
{ {
protected $taskRepo; protected $taskRepo;
protected $taskService; protected $taskService;
protected $model = 'App\Models\Task'; protected $entityType = ENTITY_TASK;
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService) public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService)
{ {
@ -66,7 +70,7 @@ class TaskController extends BaseController
* *
* @return Response * @return Response
*/ */
public function store() public function store(CreateTaskRequest $request)
{ {
return $this->save(); return $this->save();
} }
@ -83,16 +87,13 @@ class TaskController extends BaseController
* *
* @return Response * @return Response
*/ */
public function create($clientPublicId = 0) public function create(TaskRequest $request)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
$this->checkTimezone(); $this->checkTimezone();
$data = [ $data = [
'task' => null, 'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, 'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'method' => 'POST', 'method' => 'POST',
'url' => 'tasks', 'url' => 'tasks',
'title' => trans('texts.new_task'), 'title' => trans('texts.new_task'),
@ -111,15 +112,11 @@ class TaskController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function edit($publicId) public function edit(TaskRequest $request)
{ {
$this->checkTimezone(); $this->checkTimezone();
$task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail(); $task = $request->entity();
if(!$this->checkEditPermission($task, $response)){
return $response;
}
$actions = []; $actions = [];
if ($task->invoice) { if ($task->invoice) {
@ -147,7 +144,7 @@ class TaskController extends BaseController
'task' => $task, 'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0, 'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'tasks/'.$publicId, 'url' => 'tasks/'.$task->public_id,
'title' => trans('texts.edit_task'), 'title' => trans('texts.edit_task'),
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(), 'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions, 'actions' => $actions,
@ -167,9 +164,11 @@ class TaskController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function update($publicId) public function update(UpdateTaskRequest $request)
{ {
return $this->save($publicId); $task = $request->entity();
return $this->save($task->public_id);
} }
private static function getViewModel() private static function getViewModel()
@ -184,22 +183,10 @@ class TaskController extends BaseController
{ {
$action = Input::get('action'); $action = Input::get('action');
if(!$this->checkUpdatePermission(array('public_id'=>$publicId)/* Hacky, but works */, $response)){
return $response;
}
if (in_array($action, ['archive', 'delete', 'restore'])) { if (in_array($action, ['archive', 'delete', 'restore'])) {
return self::bulk(); return self::bulk();
} }
if ($validator = $this->taskRepo->getErrors(Input::all())) {
$url = $publicId ? 'tasks/'.$publicId.'/edit' : 'tasks/create';
Session::flash('error', trans('texts.task_errors'));
return Redirect::to($url)
->withErrors($validator)
->withInput();
}
$task = $this->taskRepo->save($publicId, Input::all()); $task = $this->taskRepo->save($publicId, Input::all());
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task')); Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));

View File

@ -1,68 +1,54 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use App\Services\TaxRateService;
use App\Ninja\Repositories\TaxRateRepository;
use App\Ninja\Transformers\TaxRateTransformer;
use Auth;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Ninja\Repositories\TaxRateRepository;
use App\Http\Requests\CreateTaxRateRequest; use App\Http\Requests\CreateTaxRateRequest;
use App\Http\Requests\UpdateTaxRateRequest; use App\Http\Requests\UpdateTaxRateRequest;
class TaxRateApiController extends BaseAPIController class TaxRateApiController extends BaseAPIController
{ {
protected $taxRateService;
protected $taxRateRepo; protected $taxRateRepo;
protected $entityType = ENTITY_TAX_RATE;
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo) public function __construct(TaxRateRepository $taxRateRepo)
{ {
parent::__construct(); parent::__construct();
$this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo; $this->taxRateRepo = $taxRateRepo;
} }
public function index() public function index()
{ {
$taxRates = TaxRate::scope()->withTrashed(); $taxRates = TaxRate::scope()
$taxRates = $taxRates->paginate(); ->withTrashed()
->orderBy('created_at', 'desc');
$paginator = TaxRate::scope()->withTrashed()->paginate(); return $this->listResponse($taxRates);
$transformer = new TaxRateTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($taxRates, $transformer, 'tax_rates', $paginator);
return $this->response($data);
} }
public function store(CreateTaxRateRequest $request) public function store(CreateTaxRateRequest $request)
{ {
return $this->save($request); $taxRate = $this->taxRateRepo->save($request->input());
return $this->itemResponse($taxRate);
} }
public function update(UpdateTaxRateRequest $request, $taxRatePublicId) public function update(UpdateTaxRateRequest $request, $publicId)
{ {
$taxRate = TaxRate::scope($taxRatePublicId)->firstOrFail(); if ($request->action) {
return $this->handleAction($request);
if ($request->action == ACTION_ARCHIVE) {
$this->taxRateRepo->archive($taxRate);
$transformer = new TaxRateTransformer(Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
} else {
return $this->save($request, $taxRate);
} }
$data = $request->input();
$data['public_id'] = $publicId;
$taxRate = $this->taxRateRepo->save($data, $request->entity());
return $this->itemResponse($taxRate);
} }
private function save($request, $taxRate = false) public function destroy($publicId)
{ {
$taxRate = $this->taxRateRepo->save($request->input(), $taxRate); //stub
$transformer = new TaxRateTransformer(\Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
} }
} }

View File

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

View File

@ -32,7 +32,7 @@ class TokenController extends BaseController
public function getDatatable() public function getDatatable()
{ {
return $this->tokenService->getDatatable(Auth::user()->account_id); return $this->tokenService->getDatatable(Auth::user()->id);
} }
public function edit($publicId) public function edit($publicId)
@ -93,7 +93,7 @@ class TokenController extends BaseController
*/ */
public function save($tokenPublicId = false) public function save($tokenPublicId = false)
{ {
if (Auth::user()->account->isPro()) { if (Auth::user()->account->hasFeature(FEATURE_API)) {
$rules = [ $rules = [
'name' => 'required', 'name' => 'required',
]; ];

View File

@ -14,6 +14,8 @@ class UserApiController extends BaseAPIController
protected $userService; protected $userService;
protected $userRepo; protected $userRepo;
protected $entityType = ENTITY_USER;
public function __construct(UserService $userService, UserRepository $userRepo) public function __construct(UserService $userService, UserRepository $userRepo)
{ {
parent::__construct(); parent::__construct();
@ -24,16 +26,11 @@ class UserApiController extends BaseAPIController
public function index() public function index()
{ {
$user = Auth::user(); $users = User::whereAccountId(Auth::user()->account_id)
$users = User::whereAccountId($user->account_id)->withTrashed(); ->withTrashed()
$users = $users->paginate(); ->orderBy('created_at', 'desc');
$paginator = User::whereAccountId($user->account_id)->withTrashed()->paginate(); return $this->listResponse($users);
$transformer = new UserTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($users, $transformer, 'users', $paginator);
return $this->response($data);
} }
/* /*
@ -45,11 +42,6 @@ class UserApiController extends BaseAPIController
public function update(UpdateUserRequest $request, $userPublicId) public function update(UpdateUserRequest $request, $userPublicId)
{ {
/*
// temporary fix for ids starting at 0
$userPublicId -= 1;
$user = User::scope($userPublicId)->firstOrFail();
*/
$user = Auth::user(); $user = Auth::user();
if ($request->action == ACTION_ARCHIVE) { if ($request->action == ACTION_ARCHIVE) {

View File

@ -164,7 +164,7 @@ class UserController extends BaseController
*/ */
public function save($userPublicId = false) public function save($userPublicId = false)
{ {
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) { if (Auth::user()->hasFeature(FEATURE_USERS)) {
$rules = [ $rules = [
'first_name' => 'required', 'first_name' => 'required',
'last_name' => 'required', 'last_name' => 'required',
@ -190,8 +190,10 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name')); $user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email')); $user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email')); $user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin')); if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->permissions = Input::get('permissions'); $user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
} else { } else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id) $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first(); ->orderBy('public_id', 'DESC')->first();
@ -202,12 +204,14 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name')); $user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email')); $user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email')); $user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->registered = true; $user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH); $user->password = str_random(RANDOM_KEY_LENGTH);
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
$user->public_id = $lastUser->public_id + 1; $user->public_id = $lastUser->public_id + 1;
$user->permissions = Input::get('permissions'); if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
} }
$user->save(); $user->save();
@ -286,6 +290,9 @@ class UserController extends BaseController
if (!Auth::user()->registered) { if (!Auth::user()->registered) {
$account = Auth::user()->account; $account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account); $this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete(); $account->forceDelete();
} }
} }

View File

@ -14,6 +14,8 @@ class VendorApiController extends BaseAPIController
{ {
protected $vendorRepo; protected $vendorRepo;
protected $entityType = ENTITY_VENDOR;
public function __construct(VendorRepository $vendorRepo) public function __construct(VendorRepository $vendorRepo)
{ {
parent::__construct(); parent::__construct();
@ -46,17 +48,11 @@ class VendorApiController extends BaseAPIController
*/ */
public function index() public function index()
{ {
$vendors = Vendor::scope() $vendors = Vendor::scope()
->with($this->getIncluded())
->withTrashed() ->withTrashed()
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc');
->paginate();
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer')); return $this->listResponse($vendors);
$paginator = Vendor::scope()->paginate();
$data = $this->createCollection($vendors, $transformer, ENTITY_VENDOR, $paginator);
return $this->response($data);
} }
/** /**
@ -85,11 +81,9 @@ class VendorApiController extends BaseAPIController
$vendor = $this->vendorRepo->save($request->input()); $vendor = $this->vendorRepo->save($request->input());
$vendor = Vendor::scope($vendor->public_id) $vendor = Vendor::scope($vendor->public_id)
->with('country', 'vendorcontacts', 'industry', 'size', 'currency') ->with('country', 'vendor_contacts', 'industry', 'size', 'currency')
->first(); ->first();
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer')); return $this->itemResponse($vendor);
$data = $this->createItem($vendor, $transformer, ENTITY_VENDOR);
return $this->response($data);
} }
} }

View File

@ -23,14 +23,15 @@ use App\Models\Country;
use App\Ninja\Repositories\VendorRepository; use App\Ninja\Repositories\VendorRepository;
use App\Services\VendorService; use App\Services\VendorService;
use App\Http\Requests\VendorRequest;
use App\Http\Requests\CreateVendorRequest; use App\Http\Requests\CreateVendorRequest;
use App\Http\Requests\UpdateVendorRequest; use App\Http\Requests\UpdateVendorRequest;
// vendor
class VendorController extends BaseController class VendorController extends BaseController
{ {
protected $vendorService; protected $vendorService;
protected $vendorRepo; protected $vendorRepo;
protected $model = 'App\Models\Vendor'; protected $entityType = ENTITY_VENDOR;
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService) public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
{ {
@ -38,8 +39,6 @@ class VendorController extends BaseController
$this->vendorRepo = $vendorRepo; $this->vendorRepo = $vendorRepo;
$this->vendorService = $vendorService; $this->vendorService = $vendorService;
} }
/** /**
@ -77,13 +76,7 @@ class VendorController extends BaseController
*/ */
public function store(CreateVendorRequest $request) public function store(CreateVendorRequest $request)
{ {
$data = $request->input(); $vendor = $this->vendorService->save($request->input());
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
Session::flash('message', trans('texts.created_vendor')); Session::flash('message', trans('texts.created_vendor'));
@ -96,18 +89,14 @@ class VendorController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function show($publicId) public function show(VendorRequest $request)
{ {
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail(); $vendor = $request->entity();
if(!$this->checkViewPermission($vendor, $response)){
return $response;
}
Utils::trackViewed($vendor->getDisplayName(), 'vendor'); Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [ $actionLinks = [
['label' => trans('texts.new_vendor'), 'url' => '/vendors/create/' . $vendor->public_id] ['label' => trans('texts.new_vendor'), 'url' => URL::to('/vendors/create/' . $vendor->public_id)]
]; ];
$data = array( $data = array(
@ -129,12 +118,8 @@ class VendorController extends BaseController
* *
* @return Response * @return Response
*/ */
public function create() public function create(VendorRequest $request)
{ {
if(!$this->checkCreatePermission($response)){
return $response;
}
if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) { if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]); return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]);
} }
@ -157,26 +142,22 @@ class VendorController extends BaseController
* @param int $id * @param int $id
* @return Response * @return Response
*/ */
public function edit($publicId) public function edit(VendorRequest $request)
{ {
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail(); $vendor = $request->entity();
if(!$this->checkEditPermission($vendor, $response)){
return $response;
}
$data = [ $data = [
'vendor' => $vendor, 'vendor' => $vendor,
'method' => 'PUT', 'method' => 'PUT',
'url' => 'vendors/'.$publicId, 'url' => 'vendors/'.$vendor->public_id,
'title' => trans('texts.edit_vendor'), 'title' => trans('texts.edit_vendor'),
]; ];
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) { if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($vendor->public_id)->first()) { if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid']; $data['planDetails'] = $account->getPlanDetails(false, false);
} }
} }
@ -201,13 +182,7 @@ class VendorController extends BaseController
*/ */
public function update(UpdateVendorRequest $request) public function update(UpdateVendorRequest $request)
{ {
$data = $request->input(); $vendor = $this->vendorService->save($request->input(), $request->entity());
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
Session::flash('message', trans('texts.updated_vendor')); Session::flash('message', trans('texts.updated_vendor'));

View File

@ -34,7 +34,8 @@ class ApiCheck {
// check for a valid token // check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']); $token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
if ($token) { // check if user is archived
if ($token && $token->user) {
Auth::loginUsingId($token->user_id); Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id); Session::set('token_id', $token->id);
} else { } else {
@ -47,7 +48,7 @@ class ApiCheck {
return $next($request); return $next($request);
} }
if (!Utils::isPro() && !$loggingIn) { if (!Utils::hasFeature(FEATURE_API) && !$loggingIn) {
return Response::json('API requires pro plan', 403, $headers); return Response::json('API requires pro plan', 403, $headers);
} else { } else {
$key = Auth::check() ? Auth::user()->account->id : $request->getClientIp(); $key = Auth::check() ? Auth::user()->account->id : $request->getClientIp();

View File

@ -42,7 +42,7 @@ class Authenticate {
// Does this account require portal passwords? // Does this account require portal passwords?
$account = Account::whereId($account_id)->first(); $account = Account::whereId($account_id)->first();
if(!$account->enable_portal_password || !$account->isPro()){ if($account && (!$account->enable_portal_password || !$account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD))){
$authenticated = true; $authenticated = true;
} }

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Middleware; <?php namespace App\Http\Middleware;
use Closure; use Closure;

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Middleware; <?php namespace App\Http\Middleware;
use Request; use Request;
use Closure; use Closure;
@ -124,7 +124,8 @@ class StartupCheck
$licenseKey = Input::get('license_key'); $licenseKey = Input::get('license_key');
$productId = Input::get('product_id'); $productId = Input::get('product_id');
$data = trim(file_get_contents((Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL)."/claim_license?license_key={$licenseKey}&product_id={$productId}")); $url = (Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}&get_date=true";
$data = trim(file_get_contents($url));
if ($productId == PRODUCT_INVOICE_DESIGNS) { if ($productId == PRODUCT_INVOICE_DESIGNS) {
if ($data = json_decode($data)) { if ($data = json_decode($data)) {
@ -140,10 +141,13 @@ class StartupCheck
Session::flash('message', trans('texts.bought_designs')); Session::flash('message', trans('texts.bought_designs'));
} }
} elseif ($productId == PRODUCT_WHITE_LABEL) { } elseif ($productId == PRODUCT_WHITE_LABEL) {
if ($data == 'valid') { if ($data && $data != RESULT_FAILURE) {
$account = Auth::user()->account; $company = Auth::user()->account->company;
$account->pro_plan_paid = date_create()->format('Y-m-d'); $company->plan_term = PLAN_TERM_YEARLY;
$account->save(); $company->plan_paid = $data;
$company->plan_expires = date_create($data)->modify('+1 year')->format('Y-m-d');
$company->plan = PLAN_WHITE_LABEL;
$company->save();
Session::flash('message', trans('texts.bought_white_label')); Session::flash('message', trans('texts.bought_white_label'));
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier { class VerifyCsrfToken extends BaseVerifier {
private $openRoutes = [ private $openRoutes = [
'complete',
'signup/register', 'signup/register',
'api/v1/*', 'api/v1/*',
'api/v1/login', 'api/v1/login',

View File

@ -0,0 +1,18 @@
<?php namespace App\Http\Requests;
class ClientRequest extends EntityRequest {
protected $entityType = ENTITY_CLIENT;
public function entity()
{
$client = parent::entity();
// eager load the contacts
if ($client && ! $client->relationLoaded('contacts')) {
$client->load('contacts');
}
return $client;
}
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class CreateClientRequest extends ClientRequest
use Illuminate\Validation\Factory;
class CreateClientRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateClientRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_CLIENT);
} }
/** /**
@ -26,21 +23,4 @@ class CreateClientRequest extends Request
'contacts' => 'valid_contacts', 'contacts' => 'valid_contacts',
]; ];
} }
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['contact'])) {
$input['contacts'] = [$input['contact']];
unset($input['contact']);
$this->replace($input);
}
return $factory->make(
$this->input(),
$this->container->call([$this, 'rules']),
$this->messages()
);
}
} }

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class CreateCreditRequest extends CreditRequest
use Illuminate\Validation\Factory;
class CreateCreditRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateCreditRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_CREDIT);
} }
/** /**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class CreateDocumentRequest extends DocumentRequest
use Illuminate\Validation\Factory;
class UpdateExpenseRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateExpenseRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_DOCUMENT);
} }
/** /**
@ -23,8 +20,7 @@ class UpdateExpenseRequest extends Request
public function rules() public function rules()
{ {
return [ return [
'amount' => 'required|positive',
]; ];
} }
} }

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class CreateExpenseRequest extends ExpenseRequest
use Illuminate\Validation\Factory;
class CreateExpenseRequest extends Request
{ {
// Expenses // Expenses
/** /**
@ -13,7 +10,7 @@ class CreateExpenseRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_EXPENSE);
} }
/** /**

View File

@ -0,0 +1,32 @@
<?php namespace App\Http\Requests;
class CreateInvoiceAPIRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_INVOICE);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'email' => 'required_without:client_id',
'client_id' => 'required_without:email',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,,id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
return $rules;
}
}

View File

@ -1,11 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use Auth; class CreateInvoiceRequest extends InvoiceRequest
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class CreateInvoiceRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -14,7 +9,7 @@ class CreateInvoiceRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_INVOICE);
} }
/** /**
@ -25,13 +20,18 @@ class CreateInvoiceRequest extends Request
public function rules() public function rules()
{ {
$rules = [ $rules = [
'email' => 'required_without:client_id', 'client.contacts' => 'valid_contacts',
'client_id' => 'required_without:email',
'invoice_items' => 'valid_invoice_items', 'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id, 'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,' . $this->user()->account_id,
'discount' => 'positive', 'discount' => 'positive',
]; ];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules; return $rules;
} }
} }

View File

@ -0,0 +1,48 @@
<?php namespace App\Http\Requests;
use App\Models\Invoice;
class CreatePaymentAPIRequest extends PaymentRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_PAYMENT);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ( ! $this->invoice_id || ! $this->amount) {
return [
'invoice_id' => 'required',
'amount' => 'required',
];
}
$invoice = Invoice::scope($this->invoice_id)->firstOrFail();
$this->merge([
'invoice_id' => $invoice->id,
'client_id' => $invoice->client->id,
]);
$rules = array(
'amount' => "required|less_than:{$invoice->balance}|positive",
);
if ($this->payment_type_id == PAYMENT_TYPE_CREDIT) {
$rules['payment_type_id'] = 'has_credit:' . $invoice->client->public_id . ',' . $this->amount;
}
return $rules;
}
}

View File

@ -1,10 +1,8 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice; use App\Models\Invoice;
class CreatePaymentRequest extends Request class CreatePaymentRequest extends PaymentRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -13,7 +11,7 @@ class CreatePaymentRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_PAYMENT);
} }
/** /**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class CreateProductRequest extends ProductRequest
use Illuminate\Validation\Factory;
class CreatePaymentTermRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreatePaymentTermRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_PRODUCT);
} }
/** /**
@ -23,8 +20,7 @@ class CreatePaymentTermRequest extends Request
public function rules() public function rules()
{ {
return [ return [
'num_days' => 'required', 'product_key' => 'required',
'name' => 'required',
]; ];
} }
} }

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class CreateTaskRequest extends TaskRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_TASK);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'time_log' => 'time_log',
];
}
}

View File

@ -1,9 +1,9 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;
class CreateTaxRateRequest extends Request class CreateTaxRateRequest extends TaxRateRequest
{ {
// Expenses // Expenses
/** /**
@ -13,7 +13,7 @@ class CreateTaxRateRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_TAX_RATE);
} }
/** /**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
// vendor
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateVendorRequest extends Request class CreateVendorRequest extends VendorRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateVendorRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('create', ENTITY_VENDOR);
} }
/** /**
@ -26,21 +23,4 @@ class CreateVendorRequest extends Request
'name' => 'required', 'name' => 'required',
]; ];
} }
/*
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['vendor_contact'])) {
$input['vendor_contacts'] = [$input['vendor_contact']];
unset($input['vendor_contact']);
$this->replace($input);
}
return $factory->make(
$this->input(), $this->container->call([$this, 'rules']), $this->messages()
);
}
*/
} }

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class CreditRequest extends EntityRequest {
protected $entityType = ENTITY_CREDIT;
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class DocumentRequest extends EntityRequest {
protected $entityType = ENTITY_DOCUMENT;
}

View File

@ -0,0 +1,57 @@
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Input;
use Utils;
class EntityRequest extends Request {
protected $entityType;
private $entity;
public function entity()
{
if ($this->entity) {
return $this->entity;
}
// The entity id can appear as invoices, invoice_id, public_id or id
$publicId = false;
foreach (['_id', 's'] as $suffix) {
$field = $this->entityType . $suffix;
if ($this->$field) {
$publicId= $this->$field;
}
}
if ( ! $publicId) {
$publicId = Input::get('public_id') ?: Input::get('id');
}
if ( ! $publicId) {
return null;
}
$class = Utils::getEntityClass($this->entityType);
if (method_exists($class, 'withTrashed')) {
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
} else {
$this->entity = $class::scope($publicId)->firstOrFail();
}
return $this->entity;
}
public function authorize()
{
if ($this->entity()) {
return $this->user()->can('view', $this->entity());
} else {
return $this->user()->can('create', $this->entityType);
}
}
public function rules()
{
return [];
}
}

View File

@ -0,0 +1,18 @@
<?php namespace App\Http\Requests;
class ExpenseRequest extends EntityRequest {
protected $entityType = ENTITY_EXPENSE;
public function entity()
{
$expense = parent::entity();
// eager load the documents
if ($expense && ! $expense->relationLoaded('documents')) {
$expense->load('documents');
}
return $expense;
}
}

View File

@ -0,0 +1,19 @@
<?php namespace App\Http\Requests;
class InvoiceRequest extends EntityRequest {
protected $entityType = ENTITY_INVOICE;
public function entity()
{
$invoice = parent::entity();
// eager load the invoice items
if ($invoice && ! $invoice->relationLoaded('invoice_items')) {
$invoice->load('invoice_items');
}
return $invoice;
}
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class PaymentRequest extends EntityRequest {
protected $entityType = ENTITY_PAYMENT;
}

View File

@ -0,0 +1,6 @@
<?php namespace App\Http\Requests;
class ProductRequest extends EntityRequest {
protected $entityType = ENTITY_PRODUCT;
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use Auth; use Auth;
use App\Http\Requests\Request; use App\Http\Requests\Request;

View File

@ -1,45 +0,0 @@
<?php namespace app\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class SaveInvoiceWithClientRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$publicId = Request::get('public_id');
$invoiceId = $publicId ? Invoice::getPrivateId($publicId) : '';
$rules = [
'client.contacts' => 'valid_contacts',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.Auth::user()->account_id,
'discount' => 'positive',
];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules;
}
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class TaskRequest extends EntityRequest {
protected $entityType = ENTITY_TASK;
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class TaxRateRequest extends EntityRequest {
protected $entityType = ENTITY_TAX_RATE;
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class UpdateClientRequest extends ClientRequest
use Illuminate\Validation\Factory;
class UpdateClientRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateClientRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -1,10 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class UpdateExpenseRequest extends ExpenseRequest
use Illuminate\Validation\Factory;
class UpdateExpenseRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -13,7 +9,7 @@ class UpdateExpenseRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -0,0 +1,36 @@
<?php namespace App\Http\Requests;
class UpdateInvoiceAPIRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ($this->action == ACTION_ARCHIVE) {
return [];
}
$invoiceId = $this->entity()->id;
$rules = [
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
return $rules;
}
}

View File

@ -1,11 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use Auth; class UpdateInvoiceRequest extends InvoiceRequest
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class UpdateInvoiceRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -14,7 +9,7 @@ class UpdateInvoiceRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**
@ -24,19 +19,21 @@ class UpdateInvoiceRequest extends Request
*/ */
public function rules() public function rules()
{ {
if ($this->action == ACTION_ARCHIVE) { $invoiceId = $this->entity()->id;
return [];
}
$publicId = $this->route('invoices');
$invoiceId = Invoice::getPrivateId($publicId);
$rules = [ $rules = [
'client.contacts' => 'valid_contacts',
'invoice_items' => 'valid_invoice_items', 'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.Auth::user()->account_id, 'invoice_number' => 'required|unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id,
'discount' => 'positive', 'discount' => 'positive',
]; ];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules; return $rules;
} }
} }

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; class UpdatePaymentRequest extends PaymentRequest
use Illuminate\Validation\Factory;
class UpdatePaymentRequest extends Request
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdatePaymentRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateProductRequest extends ProductRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'product_key' => 'required',
];
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateTaskRequest extends TaskRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'time_log' => 'time_log',
];
}
}

View File

@ -1,9 +1,9 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;
class UpdateTaxRateRequest extends Request class UpdateTaxRateRequest extends TaxRateRequest
{ {
// Expenses // Expenses
/** /**
@ -13,7 +13,7 @@ class UpdateTaxRateRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
use Auth; use Auth;
use App\Http\Requests\Request; use App\Http\Requests\Request;
@ -14,7 +14,7 @@ class UpdateUserRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests; <?php namespace App\Http\Requests;
// vendor
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateVendorRequest extends Request class UpdateVendorRequest extends VendorRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateVendorRequest extends Request
*/ */
public function authorize() public function authorize()
{ {
return true; return $this->user()->can('edit', $this->entity());
} }
/** /**

View File

@ -0,0 +1,19 @@
<?php namespace App\Http\Requests;
class VendorRequest extends EntityRequest {
protected $entityType = ENTITY_VENDOR;
public function entity()
{
$vendor = parent::entity();
// eager load the contacts
if ($vendor && ! $vendor->relationLoaded('vendor_contacts')) {
$vendor->load('vendor_contacts');
}
return $vendor;
}
}

View File

@ -43,17 +43,23 @@ Route::group(['middleware' => 'auth:client'], function() {
Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment'); Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex'); Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex'); Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/documents', 'PublicClientController@documentIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex'); Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard'); Route::get('client/dashboard', 'PublicClientController@dashboard');
Route::get('client/documents/js/{documents}/{filename}', 'PublicClientController@getDocumentVFSJS');
Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'PublicClientController@getDocument');
Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip');
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.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
}); });
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'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
Route::get('license', 'PaymentController@show_license_payment'); Route::get('license', 'PaymentController@show_license_payment');
Route::post('license', 'PaymentController@do_license_payment'); Route::post('license', 'PaymentController@do_license_payment');
@ -74,8 +80,8 @@ Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@po
Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); Route::get('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); Route::post('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
Route::get('/user/confirm/{code}', 'UserController@confirm'); Route::get('/user/confirm/{code}', 'UserController@confirm');
@ -84,8 +90,8 @@ Route::get('/user/confirm/{code}', 'UserController@confirm');
Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin')); Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin'));
Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin')); 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/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout'));
Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail')); Route::get('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail')); Route::post('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset')); 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')); Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset'));
@ -105,9 +111,11 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage'); Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::get('settings/user_details', 'AccountController@showUserDetails');
Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails');
Route::post('users/change_password', 'UserController@changePassword');
Route::resource('clients', 'ClientController'); Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
@ -129,15 +137,20 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring'); Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('recurring_invoices', 'RecurringInvoiceController@index'); Route::get('recurring_invoices', 'RecurringInvoiceController@index');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice'); Route::get('invoices/{invoices}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk'); Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('documents/{documents}/{filename?}', 'DocumentController@get');
Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS');
Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview');
Route::post('document', 'DocumentController@postUpload');
Route::get('quotes/create/{client_id?}', 'QuoteController@create'); Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice'); Route::get('quotes/{invoices}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit'); Route::get('quotes/{invoices}/edit', 'InvoiceController@edit');
Route::put('quotes/{public_id}', 'InvoiceController@update'); Route::put('quotes/{invoices}', 'InvoiceController@update');
Route::get('quotes/{public_id}', 'InvoiceController@edit'); Route::get('quotes/{invoices}', 'InvoiceController@edit');
Route::post('quotes', 'InvoiceController@store'); Route::post('quotes', 'InvoiceController@store');
Route::get('quotes', 'QuoteController@index'); Route::get('quotes', 'QuoteController@index');
Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable')); Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable'));
@ -178,9 +191,9 @@ Route::group([
Route::resource('users', 'UserController'); Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk'); Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('start_trial', 'AccountController@startTrial'); Route::get('start_trial/{plan}', 'AccountController@startTrial')
->where(['plan'=>'pro']);
Route::get('restore_user/{user_id}', 'UserController@restoreUser'); Route::get('restore_user/{user_id}', 'UserController@restoreUser');
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount');
Route::get('/manage_companies', 'UserController@manageCompanies'); Route::get('/manage_companies', 'UserController@manageCompanies');
@ -197,21 +210,18 @@ Route::group([
Route::resource('tax_rates', 'TaxRateController'); Route::resource('tax_rates', 'TaxRateController');
Route::post('tax_rates/bulk', 'TaxRateController@bulk'); Route::post('tax_rates/bulk', 'TaxRateController@bulk');
Route::get('settings/email_preview', 'AccountController@previewEmail');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3'); Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/charts_and_reports', 'ReportController@showReports'); Route::get('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/charts_and_reports', 'ReportController@showReports'); Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/change_plan', 'AccountController@changePlan');
Route::post('settings/cancel_account', 'AccountController@cancelAccount'); Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::post('settings/company_details', 'AccountController@updateDetails'); Route::post('settings/company_details', 'AccountController@updateDetails');
Route::get('settings/{section?}', 'AccountController@showSection'); Route::get('settings/{section?}', 'AccountController@showSection');
Route::post('settings/{section?}', 'AccountController@doSection'); Route::post('settings/{section?}', 'AccountController@doSection');
//Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable'));
//Route::resource('payment_terms', 'PaymentTermController');
//Route::post('payment_terms/bulk', 'PaymentTermController@bulk');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::post('user/setTheme', 'UserController@setTheme'); Route::post('user/setTheme', 'UserController@setTheme');
Route::post('remove_logo', 'AccountController@removeLogo'); Route::post('remove_logo', 'AccountController@removeLogo');
Route::post('account/go_pro', 'AccountController@enableProPlan'); Route::post('account/go_pro', 'AccountController@enableProPlan');
@ -234,15 +244,15 @@ Route::group([
// Route groups for API // Route groups for API
Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{ {
Route::get('ping', 'ClientApiController@ping'); Route::get('ping', 'AccountApiController@ping');
Route::post('login', 'AccountApiController@login'); Route::post('login', 'AccountApiController@login');
Route::post('register', 'AccountApiController@register'); Route::post('register', 'AccountApiController@register');
Route::get('static', 'AccountApiController@getStaticData'); Route::get('static', 'AccountApiController@getStaticData');
Route::get('accounts', 'AccountApiController@show'); Route::get('accounts', 'AccountApiController@show');
Route::put('accounts', 'AccountApiController@update'); Route::put('accounts', 'AccountApiController@update');
Route::resource('clients', 'ClientApiController'); Route::resource('clients', 'ClientApiController');
Route::get('quotes', 'QuoteApiController@index'); //Route::get('quotes', 'QuoteApiController@index');
Route::resource('quotes', 'QuoteApiController'); //Route::resource('quotes', 'QuoteApiController');
Route::get('invoices', 'InvoiceApiController@index'); Route::get('invoices', 'InvoiceApiController@index');
Route::resource('invoices', 'InvoiceApiController'); Route::resource('invoices', 'InvoiceApiController');
Route::get('payments', 'PaymentApiController@index'); Route::get('payments', 'PaymentApiController@index');
@ -268,7 +278,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
}); });
// Redirects for legacy links // Redirects for legacy links
/*
Route::get('/rocksteady', function() { Route::get('/rocksteady', function() {
return Redirect::to(NINJA_WEB_URL, 301); return Redirect::to(NINJA_WEB_URL, 301);
}); });
@ -296,7 +305,7 @@ Route::get('/compare-online-invoicing{sites?}', function() {
Route::get('/forgot_password', function() { Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301); return Redirect::to(NINJA_APP_URL.'/forgot', 301);
}); });
*/
if (!defined('CONTACT_EMAIL')) { if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address')); define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -311,6 +320,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_CLIENT', 'client'); define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact'); define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice'); define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEMS', 'invoice_items'); define('ENTITY_INVOICE_ITEMS', 'invoice_items');
define('ENTITY_INVITATION', 'invitation'); define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice'); define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
@ -344,6 +354,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_LOCALIZATION', 'localization'); define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications'); define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export'); define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_MANAGEMENT', 'account_management');
define('ACCOUNT_PAYMENTS', 'online_payments'); define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts'); define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses'); define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
@ -426,6 +437,10 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_IFRAME_URL_LENGTH', 250); define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10); define('MAX_FAILED_LOGINS', 10);
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels
define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1);// Roboto define('DEFAULT_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto define('DEFAULT_BODY_FONT', 1);// Roboto
@ -521,6 +536,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_BITPAY', 42); define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43); define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47); define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2); define('EVENT_CREATE_INVOICE', 2);
@ -534,25 +550,26 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'); define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE); define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.invoiceninja.com'));
define('NINJA_APP_URL', 'https://app.invoiceninja.com'); define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_VERSION', '2.5.1.3');
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '2.5.2' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
define('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'); define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'));
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'); define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
define('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'); define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
define('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'); define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'); define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'); define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'));
define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'); define('PHANTOMJS_CLOUD', env('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'));
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'); define('PHP_DATE_FORMATS', env('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'));
define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'); define('REFERRAL_PROGRAM_URL', env('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'));
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'); define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'));
define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'); define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='); define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
@ -566,9 +583,12 @@ if (!defined('CONTACT_EMAIL')) {
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74'); define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD'); define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50); define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 5));
define('WHITE_LABEL_PRICE', 20); define('PLAN_PRICE_PRO_YEARLY', env('PLAN_PRICE_PRO_YEARLY', 50));
define('INVOICE_DESIGNS_PRICE', 10); define('PLAN_PRICE_ENTERPRISE_MONTHLY', env('PLAN_PRICE_ENTERPRISE_MONTHLY', 10));
define('PLAN_PRICE_ENTERPRISE_YEARLY', env('PLAN_PRICE_ENTERPRISE_YEARLY', 100));
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20));
define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10));
define('USER_TYPE_SELF_HOST', 'SELF_HOST'); define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST'); define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
@ -577,9 +597,11 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_USERNAME', 'user@example.com'); define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password'); define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET'); define('API_SECRET', 'API_SECRET');
define('DEFAULT_API_PAGE_SIZE', 15);
define('MAX_API_PAGE_SIZE', 100);
define('IOS_PRODUCTION_PUSH','ninjaIOS'); define('IOS_PRODUCTION_PUSH', env('IOS_PRODUCTION_PUSH', 'ninjaIOS'));
define('IOS_DEV_PUSH','devNinjaIOS'); define('IOS_DEV_PUSH', env('IOS_DEV_PUSH', 'devNinjaIOS'));
define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_IN', 2);
@ -629,7 +651,46 @@ if (!defined('CONTACT_EMAIL')) {
define('RESELLER_REVENUE_SHARE', 'A'); define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B'); define('RESELLER_LIMITED_USERS', 'B');
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_WHITE_LABEL', 'white_label');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
// Pro
define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design');
define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by');
define('FEATURE_DIFFERENT_DESIGNS', 'different_designs');
define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders');
define('FEATURE_INVOICE_SETTINGS', 'invoice_settings');
define('FEATURE_CUSTOM_EMAILS', 'custom_emails');
define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment');
define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs');
define('FEATURE_QUOTES', 'quotes');
define('FEATURE_REPORTS', 'reports');
define('FEATURE_API', 'api');
define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password');
define('FEATURE_CUSTOM_URL', 'custom_url');
define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed
// Whitelabel
define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css');
define('FEATURE_WHITE_LABEL', 'feature_white_label');
// Enterprise
define('FEATURE_DOCUMENTS', 'documents');
// No Trial allowed
define('FEATURE_USERS', 'users');// Grandfathered for old Pro users
define('FEATURE_USER_PERMISSIONS', 'user_permissions');
// Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15');
$creditCards = [ $creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
@ -679,30 +740,6 @@ if (!defined('CONTACT_EMAIL')) {
} }
} }
/*
// Log all SQL queries to laravel.log
if (Utils::isNinjaDev()) {
Event::listen('illuminate.query', function($query, $bindings, $time, $name) {
$data = compact('bindings', 'time', 'name');
// Format binding data for sql insertion
foreach ($bindings as $i => $binding) {
if ($binding instanceof \DateTime) {
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
} elseif (is_string($binding)) {
$bindings[$i] = "'$binding'";
}
}
// Insert bindings into query
$query = str_replace(array('%', '?'), array('%%', '%s'), $query);
$query = vsprintf($query, $bindings);
Log::info($query, $data);
});
}
*/
/* /*
if (Utils::isNinjaDev()) if (Utils::isNinjaDev())
{ {

View File

@ -2,6 +2,8 @@
// https://github.com/denvertimothy/OFX // https://github.com/denvertimothy/OFX
use Utils;
use Log;
use SimpleXMLElement; use SimpleXMLElement;
class OFX class OFX
@ -21,13 +23,19 @@ class OFX
$c = curl_init(); $c = curl_init();
curl_setopt($c, CURLOPT_URL, $this->bank->url); curl_setopt($c, CURLOPT_URL, $this->bank->url);
curl_setopt($c, CURLOPT_POST, 1); curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx')); // User-Agent: http://www.ofxhome.com/ofxforum/viewtopic.php?pid=108091#p108091
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx', 'User-Agent: httpclient'));
curl_setopt($c, CURLOPT_POSTFIELDS, $this->request); curl_setopt($c, CURLOPT_POSTFIELDS, $this->request);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
$this->response = curl_exec($c); $this->response = curl_exec($c);
//print_r($this->response);
//\Log::info(print_r($this->response, true)); if (Utils::isNinjaDev()) {
Log::info(print_r($this->response, true));
}
curl_close($c); curl_close($c);
$tmp = explode('<OFX>', $this->response); $tmp = explode('<OFX>', $this->response);
$this->responseHeader = $tmp[0]; $this->responseHeader = $tmp[0];
$this->responseBody = '<OFX>'.$tmp[1]; $this->responseBody = '<OFX>'.$tmp[1];
@ -35,14 +43,15 @@ class OFX
public function xml() public function xml()
{ {
$xml = $this->responseBody; $xml = $this->responseBody;
self::closeTags($xml); $xml = self::closeTags($xml);
$x = new SimpleXMLElement($xml); $x = new SimpleXMLElement($xml);
return $x; return $x;
} }
public static function closeTags(&$x) public static function closeTags($x)
{ {
$x = preg_replace('/(<([^<\/]+)>)(?!.*?<\/\2>)([^<]+)/', '\1\3</\2>', $x); $x = preg_replace('/\s+/', '', $x);
return preg_replace('/(<([^<\/]+)>)(?!.*?<\/\2>)([^<]+)/', '\1\3</\2>', $x);
} }
} }
@ -224,3 +233,4 @@ class Account
} }
} }
} }

View File

@ -51,6 +51,11 @@ class Utils
return php_sapi_name() == 'cli'; return php_sapi_name() == 'cli';
} }
public static function isTravis()
{
return env('TRAVIS') == 'true';
}
public static function isNinja() public static function isNinja()
{ {
return self::isNinjaProd() || self::isNinjaDev(); return self::isNinjaProd() || self::isNinjaDev();
@ -118,6 +123,11 @@ class Utils
return Auth::check() && Auth::user()->isPro(); return Auth::check() && Auth::user()->isPro();
} }
public static function hasFeature($feature)
{
return Auth::check() && Auth::user()->hasFeature($feature);
}
public static function isAdmin() public static function isAdmin()
{ {
return Auth::check() && Auth::user()->is_admin; return Auth::check() && Auth::user()->is_admin;
@ -130,7 +140,7 @@ class Utils
public static function hasAllPermissions($permission) public static function hasAllPermissions($permission)
{ {
return Auth::check() && Auth::user()->hasPermissions($permission); return Auth::check() && Auth::user()->hasPermission($permission);
} }
public static function isTrial() public static function isTrial()
@ -331,6 +341,7 @@ class Utils
$currency = self::getFromCache($currencyId, 'currencies'); $currency = self::getFromCache($currencyId, 'currencies');
$thousand = $currency->thousand_separator; $thousand = $currency->thousand_separator;
$decimal = $currency->decimal_separator; $decimal = $currency->decimal_separator;
$precision = $currency->precision;
$code = $currency->code; $code = $currency->code;
$swapSymbol = false; $swapSymbol = false;
@ -345,7 +356,7 @@ class Utils
} }
} }
$value = number_format($value, $currency->precision, $decimal, $thousand); $value = number_format($value, $precision, $decimal, $thousand);
$symbol = $currency->symbol; $symbol = $currency->symbol;
if ($showCode || !$symbol) { if ($showCode || !$symbol) {
@ -440,7 +451,12 @@ class Utils
return false; return false;
} }
$dateTime = new DateTime($date); if ($date instanceof DateTime) {
$dateTime = $date;
} else {
$dateTime = new DateTime($date);
}
$timestamp = $dateTime->getTimestamp(); $timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
@ -659,9 +675,14 @@ class Utils
return $year + $offset; return $year + $offset;
} }
public static function getEntityClass($entityType)
{
return 'App\\Models\\' . static::getEntityName($entityType);
}
public static function getEntityName($entityType) public static function getEntityName($entityType)
{ {
return ucwords(str_replace('_', ' ', $entityType)); return ucwords(Utils::toCamelCase($entityType));
} }
public static function getClientDisplayName($model) public static function getClientDisplayName($model)
@ -961,38 +982,6 @@ class Utils
return $entity1; return $entity1;
} }
public static function withinPastYear($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
$interval = $today->diff($datePaid);
return $interval->y == 0;
}
public static function getInterval($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
return $today->diff($datePaid);
}
public static function withinPastTwoWeeks($date)
{
$interval = Utils::getInterval($date);
return $interval && $interval->d <= 14;
}
public static function addHttp($url) public static function addHttp($url)
{ {
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) { if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners; <?php namespace App\Listeners;
use App\Models\Invoice; use App\Models\Invoice;
use App\Events\ClientWasCreated; use App\Events\ClientWasCreated;

View File

@ -0,0 +1,54 @@
<?php namespace App\Listeners;
use Log;
use Utils;
use App\Events\PaymentWasCreated;
class AnalyticsListener
{
public function trackRevenue(PaymentWasCreated $event)
{
if ( ! Utils::isNinja() || ! env('ANALYTICS_KEY')) {
return;
}
$payment = $event->payment;
$invoice = $payment->invoice;
$account = $payment->account;
if ($account->account_key != NINJA_ACCOUNT_KEY) {
return;
}
$analyticsId = env('ANALYTICS_KEY');
$client = $payment->client;
$amount = $payment->amount;
$base = "v=1&tid={$analyticsId}&cid{$client->public_id}&cu=USD&ti={$invoice->invoice_number}";
$url = $base . "&t=transaction&ta=ninja&tr={$amount}";
$this->sendAnalytics($url);
//Log::info($url);
$url = $base . "&t=item&in=plan&ip={$amount}&iq=1";
$this->sendAnalytics($url);
//Log::info($url);
}
private function sendAnalytics($data)
{
$data = json_encode($data);
$curl = curl_init();
$opts = [
CURLOPT_URL => GOOGLE_ANALYITCS_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => 'POST',
CURLOPT_POSTFIELDS => $data,
];
curl_setopt_array($curl, $opts);
$response = curl_exec($curl);
curl_close($curl);
}
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners; <?php namespace App\Listeners;
use Carbon; use Carbon;
use App\Models\Credit; use App\Models\Credit;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners; <?php namespace App\Listeners;
use Carbon; use Carbon;
use App\Models\Expense; use App\Models\Expense;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners; <?php namespace App\Listeners;
use Utils; use Utils;
use Auth; use Auth;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners; <?php namespace App\Listeners;
use Utils; use Utils;
use Auth; use Auth;
@ -14,10 +14,11 @@ class InvoiceListener
{ {
public function createdInvoice(InvoiceWasCreated $event) public function createdInvoice(InvoiceWasCreated $event)
{ {
if (Utils::isPro()) { if (Utils::hasFeature(FEATURE_DIFFERENT_DESIGNS)) {
return; return;
} }
// Make sure the account has the same design set as the invoice does
if (Auth::check()) { if (Auth::check()) {
$invoice = $event->invoice; $invoice = $event->invoice;
$account = Auth::user()->account; $account = Auth::user()->account;

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