mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Added task projects
This commit is contained in:
parent
408aede80a
commit
bbbf1237c5
@ -75,7 +75,7 @@ class AccountApiController extends BaseAPIController
|
|||||||
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
||||||
|
|
||||||
$transformer = new AccountTransformer(null, $request->serializer);
|
$transformer = new AccountTransformer(null, $request->serializer);
|
||||||
$account->load($transformer->getDefaultIncludes());
|
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client']));
|
||||||
$account = $this->createItem($account, $transformer, 'account');
|
$account = $this->createItem($account, $transformer, 'account');
|
||||||
|
|
||||||
return $this->response($account);
|
return $this->response($account);
|
||||||
|
@ -44,7 +44,7 @@ class ExpenseApiController extends BaseAPIController
|
|||||||
{
|
{
|
||||||
$expenses = Expense::scope()
|
$expenses = Expense::scope()
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
->with('client', 'invoice', 'vendor')
|
->with('client', 'invoice', 'vendor', 'expense_category')
|
||||||
->orderBy('created_at','desc');
|
->orderBy('created_at','desc');
|
||||||
|
|
||||||
return $this->listResponse($expenses);
|
return $this->listResponse($expenses);
|
||||||
|
@ -6,6 +6,7 @@ use Input;
|
|||||||
use Session;
|
use Session;
|
||||||
use App\Services\ExpenseCategoryService;
|
use App\Services\ExpenseCategoryService;
|
||||||
use App\Ninja\Repositories\ExpenseCategoryRepository;
|
use App\Ninja\Repositories\ExpenseCategoryRepository;
|
||||||
|
use App\Ninja\Datatables\ExpenseCategoryDatatable;
|
||||||
use App\Http\Requests\ExpenseCategoryRequest;
|
use App\Http\Requests\ExpenseCategoryRequest;
|
||||||
use App\Http\Requests\CreateExpenseCategoryRequest;
|
use App\Http\Requests\CreateExpenseCategoryRequest;
|
||||||
use App\Http\Requests\UpdateExpenseCategoryRequest;
|
use App\Http\Requests\UpdateExpenseCategoryRequest;
|
||||||
@ -29,14 +30,10 @@ class ExpenseCategoryController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return View::make('list', [
|
return View::make('list_wrapper', [
|
||||||
'entityType' => ENTITY_EXPENSE_CATEGORY,
|
'entityType' => ENTITY_EXPENSE_CATEGORY,
|
||||||
|
'datatable' => new ExpenseCategoryDatatable(),
|
||||||
'title' => trans('texts.expense_categories'),
|
'title' => trans('texts.expense_categories'),
|
||||||
'columns' => Utils::trans([
|
|
||||||
'checkbox',
|
|
||||||
'name',
|
|
||||||
''
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +74,7 @@ class ExpenseCategoryController extends BaseController
|
|||||||
|
|
||||||
Session::flash('message', trans('texts.created_expense_category'));
|
Session::flash('message', trans('texts.created_expense_category'));
|
||||||
|
|
||||||
return redirect()->to($category->getRoute());
|
return redirect()->to('/expense_categories');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(UpdateExpenseCategoryRequest $request)
|
public function update(UpdateExpenseCategoryRequest $request)
|
||||||
|
@ -252,7 +252,7 @@ class ExpenseController extends BaseController
|
|||||||
'countries' => Cache::get('countries'),
|
'countries' => Cache::get('countries'),
|
||||||
'customLabel1' => Auth::user()->account->custom_vendor_label1,
|
'customLabel1' => Auth::user()->account->custom_vendor_label1,
|
||||||
'customLabel2' => Auth::user()->account->custom_vendor_label2,
|
'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(),
|
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,8 @@ class ProductController extends BaseController
|
|||||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||||
$count = $this->productService->bulk($ids, $action);
|
$count = $this->productService->bulk($ids, $action);
|
||||||
|
|
||||||
Session::flash('message', trans('texts.archived_product'));
|
$message = Utils::pluralize($action.'d_product', $count);
|
||||||
|
Session::flash('message', $message);
|
||||||
|
|
||||||
return $this->returnBulk(ENTITY_PRODUCT, $action, $ids);
|
return $this->returnBulk(ENTITY_PRODUCT, $action, $ids);
|
||||||
}
|
}
|
||||||
|
113
app/Http/Controllers/ProjectController.php
Normal file
113
app/Http/Controllers/ProjectController.php
Normal 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->projectRepo->save($request->input());
|
||||||
|
|
||||||
|
Session::flash('message', trans('texts.created_project'));
|
||||||
|
|
||||||
|
return redirect()->to($project->getRoute());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(UpdateProjectRequest $request)
|
||||||
|
{
|
||||||
|
$project = $this->projectRepo->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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -41,7 +41,7 @@ class TaskApiController extends BaseAPIController
|
|||||||
{
|
{
|
||||||
$tasks = Task::scope()
|
$tasks = Task::scope()
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
->with('client', 'invoice')
|
->with('client', 'invoice', 'project')
|
||||||
->orderBy('created_at', 'desc');
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
return $this->listResponse($tasks);
|
return $this->listResponse($tasks);
|
||||||
|
@ -10,6 +10,7 @@ use Session;
|
|||||||
use DropdownButton;
|
use DropdownButton;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
|
use App\Models\Project;
|
||||||
use App\Ninja\Repositories\TaskRepository;
|
use App\Ninja\Repositories\TaskRepository;
|
||||||
use App\Ninja\Repositories\InvoiceRepository;
|
use App\Ninja\Repositories\InvoiceRepository;
|
||||||
use App\Services\TaskService;
|
use App\Services\TaskService;
|
||||||
@ -120,6 +121,7 @@ class TaskController extends BaseController
|
|||||||
$data = [
|
$data = [
|
||||||
'task' => null,
|
'task' => null,
|
||||||
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
|
'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',
|
'method' => 'POST',
|
||||||
'url' => 'tasks',
|
'url' => 'tasks',
|
||||||
'title' => trans('texts.new_task'),
|
'title' => trans('texts.new_task'),
|
||||||
@ -171,6 +173,7 @@ class TaskController extends BaseController
|
|||||||
'task' => $task,
|
'task' => $task,
|
||||||
'entity' => $task,
|
'entity' => $task,
|
||||||
'clientPublicId' => $task->client ? $task->client->public_id : 0,
|
'clientPublicId' => $task->client ? $task->client->public_id : 0,
|
||||||
|
'projectPublicId' => $task->project ? $task->project->public_id : 0,
|
||||||
'method' => 'PUT',
|
'method' => 'PUT',
|
||||||
'url' => 'tasks/'.$task->public_id,
|
'url' => 'tasks/'.$task->public_id,
|
||||||
'title' => trans('texts.edit_task'),
|
'title' => trans('texts.edit_task'),
|
||||||
@ -206,6 +209,7 @@ class TaskController extends BaseController
|
|||||||
return [
|
return [
|
||||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||||
'account' => Auth::user()->account,
|
'account' => Auth::user()->account,
|
||||||
|
'projects' => Project::scope()->with('client.contacts')->withArchived()->orderBy('name')->get(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ class UserController extends BaseController
|
|||||||
Session::flash('message', $message);
|
Session::flash('message', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
return Redirect::to('users/' . $user->public_id . '/edit');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendConfirmation($userPublicId)
|
public function sendConfirmation($userPublicId)
|
||||||
|
26
app/Http/Requests/CreateProjectRequest.php
Normal file
26
app/Http/Requests/CreateProjectRequest.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?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),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
7
app/Http/Requests/ProjectRequest.php
Normal file
7
app/Http/Requests/ProjectRequest.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php namespace App\Http\Requests;
|
||||||
|
|
||||||
|
class ProjectRequest extends EntityRequest {
|
||||||
|
|
||||||
|
protected $entityType = ENTITY_PROJECT;
|
||||||
|
|
||||||
|
}
|
27
app/Http/Requests/UpdateProjectRequest.php
Normal file
27
app/Http/Requests/UpdateProjectRequest.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?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' => 'required',
|
||||||
|
'name' => sprintf('required|unique:projects,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -146,6 +146,13 @@ Route::group(['middleware' => 'auth:user'], function() {
|
|||||||
Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable');
|
Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable');
|
||||||
Route::get('tasks/create/{client_id?}', 'TaskController@create');
|
Route::get('tasks/create/{client_id?}', 'TaskController@create');
|
||||||
Route::post('tasks/bulk', 'TaskController@bulk');
|
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?}', 'InvoiceController@getRecurringDatatable');
|
Route::get('api/recurring_invoices/{client_id?}', 'InvoiceController@getRecurringDatatable');
|
||||||
|
|
||||||
@ -388,6 +395,7 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
define('ENTITY_BANK_ACCOUNT', 'bank_account');
|
define('ENTITY_BANK_ACCOUNT', 'bank_account');
|
||||||
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
|
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
|
||||||
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
|
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
|
||||||
|
define('ENTITY_PROJECT', 'project');
|
||||||
|
|
||||||
define('INVOICE_TYPE_STANDARD', 1);
|
define('INVOICE_TYPE_STANDARD', 1);
|
||||||
define('INVOICE_TYPE_QUOTE', 2);
|
define('INVOICE_TYPE_QUOTE', 2);
|
||||||
|
@ -303,6 +303,14 @@ class Account extends Eloquent
|
|||||||
return $this->hasMany('App\Models\ExpenseCategory','account_id','id')->withTrashed();
|
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
|
* @param $value
|
||||||
*/
|
*/
|
||||||
|
@ -47,13 +47,4 @@ class ExpenseCategory extends EntityModel
|
|||||||
{
|
{
|
||||||
return "/expense_categories/{$this->public_id}/edit";
|
return "/expense_categories/{$this->public_id}/edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getStates($entityType = false)
|
|
||||||
{
|
|
||||||
$statuses = parent::getStates($entityType);
|
|
||||||
|
|
||||||
unset($statuses[STATUS_DELETED]);
|
|
||||||
|
|
||||||
return $statuses;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -87,13 +87,4 @@ class Product extends EntityModel
|
|||||||
{
|
{
|
||||||
return $this->belongsTo('App\Models\TaxRate');
|
return $this->belongsTo('App\Models\TaxRate');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getStates($entityType = false)
|
|
||||||
{
|
|
||||||
$statuses = parent::getStates($entityType);
|
|
||||||
|
|
||||||
unset($statuses[STATUS_DELETED]);
|
|
||||||
|
|
||||||
return $statuses;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
65
app/Models/Project.php
Normal file
65
app/Models/Project.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?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',
|
||||||
|
'client_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
});
|
@ -69,6 +69,14 @@ class Task extends EntityModel
|
|||||||
return $this->belongsTo('App\Models\Client')->withTrashed();
|
return $this->belongsTo('App\Models\Client')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\Project')->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $task
|
* @param $task
|
||||||
* @return string
|
* @return string
|
||||||
|
59
app/Ninja/Datatables/ProjectDatatable.php
Normal file
59
app/Ninja/Datatables/ProjectDatatable.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php namespace App\Ninja\Datatables;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use URL;
|
||||||
|
use Auth;
|
||||||
|
|
||||||
|
class ProjectDatatable extends EntityDatatable
|
||||||
|
{
|
||||||
|
public $entityType = ENTITY_PROJECT;
|
||||||
|
public $sortCol = 1;
|
||||||
|
|
||||||
|
public function columns()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'project',
|
||||||
|
function ($model)
|
||||||
|
{
|
||||||
|
if ( ! Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->user_id])) {
|
||||||
|
return $model->project;
|
||||||
|
}
|
||||||
|
|
||||||
|
return link_to("projects/{$model->public_id}/edit", $model->project)->toHtml();
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'client_name',
|
||||||
|
function ($model)
|
||||||
|
{
|
||||||
|
if ($model->client_public_id) {
|
||||||
|
if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){
|
||||||
|
return Utils::getClientDisplayName($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
trans('texts.edit_project'),
|
||||||
|
function ($model) {
|
||||||
|
return URL::to("projects/{$model->public_id}/edit") ;
|
||||||
|
},
|
||||||
|
function ($model) {
|
||||||
|
return Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->user_id]);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,7 +8,7 @@ use App\Models\Task;
|
|||||||
class TaskDatatable extends EntityDatatable
|
class TaskDatatable extends EntityDatatable
|
||||||
{
|
{
|
||||||
public $entityType = ENTITY_TASK;
|
public $entityType = ENTITY_TASK;
|
||||||
public $sortCol = 2;
|
public $sortCol = 3;
|
||||||
|
|
||||||
public function columns()
|
public function columns()
|
||||||
{
|
{
|
||||||
@ -24,6 +24,16 @@ class TaskDatatable extends EntityDatatable
|
|||||||
},
|
},
|
||||||
! $this->hideClient
|
! $this->hideClient
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'project',
|
||||||
|
function ($model) {
|
||||||
|
if(!Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->project_user_id])){
|
||||||
|
return $model->project;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $model->project_public_id ? link_to("projects/{$model->project_public_id}/edit", $model->project)->toHtml() : '';
|
||||||
|
}
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'date',
|
'date',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
|
@ -187,7 +187,8 @@ class AccountRepository
|
|||||||
ENTITY_VENDOR,
|
ENTITY_VENDOR,
|
||||||
ENTITY_RECURRING_INVOICE,
|
ENTITY_RECURRING_INVOICE,
|
||||||
ENTITY_PAYMENT,
|
ENTITY_PAYMENT,
|
||||||
ENTITY_CREDIT
|
ENTITY_CREDIT,
|
||||||
|
ENTITY_PROJECT,
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($entityTypes as $entityType) {
|
foreach ($entityTypes as $entityType) {
|
||||||
|
@ -25,7 +25,8 @@ class ExpenseCategoryRepository extends BaseRepository
|
|||||||
'expense_categories.name as category',
|
'expense_categories.name as category',
|
||||||
'expense_categories.public_id',
|
'expense_categories.public_id',
|
||||||
'expense_categories.user_id',
|
'expense_categories.user_id',
|
||||||
'expense_categories.deleted_at'
|
'expense_categories.deleted_at',
|
||||||
|
'expense_categories.is_deleted'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->applyFilters($query, ENTITY_EXPENSE_CATEGORY);
|
$this->applyFilters($query, ENTITY_EXPENSE_CATEGORY);
|
||||||
|
@ -54,7 +54,7 @@ class ExpenseRepository extends BaseRepository
|
|||||||
->where('contacts.deleted_at', '=', null)
|
->where('contacts.deleted_at', '=', null)
|
||||||
->where('vendors.deleted_at', '=', null)
|
->where('vendors.deleted_at', '=', null)
|
||||||
->where('clients.deleted_at', '=', null)
|
->where('clients.deleted_at', '=', null)
|
||||||
->where(function ($query) {
|
->where(function ($query) { // handle when client isn't set
|
||||||
$query->where('contacts.is_primary', '=', true)
|
$query->where('contacts.is_primary', '=', true)
|
||||||
->orWhere('contacts.is_primary', '=', null);
|
->orWhere('contacts.is_primary', '=', null);
|
||||||
})
|
})
|
||||||
|
@ -32,7 +32,8 @@ class ProductRepository extends BaseRepository
|
|||||||
'products.cost',
|
'products.cost',
|
||||||
'tax_rates.name as tax_name',
|
'tax_rates.name as tax_name',
|
||||||
'tax_rates.rate as tax_rate',
|
'tax_rates.rate as tax_rate',
|
||||||
'products.deleted_at'
|
'products.deleted_at',
|
||||||
|
'products.is_deleted'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
|
75
app/Ninja/Repositories/ProjectRepository.php
Normal file
75
app/Ninja/Repositories/ProjectRepository.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php namespace App\Ninja\Repositories;
|
||||||
|
|
||||||
|
use DB;
|
||||||
|
use Utils;
|
||||||
|
use Auth;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
class ProjectRepository extends BaseRepository
|
||||||
|
{
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return 'App\Models\Project';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return Project::scope()->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($filter = null, $userId = false)
|
||||||
|
{
|
||||||
|
$query = DB::table('projects')
|
||||||
|
->where('projects.account_id', '=', Auth::user()->account_id)
|
||||||
|
->leftjoin('clients', 'clients.id', '=', 'projects.client_id')
|
||||||
|
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||||
|
->where('contacts.deleted_at', '=', null)
|
||||||
|
->where('clients.deleted_at', '=', null)
|
||||||
|
->where(function ($query) { // handle when client isn't set
|
||||||
|
$query->where('contacts.is_primary', '=', true)
|
||||||
|
->orWhere('contacts.is_primary', '=', null);
|
||||||
|
})
|
||||||
|
->select(
|
||||||
|
'projects.name as project',
|
||||||
|
'projects.public_id',
|
||||||
|
'projects.user_id',
|
||||||
|
'projects.deleted_at',
|
||||||
|
'projects.is_deleted',
|
||||||
|
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
|
||||||
|
'clients.user_id as client_user_id',
|
||||||
|
'clients.public_id as client_public_id'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->applyFilters($query, ENTITY_PROJECT);
|
||||||
|
|
||||||
|
if ($filter) {
|
||||||
|
$query->where(function ($query) use ($filter) {
|
||||||
|
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('contacts.email', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('projects.name', 'like', '%'.$filter.'%');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userId) {
|
||||||
|
$query->where('projects.user_id', '=', $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save($input, $project = false)
|
||||||
|
{
|
||||||
|
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
|
||||||
|
|
||||||
|
if ( ! $project) {
|
||||||
|
$project = Project::createNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->fill($input);
|
||||||
|
$project->save();
|
||||||
|
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
use Auth;
|
use Auth;
|
||||||
use Session;
|
use Session;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
|
use App\Models\Project;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
|
|
||||||
class TaskRepository extends BaseRepository
|
class TaskRepository extends BaseRepository
|
||||||
@ -18,8 +19,9 @@ class TaskRepository extends BaseRepository
|
|||||||
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
|
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
|
||||||
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||||
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
||||||
|
->leftJoin('projects', 'projects.id', '=', 'tasks.project_id')
|
||||||
->where('tasks.account_id', '=', Auth::user()->account_id)
|
->where('tasks.account_id', '=', Auth::user()->account_id)
|
||||||
->where(function ($query) {
|
->where(function ($query) { // handle when client isn't set
|
||||||
$query->where('contacts.is_primary', '=', true)
|
$query->where('contacts.is_primary', '=', true)
|
||||||
->orWhere('contacts.is_primary', '=', null);
|
->orWhere('contacts.is_primary', '=', null);
|
||||||
})
|
})
|
||||||
@ -46,7 +48,10 @@ class TaskRepository extends BaseRepository
|
|||||||
'tasks.time_log as duration',
|
'tasks.time_log as duration',
|
||||||
'tasks.created_at',
|
'tasks.created_at',
|
||||||
'tasks.created_at as date',
|
'tasks.created_at as date',
|
||||||
'tasks.user_id'
|
'tasks.user_id',
|
||||||
|
'projects.name as project',
|
||||||
|
'projects.public_id as project_public_id',
|
||||||
|
'projects.user_id as project_user_id'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($clientPublicId) {
|
if ($clientPublicId) {
|
||||||
@ -84,7 +89,9 @@ class TaskRepository extends BaseRepository
|
|||||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||||
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
||||||
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
||||||
->orWhere('tasks.description', 'like', '%'.$filter.'%');
|
->orWhere('tasks.description', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('contacts.email', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('projects.name', 'like', '%'.$filter.'%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,9 +112,13 @@ class TaskRepository extends BaseRepository
|
|||||||
return $task;
|
return $task;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['client']) && $data['client']) {
|
if (isset($data['client'])) {
|
||||||
$task->client_id = Client::getPrivateId($data['client']);
|
$task->client_id = $data['client'] ? Client::getPrivateId($data['client']) : null;
|
||||||
}
|
}
|
||||||
|
if (isset($data['project_id'])) {
|
||||||
|
$task->project_id = $data['project_id'] ? Project::getPrivateId($data['project_id']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($data['description'])) {
|
if (isset($data['description'])) {
|
||||||
$task->description = trim($data['description']);
|
$task->description = trim($data['description']);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ class AccountTransformer extends EntityTransformer
|
|||||||
'users',
|
'users',
|
||||||
'products',
|
'products',
|
||||||
'tax_rates',
|
'tax_rates',
|
||||||
'expense_categories'
|
'expense_categories',
|
||||||
|
'projects',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +37,16 @@ class AccountTransformer extends EntityTransformer
|
|||||||
return $this->includeCollection($account->expense_categories, $transformer, 'expense_categories');
|
return $this->includeCollection($account->expense_categories, $transformer, 'expense_categories');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @return \League\Fractal\Resource\Collection
|
||||||
|
*/
|
||||||
|
public function includeProjects(Account $account)
|
||||||
|
{
|
||||||
|
$transformer = new ProjectTransformer($account, $this->serializer);
|
||||||
|
return $this->includeCollection($account->projects, $transformer, 'projects');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
* @return \League\Fractal\Resource\Collection
|
* @return \League\Fractal\Resource\Collection
|
||||||
|
@ -7,7 +7,7 @@ class ExpenseTransformer extends EntityTransformer
|
|||||||
public function __construct($account = null, $serializer = null, $client = null)
|
public function __construct($account = null, $serializer = null, $client = null)
|
||||||
{
|
{
|
||||||
parent::__construct($account, $serializer);
|
parent::__construct($account, $serializer);
|
||||||
|
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class ExpenseTransformer extends EntityTransformer
|
|||||||
'transaction_id' => $expense->transaction_id,
|
'transaction_id' => $expense->transaction_id,
|
||||||
'bank_id' => $expense->bank_id,
|
'bank_id' => $expense->bank_id,
|
||||||
'expense_currency_id' => (int) $expense->expense_currency_id,
|
'expense_currency_id' => (int) $expense->expense_currency_id,
|
||||||
'expense_category_id' => (int) $expense->expense_category_id,
|
'expense_category_id' => $expense->expense_category ? (int) $expense->expense_category->public_id : null,
|
||||||
'amount' => (float) $expense->amount,
|
'amount' => (float) $expense->amount,
|
||||||
'expense_date' => $expense->expense_date,
|
'expense_date' => $expense->expense_date,
|
||||||
'exchange_rate' => (float) $expense->exchange_rate,
|
'exchange_rate' => (float) $expense->exchange_rate,
|
||||||
@ -38,4 +38,4 @@ class ExpenseTransformer extends EntityTransformer
|
|||||||
'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,
|
'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
app/Ninja/Transformers/ProjectTransformer.php
Normal file
18
app/Ninja/Transformers/ProjectTransformer.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php namespace App\Ninja\Transformers;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
class ProjectTransformer extends EntityTransformer
|
||||||
|
{
|
||||||
|
public function transform(Project $project)
|
||||||
|
{
|
||||||
|
return array_merge($this->getDefaults($project), [
|
||||||
|
'id' => (int) $project->public_id,
|
||||||
|
'name' => $project->name,
|
||||||
|
'client_id' => $project->client ? (int) $project->client->public_id : null,
|
||||||
|
'updated_at' => $this->getTimestamp($project->updated_at),
|
||||||
|
'archived_at' => $this->getTimestamp($project->deleted_at),
|
||||||
|
'is_deleted' => (bool) $project->is_deleted,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,7 @@ class TaskTransformer extends EntityTransformer
|
|||||||
'archived_at' => (int) $this->getTimestamp($task->deleted_at),
|
'archived_at' => (int) $this->getTimestamp($task->deleted_at),
|
||||||
'invoice_id' => $task->invoice ? (int) $task->invoice->public_id : false,
|
'invoice_id' => $task->invoice ? (int) $task->invoice->public_id : false,
|
||||||
'client_id' => $task->client ? (int) $task->client->public_id : false,
|
'client_id' => $task->client ? (int) $task->client->public_id : false,
|
||||||
|
'project_id' => $task->project ? (int) $task->project->public_id : false,
|
||||||
'is_deleted' => (bool) $task->is_deleted,
|
'is_deleted' => (bool) $task->is_deleted,
|
||||||
'time_log' => $task->time_log,
|
'time_log' => $task->time_log,
|
||||||
'is_running' => (bool) $task->is_running,
|
'is_running' => (bool) $task->is_running,
|
||||||
|
10
app/Policies/ProjectPolicy.php
Normal file
10
app/Policies/ProjectPolicy.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class ProjectPolicy extends EntityPolicy
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -28,6 +28,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
\App\Models\AccountToken::class => \App\Policies\TokenPolicy::class,
|
\App\Models\AccountToken::class => \App\Policies\TokenPolicy::class,
|
||||||
\App\Models\BankAccount::class => \App\Policies\BankAccountPolicy::class,
|
\App\Models\BankAccount::class => \App\Policies\BankAccountPolicy::class,
|
||||||
\App\Models\PaymentTerm::class => \App\Policies\PaymentTermPolicy::class,
|
\App\Models\PaymentTerm::class => \App\Policies\PaymentTermPolicy::class,
|
||||||
|
\App\Models\Project::class => \App\Policies\ProjectPolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
71
app/Services/ProjectService.php
Normal file
71
app/Services/ProjectService.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php namespace App\Services;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use Auth;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Ninja\Repositories\ProjectRepository;
|
||||||
|
use App\Ninja\Datatables\ProjectDatatable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ProjectService
|
||||||
|
*/
|
||||||
|
class ProjectService extends BaseService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ProjectRepository
|
||||||
|
*/
|
||||||
|
protected $projectRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DatatableService
|
||||||
|
*/
|
||||||
|
protected $datatableService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CreditService constructor.
|
||||||
|
*
|
||||||
|
* @param ProjectRepository $creditRepo
|
||||||
|
* @param DatatableService $datatableService
|
||||||
|
*/
|
||||||
|
public function __construct(ProjectRepository $projectRepo, DatatableService $datatableService)
|
||||||
|
{
|
||||||
|
$this->projectRepo = $projectRepo;
|
||||||
|
$this->datatableService = $datatableService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CreditRepository
|
||||||
|
*/
|
||||||
|
protected function getRepo()
|
||||||
|
{
|
||||||
|
return $this->projectRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $data
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
public function save($data)
|
||||||
|
{
|
||||||
|
if (isset($data['client_id']) && $data['client_id']) {
|
||||||
|
$data['client_id'] = Client::getPrivateId($data['client_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->projectRepo->save($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $clientPublicId
|
||||||
|
* @param $search
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getDatatable($search, $userId)
|
||||||
|
{
|
||||||
|
// we don't support bulk edit and hide the client on the individual client page
|
||||||
|
$datatable = new ProjectDatatable();
|
||||||
|
|
||||||
|
$query = $this->projectRepo->find($search, $userId);
|
||||||
|
|
||||||
|
return $this->datatableService->createDatatable($datatable, $query);
|
||||||
|
}
|
||||||
|
}
|
@ -54,7 +54,7 @@ class CreateGatewayTypes extends Migration
|
|||||||
});
|
});
|
||||||
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse the migrations.
|
* Reverse the migrations.
|
||||||
*
|
*
|
||||||
|
106
database/migrations/2016_11_28_092904_add_task_projects.php
Normal file
106
database/migrations/2016_11_28_092904_add_task_projects.php
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddTaskProjects extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('projects', function($table)
|
||||||
|
{
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('user_id');
|
||||||
|
$table->unsignedInteger('account_id')->index();
|
||||||
|
$table->unsignedInteger('client_id')->index()->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$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( array('account_id','public_id') );
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('tasks', function ($table)
|
||||||
|
{
|
||||||
|
$table->unsignedInteger('project_id')->nullable()->index();
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||||
|
Schema::table('tasks', function ($table)
|
||||||
|
{
|
||||||
|
$table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||||
|
|
||||||
|
// is_deleted to standardize tables
|
||||||
|
Schema::table('expense_categories', function ($table)
|
||||||
|
{
|
||||||
|
$table->boolean('is_deleted')->default(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('products', function ($table)
|
||||||
|
{
|
||||||
|
$table->boolean('is_deleted')->default(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// add 'delete cascase' to resolve error when deleting an account
|
||||||
|
Schema::table('account_gateway_tokens', function($table)
|
||||||
|
{
|
||||||
|
$table->dropForeign('account_gateway_tokens_default_payment_method_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('account_gateway_tokens', function($table)
|
||||||
|
{
|
||||||
|
$table->foreign('default_payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('invoices', function ($table)
|
||||||
|
{
|
||||||
|
$table->boolean('is_public')->default(false);
|
||||||
|
});
|
||||||
|
DB::table('invoices')->update(['is_public' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('tasks', function ($table)
|
||||||
|
{
|
||||||
|
$table->dropForeign('tasks_project_id_foreign');
|
||||||
|
$table->dropColumn('project_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('projects');
|
||||||
|
|
||||||
|
Schema::table('expense_categories', function ($table)
|
||||||
|
{
|
||||||
|
$table->dropColumn('is_deleted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('products', function ($table)
|
||||||
|
{
|
||||||
|
$table->dropColumn('is_deleted');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('invoices', function ($table)
|
||||||
|
{
|
||||||
|
$table->dropColumn('is_public');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2228,6 +2228,28 @@ $LANG = array(
|
|||||||
'payment_status_name' => 'Status',
|
'payment_status_name' => 'Status',
|
||||||
'client_created_at' => 'Date Created',
|
'client_created_at' => 'Date Created',
|
||||||
'postmark_error' => 'There was a problem sending the email through Postmark: :link',
|
'postmark_error' => 'There was a problem sending the email through Postmark: :link',
|
||||||
|
'project' => 'Project',
|
||||||
|
'projects' => 'Projects',
|
||||||
|
'new_project' => 'New Project',
|
||||||
|
'edit_project' => 'Edit Project',
|
||||||
|
'archive_project' => 'Archive Project',
|
||||||
|
'list_projects' => 'List Projects',
|
||||||
|
'updated_project' => 'Successfully updated project',
|
||||||
|
'created_project' => 'Successfully created project',
|
||||||
|
'archived_project' => 'Successfully archived project',
|
||||||
|
'archived_projects' => 'Successfully archived :count projects',
|
||||||
|
'restore_project' => 'Restore project',
|
||||||
|
'restored_project' => 'Successfully restored project',
|
||||||
|
'delete_project' => 'Delete project',
|
||||||
|
'deleted_project' => 'Successfully deleted project',
|
||||||
|
'deleted_projects' => 'Successfully deleted :count projects',
|
||||||
|
'delete_expense_category' => 'Delete category',
|
||||||
|
'deleted_expense_category' => 'Successfully deleted category',
|
||||||
|
'delete_product' => 'Delete product',
|
||||||
|
'deleted_product' => 'Successfully deleted product',
|
||||||
|
'deleted_products' => 'Successfully deleted :count products',
|
||||||
|
'restored_product' => 'Successfully restored product',
|
||||||
|
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -506,7 +506,9 @@
|
|||||||
'settings',
|
'settings',
|
||||||
//'self-update'
|
//'self-update'
|
||||||
] as $option)
|
] as $option)
|
||||||
@if (in_array($option, ['dashboard', 'settings']) || Auth::user()->can('view', substr($option, 0, -1)))
|
@if (in_array($option, ['dashboard', 'settings'])
|
||||||
|
|| Auth::user()->can('view', substr($option, 0, -1))
|
||||||
|
|| Auth::user()->can('create', substr($option, 0, -1)))
|
||||||
<li class="{{ Request::is("{$option}*") ? 'active' : '' }}">
|
<li class="{{ Request::is("{$option}*") ? 'active' : '' }}">
|
||||||
@if ($option == 'settings')
|
@if ($option == 'settings')
|
||||||
<a type="button" class="btn btn-default btn-sm pull-right"
|
<a type="button" class="btn btn-default btn-sm pull-right"
|
||||||
|
@ -17,14 +17,10 @@
|
|||||||
@endif
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
@if (in_array($entityType, [ENTITY_EXPENSE_CATEGORY, ENTITY_PRODUCT]))
|
{!! DropdownButton::normal(trans('texts.archive'))->withContents([
|
||||||
{!! Button::normal(trans('texts.archive'))->asLinkTo('javascript:submitForm_'.$entityType.'("archive")')->appendIcon(Icon::create('trash')) !!}
|
['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm_'.$entityType.'("archive")'],
|
||||||
@else
|
['label' => trans('texts.delete_'.$entityType), 'url' => 'javascript:submitForm_'.$entityType.'("delete")'],
|
||||||
{!! DropdownButton::normal(trans('texts.archive'))->withContents([
|
])->withAttributes(['class'=>'archive'])->split() !!}
|
||||||
['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm_'.$entityType.'("archive")'],
|
|
||||||
['label' => trans('texts.delete_'.$entityType), 'url' => 'javascript:submitForm_'.$entityType.'("delete")'],
|
|
||||||
])->withAttributes(['class'=>'archive'])->split() !!}
|
|
||||||
@endif
|
|
||||||
|
|
||||||
|
|
||||||
<span id="statusWrapper_{{ $entityType }}" style="display:none">
|
<span id="statusWrapper_{{ $entityType }}" style="display:none">
|
||||||
@ -53,14 +49,14 @@
|
|||||||
<input id="tableFilter_{{ $entityType }}" type="text" style="width:140px;margin-right:17px;background-color: white !important"
|
<input id="tableFilter_{{ $entityType }}" type="text" style="width:140px;margin-right:17px;background-color: white !important"
|
||||||
class="form-control pull-left" placeholder="{{ trans('texts.filter') }}" value="{{ Input::get('filter') }}"/>
|
class="form-control pull-left" placeholder="{{ trans('texts.filter') }}" value="{{ Input::get('filter') }}"/>
|
||||||
|
|
||||||
@if (empty($clientId))
|
@if ($entityType == ENTITY_EXPENSE)
|
||||||
@if ($entityType == ENTITY_EXPENSE)
|
{!! Button::normal(trans('texts.categories'))->asLinkTo(URL::to('/expense_categories'))->appendIcon(Icon::create('list')) !!}
|
||||||
{!! Button::normal(trans('texts.categories'))->asLinkTo(URL::to('/expense_categories'))->appendIcon(Icon::create('list')) !!}
|
@elseif ($entityType == ENTITY_TASK)
|
||||||
@endif
|
{!! Button::normal(trans('texts.projects'))->asLinkTo(URL::to('/projects'))->appendIcon(Icon::create('list')) !!}
|
||||||
|
@endif
|
||||||
|
|
||||||
@if (empty($vendorId) && Auth::user()->can('create', $entityType))
|
@if (empty($clientId) && empty($vendorId) && Auth::user()->can('create', $entityType))
|
||||||
{!! Button::primary(trans("texts.new_{$entityType}"))->asLinkTo(url(Utils::pluralizeEntityType($entityType) . '/create'))->appendIcon(Icon::create('plus-sign')) !!}
|
{!! Button::primary(trans("texts.new_{$entityType}"))->asLinkTo(url(Utils::pluralizeEntityType($entityType) . '/create'))->appendIcon(Icon::create('plus-sign')) !!}
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -73,7 +69,7 @@
|
|||||||
->setCustomValues('entityType', Utils::pluralizeEntityType($entityType))
|
->setCustomValues('entityType', Utils::pluralizeEntityType($entityType))
|
||||||
->setCustomValues('clientId', isset($clientId) && $clientId)
|
->setCustomValues('clientId', isset($clientId) && $clientId)
|
||||||
->setOptions('sPaginationType', 'bootstrap')
|
->setOptions('sPaginationType', 'bootstrap')
|
||||||
->setOptions('aaSorting', [[$datatable->sortCol, 'desc']])
|
->setOptions('aaSorting', [[isset($clientId) ? ($datatable->sortCol-1) : $datatable->sortCol, 'desc']])
|
||||||
->render('datatable') !!}
|
->render('datatable') !!}
|
||||||
|
|
||||||
@if ($entityType == ENTITY_PAYMENT)
|
@if ($entityType == ENTITY_PAYMENT)
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (account && ! decorator) {
|
if (account && ! decorator) {
|
||||||
decorator = account.show_currency_code ? 'code' : 'symbol';
|
decorator = parseInt(account.show_currency_code) ? 'code' : 'symbol';
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatMoney(value, currencyId, countryId, decorator)
|
return formatMoney(value, currencyId, countryId, decorator)
|
||||||
|
73
resources/views/projects/edit.blade.php
Normal file
73
resources/views/projects/edit.blade.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
@extends('header')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
{!! Former::open($url)
|
||||||
|
->addClass('col-md-10 col-md-offset-1 warn-on-exit')
|
||||||
|
->method($method)
|
||||||
|
->rules([
|
||||||
|
'name' => 'required',
|
||||||
|
]) !!}
|
||||||
|
|
||||||
|
@if ($project)
|
||||||
|
{!! Former::populate($project) !!}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<span style="display:none">
|
||||||
|
{!! Former::text('public_id') !!}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10 col-md-offset-1">
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{!! trans('texts.project') !!}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
|
||||||
|
{!! Former::text('name') !!}
|
||||||
|
|
||||||
|
{!! Former::select('client_id')
|
||||||
|
->addOption('', '')
|
||||||
|
->label(trans('texts.client')) !!}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<center class="buttons">
|
||||||
|
{!! Button::normal(trans('texts.cancel'))->large()->asLinkTo(url('/expense_categories'))->appendIcon(Icon::create('remove-circle')) !!}
|
||||||
|
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
|
||||||
|
</center>
|
||||||
|
|
||||||
|
{!! Former::close() !!}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var clients = {!! $clients !!};
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$('#name').focus();
|
||||||
|
|
||||||
|
var $clientSelect = $('select#client_id');
|
||||||
|
for (var i=0; i<clients.length; i++) {
|
||||||
|
var client = clients[i];
|
||||||
|
var clientName = getClientDisplayName(client);
|
||||||
|
if (!clientName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$clientSelect.append(new Option(clientName, client.public_id));
|
||||||
|
}
|
||||||
|
@if ($clientPublicId)
|
||||||
|
$clientSelect.val({{ $clientPublicId }});
|
||||||
|
@endif
|
||||||
|
$clientSelect.combobox();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@stop
|
@ -48,6 +48,8 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
{!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!}
|
{!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!}
|
||||||
|
{!! Former::select('project_id')->addOption('', '')->addGroupClass('project-select')
|
||||||
|
->label(trans('texts.project')) !!}
|
||||||
{!! Former::textarea('description')->rows(3) !!}
|
{!! Former::textarea('description')->rows(3) !!}
|
||||||
|
|
||||||
@if ($task)
|
@if ($task)
|
||||||
@ -208,6 +210,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var clients = {!! $clients !!};
|
var clients = {!! $clients !!};
|
||||||
|
var projects = {!! $projects !!};
|
||||||
|
|
||||||
var timeLabels = {};
|
var timeLabels = {};
|
||||||
@foreach (['hour', 'minute', 'second'] as $period)
|
@foreach (['hour', 'minute', 'second'] as $period)
|
||||||
timeLabels['{{ $period }}'] = '{{ trans("texts.{$period}") }}';
|
timeLabels['{{ $period }}'] = '{{ trans("texts.{$period}") }}';
|
||||||
@ -425,22 +429,6 @@
|
|||||||
ko.applyBindings(model);
|
ko.applyBindings(model);
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
var $clientSelect = $('select#client');
|
|
||||||
for (var i=0; i<clients.length; i++) {
|
|
||||||
var client = clients[i];
|
|
||||||
var clientName = getClientDisplayName(client);
|
|
||||||
if (!clientName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$clientSelect.append(new Option(clientName, client.public_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ({{ $clientPublicId ? 'true' : 'false' }}) {
|
|
||||||
$clientSelect.val({{ $clientPublicId }});
|
|
||||||
}
|
|
||||||
|
|
||||||
$clientSelect.combobox();
|
|
||||||
|
|
||||||
@if (!$task && !$clientPublicId)
|
@if (!$task && !$clientPublicId)
|
||||||
$('.client-select input.form-control').focus();
|
$('.client-select input.form-control').focus();
|
||||||
@else
|
@else
|
||||||
@ -494,6 +482,98 @@
|
|||||||
model.showTimeOverlaps();
|
model.showTimeOverlaps();
|
||||||
showTimeDetails();
|
showTimeDetails();
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
// setup clients and project comboboxes
|
||||||
|
var clientId = {{ $clientPublicId }};
|
||||||
|
var projectId = {{ $projectPublicId }};
|
||||||
|
|
||||||
|
var clientMap = {};
|
||||||
|
var projectMap = {};
|
||||||
|
var projectsForClientMap = {};
|
||||||
|
var projectsForAllClients = [];
|
||||||
|
var $clientSelect = $('select#client');
|
||||||
|
|
||||||
|
for (var i=0; i<projects.length; i++) {
|
||||||
|
var project = projects[i];
|
||||||
|
projectMap[project.public_id] = project;
|
||||||
|
|
||||||
|
var client = project.client;
|
||||||
|
if (!client) {
|
||||||
|
projectsForAllClients.push(project);
|
||||||
|
} else {
|
||||||
|
if (!projectsForClientMap.hasOwnProperty(client.public_id)) {
|
||||||
|
projectsForClientMap[client.public_id] = [];
|
||||||
|
}
|
||||||
|
projectsForClientMap[client.public_id].push(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0; i<clients.length; i++) {
|
||||||
|
var client = clients[i];
|
||||||
|
clientMap[client.public_id] = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientSelect.append(new Option('', ''));
|
||||||
|
for (var i=0; i<clients.length; i++) {
|
||||||
|
var client = clients[i];
|
||||||
|
var clientName = getClientDisplayName(client);
|
||||||
|
if (!clientName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$clientSelect.append(new Option(clientName, client.public_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientId) {
|
||||||
|
$clientSelect.val(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientSelect.combobox();
|
||||||
|
$clientSelect.on('change', function(e) {
|
||||||
|
var clientId = $('input[name=client]').val();
|
||||||
|
var projectId = $('input[name=project_id]').val();
|
||||||
|
var project = projectMap[projectId];
|
||||||
|
if (project && ((project.client && project.client.public_id == clientId) || !project.client)) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setComboboxValue($('.project-select'), '', '');
|
||||||
|
$projectCombobox = $('select#project_id');
|
||||||
|
$projectCombobox.find('option').remove().end().combobox('refresh');
|
||||||
|
$projectCombobox.append(new Option('', ''));
|
||||||
|
var list = clientId ? (projectsForClientMap.hasOwnProperty(clientId) ? projectsForClientMap[clientId] : []).concat(projectsForAllClients) : projects;
|
||||||
|
for (var i=0; i<list.length; i++) {
|
||||||
|
var project = list[i];
|
||||||
|
$projectCombobox.append(new Option(project.name, project.public_id));
|
||||||
|
}
|
||||||
|
$('select#project_id').combobox('refresh');
|
||||||
|
});
|
||||||
|
|
||||||
|
var $projectSelect = $('select#project_id').on('change', function(e) {
|
||||||
|
$clientCombobox = $('select#client');
|
||||||
|
var projectId = $('input[name=project_id]').val();
|
||||||
|
if (projectId) {
|
||||||
|
var project = projectMap[projectId];
|
||||||
|
if (project.client) {
|
||||||
|
var client = clientMap[project.client.public_id];
|
||||||
|
if (client) {
|
||||||
|
project.client = client;
|
||||||
|
setComboboxValue($('.client-select'), client.public_id, getClientDisplayName(client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$clientSelect.trigger('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$projectSelect.combobox();
|
||||||
|
|
||||||
|
if (projectId) {
|
||||||
|
var project = projectMap[projectId];
|
||||||
|
setComboboxValue($('.project-select'), project.public_id, project.name);
|
||||||
|
$projectSelect.trigger('change');
|
||||||
|
} else {
|
||||||
|
$clientSelect.trigger('change');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user