Merge branch 'release-2.9.0'

This commit is contained in:
Hillel Coren 2016-12-15 17:28:48 +02:00
commit 42f7d65aff
241 changed files with 8970 additions and 2716 deletions

View File

@ -81,3 +81,6 @@ WEPAY_FEE_PAYER=payee
WEPAY_APP_FEE_MULTIPLIER=0.002
WEPAY_APP_FEE_FIXED=0
WEPAY_THEME='{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}' # See https://www.wepay.com/developer/reference/structures#theme
BLUEVINE_PARTNER_UNIQUE_ID=
BLUEVINE_PARTNER_TOKEN=

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ Thumbs.db
/error_log
/auth.json
/public/error_log
/Modules
/ninja.sublime-project
/ninja.sublime-workspace

View File

@ -73,12 +73,16 @@ class CheckData extends Command {
$this->logMessage('Done');
$errorEmail = env('ERROR_EMAIL');
if ( ! $this->isValid && $errorEmail) {
Mail::raw($this->log, function ($message) use ($errorEmail) {
$message->to($errorEmail)
->from(CONTACT_EMAIL)
->subject('Check-Data');
});
if ( ! $this->isValid) {
if ($errorEmail) {
Mail::raw($this->log, function ($message) use ($errorEmail) {
$message->to($errorEmail)
->from(CONTACT_EMAIL)
->subject('Check-Data');
});
} else {
$this->info($this->log);
}
}
}
@ -209,6 +213,7 @@ class CheckData extends Command {
->where('accounts.id', '!=', 20432)
->where('clients.is_deleted', '=', 0)
->where('invoices.is_deleted', '=', 0)
->where('invoices.is_public', '=', 1)
->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('invoices.is_recurring', '=', 0)
->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999');

View File

@ -0,0 +1,174 @@
<?php
namespace App\Console\Commands;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Nwidart\Modules\Commands\GeneratorCommand;
use Nwidart\Modules\Support\Stub;
use Nwidart\Modules\Traits\ModuleCommandTrait;
class MakeClass extends GeneratorCommand
{
use ModuleCommandTrait;
protected $argumentName = 'name';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $name = 'ninja:make-class';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create class stub';
protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name of the module.'],
['module', InputArgument::REQUIRED, 'The name of module will be used.'],
['class', InputArgument::REQUIRED, 'The name of the class.'],
['prefix', InputArgument::OPTIONAL, 'The prefix of the class.'],
];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('fields', null, InputOption::VALUE_OPTIONAL, 'The model attributes.', null),
array('filename', null, InputOption::VALUE_OPTIONAL, 'The class filename.', null),
);
}
public function getTemplateContents()
{
$module = $this->laravel['modules']->findOrFail($this->getModuleName());
$path = str_replace('/', '\\', config('modules.paths.generator.' . $this->argument('class')));
return (new Stub('/' . $this->argument('prefix') . $this->argument('class') . '.stub', [
'NAMESPACE' => $this->getClassNamespace($module) . "\\" . $path,
'LOWER_NAME' => $module->getLowerName(),
'CLASS' => $this->getClass(),
'STUDLY_NAME' => Str::studly($module->getLowerName()),
'DATATABLE_COLUMNS' => $this->getColumns(),
'FORM_FIELDS' => $this->getFormFields(),
'DATABASE_FIELDS' => $this->getDatabaseFields($module),
'TRANSFORMER_FIELDS' => $this->getTransformerFields($module),
]))->render();
}
public function getDestinationFilePath()
{
$path = $this->laravel['modules']->getModulePath($this->getModuleName());
$seederPath = $this->laravel['modules']->config('paths.generator.' . $this->argument('class'));
return $path . $seederPath . '/' . $this->getFileName() . '.php';
}
/**
* @return string
*/
protected function getFileName()
{
if ($this->option('filename')) {
return $this->option('filename');
}
return studly_case($this->argument('prefix')) . studly_case($this->argument('name')) . Str::studly($this->argument('class'));
}
protected function getColumns()
{
$fields = $this->option('fields');
$fields = explode(',', $fields);
$str = '';
foreach ($fields as $field) {
if ( ! $field) {
continue;
}
$field = explode(':', $field)[0];
$str .= '[
\''. $field . '\',
function ($model) {
return $model->' . $field . ';
}
],';
}
return $str;
}
protected function getFormFields()
{
$fields = $this->option('fields');
$fields = explode(',', $fields);
$str = '';
foreach ($fields as $field) {
if ( ! $field) {
continue;
}
$parts = explode(':', $field);
$field = $parts[0];
$type = $parts[1];
if ($type == 'text') {
$str .= "{!! Former::textarea('" . $field . "') !!}\n";
} else {
$str .= "{!! Former::text('" . $field . "') !!}\n";
}
}
return $str;
}
protected function getDatabaseFields($module)
{
$fields = $this->option('fields');
$fields = explode(',', $fields);
$str = '';
foreach ($fields as $field) {
if ( ! $field) {
continue;
}
$field = explode(':', $field)[0];
$str .= "'" . $module->getLowerName() . ".{$field}', ";
}
return $str;
}
protected function getTransformerFields($module)
{
$fields = $this->option('fields');
$fields = explode(',', $fields);
$str = '';
foreach ($fields as $field) {
if ( ! $field) {
continue;
}
$field = explode(':', $field)[0];
$str .= "'{$field}' => $" . $module->getLowerName() . "->$field,\n ";
}
return rtrim($str);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Console\Commands;
use Artisan;
use Illuminate\Console\Command;
class MakeModule extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:make-module {name} {fields?} {--migrate=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate Module CRUD';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$name = $this->argument('name');
$fields = $this->argument('fields');
$migrate = $this->option('migrate');
$lower = strtolower($name);
// convert 'name:string,description:text' to 'name,description'
$fillable = explode(',', $fields);
$fillable = array_map(function($item) {
return explode(':', $item)[0];
}, $fillable);
$fillable = join(',', $fillable);
$this->info("Creating module: {$name}...");
Artisan::call('module:make', ['name' => [$name]]);
Artisan::call('module:make-migration', ['name' => "create_{$lower}_table", '--fields' => $fields, 'module' => $name]);
Artisan::call('module:make-model', ['model' => $name, 'module' => $name, '--fillable' => $fillable]);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'views', '--fields' => $fields, '--filename' => 'edit.blade']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'datatable', '--fields' => $fields]);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'repository', '--fields' => $fields]);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'policy']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'auth-provider']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'presenter']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request', 'prefix' => 'create']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request', 'prefix' => 'update']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'api-controller']);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'transformer', '--fields' => $fields]);
Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'lang', '--filename' => 'texts']);
if ($migrate == 'false') {
$this->info("Use the following command to run the migrations:\nphp artisan module:migrate $name");
} else {
Artisan::call('module:migrate', ['module' => $name]);
}
Artisan::call('module:dump');
$this->info("Done");
}
protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name of the module.'],
['fields', InputArgument::OPTIONAL, 'The fields of the module.']
];
}
protected function getOptions()
{
return array(
array('migrate', null, InputOption::VALUE_OPTIONAL, 'The model attributes.', null),
);
}
}

View File

@ -58,7 +58,7 @@ class SendRecurringInvoices extends Command
$today = new DateTime();
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
->orderBy('id', 'asc')
->get();
$this->info(count($invoices).' recurring invoice(s) found');
@ -81,7 +81,7 @@ class SendRecurringInvoices extends Command
}
$delayedAutoBillInvoices = Invoice::with('account.timezone', 'recurring_invoice', 'invoice_items', 'client', 'user')
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS FALSE
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS FALSE AND is_public IS TRUE
AND balance > 0 AND due_date = ? AND recurring_invoice_id IS NOT NULL',
[$today->format('Y-m-d')])
->orderBy('invoices.id', 'asc')

View File

@ -0,0 +1,164 @@
<?php
namespace $NAMESPACE$;
use App\Http\Controllers\BaseAPIController;
use Modules\$STUDLY_NAME$\Repositories\$STUDLY_NAME$Repository;
use Modules\$STUDLY_NAME$\Http\Requests\$STUDLY_NAME$Request;
use Modules\$STUDLY_NAME$\Http\Requests\Create$STUDLY_NAME$Request;
use Modules\$STUDLY_NAME$\Http\Requests\Update$STUDLY_NAME$Request;
class $STUDLY_NAME$ApiController extends BaseAPIController
{
protected $$STUDLY_NAME$Repo;
protected $entityType = '$LOWER_NAME$';
public function __construct($STUDLY_NAME$Repository $$LOWER_NAME$Repo)
{
parent::__construct();
$this->$LOWER_NAME$Repo = $$LOWER_NAME$Repo;
}
/**
* @SWG\Get(
* path="/$LOWER_NAME$",
* summary="List of $LOWER_NAME$",
* tags={"$LOWER_NAME$"},
* @SWG\Response(
* response=200,
* description="A list with $LOWER_NAME$",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
$data = $this->$LOWER_NAME$Repo->all();
return $this->listResponse($data);
}
/**
* @SWG\Get(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
* summary="Individual $STUDLY_NAME$",
* tags={"$LOWER_NAME$"},
* @SWG\Response(
* response=200,
* description="A single $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function show($STUDLY_NAME$Request $request)
{
return $this->itemResponse($request->entity());
}
/**
* @SWG\Post(
* path="/$LOWER_NAME$",
* tags={"$LOWER_NAME$"},
* summary="Create a $LOWER_NAME$",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
* ),
* @SWG\Response(
* response=200,
* description="New $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function store(Create$STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input());
return $this->itemResponse($$LOWER_NAME$);
}
/**
* @SWG\Put(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
* tags={"$LOWER_NAME$"},
* summary="Update a $LOWER_NAME$",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
* ),
* @SWG\Response(
* response=200,
* description="Update $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(Update$STUDLY_NAME$Request $request, $publicId)
{
if ($request->action) {
return $this->handleAction($request);
}
$$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input(), $request->entity());
return $this->itemResponse($$LOWER_NAME$);
}
/**
* @SWG\Delete(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
* tags={"$LOWER_NAME$"},
* summary="Delete a $LOWER_NAME$",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
* ),
* @SWG\Response(
* response=200,
* description="Delete $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function destroy(Update$STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $request->entity();
$this->$LOWER_NAME$Repo->delete($$LOWER_NAME$);
return $this->itemResponse($$LOWER_NAME$);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace $NAMESPACE$;
use App\Providers\AuthServiceProvider;
class $STUDLY_NAME$AuthProvider extends AuthServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
\Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$::class => \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class,
];
}

View File

@ -0,0 +1,68 @@
<?php
namespace $NAMESPACE$;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class $CLASS$ extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = '$COMMAND_NAME$';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
//
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return [
['example', InputArgument::REQUIRED, 'An example argument.'],
];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
];
}
}

View File

@ -0,0 +1,15 @@
{
"name": "$VENDOR$/$LOWER_NAME$",
"description": "",
"authors": [
{
"name": "$AUTHOR_NAME$",
"email": "$AUTHOR_EMAIL$"
}
],
"autoload": {
"psr-4": {
"$MODULE_NAMESPACE$\\$STUDLY_NAME$\\": ""
}
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace $CLASS_NAMESPACE$;
use Illuminate\Routing\Controller;
class $CLASS$ extends Controller
{
}

View File

@ -0,0 +1,131 @@
<?php
namespace $CLASS_NAMESPACE$;
use Auth;
use App\Http\Controllers\BaseController;
use App\Services\DatatableService;
use Modules\$STUDLY_NAME$\Datatables\$STUDLY_NAME$Datatable;
use Modules\$STUDLY_NAME$\Repositories\$STUDLY_NAME$Repository;
use Modules\$STUDLY_NAME$\Http\Requests\$STUDLY_NAME$Request;
use Modules\$STUDLY_NAME$\Http\Requests\Create$STUDLY_NAME$Request;
use Modules\$STUDLY_NAME$\Http\Requests\Update$STUDLY_NAME$Request;
class $CLASS$ extends BaseController
{
protected $$STUDLY_NAME$Repo;
//protected $entityType = '$LOWER_NAME$';
public function __construct($STUDLY_NAME$Repository $$LOWER_NAME$Repo)
{
//parent::__construct();
$this->$LOWER_NAME$Repo = $$LOWER_NAME$Repo;
}
/**
* Display a listing of the resource.
* @return Response
*/
public function index()
{
return view('list_wrapper', [
'entityType' => '$LOWER_NAME$',
'datatable' => new $STUDLY_NAME$Datatable(),
'title' => mtrans('$LOWER_NAME$', '$LOWER_NAME$_list'),
]);
}
public function datatable(DatatableService $datatableService)
{
$search = request()->input('test');
$userId = Auth::user()->filterId();
$datatable = new $STUDLY_NAME$Datatable();
$query = $this->$LOWER_NAME$Repo->find($search, $userId);
return $datatableService->createDatatable($datatable, $query);
}
/**
* Show the form for creating a new resource.
* @return Response
*/
public function create($STUDLY_NAME$Request $request)
{
$data = [
'$LOWER_NAME$' => null,
'method' => 'POST',
'url' => '$LOWER_NAME$',
'title' => mtrans('$LOWER_NAME$', 'new_$LOWER_NAME$'),
];
return view('$LOWER_NAME$::edit', $data);
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Response
*/
public function store(Create$STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input());
return redirect()->to($$LOWER_NAME$->present()->editUrl)
->with('message', mtrans('$LOWER_NAME$', 'created_$LOWER_NAME$'));
}
/**
* Show the form for editing the specified resource.
* @return Response
*/
public function edit($STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $request->entity();
$data = [
'$LOWER_NAME$' => $$LOWER_NAME$,
'method' => 'PUT',
'url' => '$LOWER_NAME$/' . $$LOWER_NAME$->public_id,
'title' => mtrans('$LOWER_NAME$', 'edit_$LOWER_NAME$'),
];
return view('$LOWER_NAME$::edit', $data);
}
/**
* Show the form for editing a resource.
* @return Response
*/
public function show($STUDLY_NAME$Request $request)
{
return redirect()->to("$LOWER_NAME$/{$request->$LOWER_NAME$}/edit");
}
/**
* Update the specified resource in storage.
* @param Request $request
* @return Response
*/
public function update(Update$STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input(), $request->entity());
return redirect()->to($$LOWER_NAME$->present()->editUrl)
->with('message', mtrans('$LOWER_NAME$', 'updated_$LOWER_NAME$'));
}
/**
* Update multiple resources
*/
public function bulk()
{
$action = request()->input('action');
$ids = request()->input('public_id') ?: request()->input('ids');
$count = $this->$LOWER_NAME$Repo->bulk($ids, $action);
return redirect()->to('$LOWER_NAME$')
->with('message', mtrans('$LOWER_NAME$', $action . '_$LOWER_NAME$_complete'));
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace $NAMESPACE$;
class Create$CLASS$Request extends $CLASS$Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', '$LOWER_NAME$');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace $NAMESPACE$;
use Utils;
use URL;
use Auth;
use App\Ninja\Datatables\EntityDatatable;
class $CLASS$Datatable extends EntityDatatable
{
public $entityType = '$LOWER_NAME$';
public $sortCol = 1;
public function columns()
{
return [
$DATATABLE_COLUMNS$
[
'created_at',
function ($model) {
return Utils::fromSqlDateTime($model->created_at);
}
],
];
}
public function actions()
{
return [
[
mtrans('$LOWER_NAME$', 'edit_$LOWER_NAME$'),
function ($model) {
return URL::to("$LOWER_NAME$/{$model->public_id}/edit");
},
function ($model) {
return Auth::user()->can('editByOwner', ['$LOWER_NAME$', $model->user_id]);
}
],
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace $NAMESPACE$;
use Illuminate\Queue\SerializesModels;
class $CLASS$
{
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace $NAMESPACE$;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
class $CLASS$ implements ShouldQueue
{
use InteractsWithQueue, SerializesModels, Queueable;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
//
}
}

View File

@ -0,0 +1,17 @@
{
"name": "$STUDLY_NAME$",
"alias": "$LOWER_NAME$",
"description": "",
"keywords": [],
"active": 1,
"order": 0,
"providers": [
"$MODULE_NAMESPACE$\\$STUDLY_NAME$\\Providers\\$STUDLY_NAME$ServiceProvider"
],
"aliases":{},
"files": [
"start.php"
],
"icon": "th-large",
"plural": "$LOWER_NAME$"
}

View File

@ -0,0 +1,19 @@
<?php
$LANG = array(
'$LOWER_NAME$' => '$STUDLY_NAME$',
'$LOWER_NAME$_list' => '$STUDLY_NAME$ List',
'archive_$LOWER_NAME$' => 'Archive $STUDLY_NAME$',
'delete_$LOWER_NAME$' => 'Delete $STUDLY_NAME$',
'edit_$LOWER_NAME$' => 'Edit $STUDLY_NAME$',
'restore_$LOWER_NAME$' => 'Restore $STUDLY_NAME$',
'new_$LOWER_NAME$' => 'New $STUDLY_NAME$',
'created_$LOWER_NAME$' => 'Successfully created $LOWER_NAME$',
'updated_$LOWER_NAME$' => 'Successfully updated $LOWER_NAME$',
);
return $LANG;
?>

View File

@ -0,0 +1,31 @@
<?php
namespace $NAMESPACE$;
use $EVENTNAME$;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class $CLASS$
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param \$EVENTNAME$ $event
* @return void
*/
public function handle(\$EVENTNAME$ $event)
{
//
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace $NAMESPACE$;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class $CLASS$ extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('view.name');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace $NAMESPACE$;
use Closure;
use Illuminate\Http\Request;
class $CLASS$
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
return $next($request);
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $CLASS$ extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('$TABLE$', function (Blueprint $table) {
$FIELDS_UP$
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('$TABLE$', function (Blueprint $table) {
$FIELDS_DOWN$
});
}
}

View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $CLASS$ extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create(strtolower('$TABLE$'), function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->index();
$table->unsignedInteger('account_id')->index();
$table->unsignedInteger('client_id')->index()->nullable();
$FIELDS$
$table->timestamps();
$table->softDeletes();
$table->boolean('is_deleted')->default(false);
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique( ['account_id', 'public_id'] );
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists(strtolower('$TABLE$'));
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $CLASS$ extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('$TABLE$', function (Blueprint $table) {
$FIELDS_UP$
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('$TABLE$', function (Blueprint $table) {
$FIELDS_DOWN$
});
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $CLASS$ extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('$TABLE$');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::create('$TABLE$', function (Blueprint $table) {
$table->increments('id');
$FIELDS$
$table->timestamps();
});
}
}

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $CLASS$ extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace $NAMESPACE$;
use App\Models\EntityModel;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes;
class $CLASS$ extends EntityModel
{
use PresentableTrait;
use SoftDeletes;
/**
* @var string
*/
protected $presenter = 'Modules\$CLASS$\Presenters\$CLASS$Presenter';
/**
* @var string
*/
protected $fillable = $FILLABLE$;
/**
* @var string
*/
protected $table = '$LOWER_NAME$';
public function getEntityType()
{
return '$LOWER_NAME$';
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace $NAMESPACE$;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class $CLASS$ extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', 'https://laravel.com')
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace $NAMESPACE$;
use App\Policies\EntityPolicy;
class $STUDLY_NAME$Policy extends EntityPolicy
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace $NAMESPACE$;
use App\Ninja\Presenters\EntityPresenter;
class $STUDLY_NAME$Presenter extends EntityPresenter
{
}

View File

@ -0,0 +1,44 @@
<?php
namespace $NAMESPACE$;
use App\Providers\AuthServiceProvider;
class $CLASS$ extends AuthServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
\Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$::class => \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class,
];
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace $NAMESPACE$;
use DB;
use Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$;
use App\Ninja\Repositories\BaseRepository;
//use App\Events\$STUDLY_NAME$WasCreated;
//use App\Events\$STUDLY_NAME$WasUpdated;
class $STUDLY_NAME$Repository extends BaseRepository
{
public function getClassName()
{
return 'Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$';
}
public function all()
{
return $STUDLY_NAME$::scope()
->orderBy('created_at', 'desc')
->withTrashed();
}
public function find($filter = null, $userId = false)
{
$query = DB::table('$LOWER_NAME$')
->where('$LOWER_NAME$.account_id', '=', \Auth::user()->account_id)
->select(
$DATABASE_FIELDS$
'$LOWER_NAME$.public_id',
'$LOWER_NAME$.deleted_at',
'$LOWER_NAME$.created_at',
'$LOWER_NAME$.is_deleted',
'$LOWER_NAME$.user_id'
);
$this->applyFilters($query, '$LOWER_NAME$');
if ($userId) {
$query->where('clients.user_id', '=', $userId);
}
/*
if ($filter) {
$query->where();
}
*/
return $query;
}
public function save($data, $$LOWER_NAME$ = null)
{
$entity = $$LOWER_NAME$ ?: $STUDLY_NAME$::createNew();
$entity->fill($data);
$entity->save();
/*
if (!$publicId || $publicId == '-1') {
event(new ClientWasCreated($client));
} else {
event(new ClientWasUpdated($client));
}
*/
return $entity;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace $NAMESPACE$;
use App\Http\Requests\EntityRequest;
class $CLASS$Request extends EntityRequest
{
protected $entityType = '$LOWER_NAME$';
}

View File

@ -0,0 +1,39 @@
<?php
namespace $MODULE_NAMESPACE$\$MODULE$\Providers;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class $NAME$ extends ServiceProvider
{
/**
* The root namespace to assume when generating URLs to actions.
*
* @var string
*/
protected $rootUrlNamespace = '$MODULE_NAMESPACE$\$MODULE$\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*
* @param Router $router
* @return void
*/
public function before(Router $router)
{
//
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map(Router $router)
{
// require __DIR__ . '/../Http/routes.php';
}
}

View File

@ -0,0 +1,13 @@
<?php
Route::group(['middleware' => 'auth', 'namespace' => '$MODULE_NAMESPACE$\$STUDLY_NAME$\Http\Controllers'], function()
{
Route::resource('$LOWER_NAME$', '$STUDLY_NAME$Controller');
Route::post('$LOWER_NAME$/bulk', '$STUDLY_NAME$Controller@bulk');
Route::get('api/$LOWER_NAME$', '$STUDLY_NAME$Controller@datatable');
});
Route::group(['middleware' => 'api', 'namespace' => '$MODULE_NAMESPACE$\$STUDLY_NAME$\Http\ApiControllers', 'prefix' => 'api/v1'], function()
{
Route::resource('$LOWER_NAME$', '$STUDLY_NAME$ApiController');
});

View File

@ -0,0 +1,5 @@
<?php
return [
'name' => '$STUDLY_NAME$'
];

View File

@ -0,0 +1,110 @@
<?php
namespace $NAMESPACE$;
use App\Providers\AuthServiceProvider;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
class $CLASS$ extends AuthServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
\Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$::class => \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class,
];
/**
* Boot the application events.
*
* @return void
*/
public function boot(GateContract $gate)
{
parent::boot($gate);
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
/**
* Register config.
*
* @return void
*/
protected function registerConfig()
{
$this->publishes([
__DIR__.'/../$PATH_CONFIG$/config.php' => config_path('$LOWER_NAME$.php'),
]);
$this->mergeConfigFrom(
__DIR__.'/../$PATH_CONFIG$/config.php', '$LOWER_NAME$'
);
}
/**
* Register views.
*
* @return void
*/
public function registerViews()
{
$viewPath = base_path('resources/views/modules/$LOWER_NAME$');
$sourcePath = __DIR__.'/../$PATH_VIEWS$';
$this->publishes([
$sourcePath => $viewPath
]);
$this->loadViewsFrom(array_merge(array_map(function ($path) {
return $path . '/modules/$LOWER_NAME$';
}, \Config::get('view.paths')), [$sourcePath]), '$LOWER_NAME$');
}
/**
* Register translations.
*
* @return void
*/
public function registerTranslations()
{
$langPath = base_path('resources/lang/modules/$LOWER_NAME$');
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, '$LOWER_NAME$');
} else {
$this->loadTranslationsFrom(__DIR__ .'/../$PATH_LANG$', '$LOWER_NAME$');
}
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace $NAMESPACE$\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class $NAME$ extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
// $this->call("OthersTableSeeder");
}
}

View File

@ -0,0 +1,15 @@
<?php
/*
|--------------------------------------------------------------------------
| Register Namespaces And Routes
|--------------------------------------------------------------------------
|
| When a module starting, this file will executed automatically. This helps
| to register some namespaces like translator or view. Also this file
| will load the routes file for each module. You may also modify
| this file as you want.
|
*/
require __DIR__ . '/Http/routes.php';

View File

@ -0,0 +1,35 @@
<?php
namespace $NAMESPACE$;
use Modules\$STUDLY_NAME$\Models\$STUDLY_NAME$;
use App\Ninja\Transformers\EntityTransformer;
/**
* @SWG\Definition(definition="$STUDLY_NAME$", @SWG\Xml(name="$STUDLY_NAME$"))
*/
class $STUDLY_NAME$Transformer extends EntityTransformer
{
/**
* @SWG\Property(property="id", type="integer", example=1, readOnly=true)
* @SWG\Property(property="user_id", type="integer", example=1)
* @SWG\Property(property="account_key", type="string", example="123456")
* @SWG\Property(property="updated_at", type="timestamp", example="")
* @SWG\Property(property="archived_at", type="timestamp", example="1451160233")
*/
/**
* @param $STUDLY_NAME$ $$LOWER_NAME$
* @return array
*/
public function transform($STUDLY_NAME$ $$LOWER_NAME$)
{
return array_merge($this->getDefaults($$LOWER_NAME$), [
$TRANSFORMER_FIELDS$
'id' => (int) $$LOWER_NAME$->public_id,
'updated_at' => $this->getTimestamp($$LOWER_NAME$->updated_at),
'archived_at' => $this->getTimestamp($$LOWER_NAME$->deleted_at),
]);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace $NAMESPACE$;
class Update$CLASS$Request extends $CLASS$Request
{
/**
* 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 [
];
}
}

View File

@ -0,0 +1,57 @@
@extends('header')
@section('content')
{!! Former::open($url)
->addClass('col-md-10 col-md-offset-1 warn-on-exit')
->method($method)
->rules([]) !!}
@if ($$LOWER_NAME$)
{!! Former::populate($$LOWER_NAME$) !!}
<div style="display:none">
{!! Former::text('public_id') !!}
</div>
@endif
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-body">
$FORM_FIELDS$
</div>
</div>
</div>
</div>
<center class="buttons">
{!! Button::normal(trans('texts.cancel'))
->large()
->asLinkTo(URL::to('/$LOWER_NAME$'))
->appendIcon(Icon::create('remove-circle')) !!}
{!! Button::success(trans('texts.save'))
->submit()
->large()
->appendIcon(Icon::create('floppy-disk')) !!}
</center>
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$(".warn-on-exit input").first().focus();
})
</script>
@stop

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Module $STUDLY_NAME$</title>
</head>
<body>
@yield('content')
</body>
</html>

View File

@ -23,6 +23,8 @@ class Kernel extends ConsoleKernel
'App\Console\Commands\SendReminders',
'App\Console\Commands\GenerateResources',
'App\Console\Commands\TestOFX',
'App\Console\Commands\MakeModule',
'App\Console\Commands\MakeClass',
];
/**

View File

@ -75,7 +75,7 @@ class AccountApiController extends BaseAPIController
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
$transformer = new AccountTransformer(null, $request->serializer);
$account->load($transformer->getDefaultIncludes());
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client']));
$account = $this->createItem($account, $transformer, 'account');
return $this->response($account);
@ -192,7 +192,7 @@ class AccountApiController extends BaseAPIController
$provider = $request->input('provider');
try {
$user = Socialite::driver($provider)->userFromToken($token);
$user = Socialite::driver($provider)->stateless()->userFromToken($token);
} catch (Exception $exception) {
return $this->errorResponse(['message' => $exception->getMessage()], 401);
}

View File

@ -212,7 +212,7 @@ class AccountController extends BaseController
$days_total = $planDetails['paid']->diff($planDetails['expires'])->days;
$percent_used = $days_used / $days_total;
$credit = $planDetails['plan_price'] * (1 - $percent_used);
$credit = floatval($company->payment->amount) * (1 - $percent_used);
}
if ($newPlan['price'] > $credit) {
@ -224,7 +224,9 @@ class AccountController extends BaseController
}
} else {
if ($plan != PLAN_FREE) {
if ($plan == PLAN_FREE) {
$company->discount = 0;
} else {
$company->plan_term = $term;
$company->plan_price = $newPlan['price'];
$company->num_users = $numUsers;
@ -244,9 +246,26 @@ class AccountController extends BaseController
* @param $visible
* @return mixed
*/
public function setTrashVisible($entityType, $visible)
public function setEntityFilter($entityType, $filter = '')
{
Session::put("show_trash:{$entityType}", $visible == 'true');
if ($filter == 'true') {
$filter = '';
}
// separate state and status filters
$filters = explode(',', $filter);
$stateFilter = [];
$statusFilter = [];
foreach ($filters as $filter) {
if (in_array($filter, \App\Models\EntityModel::$statuses)) {
$stateFilter[] = $filter;
} else {
$statusFilter[] = $filter;
}
}
Session::put("entity_state_filter:{$entityType}", join(',', $stateFilter));
Session::put("entity_status_filter:{$entityType}", join(',', $statusFilter));
return RESULT_SUCCESS;
}
@ -720,9 +739,27 @@ class AccountController extends BaseController
*/
private function saveAccountManagement()
{
$account = Auth::user()->account;
$user = Auth::user();
$account = $user->account;
$modules = Input::get('modules');
$user->force_pdfjs = Input::get('force_pdfjs') ? true : false;
$user->save();
$account->live_preview = Input::get('live_preview') ? true : false;
// Automatically disable live preview when using a large font
$fonts = Cache::get('fonts')->filter(function($font) use ($account) {
if ($font->google_font) {
return false;
}
return $font->id == $account->header_font_id || $font->id == $account->body_font_id;
});
if ($account->live_preview && count($fonts)) {
$account->live_preview = false;
Session::flash('warning', trans('texts.live_preview_disabled'));
}
$account->enabled_modules = $modules ? array_sum($modules) : 0;
$account->save();
@ -880,27 +917,29 @@ class AccountController extends BaseController
if (Input::get('custom_link') == 'subdomain') {
$subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH));
$exclude = [
'www',
'app',
'mail',
'admin',
'blog',
'user',
'contact',
'payment',
'payments',
'billing',
'invoice',
'business',
'owner',
'info',
'ninja',
'docs',
'doc',
'documents'
];
$rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude);
if (Utils::isNinja()) {
$exclude = [
'www',
'app',
'mail',
'admin',
'blog',
'user',
'contact',
'payment',
'payments',
'billing',
'invoice',
'business',
'owner',
'info',
'ninja',
'docs',
'doc',
'documents'
];
$rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude);
}
} else {
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH));
$iframeURL = rtrim($iframeURL, '/');
@ -1035,19 +1074,6 @@ class AccountController extends BaseController
$account->invoice_design_id = Input::get('invoice_design_id');
$account->font_size = intval(Input::get('font_size'));
$account->page_size = Input::get('page_size');
$account->live_preview = Input::get('live_preview') ? true : false;
// Automatically disable live preview when using a large font
$fonts = Cache::get('fonts')->filter(function($font) use ($account) {
if ($font->google_font) {
return false;
}
return $font->id == $account->header_font_id || $font->id == $account->body_font_id;
});
if ($account->live_preview && count($fonts)) {
$account->live_preview = false;
Session::flash('warning', trans('texts.live_preview_disabled'));
}
$labels = [];
foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due', 'subtotal', 'paid_to_date', 'discount', 'tax'] as $field) {

View File

@ -411,6 +411,7 @@ class AccountGatewayController extends BaseController
'description' => trans('texts.wepay_account_description'),
'theme_object' => json_decode(WEPAY_THEME),
'callback_uri' => $accountGateway->getWebhookUrl(),
'rbits' => $account->present()->rBits,
];
if (WEPAY_ENABLE_CANADA) {

View File

@ -60,10 +60,7 @@ class AppController extends BaseController
$database = Input::get('database');
$dbType = 'mysql'; // $database['default'];
$database['connections'] = [$dbType => $database['type']];
$mail = Input::get('mail');
$email = $mail['username'];
$mail['from']['address'] = $email;
if ($test == 'mail') {
return self::testMail($mail);
@ -97,6 +94,7 @@ class AppController extends BaseController
$_ENV['MAIL_HOST'] = $mail['host'];
$_ENV['MAIL_USERNAME'] = $mail['username'];
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
$_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address';
$_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain'];
@ -173,8 +171,8 @@ class AppController extends BaseController
$_ENV['MAIL_HOST'] = $mail['host'];
$_ENV['MAIL_USERNAME'] = $mail['username'];
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
$_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_ENV['MAIL_FROM_ADDRESS'] = $mail['username'];
$_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain'];
$_ENV['MAILGUN_SECRET'] = $mail['mailgun_secret'];
}
@ -218,7 +216,7 @@ class AppController extends BaseController
private function testMail($mail)
{
$email = $mail['username'];
$email = $mail['from']['address'];
$fromName = $mail['from']['name'];
foreach ($mail as $key => $val) {

View File

@ -1,6 +1,7 @@
<?php namespace App\Http\Controllers;
use Utils;
use Request;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@ -27,14 +28,20 @@ class BaseController extends Controller
if ( ! is_array($ids)) {
$ids = [$ids];
}
$isDatatable = filter_var(request()->datatable, FILTER_VALIDATE_BOOLEAN);
$referer = Request::server('HTTP_REFERER');
$entityTypes = Utils::pluralizeEntityType($entityType);
// when restoring redirect to entity
if ($action == 'restore' && count($ids) == 1) {
return redirect("{$entityTypes}/" . $ids[0]);
// when viewing from a datatable list
} elseif (strpos($referer, '/clients/')) {
return redirect($referer);
} elseif ($isDatatable || ($action == 'archive' || $action == 'delete')) {
return redirect("{$entityTypes}");
// when viewing individual entity
} elseif (count($ids)) {
return redirect("{$entityTypes}/" . $ids[0]);
} else {

View File

@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers;
use Auth;
use Input;
use Redirect;
use URL;
use Session;
class BlueVineController extends BaseController {
public function signup() {
$user = Auth::user();
$data = array(
'personal_user_full_name' => Input::get( 'name' ),
'business_phone_number' => Input::get( 'phone' ),
'email' => Input::get( 'email' ),
'personal_fico_score' => intval( Input::get( 'fico_score' ) ),
'business_annual_revenue' => intval( Input::get( 'annual_revenue' ) ),
'business_monthly_average_bank_balance' => intval( Input::get( 'average_bank_balance' ) ),
'business_inception_date' => date( 'Y-m-d', strtotime( Input::get( 'business_inception' ) ) ),
'partner_internal_business_id' => 'ninja_account_' . $user->account_id,
);
if ( ! empty( Input::get( 'quote_type_factoring' ) ) ) {
$data['invoice_factoring_offer'] = true;
$data['desired_credit_line'] = intval( Input::get( 'desired_credit_limit' )['invoice_factoring'] );
}
if ( ! empty( Input::get( 'quote_type_loc' ) ) ) {
$data['line_of_credit_offer'] = true;
$data['desired_credit_line_for_loc'] = intval( Input::get( 'desired_credit_limit' )['line_of_credit'] );
}
$api_client = new \GuzzleHttp\Client();
try {
$response = $api_client->request( 'POST',
'https://app.bluevine.com/api/v1/user/register_external?' . http_build_query( array(
'external_register_token' => env( 'BLUEVINE_PARTNER_TOKEN' ),
'c' => env( 'BLUEVINE_PARTNER_UNIQUE_ID' ),
'signup_parent_url' => URL::to( '/bluevine/completed' ),
) ), array(
'json' => $data
)
);
} catch ( \GuzzleHttp\Exception\RequestException $ex ) {
if ( $ex->getCode() == 403 ) {
$response_body = $ex->getResponse()->getBody( true );
$response_data = json_decode( $response_body );
return response()->json( [
'error' => true,
'message' => $response_data->reason
] );
} else {
return response()->json( [
'error' => true
] );
}
}
$user->account->bluevine_status = 'signed_up';
$user->account->save();
$quote_data = json_decode( $response->getBody() );
return response()->json( $quote_data );
}
public function hideMessage() {
$user = Auth::user();
if ( $user ) {
$user->account->bluevine_status = 'ignored';
$user->account->save();
}
return 'success';
}
public function handleCompleted() {
Session::flash( 'message', trans( 'texts.bluevine_completed' ) );
return Redirect::to( '/dashboard' );
}
}

View File

@ -19,6 +19,7 @@ use App\Services\ClientService;
use App\Http\Requests\ClientRequest;
use App\Http\Requests\CreateClientRequest;
use App\Http\Requests\UpdateClientRequest;
use App\Ninja\Datatables\ClientDatatable;
class ClientController extends BaseController
{
@ -41,20 +42,11 @@ class ClientController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_CLIENT,
'datatable' => new ClientDatatable(),
'title' => trans('texts.clients'),
'sortCol' => '4',
'columns' => Utils::trans([
'checkbox',
'client',
'contact',
'email',
'date_created',
'last_login',
'balance',
''
]),
'statuses' => Client::getStatuses(),
]);
}
@ -123,9 +115,9 @@ class ClientController extends BaseController
'client' => $client,
'credit' => $client->getTotalCredit(),
'title' => trans('texts.view_client'),
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
'hasQuotes' => Invoice::scope()->invoiceType(INVOICE_TYPE_QUOTE)->whereClientId($client->id)->count() > 0,
'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0,
'hasRecurringInvoices' => Invoice::scope()->recurring()->withArchived()->whereClientId($client->id)->count() > 0,
'hasQuotes' => Invoice::scope()->quotes()->withArchived()->whereClientId($client->id)->count() > 0,
'hasTasks' => Task::scope()->withArchived()->whereClientId($client->id)->count() > 0,
'gatewayLink' => $token ? $token->gatewayLink() : false,
'gatewayName' => $token ? $token->gatewayName() : false,
];

View File

@ -203,7 +203,7 @@ class ClientPortalController extends BaseController
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return RESULT_FAILURE;
}
$invitation->signature_base64 = Input::get('signature');
$invitation->signature_date = date_create();
$invitation->save();

View File

@ -7,10 +7,13 @@ use URL;
use Utils;
use View;
use App\Models\Client;
use App\Models\Credit;
use App\Services\CreditService;
use App\Ninja\Repositories\CreditRepository;
use App\Http\Requests\UpdateCreditRequest;
use App\Http\Requests\CreateCreditRequest;
use App\Http\Requests\CreditRequest;
use App\Ninja\Datatables\CreditDatatable;
class CreditController extends BaseController
{
@ -33,19 +36,10 @@ class CreditController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_CREDIT,
'datatable' => new CreditDatatable(),
'title' => trans('texts.credits'),
'sortCol' => '4',
'columns' => Utils::trans([
'checkbox',
'client',
'credit_amount',
'credit_balance',
'credit_date',
'private_notes',
''
]),
]);
}
@ -62,40 +56,53 @@ class CreditController extends BaseController
'method' => 'POST',
'url' => 'credits',
'title' => trans('texts.new_credit'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
];
return View::make('credits.edit', $data);
}
/*
public function edit($publicId)
{
$credit = Credit::scope($publicId)->firstOrFail();
$credit = Credit::withTrashed()->scope($publicId)->firstOrFail();
$this->authorize('edit', $credit);
$credit->credit_date = Utils::fromSqlDate($credit->credit_date);
$data = array(
'client' => null,
'client' => $credit->client,
'clientPublicId' => $credit->client->public_id,
'credit' => $credit,
'method' => 'PUT',
'url' => 'credits/'.$publicId,
'title' => 'Edit Credit',
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
'clients' => null,
);
return View::make('credit.edit', $data);
return View::make('credits.edit', $data);
}
public function update(UpdateCreditRequest $request)
{
$credit = $request->entity();
return $this->save($credit);
}
*/
public function store(CreateCreditRequest $request)
{
$credit = $this->creditRepo->save($request->input());
return $this->save();
}
Session::flash('message', trans('texts.created_credit'));
private function save($credit = null)
{
$credit = $this->creditService->save(Input::all(), $credit);
return redirect()->to($credit->client->getRoute());
$message = $credit->wasRecentlyCreated ? trans('texts.created_credit') : trans('texts.updated_credit');
Session::flash('message', $message);
return redirect()->to("clients/{$credit->client->public_id}#credits");
}
public function bulk()
@ -109,6 +116,6 @@ class CreditController extends BaseController
Session::flash('message', $message);
}
return Redirect::to('credits');
return $this->returnBulk(ENTITY_CREDIT, $action, $ids);
}
}

View File

@ -35,7 +35,7 @@ class DashboardController extends BaseController
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
$paidToDate = $dashboardRepo->paidToDate($account, $userId, $viewAll);
$averageInvoice = $dashboardRepo->averages($account, $userId, $viewAll);
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
$balances = $dashboardRepo->balances($accountId, $userId, $viewAll);
$activities = $dashboardRepo->activities($accountId, $userId, $viewAll);
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
$upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll);
@ -43,6 +43,10 @@ class DashboardController extends BaseController
$expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll);
$tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll);
$showBlueVinePromo = ! $account->bluevine_status
&& env('BLUEVINE_PARTNER_UNIQUE_ID')
&& $account->created_at <= date( 'Y-m-d', strtotime( '-1 month' ));
// check if the account has quotes
$hasQuotes = false;
foreach ([$upcoming, $pastDue] as $data) {
@ -75,6 +79,7 @@ class DashboardController extends BaseController
$data = [
'account' => $user->account,
'user' => $user,
'paidToDate' => $paidToDate,
'balances' => $balances,
'averageInvoice' => $averageInvoice,
@ -90,8 +95,29 @@ class DashboardController extends BaseController
'currencies' => $currencies,
'expenses' => $expenses,
'tasks' => $tasks,
'showBlueVinePromo' => $showBlueVinePromo,
];
if ($showBlueVinePromo) {
$usdLast12Months = 0;
$pastYear = date( 'Y-m-d', strtotime( '-1 year' ));
$paidLast12Months = $dashboardRepo->paidToDate( $account, $userId, $viewAll, $pastYear );
foreach ( $paidLast12Months as $item ) {
if ( $item->currency_id == null ) {
$currency = $user->account->currency_id ?: DEFAULT_CURRENCY;
} else {
$currency = $item->currency_id;
}
if ( $currency == CURRENCY_DOLLAR ) {
$usdLast12Months += $item->value;
}
}
$data['usdLast12Months'] = $usdLast12Months;
}
return View::make('dashboard', $data);
}

View File

@ -6,6 +6,7 @@ use Input;
use Session;
use App\Services\ExpenseCategoryService;
use App\Ninja\Repositories\ExpenseCategoryRepository;
use App\Ninja\Datatables\ExpenseCategoryDatatable;
use App\Http\Requests\ExpenseCategoryRequest;
use App\Http\Requests\CreateExpenseCategoryRequest;
use App\Http\Requests\UpdateExpenseCategoryRequest;
@ -29,15 +30,10 @@ class ExpenseCategoryController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_EXPENSE_CATEGORY,
'datatable' => new ExpenseCategoryDatatable(),
'title' => trans('texts.expense_categories'),
'sortCol' => '1',
'columns' => Utils::trans([
'checkbox',
'name',
''
]),
]);
}
@ -78,7 +74,7 @@ class ExpenseCategoryController extends BaseController
Session::flash('message', trans('texts.created_expense_category'));
return redirect()->to($category->getRoute());
return redirect()->to('/expense_categories');
}
public function update(UpdateExpenseCategoryRequest $request)

View File

@ -19,6 +19,7 @@ use App\Ninja\Repositories\ExpenseRepository;
use App\Http\Requests\ExpenseRequest;
use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\UpdateExpenseRequest;
use App\Ninja\Datatables\ExpenseDatatable;
class ExpenseController extends BaseController
{
@ -48,21 +49,10 @@ class ExpenseController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_EXPENSE,
'datatable' => new ExpenseDatatable(),
'title' => trans('texts.expenses'),
'sortCol' => '3',
'columns' => Utils::trans([
'checkbox',
'vendor',
'client',
'expense_date',
'amount',
'category',
'public_notes',
'status',
''
]),
]);
}
@ -262,7 +252,7 @@ class ExpenseController extends BaseController
'countries' => Cache::get('countries'),
'customLabel1' => Auth::user()->account->custom_vendor_label1,
'customLabel2' => Auth::user()->account->custom_vendor_label2,
'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->orderBy('name')->get(),
'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
];
}

View File

@ -12,7 +12,9 @@ use App\Models\Contact;
use App\Models\Credit;
use App\Models\Task;
use App\Models\Invoice;
use App\Models\Product;
use App\Models\Payment;
use App\Models\Expense;
use App\Models\Vendor;
use App\Models\VendorContact;
@ -212,6 +214,19 @@ class ExportController extends BaseController
->get();
}
if ($request->input('include') === 'all' || $request->input('expenses')) {
$data['expenses'] = Expense::scope()
->with('user', 'vendor.vendor_contacts', 'client.contacts', 'expense_category')
->withArchived()
->get();
}
if ($request->input('include') === 'all' || $request->input('products')) {
$data['products'] = Product::scope()
->withArchived()
->get();
}
if ($request->input('include') === 'all' || $request->input('vendors')) {
$data['vendors'] = Vendor::scope()
->with('user', 'vendor_contacts', 'country')

View File

@ -38,6 +38,6 @@ class IntegrationController extends Controller
return Response::json('Failed to create subscription', 500);
}
return Response::json('{"id":'.$subscription->id.'}', 201);
return Response::json(['id' => $subscription->id], 201);
}
}

View File

@ -22,6 +22,7 @@ use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Ninja\Datatables\InvoiceDatatable;
use App\Services\InvoiceService;
use App\Services\PaymentService;
use App\Services\RecurringInvoiceService;
@ -57,21 +58,11 @@ class InvoiceController extends BaseController
$data = [
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'sortCol' => '3',
'columns' => Utils::trans([
'checkbox',
'invoice_number',
'client',
'invoice_date',
'invoice_total',
'balance_due',
'due_date',
'status',
''
]),
'statuses' => Invoice::getStatuses(),
'datatable' => new InvoiceDatatable(),
];
return response()->view('list', $data);
return response()->view('list_wrapper', $data);
}
public function getDatatable($clientPublicId = null)
@ -108,6 +99,7 @@ class InvoiceController extends BaseController
if ($clone) {
$invoice->id = $invoice->public_id = null;
$invoice->is_public = false;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->balance = $invoice->amount;
$invoice->invoice_status_id = 0;
@ -138,10 +130,6 @@ class InvoiceController extends BaseController
DropdownButton::DIVIDER
];
if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring) {
$actions[] = ['url' => 'javascript:onMarkClick()', 'label' => trans('texts.mark_sent')];
}
if ($entityType == ENTITY_QUOTE) {
if ($invoice->quote_invoice_id) {
$actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')];
@ -153,7 +141,8 @@ class InvoiceController extends BaseController
$actions[] = ['url' => URL::to("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')];
}
if (!$invoice->is_recurring && $invoice->balance > 0) {
if (!$invoice->is_recurring && $invoice->balance > 0 && $invoice->is_public) {
$actions[] = ['url' => 'javascript:submitBulkAction("markPaid")', 'label' => trans('texts.mark_paid')];
$actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')];
}
@ -203,7 +192,7 @@ class InvoiceController extends BaseController
}
// Set the invitation data on the client's contacts
if (!$clone) {
if ($invoice->is_public && ! $clone) {
$clients = $data['clients'];
foreach ($clients as $client) {
if ($client->id != $invoice->client->id) {
@ -523,7 +512,13 @@ class InvoiceController extends BaseController
$count = $this->invoiceService->bulk($ids, $action);
if ($count > 0) {
$key = $action == 'markSent' ? "updated_{$entityType}" : "{$action}d_{$entityType}";
if ($action == 'markSent') {
$key = 'marked_sent_invoice';
} elseif ($action == 'markPaid') {
$key = 'created_payment';
} else {
$key = "{$action}d_{$entityType}";
}
$message = Utils::pluralize($key, $count);
Session::flash('message', $message);
}

View File

@ -117,7 +117,8 @@ class OnlinePaymentController extends BaseController
} else {
Session::flash('message', trans('texts.applied_payment'));
}
return redirect()->to('view/' . $invitation->invitation_key);
return $this->completePurchase($invitation);
} catch (Exception $exception) {
return $this->error($paymentDriver, $exception, true);
}
@ -152,12 +153,27 @@ class OnlinePaymentController extends BaseController
if ($paymentDriver->completeOffsitePurchase(Input::all())) {
Session::flash('message', trans('texts.applied_payment'));
}
return redirect()->to($invitation->getLink());
return $this->completePurchase($invitation, true);
} catch (Exception $exception) {
return $this->error($paymentDriver, $exception);
}
}
private function completePurchase($invitation, $isOffsite = false)
{
if ($redirectUrl = session('redirect_url:' . $invitation->invitation_key)) {
$separator = strpos($redirectUrl, '?') === false ? '?' : '&';
return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $invitation->invoice->public_id);
} else {
// Allow redirecting to iFrame for offsite payments
if ($isOffsite) {
return redirect()->to($invitation->getLink());
} else {
return redirect()->to('view/' . $invitation->invitation_key);
}
}
}
/**
* @param $paymentDriver
* @param $exception
@ -253,17 +269,18 @@ class OnlinePaymentController extends BaseController
}
$account = Account::whereAccountKey(Input::get('account_key'))->first();
$redirectUrl = Input::get('redirect_url', URL::previous());
$redirectUrl = Input::get('redirect_url');
$failureUrl = URL::previous();
if ( ! $account || ! $account->enable_buy_now_buttons || ! $account->hasFeature(FEATURE_BUY_NOW_BUTTONS)) {
return redirect()->to("{$redirectUrl}/?error=invalid account");
return redirect()->to("{$failureUrl}/?error=invalid account");
}
Auth::onceUsingId($account->users[0]->id);
$product = Product::scope(Input::get('product_id'))->first();
if ( ! $product) {
return redirect()->to("{$redirectUrl}/?error=invalid product");
return redirect()->to("{$failureUrl}/?error=invalid product");
}
$rules = [
@ -274,7 +291,7 @@ class OnlinePaymentController extends BaseController
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return redirect()->to("{$redirectUrl}/?error=" . $validator->errors()->first());
return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first());
}
$data = [
@ -300,6 +317,10 @@ class OnlinePaymentController extends BaseController
$invitation = $invoice->invitations[0];
$link = $invitation->getLink();
if ($redirectUrl) {
session(['redirect_url:' . $invitation->invitation_key => $redirectUrl]);
}
if ($gatewayTypeAlias) {
return redirect()->to($invitation->getLink('payment') . "/{$gatewayTypeAlias}");
} else {

View File

@ -13,6 +13,7 @@ use App\Services\PaymentService;
use App\Http\Requests\PaymentRequest;
use App\Http\Requests\CreatePaymentRequest;
use App\Http\Requests\UpdatePaymentRequest;
use App\Ninja\Datatables\PaymentDatatable;
class PaymentController extends BaseController
{
@ -59,22 +60,10 @@ class PaymentController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_PAYMENT,
'datatable' => new PaymentDatatable(),
'title' => trans('texts.payments'),
'sortCol' => '7',
'columns' => Utils::trans([
'checkbox',
'invoice',
'client',
'transaction_reference',
'method',
'source',
'payment_amount',
'payment_date',
'status',
''
]),
]);
}
@ -94,8 +83,8 @@ class PaymentController extends BaseController
public function create(PaymentRequest $request)
{
$invoices = Invoice::scope()
->invoiceType(INVOICE_TYPE_STANDARD)
->where('is_recurring', '=', false)
->invoices()
->whereIsPublic(true)
->where('invoices.balance', '>', 0)
->with('client', 'invoice_status')
->orderBy('invoice_number')->get();
@ -139,8 +128,11 @@ class PaymentController extends BaseController
$data = [
'client' => null,
'invoice' => null,
'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false)
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'invoices' => Invoice::scope()
->invoices()
->whereIsPublic(true)
->with('client', 'invoice_status')
->orderBy('invoice_number')->get(),
'payment' => $payment,
'entity' => $payment,
'method' => 'PUT',
@ -172,7 +164,7 @@ class PaymentController extends BaseController
Session::flash('message', trans('texts.created_payment'));
}
return redirect()->to($payment->client->getRoute());
return redirect()->to($payment->client->getRoute() . '#payments');
}
/**
@ -203,6 +195,6 @@ class PaymentController extends BaseController
Session::flash('message', $message);
}
return redirect()->to('payments');
return $this->returnBulk(ENTITY_PAYMENT, $action, $ids);
}
}

View File

@ -10,6 +10,7 @@ use Redirect;
use App\Models\Product;
use App\Models\TaxRate;
use App\Services\ProductService;
use App\Ninja\Datatables\ProductDatatable;
/**
* Class ProductController
@ -38,23 +39,11 @@ class ProductController extends BaseController
*/
public function index()
{
$columns = [
'checkbox',
'product',
'description',
'unit_cost'
];
if (Auth::user()->account->invoice_item_taxes) {
$columns[] = 'tax_rate';
}
$columns[] = 'action';
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_PRODUCT,
'datatable' => new ProductDatatable(),
'title' => trans('texts.products'),
'sortCol' => '4',
'columns' => Utils::trans($columns),
'statuses' => Product::getStatuses(),
]);
}
@ -166,7 +155,8 @@ class ProductController extends BaseController
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->productService->bulk($ids, $action);
Session::flash('message', trans('texts.archived_product'));
$message = Utils::pluralize($action.'d_product', $count);
Session::flash('message', $message);
return $this->returnBulk(ENTITY_PRODUCT, $action, $ids);
}

View File

@ -0,0 +1,113 @@
<?php namespace App\Http\Controllers;
use Auth;
use View;
use Utils;
use Input;
use Session;
use App\Models\Client;
use App\Services\ProjectService;
use App\Ninja\Repositories\ProjectRepository;
use App\Ninja\Datatables\ProjectDatatable;
use App\Http\Requests\ProjectRequest;
use App\Http\Requests\CreateProjectRequest;
use App\Http\Requests\UpdateProjectRequest;
class ProjectController extends BaseController
{
protected $projectRepo;
protected $projectService;
protected $entityType = ENTITY_PROJECT;
public function __construct(ProjectRepository $projectRepo, ProjectService $projectService)
{
$this->projectRepo = $projectRepo;
$this->projectService = $projectService;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list_wrapper', [
'entityType' => ENTITY_PROJECT,
'datatable' => new ProjectDatatable(),
'title' => trans('texts.projects'),
]);
}
public function getDatatable($expensePublicId = null)
{
$search = Input::get('sSearch');
$userId = Auth::user()->filterId();
return $this->projectService->getDatatable($search, $userId);
}
public function create(ProjectRequest $request)
{
$data = [
'project' => null,
'method' => 'POST',
'url' => 'projects',
'title' => trans('texts.new_project'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $request->client_id,
];
return View::make('projects.edit', $data);
}
public function edit(ProjectRequest $request)
{
$project = $request->entity();
$data = [
'project' => $project,
'method' => 'PUT',
'url' => 'projects/' . $project->public_id,
'title' => trans('texts.edit_project'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $project->client ? $project->client->public_id : null,
];
return View::make('projects.edit', $data);
}
public function store(CreateProjectRequest $request)
{
$project = $this->projectService->save($request->input());
Session::flash('message', trans('texts.created_project'));
return redirect()->to($project->getRoute());
}
public function update(UpdateProjectRequest $request)
{
$project = $this->projectService->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_project'));
return redirect()->to($project->getRoute());
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->projectService->bulk($ids, $action);
if ($count > 0) {
$field = $count == 1 ? "{$action}d_project" : "{$action}d_projects";
$message = trans("texts.$field", ['count' => $count]);
Session::flash('message', $message);
}
return redirect()->to('/projects');
}
}

View File

@ -20,6 +20,7 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Services\InvoiceService;
use App\Http\Requests\InvoiceRequest;
use App\Ninja\Datatables\InvoiceDatatable;
class QuoteController extends BaseController
{
@ -41,23 +42,16 @@ class QuoteController extends BaseController
public function index()
{
$datatable = new InvoiceDatatable();
$datatable->entityType = ENTITY_QUOTE;
$data = [
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'sortCol' => '3',
'columns' => Utils::trans([
'checkbox',
'quote_number',
'client',
'quote_date',
'quote_total',
'valid_until',
'status',
'action'
]),
'datatable' => $datatable,
];
return response()->view('list', $data);
return response()->view('list_wrapper', $data);
}
public function getDatatable($clientPublicId = null)

View File

@ -2,6 +2,7 @@
use Utils;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Datatables\RecurringInvoiceDatatable;
/**
* Class RecurringInvoiceController
@ -32,18 +33,10 @@ class RecurringInvoiceController extends BaseController
$data = [
'title' => trans('texts.recurring_invoices'),
'entityType' => ENTITY_RECURRING_INVOICE,
'columns' => Utils::trans([
'checkbox',
'frequency',
'client',
'start_date',
'end_date',
'invoice_total',
'action'
])
'datatable' => new RecurringInvoiceDatatable(),
];
return response()->view('list', $data);
return response()->view('list_wrapper', $data);
}
}
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Http\Controllers;
use App\Http\Requests\UpdateTaskRequest;
use Auth;
use Response;
use Input;
@ -40,6 +41,7 @@ class TaskApiController extends BaseAPIController
{
$tasks = Task::scope()
->withTrashed()
->with('client', 'invoice', 'project')
->orderBy('created_at', 'desc');
return $this->listResponse($tasks);
@ -84,4 +86,38 @@ class TaskApiController extends BaseAPIController
return $this->response($data);
}
/**
* @SWG\Put(
* path="/task/{task_id}",
* tags={"task"},
* summary="Update a task",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Task")
* ),
* @SWG\Response(
* response=200,
* description="Update task",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Task"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(UpdateTaskRequest $request)
{
$task = $request->entity();
$task = $this->taskRepo->save($task->public_id, \Illuminate\Support\Facades\Input::all());
return $this->itemResponse($task);
}
}

View File

@ -10,12 +10,14 @@ use Session;
use DropdownButton;
use App\Models\Client;
use App\Models\Task;
use App\Models\Project;
use App\Ninja\Repositories\TaskRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Services\TaskService;
use App\Http\Requests\TaskRequest;
use App\Http\Requests\CreateTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
use App\Ninja\Datatables\TaskDatatable;
/**
* Class TaskController
@ -66,19 +68,10 @@ class TaskController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => ENTITY_TASK,
'datatable' => new TaskDatatable(),
'title' => trans('texts.tasks'),
'sortCol' => '2',
'columns' => Utils::trans([
'checkbox',
'client',
'date',
'duration',
'description',
'status',
''
]),
]);
}
@ -128,6 +121,7 @@ class TaskController extends BaseController
$data = [
'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'projectPublicId' => Input::old('project_id') ? Input::old('project_id') : ($request->project_id ?: 0),
'method' => 'POST',
'url' => 'tasks',
'title' => trans('texts.new_task'),
@ -179,6 +173,7 @@ class TaskController extends BaseController
'task' => $task,
'entity' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0,
'projectPublicId' => $task->project ? $task->project->public_id : 0,
'method' => 'PUT',
'url' => 'tasks/'.$task->public_id,
'title' => trans('texts.edit_task'),
@ -214,6 +209,7 @@ class TaskController extends BaseController
return [
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'account' => Auth::user()->account,
'projects' => Project::scope()->with('client.contacts')->orderBy('name')->get(),
];
}

View File

@ -215,7 +215,7 @@ class UserController extends BaseController
Session::flash('message', $message);
}
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
return Redirect::to('users/' . $user->public_id . '/edit');
}
public function sendConfirmation($userPublicId)

View File

@ -15,6 +15,7 @@ use App\Services\VendorService;
use App\Http\Requests\VendorRequest;
use App\Http\Requests\CreateVendorRequest;
use App\Http\Requests\UpdateVendorRequest;
use App\Ninja\Datatables\VendorDatatable;
class VendorController extends BaseController
{
@ -37,19 +38,10 @@ class VendorController extends BaseController
*/
public function index()
{
return View::make('list', [
return View::make('list_wrapper', [
'entityType' => 'vendor',
'datatable' => new VendorDatatable(),
'title' => trans('texts.vendors'),
'sortCol' => '4',
'columns' => Utils::trans([
'checkbox',
'vendor',
'city',
'phone',
'email',
'date_created',
''
]),
]);
}

View File

@ -16,18 +16,6 @@ class VerifyCsrfToken extends BaseVerifier
'complete/*',
'signup/register',
'api/v1/*',
'api/v1/login',
'api/v1/clients/*',
'api/v1/clients',
'api/v1/invoices/*',
'api/v1/invoices',
'api/v1/quotes',
'api/v1/payments',
'api/v1/tasks',
'api/v1/email_invoice',
'api/v1/hooks',
'api/v1/users',
'api/v1/users/*',
'hook/email_opened',
'hook/email_bounced',
'reseller_stats',

View File

@ -2,6 +2,8 @@
use App\Models\Invoice;
class CreatePaymentAPIRequest extends PaymentRequest
{
/**
@ -9,6 +11,9 @@ class CreatePaymentAPIRequest extends PaymentRequest
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_PAYMENT);
@ -30,6 +35,7 @@ class CreatePaymentAPIRequest extends PaymentRequest
$invoice = Invoice::scope($this->invoice_id)
->invoices()
->whereIsPublic(true)
->firstOrFail();
$this->merge([
@ -47,4 +53,7 @@ class CreatePaymentAPIRequest extends PaymentRequest
return $rules;
}
}

View File

@ -24,6 +24,7 @@ class CreatePaymentRequest extends PaymentRequest
$input = $this->input();
$invoice = Invoice::scope($input['invoice'])
->invoices()
->whereIsPublic(true)
->firstOrFail();
$rules = [

View File

@ -0,0 +1,27 @@
<?php namespace App\Http\Requests;
class CreateProjectRequest extends ProjectRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_PROJECT);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => sprintf('required|unique:projects,name,,id,account_id,%s', $this->user()->account_id),
'client_id' => 'required',
];
}
}

View File

@ -3,6 +3,7 @@
use Input;
use Utils;
use App\Libraries\HistoryUtils;
use App\Models\EntityModel;
class EntityRequest extends Request {
@ -34,13 +35,13 @@ class EntityRequest extends Request {
return null;
}
$class = Utils::getEntityClass($this->entityType);
$class = EntityModel::getClassName($this->entityType);
if (method_exists($class, 'trashed')) {
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
} else {
$this->entity = $class::scope($publicId)->firstOrFail();
}
if (method_exists($class, 'trashed')) {
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
} else {
$this->entity = $class::scope($publicId)->firstOrFail();
}
return $this->entity;
}

View File

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

View File

@ -1,6 +1,9 @@
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request as InputRequest;
use Response;
use App\Libraries\Utils;
// https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366
abstract class Request extends FormRequest {
@ -8,6 +11,11 @@ abstract class Request extends FormRequest {
// populate in subclass to auto load record
protected $autoload = [];
public function __construct(InputRequest $req)
{
$this->req = $req;
}
/**
* Validate the input.
*
@ -48,4 +56,23 @@ abstract class Request extends FormRequest {
return $this->all();
}
public function response(array $errors)
{
/* If the user is not validating from a mobile app - pass through parent::response */
if(!isset($this->req->api_secret))
return parent::response($errors);
/* If the user is validating from a mobile app - pass through first error string and return error */
foreach($errors as $error) {
foreach ($error as $key => $value) {
$message['error'] = ['message'=>$value];
$message = json_encode($message, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($message, 400, $headers);
}
}
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateCreditRequest extends CreditRequest
{
/**
* 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 [
'amount' => 'required|positive',
];
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateProjectRequest extends ProjectRequest
{
/**
* 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 [
'name' => sprintf('required|unique:projects,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id),
];
}
}

View File

@ -45,7 +45,7 @@ class ClientPortalHeaderComposer
->join('documents', 'documents.invoice_id', '=', 'invoices.id')
->count();
$view->with('hasQuotes', $client->quotes->count());
$view->with('hasQuotes', $client->publicQuotes->count());
$view->with('hasCredits', $client->creditsWithBalance->count());
$view->with('hasDocuments', $hasDocuments);
}

View File

@ -125,7 +125,7 @@ if (Utils::isReseller()) {
Route::group(['middleware' => 'auth:user'], function() {
Route::get('dashboard', 'DashboardController@index');
Route::get('dashboard_chart_data/{group_by}/{start_date}/{end_date}/{currency_id}/{include_expenses}', 'DashboardController@chartData');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('set_entity_filter/{entity_type}/{filter?}', 'AccountController@setEntityFilter');
Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']);
@ -138,22 +138,29 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::post('users/change_password', 'UserController@changePassword');
Route::resource('clients', 'ClientController');
Route::get('api/clients', ['as'=>'api.clients', 'uses'=>'ClientController@getDatatable']);
Route::get('api/activities/{client_id?}', ['as'=>'api.activities', 'uses'=>'ActivityController@getDatatable']);
Route::get('api/clients', 'ClientController@getDatatable');
Route::get('api/activities/{client_id?}', 'ActivityController@getDatatable');
Route::post('clients/bulk', 'ClientController@bulk');
Route::resource('tasks', 'TaskController');
Route::get('api/tasks/{client_id?}', ['as'=>'api.tasks', 'uses'=>'TaskController@getDatatable']);
Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable');
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('projects', 'ProjectController@index');
Route::get('api/projects', 'ProjectController@getDatatable');
Route::get('projects/create/{client_id?}', 'ProjectController@create');
Route::post('projects', 'ProjectController@store');
Route::put('projects/{projects}', 'ProjectController@update');
Route::get('projects/{projects}/edit', 'ProjectController@edit');
Route::post('projects/bulk', 'ProjectController@bulk');
Route::get('api/recurring_invoices/{client_id?}', ['as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable']);
Route::get('api/recurring_invoices/{client_id?}', 'InvoiceController@getRecurringDatatable');
Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory');
Route::resource('invoices', 'InvoiceController');
Route::get('api/invoices/{client_id?}', ['as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable']);
Route::get('api/invoices/{client_id?}', 'InvoiceController@getDatatable');
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('recurring_invoices', 'RecurringInvoiceController@index');
@ -175,20 +182,20 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('quotes/{invoices}', 'InvoiceController@edit');
Route::post('quotes', 'InvoiceController@store');
Route::get('quotes', 'QuoteController@index');
Route::get('api/quotes/{client_id?}', ['as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable']);
Route::get('api/quotes/{client_id?}', 'QuoteController@getDatatable');
Route::post('quotes/bulk', 'QuoteController@bulk');
Route::resource('payments', 'PaymentController');
Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create');
Route::get('api/payments/{client_id?}', ['as'=>'api.payments', 'uses'=>'PaymentController@getDatatable']);
Route::get('api/payments/{client_id?}', 'PaymentController@getDatatable');
Route::post('payments/bulk', 'PaymentController@bulk');
Route::resource('credits', 'CreditController');
Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create');
Route::get('api/credits/{client_id?}', ['as'=>'api.credits', 'uses'=>'CreditController@getDatatable']);
Route::get('api/credits/{client_id?}', 'CreditController@getDatatable');
Route::post('credits/bulk', 'CreditController@bulk');
Route::get('api/products', ['as'=>'api.products', 'uses'=>'ProductController@getDatatable']);
Route::get('api/products', 'ProductController@getDatatable');
Route::resource('products', 'ProductController');
Route::post('products/bulk', 'ProductController@bulk');
@ -199,29 +206,34 @@ Route::group(['middleware' => 'auth:user'], function() {
// vendor
Route::resource('vendors', 'VendorController');
Route::get('api/vendor', ['as'=>'api.vendors', 'uses'=>'VendorController@getDatatable']);
Route::get('api/vendors', 'VendorController@getDatatable');
Route::post('vendors/bulk', 'VendorController@bulk');
// Expense
Route::resource('expenses', 'ExpenseController');
Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create');
Route::get('api/expense', ['as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable']);
Route::get('api/vendor_expense/{id}', ['as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor']);
Route::get('api/expenses', 'ExpenseController@getDatatable');
Route::get('api/expenses/{id}', 'ExpenseController@getDatatableVendor');
Route::post('expenses/bulk', 'ExpenseController@bulk');
Route::get('expense_categories', 'ExpenseCategoryController@index');
Route::get('api/expense_categories', ['as'=>'api.expense_categories', 'uses'=>'ExpenseCategoryController@getDatatable']);
Route::get('api/expense_categories', 'ExpenseCategoryController@getDatatable');
Route::get('expense_categories/create', 'ExpenseCategoryController@create');
Route::post('expense_categories', 'ExpenseCategoryController@store');
Route::put('expense_categories/{expense_categories}', 'ExpenseCategoryController@update');
Route::get('expense_categories/{expense_categories}/edit', 'ExpenseCategoryController@edit');
Route::post('expense_categories/bulk', 'ExpenseCategoryController@bulk');
// BlueVine
Route::post('bluevine/signup', 'BlueVineController@signup');
Route::get('bluevine/hide_message', 'BlueVineController@hideMessage');
Route::get('bluevine/completed', 'BlueVineController@handleCompleted');
});
Route::group([
'middleware' => ['auth:user', 'permissions.required'],
'permissions' => 'admin',
], function() {
Route::get('api/users', ['as'=>'api.users', 'uses'=>'UserController@getDatatable']);
Route::get('api/users', 'UserController@getDatatable');
Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
@ -231,11 +243,11 @@ Route::group([
Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount');
Route::get('/manage_companies', 'UserController@manageCompanies');
Route::get('api/tokens', ['as'=>'api.tokens', 'uses'=>'TokenController@getDatatable']);
Route::get('api/tokens', 'TokenController@getDatatable');
Route::resource('tokens', 'TokenController');
Route::post('tokens/bulk', 'TokenController@bulk');
Route::get('api/tax_rates', ['as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable']);
Route::get('api/tax_rates', 'TaxRateController@getDatatable');
Route::resource('tax_rates', 'TaxRateController');
Route::post('tax_rates/bulk', 'TaxRateController@bulk');
@ -260,13 +272,13 @@ Route::group([
Route::get('gateways/create/{show_wepay?}', 'AccountGatewayController@create');
Route::resource('gateways', 'AccountGatewayController');
Route::get('gateways/{public_id}/resend_confirmation', 'AccountGatewayController@resendConfirmation');
Route::get('api/gateways', ['as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable']);
Route::get('api/gateways', 'AccountGatewayController@getDatatable');
Route::post('account_gateways/bulk', 'AccountGatewayController@bulk');
Route::get('bank_accounts/import_ofx', 'BankAccountController@showImportOFX');
Route::post('bank_accounts/import_ofx', 'BankAccountController@doImportOFX');
Route::resource('bank_accounts', 'BankAccountController');
Route::get('api/bank_accounts', ['as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable']);
Route::get('api/bank_accounts', 'BankAccountController@getDatatable');
Route::post('bank_accounts/bulk', 'BankAccountController@bulk');
Route::post('bank_accounts/validate', 'BankAccountController@validateAccount');
Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses');
@ -294,7 +306,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::get('invoices', 'InvoiceApiController@index');
Route::get('download/{invoice_id}', 'InvoiceApiController@download');
Route::resource('invoices', 'InvoiceApiController');
Route::get('payments', 'PaymentApiController@index');
Route::resource('payments', 'PaymentApiController');
Route::get('tasks', 'TaskApiController@index');
Route::resource('tasks', 'TaskApiController');
@ -349,9 +360,9 @@ Route::get('/comments/feed', function() {
});
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address'));
define('CONTACT_NAME', Config::get('mail.from.name'));
define('SITE_URL', Config::get('app.url'));
define('CONTACT_EMAIL', config('mail.from.address'));
define('CONTACT_NAME', config('mail.from.name'));
define('SITE_URL', config('app.url'));
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
@ -383,6 +394,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
define('ENTITY_PROJECT', 'project');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);
@ -516,12 +528,17 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_NUM_VENDORS', 100);
define('MAX_NUM_VENDORS_PRO', 20000);
define('STATUS_ACTIVE', 'active');
define('STATUS_ARCHIVED', 'archived');
define('STATUS_DELETED', 'deleted');
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
define('INVOICE_STATUS_VIEWED', 3);
define('INVOICE_STATUS_APPROVED', 4);
define('INVOICE_STATUS_PARTIAL', 5);
define('INVOICE_STATUS_PAID', 6);
define('INVOICE_STATUS_OVERDUE', 7);
define('PAYMENT_STATUS_PENDING', 1);
define('PAYMENT_STATUS_VOIDED', 2);
@ -530,6 +547,15 @@ if (!defined('CONTACT_EMAIL')) {
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
define('PAYMENT_STATUS_REFUNDED', 6);
define('TASK_STATUS_LOGGED', 1);
define('TASK_STATUS_RUNNING', 2);
define('TASK_STATUS_INVOICED', 3);
define('TASK_STATUS_PAID', 4);
define('EXPENSE_STATUS_LOGGED', 1);
define('EXPENSE_STATUS_INVOICED', 2);
define('EXPENSE_STATUS_PAID', 3);
define('CUSTOM_DESIGN', 11);
define('FREQUENCY_WEEKLY', 1);
@ -619,12 +645,14 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '2.8.2' . env('NINJA_VERSION_SUFFIX'));
define('NINJA_VERSION', '2.9.0' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'));
define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/'));
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
@ -637,6 +665,8 @@ if (!defined('CONTACT_EMAIL')) {
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en');
define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox');
define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application');
@ -879,8 +909,23 @@ if (!defined('CONTACT_EMAIL')) {
return $string != $english ? $string : '';
}
}
// include modules in translations
function mtrans($entityType, $text = false)
{
if ( ! $text) {
$text = $entityType;
}
if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) {
return trans("{$module->getLowerName()}::texts.{$text}");
} else {
return trans("texts.{$text}");
}
}
}
/*
if (Utils::isNinjaDev())
{

View File

@ -29,6 +29,11 @@ class CurlUtils
curl_setopt_array($curl, $opts);
$response = curl_exec($curl);
if ($error = curl_error($curl)) {
Utils::logError('CURL Error #' . curl_errno($curl) . ': ' . $error);
}
curl_close($curl);
return $response;

View File

@ -431,6 +431,12 @@ class Utils
public static function pluralizeEntityType($type)
{
if ( ! Utils::isNinjaProd()) {
if ($module = \Module::find($type)) {
return $module->get('plural', $type);
}
}
if ($type === ENTITY_EXPENSE_CATEGORY) {
return 'expense_categories';
} else {
@ -708,11 +714,6 @@ class Utils
return $year + $offset;
}
public static function getEntityClass($entityType)
{
return 'App\\Models\\' . static::getEntityName($entityType);
}
public static function getEntityName($entityType)
{
return ucwords(Utils::toCamelCase($entityType));
@ -725,7 +726,7 @@ class Utils
} elseif ($model->first_name || $model->last_name) {
return $model->first_name.' '.$model->last_name;
} else {
return $model->email;
return $model->email ?: '';
}
}
@ -1066,6 +1067,22 @@ class Utils
});
}
public static function getReadableUrl($path)
{
$url = static::getDocsUrl($path);
$parts = explode('/', $url);
$part = $parts[count($parts) - 1];
$part = str_replace('#', '> ', $part);
$part = str_replace(['.html', '-', '_'], ' ', $part);
if ($part) {
return trans('texts.user_guide') . ': ' . ucwords($part);
} else {
return trans('texts.user_guide');
}
}
public static function getDocsUrl($path)
{
$page = '';

View File

@ -41,27 +41,7 @@ class CreditListener
$credit->client_id = $payment->client_id;
$credit->credit_date = Carbon::now()->toDateTimeString();
$credit->balance = $credit->amount = $payment->getCompletedAmount();
$credit->private_notes = $payment->transaction_reference;
$credit->save();
}
/**
* @param PaymentWasRefunded $event
*/
public function refundedPayment(PaymentWasRefunded $event)
{
$payment = $event->payment;
// if the payment was from a credit we need to refund the credit
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
return;
}
$credit = Credit::createNew();
$credit->client_id = $payment->client_id;
$credit->credit_date = Carbon::now()->toDateTimeString();
$credit->balance = $credit->amount = $event->refundAmount;
$credit->private_notes = $payment->transaction_reference;
$credit->private_notes = trans('texts.refunded_credit_payment');
$credit->save();
}
}

View File

@ -80,6 +80,10 @@ class NotificationListener
*/
public function viewedInvoice(InvoiceInvitationWasViewed $event)
{
if ( ! floatval($event->invoice->balance)) {
return;
}
$this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
}
@ -89,6 +93,10 @@ class NotificationListener
*/
public function viewedQuote(QuoteInvitationWasViewed $event)
{
if ($event->quote->quote_invoice_id) {
return;
}
$this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
}
@ -118,4 +126,4 @@ class NotificationListener
$this->pushService->sendNotification($event->payment->invoice, 'paid');
}
}
}

View File

@ -7,6 +7,7 @@ use DateTime;
use Event;
use Cache;
use App;
use Carbon;
use App\Events\UserSettingsChanged;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -303,6 +304,14 @@ class Account extends Eloquent
return $this->hasMany('App\Models\ExpenseCategory','account_id','id')->withTrashed();
}
/**
* @return mixed
*/
public function projects()
{
return $this->hasMany('App\Models\Project','account_id','id')->withTrashed();
}
/**
* @param $value
*/
@ -1387,7 +1396,7 @@ class Account extends Eloquent
$date = date_create();
}
return $date->format('Y-m-d');
return Carbon::instance($date);
}
/**

View File

@ -173,6 +173,14 @@ class Client extends EntityModel
return $this->hasMany('App\Models\Invoice')->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function publicQuotes()
{
return $this->hasMany('App\Models\Invoice')->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE)->whereIsPublic(true);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/

View File

@ -1,7 +1,10 @@
<?php namespace App\Models;
use Carbon;
use Utils;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
/**
* Class Company
@ -9,11 +12,21 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class Company extends Eloquent
{
use SoftDeletes;
use PresentableTrait;
/**
* @var string
*/
protected $presenter = 'App\Ninja\Presenters\CompanyPresenter';
/**
* @var array
*/
protected $dates = ['deleted_at'];
protected $dates = [
'deleted_at',
'promo_expires',
'discount_expires',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
@ -30,4 +43,66 @@ class Company extends Eloquent
{
return $this->belongsTo('App\Models\Payment');
}
public function hasActivePromo()
{
if ($this->discount_expires) {
return false;
}
return $this->promo_expires && $this->promo_expires->gte(Carbon::today());
}
// handle promos and discounts
public function hasActiveDiscount(Carbon $date = null)
{
if ( ! $this->discount) {
return false;
}
$date = $date ?: Carbon::today();
return $this->discount_expires && $this->discount_expires->gt($date);
}
public function discountedPrice($price)
{
if ( ! $this->hasActivePromo() && ! $this->hasActiveDiscount()) {
return $price;
}
return $price - ($price * $this->discount);
}
public function hasEarnedPromo()
{
if ( ! Utils::isNinjaProd() || Utils::isPro()) {
return false;
}
// if they've already had a discount or a promotion is active return false
if ($this->discount_expires || $this->hasActivePromo()) {
return false;
}
// after 52 weeks, offer a 50% discount for 3 days
$discounts = [
52 => [.5, 3],
16 => [.5, 3],
10 => [.25, 5],
];
foreach ($discounts as $weeks => $promo) {
list($discount, $validFor) = $promo;
$difference = $this->created_at->diffInWeeks();
if ($difference >= $weeks) {
$this->discount = $discount;
$this->promo_expires = date_create()->modify($validFor . ' days')->format('Y-m-d');
$this->save();
return true;
}
}
return false;
}
}

View File

@ -61,6 +61,14 @@ class Credit extends EntityModel
return '';
}
/**
* @return string
*/
public function getRoute()
{
return "/credits/{$this->public_id}";
}
/**
* @return mixed
*/

View File

@ -31,6 +31,15 @@ class EntityModel extends Eloquent
*/
public static $notifySubscriptions = true;
/**
* @var array
*/
public static $statuses = [
STATUS_ACTIVE,
STATUS_ARCHIVED,
STATUS_DELETED,
];
/**
* @param null $context
* @return mixed
@ -63,7 +72,6 @@ class EntityModel extends Eloquent
$lastEntity = $className::whereAccountId($entity->account_id);
}
if (static::$hasPublicId) {
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
->first();
@ -184,6 +192,16 @@ class EntityModel extends Eloquent
*/
public static function getClassName($entityType)
{
if ( ! Utils::isNinjaProd()) {
if ($module = \Module::find($entityType)) {
return "Modules\\{$module->getName()}\\Models\\{$module->getName()}";
}
}
if ($entityType == ENTITY_QUOTE || $entityType == ENTITY_RECURRING_INVOICE) {
$entityType = ENTITY_INVOICE;
}
return 'App\\Models\\' . ucwords(Utils::toCamelCase($entityType));
}
@ -193,6 +211,12 @@ class EntityModel extends Eloquent
*/
public static function getTransformerName($entityType)
{
if ( ! Utils::isNinjaProd()) {
if ($module = \Module::find($entityType)) {
return "Modules\\{$module->getName()}\\Transformers\\{$module->getName()}Transformer";
}
}
return 'App\\Ninja\\Transformers\\' . ucwords(Utils::toCamelCase($entityType)) . 'Transformer';
}
@ -281,4 +305,34 @@ class EntityModel extends Eloquent
return false;
}
public static function getStates($entityType = false)
{
$data = [];
foreach (static::$statuses as $status) {
$data[$status] = trans("texts.{$status}");
}
return $data;
}
public static function getStatuses($entityType = false)
{
return [];
}
public static function getStatesFor($entityType = false)
{
$class = static::getClassName($entityType);
return $class::getStates($entityType);
}
public static function getStatusesFor($entityType = false)
{
$class = static::getClassName($entityType);
return $class::getStatuses($entityType);
}
}

View File

@ -211,6 +211,16 @@ class Expense extends EntityModel
{
return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2);
}
public static function getStatuses($entityType = false)
{
$statuses = [];
$statuses[EXPENSE_STATUS_LOGGED] = trans('texts.logged');
$statuses[EXPENSE_STATUS_INVOICED] = trans('texts.invoiced');
$statuses[EXPENSE_STATUS_PAID] = trans('texts.paid');
return $statuses;
}
}
Expense::creating(function ($expense) {

View File

@ -47,5 +47,4 @@ class ExpenseCategory extends EntityModel
{
return "/expense_categories/{$this->public_id}/edit";
}
}

View File

@ -97,8 +97,8 @@ class Invitation extends EntityModel
if ($this->$field && $this->field != '0000-00-00 00:00:00') {
$date = Utils::dateToString($this->$field);
$hasValue = true;
$parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date;
}
$parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date;
}
return $hasValue ? implode($parts, '<br/>') : false;
@ -123,6 +123,11 @@ class Invitation extends EntityModel
$this->save();
}
public function isSent()
{
return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00';
}
public function markViewed()
{
$invoice = $this->invoice;

View File

@ -153,7 +153,7 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function affectsBalance()
{
return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring;
return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring && $this->is_public;
}
/**
@ -161,7 +161,7 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function getAdjustment()
{
if (!$this->affectsBalance()) {
if ( ! $this->affectsBalance()) {
return 0;
}
@ -173,6 +173,11 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
private function getRawAdjustment()
{
// if we've just made the invoice public then apply the full amount
if ($this->is_public && ! $this->getOriginal('is_public')) {
return $this->amount;
}
return floatval($this->amount) - floatval($this->getOriginal('amount'));
}
@ -353,6 +358,16 @@ class Invoice extends EntityModel implements BalanceAffecting
->where('is_recurring', '=', false);
}
/**
* @param $query
* @return mixed
*/
public function scopeRecurring($query)
{
return $query->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('is_recurring', '=', true);
}
/**
* @param $query
* @return mixed
@ -400,11 +415,30 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function markInvitationsSent($notify = false)
{
if ( ! $this->relationLoaded('invitations')) {
$this->load('invitations');
}
foreach ($this->invitations as $invitation) {
$this->markInvitationSent($invitation, false, $notify);
}
}
public function areInvitationsSent()
{
if ( ! $this->relationLoaded('invitations')) {
$this->load('invitations');
}
foreach ($this->invitations as $invitation) {
if ( ! $invitation->isSent()) {
return false;
}
}
return true;
}
/**
* @param $invitation
* @param bool $messageId
@ -1249,6 +1283,35 @@ class Invoice extends EntityModel implements BalanceAffecting
return $recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill);
}
public static function getStatuses($entityType = false)
{
$statuses = [];
if ($entityType == ENTITY_RECURRING_INVOICE) {
return $statuses;
}
foreach (\Cache::get('invoiceStatus') as $status) {
if ($entityType == ENTITY_QUOTE) {
if (in_array($status->id, [INVOICE_STATUS_PAID, INVOICE_STATUS_PARTIAL])) {
continue;
}
} elseif ($entityType == ENTITY_INVOICE) {
if (in_array($status->id, [INVOICE_STATUS_APPROVED])) {
continue;
}
}
$statuses[$status->id] = trans('texts.status_' . strtolower($status->name));
}
if ($entityType == ENTITY_INVOICE) {
$statuses[INVOICE_STATUS_OVERDUE] = trans('texts.overdue');
}
return $statuses;
}
}
Invoice::creating(function ($invoice) {

View File

@ -1,5 +1,6 @@
<?php namespace App\Models;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
@ -7,7 +8,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
*/
class InvoiceItem extends EntityModel
{
use PresentableTrait;
use SoftDeletes;
/**
* @var string
*/
protected $presenter = 'App\Ninja\Presenters\InvoiceItemPresenter';
/**
* @var array
*/

64
app/Models/Project.php Normal file
View File

@ -0,0 +1,64 @@
<?php namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
/**
* Class ExpenseCategory
*/
class Project extends EntityModel
{
// Expense Categories
use SoftDeletes;
use PresentableTrait;
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var array
*/
protected $fillable = [
'name',
];
/**
* @var string
*/
protected $presenter = 'App\Ninja\Presenters\EntityPresenter';
/**
* @return mixed
*/
public function getEntityType()
{
return ENTITY_PROJECT;
}
/**
* @return string
*/
public function getRoute()
{
return "/projects/{$this->public_id}/edit";
}
/**
* @return mixed
*/
public function client()
{
return $this->belongsTo('App\Models\Client')->withTrashed();
}
}
Project::creating(function ($project) {
$project->setNullValues();
});
Project::updating(function ($project) {
$project->setNullValues();
});

View File

@ -69,6 +69,14 @@ class Task extends EntityModel
return $this->belongsTo('App\Models\Client')->withTrashed();
}
/**
* @return mixed
*/
public function project()
{
return $this->belongsTo('App\Models\Project')->withTrashed();
}
/**
* @param $task
* @return string
@ -195,6 +203,18 @@ class Task extends EntityModel
return $query;
}
public static function getStatuses($entityType = false)
{
$statuses = [];
$statuses[TASK_STATUS_LOGGED] = trans('texts.logged');
$statuses[TASK_STATUS_RUNNING] = trans('texts.running');
$statuses[TASK_STATUS_INVOICED] = trans('texts.invoiced');
$statuses[TASK_STATUS_PAID] = trans('texts.paid');
return $statuses;
}
}

View File

@ -7,6 +7,7 @@ use Auth;
class ClientDatatable extends EntityDatatable
{
public $entityType = ENTITY_CLIENT;
public $sortCol = 4;
public function columns()
{
@ -18,9 +19,9 @@ class ClientDatatable extends EntityDatatable
}
],
[
'first_name',
'contact',
function ($model) {
return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml();
return link_to("clients/{$model->public_id}", $model->contact ?: '')->toHtml();
}
],
[
@ -30,7 +31,7 @@ class ClientDatatable extends EntityDatatable
}
],
[
'clients.created_at',
'client_created_at',
function ($model) {
return Utils::timestampToDateString(strtotime($model->created_at));
}

View File

@ -7,6 +7,7 @@ use Auth;
class CreditDatatable extends EntityDatatable
{
public $entityType = ENTITY_CREDIT;
public $sortCol = 4;
public function columns()
{
@ -37,7 +38,11 @@ class CreditDatatable extends EntityDatatable
[
'credit_date',
function ($model) {
return Utils::fromSqlDate($model->credit_date);
if ( ! Auth::user()->can('viewByOwner', [ENTITY_CREDIT, $model->user_id])){
return Utils::fromSqlDate($model->credit_date);
}
return link_to("credits/{$model->public_id}/edit", Utils::fromSqlDate($model->credit_date))->toHtml();
}
],
[
@ -52,6 +57,15 @@ class CreditDatatable extends EntityDatatable
public function actions()
{
return [
[
trans('texts.edit_credit'),
function ($model) {
return URL::to("credits/{$model->public_id}/edit");
},
function ($model) {
return Auth::user()->can('editByOwner', [ENTITY_CREDIT, $model->user_id]);
}
],
[
trans('texts.apply_credit'),
function ($model) {

View File

@ -5,11 +5,16 @@ class EntityDatatable
public $entityType;
public $isBulkEdit;
public $hideClient;
public $sortCol = 1;
public function __construct($isBulkEdit = true, $hideClient = false)
public function __construct($isBulkEdit = true, $hideClient = false, $entityType = false)
{
$this->isBulkEdit = $isBulkEdit;
$this->hideClient = $hideClient;
if ($entityType) {
$this->entityType = $entityType;
}
}
public function columns()
@ -21,4 +26,42 @@ class EntityDatatable
{
return [];
}
public function bulkActions()
{
return [
[
'label' => mtrans($this->entityType, 'archive_'.$this->entityType),
'url' => 'javascript:submitForm_'.$this->entityType.'("archive")',
],
[
'label' => mtrans($this->entityType, 'delete_'.$this->entityType),
'url' => 'javascript:submitForm_'.$this->entityType.'("delete")',
]
];
}
public function columnFields()
{
$data = [];
$columns = $this->columns();
if ($this->isBulkEdit) {
$data[] = 'checkbox';
}
foreach ($columns as $column) {
if (count($column) == 3) {
// third column is optionally used to determine visibility
if (!$column[2]) {
continue;
}
}
$data[] = $column[0];
}
$data[] = '';
return $data;
}
}

View File

@ -7,6 +7,7 @@ use Auth;
class ExpenseCategoryDatatable extends EntityDatatable
{
public $entityType = ENTITY_EXPENSE_CATEGORY;
public $sortCol = 1;
public function columns()
{

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