66 changed files with 1322 additions and 1318 deletions
-
6app/Entities/BaseRepository.php
-
2app/Entities/Payments/Type.php
-
103app/Entities/Projects/FeaturesRepository.php
-
9app/Entities/Projects/Job.php
-
10app/Entities/Projects/JobPresenter.php
-
106app/Entities/Projects/JobsRepository.php
-
38app/Entities/Projects/Project.php
-
28app/Entities/Projects/ProjectsRepository.php
-
10app/Entities/Projects/Task.php
-
14app/Entities/Projects/TasksRepository.php
-
6app/Http/Controllers/Api/ProjectsController.php
-
124app/Http/Controllers/Projects/FeaturesController.php
-
23app/Http/Controllers/Projects/FilesController.php
-
123app/Http/Controllers/Projects/JobsController.php
-
34app/Http/Controllers/Projects/ProjectsController.php
-
84app/Http/Controllers/Projects/TasksController.php
-
34app/Http/Requests/Features/DeleteRequest.php
-
37app/Http/Requests/Features/UpdateRequest.php
-
4app/Http/Requests/Jobs/CreateRequest.php
-
35app/Http/Requests/Jobs/DeleteRequest.php
-
38app/Http/Requests/Jobs/UpdateRequest.php
-
54app/Http/Requests/Tasks/CreateRequest.php
-
49app/Http/Requests/Tasks/DeleteRequest.php
-
53app/Http/Requests/Tasks/UpdateRequest.php
-
6app/Providers/AuthServiceProvider.php
-
2config/queue.php
-
58database/factories/ModelFactory.php
-
40database/migrations/2016_07_09_093439_create_features_table.php
-
39database/migrations/2016_07_09_093439_create_jobs_table.php
-
59database/migrations/2016_07_09_142833_create_tasks_table.php
-
2database/migrations/2016_11_15_151228_create_payments_table.php
-
37resources/lang/id/feature.php
-
37resources/lang/id/job.php
-
2resources/lang/id/payment.php
-
32resources/lang/id/project.php
-
6resources/views/features/partials/breadcrumb.blade.php
-
14resources/views/features/partials/feature-show.blade.php
-
63resources/views/features/unfinished.blade.php
-
4resources/views/invoices/pdf.blade.php
-
30resources/views/jobs/add-from-other-project.blade.php
-
24resources/views/jobs/create.blade.php
-
14resources/views/jobs/delete.blade.php
-
30resources/views/jobs/edit.blade.php
-
6resources/views/jobs/partials/breadcrumb.blade.php
-
14resources/views/jobs/partials/job-show.blade.php
-
10resources/views/jobs/partials/job-tasks-operation.blade.php
-
26resources/views/jobs/partials/job-tasks.blade.php
-
18resources/views/jobs/show.blade.php
-
63resources/views/jobs/unfinished.blade.php
-
2resources/views/layouts/partials/sidebar.blade.php
-
129resources/views/projects/features.blade.php
-
32resources/views/projects/jobs-export-excel.blade.php
-
28resources/views/projects/jobs-export-html-2.blade.php
-
34resources/views/projects/jobs-export-html.blade.php
-
40resources/views/projects/jobs-export-progress-excel.blade.php
-
129resources/views/projects/jobs.blade.php
-
4resources/views/projects/partials/nav-tabs.blade.php
-
10resources/views/projects/partials/project-stats.blade.php
-
2routes/api/projects.php
-
26routes/web/projects.php
-
190tests/Feature/ManageFeaturesTest.php
-
190tests/Feature/ManageJobsTest.php
-
24tests/Feature/ManageProjectsTest.php
-
44tests/Feature/ManageTasksTest.php
-
92tests/Unit/Models/ProjectTest.php
-
4tests/Unit/References/PaymentTypeTest.php
@ -1,103 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Entities\Projects; |
|||
|
|||
use App\Entities\BaseRepository; |
|||
use App\Entities\Projects\Project; |
|||
use DB; |
|||
|
|||
/** |
|||
* Features Repository Class |
|||
*/ |
|||
class FeaturesRepository extends BaseRepository |
|||
{ |
|||
protected $model; |
|||
|
|||
public function __construct(Feature $model) |
|||
{ |
|||
parent::__construct($model); |
|||
} |
|||
|
|||
public function getUnfinishedFeatures() |
|||
{ |
|||
return $this->model->whereHas('tasks', function($query) { |
|||
return $query->where('progress','<',100); |
|||
}) |
|||
->whereHas('project', function($query) { |
|||
return $query->whereIn('status_id', [2,3]); |
|||
}) |
|||
->with(['tasks','project']) |
|||
->get(); |
|||
} |
|||
|
|||
public function requireProjectById($projectId) |
|||
{ |
|||
return Project::findOrFail($projectId); |
|||
} |
|||
|
|||
public function createFeature($featureData, $projectId) |
|||
{ |
|||
$featureData['project_id'] = $projectId; |
|||
$featureData['price'] = str_replace('.', '', $featureData['price']); |
|||
return $this->storeArray($featureData); |
|||
} |
|||
|
|||
public function createFeatures($featuresData, $projectId) |
|||
{ |
|||
$selectedFeatures = $this->model->whereIn('id', $featuresData['feature_ids'])->get(); |
|||
|
|||
DB::beginTransaction(); |
|||
foreach ($selectedFeatures as $feature) { |
|||
$newFeature = $feature->replicate(); |
|||
$newFeature->project_id = $projectId; |
|||
$newFeature->save(); |
|||
|
|||
$selectedTasks = $feature->tasks()->whereIn('id', $featuresData[$feature->id . '_task_ids'])->get(); |
|||
|
|||
foreach ($selectedTasks as $task) { |
|||
$newTask = $task->replicate(); |
|||
$newTask->progress = 0; |
|||
$newTask->feature_id = $newFeature->id; |
|||
$newTask->save(); |
|||
} |
|||
} |
|||
DB::commit(); |
|||
|
|||
return 'ok'; |
|||
} |
|||
|
|||
public function getTasksByFeatureId($featureId) |
|||
{ |
|||
return Task::whereFeatureId($featureId)->get(); |
|||
} |
|||
|
|||
public function requireTaskById($taskId) |
|||
{ |
|||
return Task::findOrFail($taskId); |
|||
} |
|||
|
|||
public function update($featureData = [], $featureId) |
|||
{ |
|||
foreach ($featureData as $key => $value) { |
|||
if (!$featureData[$key]) $featureData[$key] = null; |
|||
} |
|||
|
|||
$featureData['price'] = str_replace('.', '', $featureData['price']); |
|||
$feature = $this->requireById($featureId); |
|||
$feature->update($featureData); |
|||
return $feature; |
|||
} |
|||
|
|||
public function tasksReorder($sortedData) |
|||
{ |
|||
$taskOrder = explode(',', $sortedData); |
|||
|
|||
foreach ($taskOrder as $order => $taskId) { |
|||
$task = $this->requireTaskById($taskId); |
|||
$task->position = $order + 1; |
|||
$task->save(); |
|||
} |
|||
|
|||
return $taskOrder; |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
<?php |
|||
|
|||
namespace App\Entities\Projects; |
|||
|
|||
use App\Entities\BaseRepository; |
|||
use App\Entities\Projects\Project; |
|||
use DB; |
|||
|
|||
/** |
|||
* Jobs Repository Class |
|||
*/ |
|||
class JobsRepository extends BaseRepository |
|||
{ |
|||
protected $model; |
|||
|
|||
public function __construct(Job $model) |
|||
{ |
|||
parent::__construct($model); |
|||
} |
|||
|
|||
public function getUnfinishedJobs() |
|||
{ |
|||
return $this->model->whereHas('tasks', function ($query) { |
|||
return $query->where('progress', '<', 100); |
|||
}) |
|||
->whereHas('project', function ($query) { |
|||
return $query->whereIn('status_id', [2, 3]); |
|||
}) |
|||
->with(['tasks', 'project']) |
|||
->get(); |
|||
} |
|||
|
|||
public function requireProjectById($projectId) |
|||
{ |
|||
return Project::findOrFail($projectId); |
|||
} |
|||
|
|||
public function createJob($jobData, $projectId) |
|||
{ |
|||
$jobData['project_id'] = $projectId; |
|||
$jobData['price'] = str_replace('.', '', $jobData['price']); |
|||
return $this->storeArray($jobData); |
|||
} |
|||
|
|||
public function createJobs($jobsData, $projectId) |
|||
{ |
|||
$selectedJobs = $this->model->whereIn('id', $jobsData['job_ids'])->get(); |
|||
|
|||
DB::beginTransaction(); |
|||
foreach ($selectedJobs as $job) { |
|||
$newJob = $job->replicate(); |
|||
$newJob->project_id = $projectId; |
|||
$newJob->save(); |
|||
|
|||
$selectedTasks = $job->tasks()->whereIn('id', $jobsData[$job->id.'_task_ids'])->get(); |
|||
|
|||
foreach ($selectedTasks as $task) { |
|||
$newTask = $task->replicate(); |
|||
$newTask->progress = 0; |
|||
$newTask->job_id = $newJob->id; |
|||
$newTask->save(); |
|||
} |
|||
} |
|||
DB::commit(); |
|||
|
|||
return 'ok'; |
|||
} |
|||
|
|||
public function getTasksByJobId($jobId) |
|||
{ |
|||
return Task::whereJobId($jobId)->get(); |
|||
} |
|||
|
|||
public function requireTaskById($taskId) |
|||
{ |
|||
return Task::findOrFail($taskId); |
|||
} |
|||
|
|||
public function update($jobData = [], $jobId) |
|||
{ |
|||
foreach ($jobData as $key => $value) { |
|||
if (!$jobData[$key]) { |
|||
$jobData[$key] = null; |
|||
} |
|||
|
|||
} |
|||
|
|||
$jobData['price'] = str_replace('.', '', $jobData['price']); |
|||
$job = $this->requireById($jobId); |
|||
$job->update($jobData); |
|||
return $job; |
|||
} |
|||
|
|||
public function tasksReorder($sortedData) |
|||
{ |
|||
$taskOrder = explode(',', $sortedData); |
|||
|
|||
foreach ($taskOrder as $order => $taskId) { |
|||
$task = $this->requireTaskById($taskId); |
|||
$task->position = $order + 1; |
|||
$task->save(); |
|||
} |
|||
|
|||
return $taskOrder; |
|||
} |
|||
} |
|||
@ -1,124 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Projects; |
|||
|
|||
use App\Http\Requests\Features\CreateRequest; |
|||
use App\Http\Requests\Features\UpdateRequest; |
|||
use App\Http\Requests\Features\DeleteRequest; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Entities\Projects\FeaturesRepository; |
|||
|
|||
use Illuminate\Http\Request; |
|||
|
|||
class FeaturesController extends Controller { |
|||
|
|||
private $repo; |
|||
|
|||
public function __construct(FeaturesRepository $repo) |
|||
{ |
|||
$this->repo = $repo; |
|||
} |
|||
|
|||
public function index() |
|||
{ |
|||
$features = $this->repo->getUnfinishedFeatures(); |
|||
return view('features.unfinished', compact('features')); |
|||
} |
|||
|
|||
public function create($projectId) |
|||
{ |
|||
$project = $this->repo->requireProjectById($projectId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
return view('features.create',compact('project','workers')); |
|||
} |
|||
|
|||
public function addFromOtherProject(Request $req, $projectId) |
|||
{ |
|||
$selectedProject = null; |
|||
$project = $this->repo->requireProjectById($projectId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
$projects = $this->repo->getProjectsList(); |
|||
|
|||
if ($req->has('project_id')) { |
|||
$selectedProject = $this->repo->requireProjectById($req->get('project_id')); |
|||
} |
|||
return view('features.add-from-other-project',compact('project','workers','projects','selectedProject')); |
|||
} |
|||
|
|||
public function store(CreateRequest $req, $projectId) |
|||
{ |
|||
$feature = $this->repo->createFeature($req->except('_token'), $projectId); |
|||
flash()->success(trans('feature.created')); |
|||
return redirect()->route('features.show', $feature->id); |
|||
} |
|||
|
|||
public function storeFromOtherProject(Request $req, $projectId) |
|||
{ |
|||
$this->repo->createFeatures($req->except('_token'), $projectId); |
|||
flash()->success(trans('feature.created_from_other_project')); |
|||
return redirect()->route('projects.features', $projectId); |
|||
} |
|||
|
|||
public function show(Request $req, $featureId) |
|||
{ |
|||
$editableTask = null; |
|||
$feature = $this->repo->requireById($featureId); |
|||
|
|||
if ($req->get('action') == 'task_edit' && $req->has('task_id')) { |
|||
$editableTask = $this->repo->requireTaskById($req->get('task_id')); |
|||
} |
|||
|
|||
if ($req->get('action') == 'task_delete' && $req->has('task_id')) { |
|||
$editableTask = $this->repo->requireTaskById($req->get('task_id')); |
|||
} |
|||
|
|||
return view('features.show', compact('feature','editableTask')); |
|||
} |
|||
|
|||
public function edit($featureId) |
|||
{ |
|||
$feature = $this->repo->requireById($featureId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
return view('features.edit',compact('feature','workers')); |
|||
} |
|||
|
|||
public function update(UpdateRequest $req, $featureId) |
|||
{ |
|||
$feature = $this->repo->update($req->except(['_method','_token']), $featureId); |
|||
flash()->success(trans('feature.updated')); |
|||
return redirect()->route('features.show', $feature->id); |
|||
} |
|||
|
|||
public function delete($featureId) |
|||
{ |
|||
$feature = $this->repo->requireById($featureId); |
|||
return view('features.delete', compact('feature')); |
|||
} |
|||
|
|||
public function destroy(DeleteRequest $req, $featureId) |
|||
{ |
|||
$feature = $this->repo->requireById($featureId); |
|||
$projectId = $feature->project_id; |
|||
if ($featureId == $req->get('feature_id')) |
|||
{ |
|||
$feature->tasks()->delete(); |
|||
$feature->delete(); |
|||
flash()->success(trans('feature.deleted')); |
|||
} |
|||
else |
|||
flash()->error(trans('feature.undeleted')); |
|||
|
|||
return redirect()->route('projects.features', $projectId); |
|||
} |
|||
|
|||
public function tasksReorder(Request $req, $featureId) |
|||
{ |
|||
if ($req->ajax()) { |
|||
$data = $this->repo->tasksReorder($req->get('postData')); |
|||
return 'oke'; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Projects; |
|||
|
|||
use App\Entities\Projects\JobsRepository; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\Requests\Jobs\CreateRequest; |
|||
use App\Http\Requests\Jobs\DeleteRequest; |
|||
use App\Http\Requests\Jobs\UpdateRequest; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class JobsController extends Controller |
|||
{ |
|||
|
|||
private $repo; |
|||
|
|||
public function __construct(JobsRepository $repo) |
|||
{ |
|||
$this->repo = $repo; |
|||
} |
|||
|
|||
public function index() |
|||
{ |
|||
$jobs = $this->repo->getUnfinishedJobs(); |
|||
return view('jobs.unfinished', compact('jobs')); |
|||
} |
|||
|
|||
public function create($projectId) |
|||
{ |
|||
$project = $this->repo->requireProjectById($projectId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
return view('jobs.create', compact('project', 'workers')); |
|||
} |
|||
|
|||
public function addFromOtherProject(Request $req, $projectId) |
|||
{ |
|||
$selectedProject = null; |
|||
$project = $this->repo->requireProjectById($projectId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
$projects = $this->repo->getProjectsList(); |
|||
|
|||
if ($req->has('project_id')) { |
|||
$selectedProject = $this->repo->requireProjectById($req->get('project_id')); |
|||
} |
|||
return view('jobs.add-from-other-project', compact('project', 'workers', 'projects', 'selectedProject')); |
|||
} |
|||
|
|||
public function store(CreateRequest $req, $projectId) |
|||
{ |
|||
$job = $this->repo->createJob($req->except('_token'), $projectId); |
|||
flash()->success(trans('job.created')); |
|||
return redirect()->route('jobs.show', $job->id); |
|||
} |
|||
|
|||
public function storeFromOtherProject(Request $req, $projectId) |
|||
{ |
|||
$this->repo->createJobs($req->except('_token'), $projectId); |
|||
flash()->success(trans('job.created_from_other_project')); |
|||
return redirect()->route('projects.jobs', $projectId); |
|||
} |
|||
|
|||
public function show(Request $req, $jobId) |
|||
{ |
|||
$editableTask = null; |
|||
$job = $this->repo->requireById($jobId); |
|||
|
|||
if ($req->get('action') == 'task_edit' && $req->has('task_id')) { |
|||
$editableTask = $this->repo->requireTaskById($req->get('task_id')); |
|||
} |
|||
|
|||
if ($req->get('action') == 'task_delete' && $req->has('task_id')) { |
|||
$editableTask = $this->repo->requireTaskById($req->get('task_id')); |
|||
} |
|||
|
|||
return view('jobs.show', compact('job', 'editableTask')); |
|||
} |
|||
|
|||
public function edit($jobId) |
|||
{ |
|||
$job = $this->repo->requireById($jobId); |
|||
$workers = $this->repo->getWorkersList(); |
|||
return view('jobs.edit', compact('job', 'workers')); |
|||
} |
|||
|
|||
public function update(UpdateRequest $req, $jobId) |
|||
{ |
|||
$job = $this->repo->update($req->except(['_method', '_token']), $jobId); |
|||
flash()->success(trans('job.updated')); |
|||
return redirect()->route('jobs.show', $job->id); |
|||
} |
|||
|
|||
public function delete($jobId) |
|||
{ |
|||
$job = $this->repo->requireById($jobId); |
|||
return view('jobs.delete', compact('job')); |
|||
} |
|||
|
|||
public function destroy(DeleteRequest $req, $jobId) |
|||
{ |
|||
$job = $this->repo->requireById($jobId); |
|||
$projectId = $job->project_id; |
|||
if ($jobId == $req->get('job_id')) { |
|||
$job->tasks()->delete(); |
|||
$job->delete(); |
|||
flash()->success(trans('job.deleted')); |
|||
} else { |
|||
flash()->error(trans('job.undeleted')); |
|||
} |
|||
|
|||
return redirect()->route('projects.jobs', $projectId); |
|||
} |
|||
|
|||
public function tasksReorder(Request $req, $jobId) |
|||
{ |
|||
if ($req->ajax()) { |
|||
$data = $this->repo->tasksReorder($req->get('postData')); |
|||
return 'oke'; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Requests\Features; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Http\Requests\Request; |
|||
|
|||
class DeleteRequest extends Request { |
|||
|
|||
/** |
|||
* Determine if the user is authorized to make this request. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function authorize() |
|||
{ |
|||
$project = Project::findOrFail($this->get('project_id')); |
|||
return auth()->user()->can('manage_features', $project); |
|||
} |
|||
|
|||
/** |
|||
* Get the validation rules that apply to the request. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'feature_id' => 'required', |
|||
'project_id' => 'required', |
|||
]; |
|||
} |
|||
|
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Requests\Features; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Http\Requests\Request; |
|||
|
|||
class UpdateRequest extends Request { |
|||
|
|||
/** |
|||
* Determine if the user is authorized to make this request. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function authorize() |
|||
{ |
|||
$project = Project::findOrFail($this->get('project_id')); |
|||
return auth()->user()->can('manage_features', $project); |
|||
} |
|||
|
|||
/** |
|||
* Get the validation rules that apply to the request. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' => 'required|max:60', |
|||
'price' => 'required', |
|||
'worker_id' => 'required|numeric', |
|||
'type_id' => 'required|numeric', |
|||
'description' => 'max:255', |
|||
]; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Requests\Jobs; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Http\Requests\Request; |
|||
|
|||
class DeleteRequest extends Request |
|||
{ |
|||
|
|||
/** |
|||
* Determine if the user is authorized to make this request. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function authorize() |
|||
{ |
|||
$project = Project::findOrFail($this->get('project_id')); |
|||
return auth()->user()->can('manage_jobs', $project); |
|||
} |
|||
|
|||
/** |
|||
* Get the validation rules that apply to the request. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'job_id' => 'required', |
|||
'project_id' => 'required', |
|||
]; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Requests\Jobs; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Http\Requests\Request; |
|||
|
|||
class UpdateRequest extends Request |
|||
{ |
|||
|
|||
/** |
|||
* Determine if the user is authorized to make this request. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function authorize() |
|||
{ |
|||
$project = Project::findOrFail($this->get('project_id')); |
|||
return auth()->user()->can('manage_jobs', $project); |
|||
} |
|||
|
|||
/** |
|||
* Get the validation rules that apply to the request. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' => 'required|max:60', |
|||
'price' => 'required', |
|||
'worker_id' => 'required|numeric', |
|||
'type_id' => 'required|numeric', |
|||
'description' => 'max:255', |
|||
]; |
|||
} |
|||
|
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
|
|||
class CreateFeaturesTable extends Migration { |
|||
|
|||
/** |
|||
* Run the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function up() |
|||
{ |
|||
Schema::create('features', function(Blueprint $table) |
|||
{ |
|||
$table->increments('id'); |
|||
$table->integer('project_id')->unsigned(); |
|||
$table->string('name', 60); |
|||
$table->string('description')->nullable(); |
|||
$table->integer('worker_id')->unsigned()->nullable(); |
|||
$table->integer('price')->unsigned()->default(0); |
|||
$table->boolean('type_id')->default(1)->comment('1: main, 2: additional'); |
|||
$table->boolean('position')->default(0); |
|||
$table->timestamps(); |
|||
}); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function down() |
|||
{ |
|||
Schema::drop('features'); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
|
|||
class CreateJobsTable extends Migration |
|||
{ |
|||
|
|||
/** |
|||
* Run the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function up() |
|||
{ |
|||
Schema::create('jobs', function (Blueprint $table) { |
|||
$table->increments('id'); |
|||
$table->integer('project_id')->unsigned(); |
|||
$table->string('name', 60); |
|||
$table->string('description')->nullable(); |
|||
$table->integer('worker_id')->unsigned()->nullable(); |
|||
$table->integer('price')->unsigned()->default(0); |
|||
$table->boolean('type_id')->default(1)->comment('1: main, 2: additional'); |
|||
$table->boolean('position')->default(0); |
|||
$table->timestamps(); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function down() |
|||
{ |
|||
Schema::drop('jobs'); |
|||
} |
|||
|
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
// Labels
|
|||
'feature' => 'Fitur', |
|||
'features' => 'Daftar Fitur', |
|||
'on_progress' => 'Fitur on Progress', |
|||
'search' => 'Cari Fitur', |
|||
'found' => 'Fitur ditemukan', |
|||
'not_found' => 'Fitur tidak ditemukan', |
|||
'tasks' => 'Daftar Task', |
|||
'price_total' => 'Nilai Fitur Total', |
|||
'tasks_count' => 'Jumlah Task', |
|||
'empty' => 'Belum ada Fitur', |
|||
'back_to_index' => 'Kembali ke daftar Fitur', |
|||
|
|||
// Actions
|
|||
'create' => 'Input Fitur Baru', |
|||
'created' => 'Input Fitur baru telah berhasil.', |
|||
'show' => 'Detail Fitur', |
|||
'edit' => 'Edit Fitur', |
|||
'update' => 'Update Fitur', |
|||
'updated' => 'Update data Fitur telah berhasil.', |
|||
'delete' => 'Hapus Fitur', |
|||
'deleted' => 'Hapus data Fitur telah berhasil.', |
|||
'undeleted' => 'Data Fitur gagal dihapus.', |
|||
'add_from_other_project' => 'Tambah Fitur dari Project Lain', |
|||
'sort_tasks' => 'Urutkan Prioritas Task', |
|||
|
|||
// Attributes
|
|||
'name' => 'Nama Fitur', |
|||
'description' => 'Deskripsi', |
|||
'progress' => 'Progress', |
|||
'worker' => 'Pekerja', |
|||
'price' => 'Biaya Pengerjaan', |
|||
'type' => 'Jenis Fitur', |
|||
]; |
|||
@ -0,0 +1,37 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
// Labels
|
|||
'job' => 'Job', |
|||
'jobs' => 'Daftar Job', |
|||
'on_progress' => 'Job on Progress', |
|||
'search' => 'Cari Job', |
|||
'found' => 'Job ditemukan', |
|||
'not_found' => 'Job tidak ditemukan', |
|||
'tasks' => 'Daftar Task', |
|||
'price_total' => 'Nilai Job Total', |
|||
'tasks_count' => 'Jumlah Task', |
|||
'empty' => 'Belum ada Job', |
|||
'back_to_index' => 'Kembali ke daftar Job', |
|||
|
|||
// Actions
|
|||
'create' => 'Input Job Baru', |
|||
'created' => 'Input Job baru telah berhasil.', |
|||
'show' => 'Detail Job', |
|||
'edit' => 'Edit Job', |
|||
'update' => 'Update Job', |
|||
'updated' => 'Update data Job telah berhasil.', |
|||
'delete' => 'Hapus Job', |
|||
'deleted' => 'Hapus data Job telah berhasil.', |
|||
'undeleted' => 'Data Job gagal dihapus.', |
|||
'add_from_other_project' => 'Tambah Job dari Project Lain', |
|||
'sort_tasks' => 'Urutkan Prioritas Task', |
|||
|
|||
// Attributes
|
|||
'name' => 'Nama Job', |
|||
'description' => 'Deskripsi', |
|||
'progress' => 'Progress', |
|||
'worker' => 'Pekerja', |
|||
'price' => 'Biaya Pengerjaan', |
|||
'type' => 'Jenis Job', |
|||
]; |
|||
@ -1,6 +0,0 @@ |
|||
<ul class="breadcrumb hidden-print"> |
|||
<li>{{ link_to_route('projects.index',trans('project.projects')) }}</li> |
|||
<li>{{ $feature->present()->projectLink }}</li> |
|||
<li>{{ $feature->present()->projectFeaturesLink }}</li> |
|||
<li class="active">{{ isset($title) ? $title : $feature->name }}</li> |
|||
</ul> |
|||
@ -1,14 +0,0 @@ |
|||
<div class="panel panel-default"> |
|||
<div class="panel-heading"><h3 class="panel-title">{{ trans('feature.show') }}</h3></div> |
|||
<table class="table table-condensed"> |
|||
<tbody> |
|||
<tr><th class="col-md-4">{{ trans('feature.name') }}</th><td class="col-md-8">{{ $feature->name }}</td></tr> |
|||
<tr><th>{{ trans('feature.type') }}</th><td>{{ $feature->type() }}</td></tr> |
|||
<tr><th>{{ trans('feature.price') }}</th><td>{{ formatRp($feature->price) }}</td></tr> |
|||
<tr><th>{{ trans('feature.tasks_count') }}</th><td>{{ $feature->tasks->count() }}</td></tr> |
|||
<tr><th>{{ trans('feature.progress') }}</th><td>{{ formatDecimal($feature->tasks->avg('progress')) }}%</td></tr> |
|||
<tr><th>{{ trans('feature.worker') }}</th><td>{{ $feature->worker->name }}</td></tr> |
|||
<tr><th>{{ trans('feature.description') }}</th><td>{!! nl2br($feature->description) !!}</td></tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
@ -1,63 +0,0 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('project.features')) |
|||
|
|||
@section('content') |
|||
<h1 class="page-header"> |
|||
Daftar Fitur on Progress |
|||
</h1> |
|||
|
|||
<div class="panel panel-default"> |
|||
<table class="table table-condensed"> |
|||
<thead> |
|||
<th>{{ trans('app.table_no') }}</th> |
|||
<th>{{ trans('project.name') }}</th> |
|||
<th>{{ trans('feature.name') }}</th> |
|||
<th class="text-center">{{ trans('feature.tasks_count') }}</th> |
|||
<th class="text-center">{{ trans('feature.progress') }}</th> |
|||
<th class="text-right">{{ trans('feature.price') }}</th> |
|||
<th>{{ trans('feature.worker') }}</th> |
|||
<th>{{ trans('app.action') }}</th> |
|||
</thead> |
|||
<tbody> |
|||
@forelse($features as $key => $feature) |
|||
<tr> |
|||
<td>{{ 1 + $key }}</td> |
|||
<td>{{ $feature->project->name }}</td> |
|||
<td> |
|||
{{ $feature->name }} |
|||
@if ($feature->tasks->isEmpty() == false) |
|||
<ul> |
|||
@foreach($feature->tasks as $task) |
|||
<li style="cursor:pointer" title="{{ $task->progress }} %"> |
|||
<i class="fa fa-battery-{{ ceil(4 * $task->progress/100) }}"></i> |
|||
{{ $task->name }} |
|||
</li> |
|||
@endforeach |
|||
</ul> |
|||
@endif |
|||
</td> |
|||
<td class="text-center">{{ $feature->tasks_count = $feature->tasks->count() }}</td> |
|||
<td class="text-center">{{ formatDecimal($feature->progress = $feature->tasks->avg('progress')) }} %</td> |
|||
<td class="text-right">{{ formatRp($feature->price) }}</td> |
|||
<td>{{ $feature->worker->name }}</td> |
|||
<td> |
|||
{!! link_to_route('features.show', trans('app.show'),[$feature->id],['class' => 'btn btn-info btn-xs']) !!} |
|||
</td> |
|||
</tr> |
|||
@empty |
|||
<tr><td colspan="7">{{ trans('feature.empty') }}</td></tr> |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th class="text-right" colspan="3">Total</th> |
|||
<th class="text-center">{{ $features->sum('tasks_count') }}</th> |
|||
<th class="text-center">{{ formatDecimal($features->avg('progress')) }} %</th> |
|||
<th class="text-right">{{ formatRp($features->sum('price')) }}</th> |
|||
<th colspan="2"></th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
@endsection |
|||
@ -1,25 +1,25 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('feature.delete')) |
|||
@section('title', trans('job.delete')) |
|||
|
|||
@section('content') |
|||
<h1 class="page-header"> |
|||
<div class="pull-right"> |
|||
{!! FormField::delete([ |
|||
'route'=>['features.destroy',$feature->id]], |
|||
'route'=>['jobs.destroy',$job->id]], |
|||
trans('app.delete_confirm_button'), |
|||
['class'=>'btn btn-danger'], |
|||
[ |
|||
'feature_id'=>$feature->id, |
|||
'project_id'=>$feature->project_id, |
|||
'job_id'=>$job->id, |
|||
'project_id'=>$job->project_id, |
|||
]) !!} |
|||
</div> |
|||
{{ trans('app.delete_confirm') }} |
|||
{!! link_to_route('features.show', trans('app.cancel'), [$feature->id], ['class' => 'btn btn-default']) !!} |
|||
{!! link_to_route('jobs.show', trans('app.cancel'), [$job->id], ['class' => 'btn btn-default']) !!} |
|||
</h1> |
|||
<div class="row"> |
|||
<div class="col-md-4"> |
|||
@include('features.partials.feature-show') |
|||
@include('jobs.partials.job-show') |
|||
</div> |
|||
</div> |
|||
@endsection |
|||
@endsection |
|||
@ -0,0 +1,6 @@ |
|||
<ul class="breadcrumb hidden-print"> |
|||
<li>{{ link_to_route('projects.index',trans('project.projects')) }}</li> |
|||
<li>{{ $job->present()->projectLink }}</li> |
|||
<li>{{ $job->present()->projectJobsLink }}</li> |
|||
<li class="active">{{ isset($title) ? $title : $job->name }}</li> |
|||
</ul> |
|||
@ -0,0 +1,14 @@ |
|||
<div class="panel panel-default"> |
|||
<div class="panel-heading"><h3 class="panel-title">{{ trans('job.show') }}</h3></div> |
|||
<table class="table table-condensed"> |
|||
<tbody> |
|||
<tr><th class="col-md-4">{{ trans('job.name') }}</th><td class="col-md-8">{{ $job->name }}</td></tr> |
|||
<tr><th>{{ trans('job.type') }}</th><td>{{ $job->type() }}</td></tr> |
|||
<tr><th>{{ trans('job.price') }}</th><td>{{ formatRp($job->price) }}</td></tr> |
|||
<tr><th>{{ trans('job.tasks_count') }}</th><td>{{ $job->tasks->count() }}</td></tr> |
|||
<tr><th>{{ trans('job.progress') }}</th><td>{{ formatDecimal($job->tasks->avg('progress')) }}%</td></tr> |
|||
<tr><th>{{ trans('job.worker') }}</th><td>{{ $job->worker->name }}</td></tr> |
|||
<tr><th>{{ trans('job.description') }}</th><td>{!! nl2br($job->description) !!}</td></tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
@ -1,27 +1,27 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('feature.show') . ' | ' . $feature->name . ' | ' . $feature->project->name) |
|||
@section('title', trans('job.show') . ' | ' . $job->name . ' | ' . $job->project->name) |
|||
|
|||
@section('content') |
|||
@include('features.partials.breadcrumb') |
|||
@include('jobs.partials.breadcrumb') |
|||
|
|||
<h1 class="page-header"> |
|||
<div class="pull-right"> |
|||
{!! html_link_to_route('features.create', trans('feature.create'), [$feature->project_id], ['class' => 'btn btn-success','icon' => 'plus']) !!} |
|||
{!! link_to_route('features.edit', trans('feature.edit'), [$feature->id], ['class' => 'btn btn-warning']) !!} |
|||
{!! link_to_route('projects.features', trans('feature.back_to_index'), [$feature->project_id, '#' . $feature->id], ['class' => 'btn btn-default']) !!} |
|||
{!! html_link_to_route('jobs.create', trans('job.create'), [$job->project_id], ['class' => 'btn btn-success','icon' => 'plus']) !!} |
|||
{!! link_to_route('jobs.edit', trans('job.edit'), [$job->id], ['class' => 'btn btn-warning']) !!} |
|||
{!! link_to_route('projects.jobs', trans('job.back_to_index'), [$job->project_id, '#' . $job->id], ['class' => 'btn btn-default']) !!} |
|||
</div> |
|||
{{ $feature->name }} <small>{{ trans('feature.show') }}</small> |
|||
{{ $job->name }} <small>{{ trans('job.show') }}</small> |
|||
</h1> |
|||
<div class="row"> |
|||
<div class="col-md-5"> |
|||
@include('features.partials.feature-show') |
|||
@include('jobs.partials.job-show') |
|||
</div> |
|||
<div class="col-sm-7"> |
|||
@include('features.partials.feature-tasks-operation') |
|||
@include('jobs.partials.job-tasks-operation') |
|||
</div> |
|||
</div> |
|||
@include('features.partials.feature-tasks') |
|||
@include('jobs.partials.job-tasks') |
|||
@endsection |
|||
|
|||
@if (Request::get('action') == 'task_edit' && $editableTask) |
|||
@ -0,0 +1,63 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('project.jobs')) |
|||
|
|||
@section('content') |
|||
<h1 class="page-header"> |
|||
Daftar Fitur on Progress |
|||
</h1> |
|||
|
|||
<div class="panel panel-default"> |
|||
<table class="table table-condensed"> |
|||
<thead> |
|||
<th>{{ trans('app.table_no') }}</th> |
|||
<th>{{ trans('project.name') }}</th> |
|||
<th>{{ trans('job.name') }}</th> |
|||
<th class="text-center">{{ trans('job.tasks_count') }}</th> |
|||
<th class="text-center">{{ trans('job.progress') }}</th> |
|||
<th class="text-right">{{ trans('job.price') }}</th> |
|||
<th>{{ trans('job.worker') }}</th> |
|||
<th>{{ trans('app.action') }}</th> |
|||
</thead> |
|||
<tbody> |
|||
@forelse($jobs as $key => $job) |
|||
<tr> |
|||
<td>{{ 1 + $key }}</td> |
|||
<td>{{ $job->project->name }}</td> |
|||
<td> |
|||
{{ $job->name }} |
|||
@if ($job->tasks->isEmpty() == false) |
|||
<ul> |
|||
@foreach($job->tasks as $task) |
|||
<li style="cursor:pointer" title="{{ $task->progress }} %"> |
|||
<i class="fa fa-battery-{{ ceil(4 * $task->progress/100) }}"></i> |
|||
{{ $task->name }} |
|||
</li> |
|||
@endforeach |
|||
</ul> |
|||
@endif |
|||
</td> |
|||
<td class="text-center">{{ $job->tasks_count = $job->tasks->count() }}</td> |
|||
<td class="text-center">{{ formatDecimal($job->progress = $job->tasks->avg('progress')) }} %</td> |
|||
<td class="text-right">{{ formatRp($job->price) }}</td> |
|||
<td>{{ $job->worker->name }}</td> |
|||
<td> |
|||
{!! link_to_route('jobs.show', trans('app.show'),[$job->id],['class' => 'btn btn-info btn-xs']) !!} |
|||
</td> |
|||
</tr> |
|||
@empty |
|||
<tr><td colspan="7">{{ trans('job.empty') }}</td></tr> |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th class="text-right" colspan="3">Total</th> |
|||
<th class="text-center">{{ $jobs->sum('tasks_count') }}</th> |
|||
<th class="text-center">{{ formatDecimal($jobs->avg('progress')) }} %</th> |
|||
<th class="text-right">{{ formatRp($jobs->sum('price')) }}</th> |
|||
<th colspan="2"></th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
@endsection |
|||
@ -1,129 +0,0 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('project.features') . ' | ' . $project->name) |
|||
|
|||
@section('content') |
|||
@include('projects.partials.breadcrumb',['title' => trans('project.features')]) |
|||
|
|||
<h1 class="page-header"> |
|||
<div class="pull-right"> |
|||
{!! html_link_to_route('features.create', trans('feature.create'), [$project->id], ['class' => 'btn btn-success','icon' => 'plus']) !!} |
|||
{!! html_link_to_route('features.add-from-other-project', trans('feature.add_from_other_project'), [$project->id], ['class' => 'btn btn-default','icon' => 'plus']) !!} |
|||
</div> |
|||
{{ $project->name }} <small>{{ trans('project.features') }}</small> |
|||
</h1> |
|||
|
|||
@include('projects.partials.nav-tabs') |
|||
|
|||
@if ($features->isEmpty()) |
|||
<p>{{ trans('project.no_features') }}, |
|||
{{ link_to_route('features.create', trans('feature.create'), [$project->id]) }}. |
|||
</p> |
|||
@else |
|||
|
|||
@foreach($features->groupBy('type_id') as $key => $groupedFeatures) |
|||
|
|||
<div id="project-features" class="panel panel-default table-responsive"> |
|||
<div class="panel-heading"> |
|||
<div class="pull-right"> |
|||
@if (request('action') == 'sort_features') |
|||
{{ link_to_route('projects.features', trans('app.done'), [$project->id], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin-top: -2px; margin-left: 6px; margin-right: -8px']) }} |
|||
@else |
|||
{{ link_to_route('projects.features', trans('project.sort_features'), [$project->id, 'action' => 'sort_features', '#project-features'], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin-top: -2px; margin-left: 6px; margin-right: -8px']) }} |
|||
|
|||
{!! link_to_route('projects.features-export', trans('project.features_export_html'), [$project->id, 'html', 'feature_type' => $key], ['class' => '','target' => '_blank']) !!} | |
|||
{!! link_to_route('projects.features-export', trans('project.features_export_excel'), [$project->id, 'excel', 'feature_type' => $key], ['class' => '']) !!} | |
|||
{!! link_to_route('projects.features-export', trans('project.features_export_progress_excel'), [$project->id, 'excel-progress', 'feature_type' => $key], ['class' => '']) !!} |
|||
@endif |
|||
</div> |
|||
<h3 class="panel-title"> |
|||
{{ $key == 1 ? 'Daftar Fitur' : 'Fitur Tambahan' }} |
|||
</h3> |
|||
</div> |
|||
<table class="table table-condensed table-striped"> |
|||
<thead> |
|||
<th>{{ trans('app.table_no') }}</th> |
|||
<th>{{ trans('feature.name') }}</th> |
|||
<th class="text-center">{{ trans('feature.tasks_count') }}</th> |
|||
<th class="text-center">{{ trans('feature.progress') }}</th> |
|||
<th class="text-right">{{ trans('feature.price') }}</th> |
|||
{{-- <th>{{ trans('feature.worker') }}</th> --}} |
|||
<th class="text-center">{{ trans('app.action') }}</th> |
|||
</thead> |
|||
<tbody class="sort-features"> |
|||
@forelse($groupedFeatures as $key => $feature) |
|||
@php |
|||
$no = 1 + $key; |
|||
$feature->progress = $feature->tasks->avg('progress'); |
|||
@endphp |
|||
<tr id="{{ $feature->id }}" {!! $feature->progress <= 50 ? 'style="background-color: #faebcc"' : '' !!}> |
|||
<td>{{ $no }}</td> |
|||
<td> |
|||
{{ $feature->name }} |
|||
@if ($feature->tasks->isEmpty() == false) |
|||
<ul> |
|||
@foreach($feature->tasks as $task) |
|||
<li>{{ $task->name }}</li> |
|||
@endforeach |
|||
</ul> |
|||
@endif |
|||
</td> |
|||
<td class="text-center">{{ $feature->tasks_count = $feature->tasks->count() }}</td> |
|||
<td class="text-center">{{ formatDecimal($feature->progress) }} %</td> |
|||
<td class="text-right">{{ formatRp($feature->price) }}</td> |
|||
{{-- <td>{{ $feature->worker->name }}</td> --}} |
|||
<td class="text-center"> |
|||
{!! html_link_to_route('features.show', '',[$feature->id],['icon' => 'search', 'title' => 'Lihat ' . trans('feature.show'), 'class' => 'btn btn-info btn-xs','id' => 'show-feature-' . $feature->id]) !!} |
|||
{!! html_link_to_route('features.edit', '',[$feature->id],['icon' => 'edit', 'title' => trans('feature.edit'), 'class' => 'btn btn-warning btn-xs']) !!} |
|||
</td> |
|||
</tr> |
|||
@empty |
|||
<tr><td colspan="7">{{ trans('feature.empty') }}</td></tr> |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th class="text-right" colspan="2">Total</th> |
|||
<th class="text-center">{{ $groupedFeatures->sum('tasks_count') }}</th> |
|||
<th class="text-center"> |
|||
<span title="Total Progress">{{ formatDecimal($groupedFeatures->sum('progress') / $groupedFeatures->count()) }} %</span> |
|||
<span title="Overal Progress" style="font-weight:300">({{ formatDecimal($project->getFeatureOveralProgress()) }} %)</span> |
|||
</th> |
|||
<th class="text-right">{{ formatRp($groupedFeatures->sum('price')) }}</th> |
|||
<th colspan="2"> |
|||
@if (request('action') == 'sort_features') |
|||
{{ link_to_route('projects.features', trans('app.done'), [$project->id], ['class' => 'btn btn-default btn-xs pull-right']) }} |
|||
@else |
|||
{{ link_to_route('projects.features', trans('project.sort_features'), [$project->id, 'action' => 'sort_features', '#project-features'], ['class' => 'btn btn-default btn-xs pull-right']) }} |
|||
@endif |
|||
</th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
@endforeach |
|||
|
|||
@endif |
|||
@endsection |
|||
|
|||
@if (request('action') == 'sort_features') |
|||
|
|||
@section('ext_js') |
|||
{!! Html::script(url('assets/js/plugins/jquery-ui.min.js')) !!} |
|||
@endsection |
|||
|
|||
@section('script') |
|||
|
|||
<script> |
|||
(function() { |
|||
$('.sort-features').sortable({ |
|||
update: function (event, ui) { |
|||
var data = $(this).sortable('toArray').toString(); |
|||
$.post("{{ route('projects.features-reorder', $project->id) }}", {postData: data}); |
|||
} |
|||
}); |
|||
})(); |
|||
</script> |
|||
@endsection |
|||
|
|||
@endif |
|||
@ -0,0 +1,129 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', trans('project.jobs') . ' | ' . $project->name) |
|||
|
|||
@section('content') |
|||
@include('projects.partials.breadcrumb',['title' => trans('project.jobs')]) |
|||
|
|||
<h1 class="page-header"> |
|||
<div class="pull-right"> |
|||
{!! html_link_to_route('jobs.create', trans('job.create'), [$project->id], ['class' => 'btn btn-success','icon' => 'plus']) !!} |
|||
{!! html_link_to_route('jobs.add-from-other-project', trans('job.add_from_other_project'), [$project->id], ['class' => 'btn btn-default','icon' => 'plus']) !!} |
|||
</div> |
|||
{{ $project->name }} <small>{{ trans('project.jobs') }}</small> |
|||
</h1> |
|||
|
|||
@include('projects.partials.nav-tabs') |
|||
|
|||
@if ($jobs->isEmpty()) |
|||
<p>{{ trans('project.no_jobs') }}, |
|||
{{ link_to_route('jobs.create', trans('job.create'), [$project->id]) }}. |
|||
</p> |
|||
@else |
|||
|
|||
@foreach($jobs->groupBy('type_id') as $key => $groupedJobs) |
|||
|
|||
<div id="project-jobs" class="panel panel-default table-responsive"> |
|||
<div class="panel-heading"> |
|||
<div class="pull-right"> |
|||
@if (request('action') == 'sort_jobs') |
|||
{{ link_to_route('projects.jobs', trans('app.done'), [$project->id], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin-top: -2px; margin-left: 6px; margin-right: -8px']) }} |
|||
@else |
|||
{{ link_to_route('projects.jobs', trans('project.sort_jobs'), [$project->id, 'action' => 'sort_jobs', '#project-jobs'], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin-top: -2px; margin-left: 6px; margin-right: -8px']) }} |
|||
|
|||
{!! link_to_route('projects.jobs-export', trans('project.jobs_export_html'), [$project->id, 'html', 'job_type' => $key], ['class' => '','target' => '_blank']) !!} | |
|||
{!! link_to_route('projects.jobs-export', trans('project.jobs_export_excel'), [$project->id, 'excel', 'job_type' => $key], ['class' => '']) !!} | |
|||
{!! link_to_route('projects.jobs-export', trans('project.jobs_export_progress_excel'), [$project->id, 'excel-progress', 'job_type' => $key], ['class' => '']) !!} |
|||
@endif |
|||
</div> |
|||
<h3 class="panel-title"> |
|||
{{ $key == 1 ? 'Daftar Fitur' : 'Fitur Tambahan' }} |
|||
</h3> |
|||
</div> |
|||
<table class="table table-condensed table-striped"> |
|||
<thead> |
|||
<th>{{ trans('app.table_no') }}</th> |
|||
<th>{{ trans('job.name') }}</th> |
|||
<th class="text-center">{{ trans('job.tasks_count') }}</th> |
|||
<th class="text-center">{{ trans('job.progress') }}</th> |
|||
<th class="text-right">{{ trans('job.price') }}</th> |
|||
{{-- <th>{{ trans('job.worker') }}</th> --}} |
|||
<th class="text-center">{{ trans('app.action') }}</th> |
|||
</thead> |
|||
<tbody class="sort-jobs"> |
|||
@forelse($groupedJobs as $key => $job) |
|||
@php |
|||
$no = 1 + $key; |
|||
$job->progress = $job->tasks->avg('progress'); |
|||
@endphp |
|||
<tr id="{{ $job->id }}" {!! $job->progress <= 50 ? 'style="background-color: #faebcc"' : '' !!}> |
|||
<td>{{ $no }}</td> |
|||
<td> |
|||
{{ $job->name }} |
|||
@if ($job->tasks->isEmpty() == false) |
|||
<ul> |
|||
@foreach($job->tasks as $task) |
|||
<li>{{ $task->name }}</li> |
|||
@endforeach |
|||
</ul> |
|||
@endif |
|||
</td> |
|||
<td class="text-center">{{ $job->tasks_count = $job->tasks->count() }}</td> |
|||
<td class="text-center">{{ formatDecimal($job->progress) }} %</td> |
|||
<td class="text-right">{{ formatRp($job->price) }}</td> |
|||
{{-- <td>{{ $job->worker->name }}</td> --}} |
|||
<td class="text-center"> |
|||
{!! html_link_to_route('jobs.show', '',[$job->id],['icon' => 'search', 'title' => 'Lihat ' . trans('job.show'), 'class' => 'btn btn-info btn-xs','id' => 'show-job-' . $job->id]) !!} |
|||
{!! html_link_to_route('jobs.edit', '',[$job->id],['icon' => 'edit', 'title' => trans('job.edit'), 'class' => 'btn btn-warning btn-xs']) !!} |
|||
</td> |
|||
</tr> |
|||
@empty |
|||
<tr><td colspan="7">{{ trans('job.empty') }}</td></tr> |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th class="text-right" colspan="2">Total</th> |
|||
<th class="text-center">{{ $groupedJobs->sum('tasks_count') }}</th> |
|||
<th class="text-center"> |
|||
<span title="Total Progress">{{ formatDecimal($groupedJobs->sum('progress') / $groupedJobs->count()) }} %</span> |
|||
<span title="Overal Progress" style="font-weight:300">({{ formatDecimal($project->getJobOveralProgress()) }} %)</span> |
|||
</th> |
|||
<th class="text-right">{{ formatRp($groupedJobs->sum('price')) }}</th> |
|||
<th colspan="2"> |
|||
@if (request('action') == 'sort_jobs') |
|||
{{ link_to_route('projects.jobs', trans('app.done'), [$project->id], ['class' => 'btn btn-default btn-xs pull-right']) }} |
|||
@else |
|||
{{ link_to_route('projects.jobs', trans('project.sort_jobs'), [$project->id, 'action' => 'sort_jobs', '#project-jobs'], ['class' => 'btn btn-default btn-xs pull-right']) }} |
|||
@endif |
|||
</th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
@endforeach |
|||
|
|||
@endif |
|||
@endsection |
|||
|
|||
@if (request('action') == 'sort_jobs') |
|||
|
|||
@section('ext_js') |
|||
{!! Html::script(url('assets/js/plugins/jquery-ui.min.js')) !!} |
|||
@endsection |
|||
|
|||
@section('script') |
|||
|
|||
<script> |
|||
(function() { |
|||
$('.sort-jobs').sortable({ |
|||
update: function (event, ui) { |
|||
var data = $(this).sortable('toArray').toString(); |
|||
$.post("{{ route('projects.jobs-reorder', $project->id) }}", {postData: data}); |
|||
} |
|||
}); |
|||
})(); |
|||
</script> |
|||
@endsection |
|||
|
|||
@endif |
|||
@ -1,4 +1,4 @@ |
|||
<?php |
|||
|
|||
Route::resource('projects', 'ProjectsController'); |
|||
Route::get('projects/{project}/features', ['as' => 'projects.features', 'uses' => 'ProjectsController@features']); |
|||
Route::get('projects/{project}/jobs', ['as' => 'projects.jobs', 'uses' => 'ProjectsController@jobs']); |
|||
@ -1,190 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace Tests\Feature; |
|||
|
|||
use App\Entities\Partners\Customer; |
|||
use App\Entities\Projects\Feature; |
|||
use App\Entities\Projects\Project; |
|||
use App\Entities\Projects\Task; |
|||
use App\Entities\Users\User; |
|||
use Tests\TestCase; |
|||
|
|||
class ManageFeaturesTest extends TestCase |
|||
{ |
|||
/** @test */ |
|||
public function admin_can_entry_feature() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
|
|||
$worker = $this->createUser(); |
|||
|
|||
$this->visit(route('projects.features', $project->id)); |
|||
$this->click(trans('feature.create')); |
|||
$this->seePageIs(route('features.create', $project->id)); |
|||
|
|||
$this->submitForm(trans('feature.create'), [ |
|||
'name' => 'Nama Fitur Baru', |
|||
'price' => 100000, |
|||
'worker_id' => $worker->id, |
|||
'type_id' => 1, |
|||
'description' => 'Similique, eligendi fuga animi?', |
|||
]); |
|||
|
|||
$this->see(trans('feature.created')); |
|||
|
|||
$this->seeInDatabase('features', [ |
|||
'name' => 'Nama Fitur Baru', |
|||
'price' => 100000, |
|||
'worker_id' => $worker->id, |
|||
'type_id' => 1, |
|||
'project_id' => $project->id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_edit_feature_data() |
|||
{ |
|||
$users = factory(User::class, 3)->create(); |
|||
$this->actingAs($users[0]); |
|||
|
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
|
|||
$feature = factory(Feature::class)->create(['worker_id' => $users[1]->id, 'project_id' => $project->id]); |
|||
|
|||
$this->visit(route('features.edit', $feature->id)); |
|||
|
|||
$this->submitForm(trans('feature.update'), [ |
|||
'name' => 'Nama Fitur Edit', |
|||
'price' => 33333, |
|||
'worker_id' => $users[2]->id, |
|||
'type_id' => 2, |
|||
]); |
|||
|
|||
$this->seePageIs(route('features.show', $feature->id)); |
|||
|
|||
$this->see(trans('feature.updated')); |
|||
|
|||
$this->seeInDatabase('features', [ |
|||
'name' => 'Nama Fitur Edit', |
|||
'price' => 33333, |
|||
'worker_id' => $users[2]->id, |
|||
'project_id' => $project->id, |
|||
'type_id' => 2, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_delete_a_feature() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
$feature = factory(Feature::class)->create(['project_id' => $project->id]); |
|||
$tasks = factory(Task::class, 2)->create(['feature_id' => $feature->id]); |
|||
|
|||
$this->seeInDatabase('features', [ |
|||
'name' => $feature->name, |
|||
'price' => $feature->price, |
|||
'project_id' => $project->id, |
|||
]); |
|||
|
|||
$this->visit(route('features.show', $feature->id)); |
|||
|
|||
$this->click(trans('app.edit')); |
|||
$this->click(trans('feature.delete')); |
|||
$this->press(trans('app.delete_confirm_button')); |
|||
|
|||
$this->seePageIs(route('projects.features', $project->id)); |
|||
|
|||
$this->see(trans('feature.deleted')); |
|||
|
|||
$this->notSeeInDatabase('features', [ |
|||
'name' => $feature->name, |
|||
'price' => $feature->price, |
|||
'project_id' => $project->id, |
|||
]); |
|||
|
|||
$this->notSeeInDatabase('tasks', [ |
|||
'feature_id' => $feature->id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_see_a_feature() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$project = factory(Project::class)->create(); |
|||
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1]); |
|||
|
|||
$this->visit(route('projects.features', $project->id)); |
|||
$this->click('show-feature-'.$feature->id); |
|||
$this->seePageIs(route('features.show', $project->id)); |
|||
$this->see(trans('feature.show')); |
|||
$this->see($feature->name); |
|||
$this->see(formatRp($feature->price)); |
|||
$this->see($feature->worker->name); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_may_clone_many_features_from_other_projects() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
$customer = factory(Customer::class)->create(); |
|||
$projects = factory(Project::class, 2)->create(['customer_id' => $customer->id]); |
|||
$features = factory(Feature::class, 3)->create(['project_id' => $projects[0]->id]); |
|||
$tasks1 = factory(Task::class, 3)->create(['feature_id' => $features[0]->id]); |
|||
$tasks2 = factory(Task::class, 3)->create(['feature_id' => $features[1]->id]); |
|||
|
|||
$this->visit(route('projects.features', $projects[1]->id)); |
|||
|
|||
$this->click(trans('feature.add_from_other_project')); |
|||
$this->seePageIs(route('features.add-from-other-project', $projects[1]->id)); |
|||
|
|||
$this->select($projects[0]->id, 'project_id'); |
|||
$this->press(trans('project.show_features')); |
|||
$this->seePageIs(route('features.add-from-other-project', [$projects[1]->id, 'project_id' => $projects[0]->id])); |
|||
|
|||
$form = $this->getForm(trans('feature.create')); |
|||
$form['feature_ids'][$features[0]->id]->tick(); |
|||
$form['feature_ids'][$features[1]->id]->tick(); |
|||
$form[$features[0]->id.'_task_ids'][$tasks1[0]->id]->tick(); |
|||
$form[$features[0]->id.'_task_ids'][$tasks1[1]->id]->tick(); |
|||
$form[$features[0]->id.'_task_ids'][$tasks1[2]->id]->tick(); |
|||
$form[$features[1]->id.'_task_ids'][$tasks2[0]->id]->tick(); |
|||
$form[$features[1]->id.'_task_ids'][$tasks2[1]->id]->tick(); |
|||
$form[$features[1]->id.'_task_ids'][$tasks2[2]->id]->tick(); |
|||
$this->makeRequestUsingForm($form); |
|||
|
|||
$this->seePageIs(route('projects.features', $projects[1]->id)); |
|||
|
|||
$this->see(trans('feature.created_from_other_project')); |
|||
|
|||
$this->seeInDatabase('features', [ |
|||
'project_id' => $projects[1]->id, |
|||
'name' => $features[0]->name, |
|||
'price' => $features[0]->price, |
|||
'worker_id' => $features[0]->worker_id, |
|||
]); |
|||
|
|||
$this->seeInDatabase('features', [ |
|||
'project_id' => $projects[1]->id, |
|||
'name' => $features[1]->name, |
|||
'price' => $features[1]->price, |
|||
'worker_id' => $features[1]->worker_id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_see_unfinished_features_list() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$this->visit(route('features.index')); |
|||
$this->seePageIs(route('features.index')); |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
<?php |
|||
|
|||
namespace Tests\Feature; |
|||
|
|||
use App\Entities\Partners\Customer; |
|||
use App\Entities\Projects\Job; |
|||
use App\Entities\Projects\Project; |
|||
use App\Entities\Projects\Task; |
|||
use App\Entities\Users\User; |
|||
use Tests\TestCase; |
|||
|
|||
class ManageJobsTest extends TestCase |
|||
{ |
|||
/** @test */ |
|||
public function admin_can_entry_job() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
|
|||
$worker = $this->createUser(); |
|||
|
|||
$this->visit(route('projects.jobs', $project->id)); |
|||
$this->click(trans('job.create')); |
|||
$this->seePageIs(route('jobs.create', $project->id)); |
|||
|
|||
$this->submitForm(trans('job.create'), [ |
|||
'name' => 'Nama Fitur Baru', |
|||
'price' => 100000, |
|||
'worker_id' => $worker->id, |
|||
'type_id' => 1, |
|||
'description' => 'Similique, eligendi fuga animi?', |
|||
]); |
|||
|
|||
$this->see(trans('job.created')); |
|||
|
|||
$this->seeInDatabase('jobs', [ |
|||
'name' => 'Nama Fitur Baru', |
|||
'price' => 100000, |
|||
'worker_id' => $worker->id, |
|||
'type_id' => 1, |
|||
'project_id' => $project->id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_edit_job_data() |
|||
{ |
|||
$users = factory(User::class, 3)->create(); |
|||
$this->actingAs($users[0]); |
|||
|
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
|
|||
$job = factory(Job::class)->create(['worker_id' => $users[1]->id, 'project_id' => $project->id]); |
|||
|
|||
$this->visit(route('jobs.edit', $job->id)); |
|||
|
|||
$this->submitForm(trans('job.update'), [ |
|||
'name' => 'Nama Fitur Edit', |
|||
'price' => 33333, |
|||
'worker_id' => $users[2]->id, |
|||
'type_id' => 2, |
|||
]); |
|||
|
|||
$this->seePageIs(route('jobs.show', $job->id)); |
|||
|
|||
$this->see(trans('job.updated')); |
|||
|
|||
$this->seeInDatabase('jobs', [ |
|||
'name' => 'Nama Fitur Edit', |
|||
'price' => 33333, |
|||
'worker_id' => $users[2]->id, |
|||
'project_id' => $project->id, |
|||
'type_id' => 2, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_delete_a_job() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
$customer = factory(Customer::class)->create(); |
|||
$project = factory(Project::class)->create(['customer_id' => $customer->id]); |
|||
$job = factory(Job::class)->create(['project_id' => $project->id]); |
|||
$tasks = factory(Task::class, 2)->create(['job_id' => $job->id]); |
|||
|
|||
$this->seeInDatabase('jobs', [ |
|||
'name' => $job->name, |
|||
'price' => $job->price, |
|||
'project_id' => $project->id, |
|||
]); |
|||
|
|||
$this->visit(route('jobs.show', $job->id)); |
|||
|
|||
$this->click(trans('app.edit')); |
|||
$this->click(trans('job.delete')); |
|||
$this->press(trans('app.delete_confirm_button')); |
|||
|
|||
$this->seePageIs(route('projects.jobs', $project->id)); |
|||
|
|||
$this->see(trans('job.deleted')); |
|||
|
|||
$this->notSeeInDatabase('jobs', [ |
|||
'name' => $job->name, |
|||
'price' => $job->price, |
|||
'project_id' => $project->id, |
|||
]); |
|||
|
|||
$this->notSeeInDatabase('tasks', [ |
|||
'job_id' => $job->id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_see_a_job() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$project = factory(Project::class)->create(); |
|||
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1]); |
|||
|
|||
$this->visit(route('projects.jobs', $project->id)); |
|||
$this->click('show-job-'.$job->id); |
|||
$this->seePageIs(route('jobs.show', $project->id)); |
|||
$this->see(trans('job.show')); |
|||
$this->see($job->name); |
|||
$this->see(formatRp($job->price)); |
|||
$this->see($job->worker->name); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_may_clone_many_jobs_from_other_projects() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
$customer = factory(Customer::class)->create(); |
|||
$projects = factory(Project::class, 2)->create(['customer_id' => $customer->id]); |
|||
$jobs = factory(Job::class, 3)->create(['project_id' => $projects[0]->id]); |
|||
$tasks1 = factory(Task::class, 3)->create(['job_id' => $jobs[0]->id]); |
|||
$tasks2 = factory(Task::class, 3)->create(['job_id' => $jobs[1]->id]); |
|||
|
|||
$this->visit(route('projects.jobs', $projects[1]->id)); |
|||
|
|||
$this->click(trans('job.add_from_other_project')); |
|||
$this->seePageIs(route('jobs.add-from-other-project', $projects[1]->id)); |
|||
|
|||
$this->select($projects[0]->id, 'project_id'); |
|||
$this->press(trans('project.show_jobs')); |
|||
$this->seePageIs(route('jobs.add-from-other-project', [$projects[1]->id, 'project_id' => $projects[0]->id])); |
|||
|
|||
$form = $this->getForm(trans('job.create')); |
|||
$form['job_ids'][$jobs[0]->id]->tick(); |
|||
$form['job_ids'][$jobs[1]->id]->tick(); |
|||
$form[$jobs[0]->id.'_task_ids'][$tasks1[0]->id]->tick(); |
|||
$form[$jobs[0]->id.'_task_ids'][$tasks1[1]->id]->tick(); |
|||
$form[$jobs[0]->id.'_task_ids'][$tasks1[2]->id]->tick(); |
|||
$form[$jobs[1]->id.'_task_ids'][$tasks2[0]->id]->tick(); |
|||
$form[$jobs[1]->id.'_task_ids'][$tasks2[1]->id]->tick(); |
|||
$form[$jobs[1]->id.'_task_ids'][$tasks2[2]->id]->tick(); |
|||
$this->makeRequestUsingForm($form); |
|||
|
|||
$this->seePageIs(route('projects.jobs', $projects[1]->id)); |
|||
|
|||
$this->see(trans('job.created_from_other_project')); |
|||
|
|||
$this->seeInDatabase('jobs', [ |
|||
'project_id' => $projects[1]->id, |
|||
'name' => $jobs[0]->name, |
|||
'price' => $jobs[0]->price, |
|||
'worker_id' => $jobs[0]->worker_id, |
|||
]); |
|||
|
|||
$this->seeInDatabase('jobs', [ |
|||
'project_id' => $projects[1]->id, |
|||
'name' => $jobs[1]->name, |
|||
'price' => $jobs[1]->price, |
|||
'worker_id' => $jobs[1]->worker_id, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function admin_can_see_unfinished_jobs_list() |
|||
{ |
|||
$user = $this->adminUserSigningIn(); |
|||
|
|||
$this->visit(route('jobs.index')); |
|||
$this->seePageIs(route('jobs.index')); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue