Browse Source

Change Feature entity to Job entity

pull/1/head
Nafies Luthfi 9 years ago
parent
commit
603edd3981
  1. 6
      app/Entities/BaseRepository.php
  2. 2
      app/Entities/Payments/Type.php
  3. 103
      app/Entities/Projects/FeaturesRepository.php
  4. 9
      app/Entities/Projects/Job.php
  5. 10
      app/Entities/Projects/JobPresenter.php
  6. 106
      app/Entities/Projects/JobsRepository.php
  7. 38
      app/Entities/Projects/Project.php
  8. 28
      app/Entities/Projects/ProjectsRepository.php
  9. 10
      app/Entities/Projects/Task.php
  10. 14
      app/Entities/Projects/TasksRepository.php
  11. 6
      app/Http/Controllers/Api/ProjectsController.php
  12. 124
      app/Http/Controllers/Projects/FeaturesController.php
  13. 23
      app/Http/Controllers/Projects/FilesController.php
  14. 123
      app/Http/Controllers/Projects/JobsController.php
  15. 34
      app/Http/Controllers/Projects/ProjectsController.php
  16. 84
      app/Http/Controllers/Projects/TasksController.php
  17. 34
      app/Http/Requests/Features/DeleteRequest.php
  18. 37
      app/Http/Requests/Features/UpdateRequest.php
  19. 4
      app/Http/Requests/Jobs/CreateRequest.php
  20. 35
      app/Http/Requests/Jobs/DeleteRequest.php
  21. 38
      app/Http/Requests/Jobs/UpdateRequest.php
  22. 54
      app/Http/Requests/Tasks/CreateRequest.php
  23. 49
      app/Http/Requests/Tasks/DeleteRequest.php
  24. 53
      app/Http/Requests/Tasks/UpdateRequest.php
  25. 6
      app/Providers/AuthServiceProvider.php
  26. 2
      config/queue.php
  27. 58
      database/factories/ModelFactory.php
  28. 40
      database/migrations/2016_07_09_093439_create_features_table.php
  29. 39
      database/migrations/2016_07_09_093439_create_jobs_table.php
  30. 59
      database/migrations/2016_07_09_142833_create_tasks_table.php
  31. 2
      database/migrations/2016_11_15_151228_create_payments_table.php
  32. 37
      resources/lang/id/feature.php
  33. 37
      resources/lang/id/job.php
  34. 2
      resources/lang/id/payment.php
  35. 32
      resources/lang/id/project.php
  36. 6
      resources/views/features/partials/breadcrumb.blade.php
  37. 14
      resources/views/features/partials/feature-show.blade.php
  38. 63
      resources/views/features/unfinished.blade.php
  39. 4
      resources/views/invoices/pdf.blade.php
  40. 30
      resources/views/jobs/add-from-other-project.blade.php
  41. 24
      resources/views/jobs/create.blade.php
  42. 14
      resources/views/jobs/delete.blade.php
  43. 30
      resources/views/jobs/edit.blade.php
  44. 6
      resources/views/jobs/partials/breadcrumb.blade.php
  45. 14
      resources/views/jobs/partials/job-show.blade.php
  46. 10
      resources/views/jobs/partials/job-tasks-operation.blade.php
  47. 26
      resources/views/jobs/partials/job-tasks.blade.php
  48. 18
      resources/views/jobs/show.blade.php
  49. 63
      resources/views/jobs/unfinished.blade.php
  50. 2
      resources/views/layouts/partials/sidebar.blade.php
  51. 129
      resources/views/projects/features.blade.php
  52. 32
      resources/views/projects/jobs-export-excel.blade.php
  53. 28
      resources/views/projects/jobs-export-html-2.blade.php
  54. 34
      resources/views/projects/jobs-export-html.blade.php
  55. 40
      resources/views/projects/jobs-export-progress-excel.blade.php
  56. 129
      resources/views/projects/jobs.blade.php
  57. 4
      resources/views/projects/partials/nav-tabs.blade.php
  58. 10
      resources/views/projects/partials/project-stats.blade.php
  59. 2
      routes/api/projects.php
  60. 26
      routes/web/projects.php
  61. 190
      tests/Feature/ManageFeaturesTest.php
  62. 190
      tests/Feature/ManageJobsTest.php
  63. 24
      tests/Feature/ManageProjectsTest.php
  64. 44
      tests/Feature/ManageTasksTest.php
  65. 92
      tests/Unit/Models/ProjectTest.php
  66. 4
      tests/Unit/References/PaymentTypeTest.php

6
app/Entities/BaseRepository.php

@ -4,7 +4,7 @@ namespace App\Entities;
use App\Entities\Partners\Customer;
use App\Entities\Partners\Vendor;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Entities\Projects\Project;
use App\Entities\Users\User;
@ -45,8 +45,8 @@ abstract class BaseRepository extends EloquentRepository
return Project::orderBy('name')->pluck('name', 'id');
}
public function requireFeatureById($featureId)
public function requireJobById($jobId)
{
return Feature::findOrFail($featureId);
return Job::findOrFail($jobId);
}
}

2
app/Entities/Payments/Type.php

@ -8,7 +8,7 @@ class Type extends ReferenceAbstract
{
protected static $lists = [
1 => 'project',
2 => 'add_feature',
2 => 'add_job',
3 => 'maintenance',
];

103
app/Entities/Projects/FeaturesRepository.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;
}
}

9
app/Entities/Projects/Feature.php → app/Entities/Projects/Job.php

@ -2,18 +2,19 @@
namespace App\Entities\Projects;
use App\Entities\Projects\FeaturePresenter;
use App\Entities\Projects\JobPresenter;
use App\Entities\Projects\Project;
use App\Entities\Users\User;
use Illuminate\Database\Eloquent\Model;
use Laracasts\Presenter\PresentableTrait;
class Feature extends Model {
class Job extends Model
{
use PresentableTrait;
protected $presenter = FeaturePresenter::class;
protected $guarded = ['id','created_at','updated_at'];
protected $presenter = JobPresenter::class;
protected $guarded = ['id', 'created_at', 'updated_at'];
public function project()
{

10
app/Entities/Projects/FeaturePresenter.php → app/Entities/Projects/JobPresenter.php

@ -4,11 +4,11 @@ namespace App\Entities\Projects;
use Laracasts\Presenter\Presenter;
class FeaturePresenter extends Presenter
class JobPresenter extends Presenter
{
public function workerNameAndEmail()
{
return $this->worker_id ? $this->worker->name . ' (' . $this->worker->email . ')' : '-';
return $this->worker_id ? $this->worker->name.' ('.$this->worker->email.')' : '-';
}
public function projectLink()
@ -16,9 +16,9 @@ class FeaturePresenter extends Presenter
return link_to_route('projects.show', $this->project->name, [$this->project_id]);
}
public function projectFeaturesLink()
public function projectJobsLink()
{
return link_to_route('projects.features', trans('project.features'), [$this->project_id]);
return link_to_route('projects.jobs', trans('project.jobs'), [$this->project_id]);
}
}
}

106
app/Entities/Projects/JobsRepository.php

@ -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;
}
}

38
app/Entities/Projects/Project.php

@ -16,7 +16,7 @@ class Project extends Model
use PresentableTrait;
protected $presenter = ProjectPresenter::class;
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
// protected $dates = ['start_date','end_date'];
public function nameLink()
@ -24,24 +24,24 @@ class Project extends Model
return link_to_route('projects.show', $this->name, [$this->id]);
}
public function features()
public function jobs()
{
return $this->hasMany(Feature::class)->orderBy('position');
return $this->hasMany(Job::class)->orderBy('position');
}
public function tasks()
{
return $this->hasManyThrough(Task::class, Feature::class);
return $this->hasManyThrough(Task::class, Job::class);
}
public function mainFeatures()
public function mainJobs()
{
return $this->hasMany(Feature::class)->orderBy('position')->whereTypeId(1);
return $this->hasMany(Job::class)->orderBy('position')->whereTypeId(1);
}
public function additionalFeatures()
public function additionalJobs()
{
return $this->hasMany(Feature::class)->orderBy('position')->whereTypeId(2);
return $this->hasMany(Job::class)->orderBy('position')->whereTypeId(2);
}
public function subscriptions()
@ -78,15 +78,15 @@ class Project extends Model
});
}
public function getFeatureOveralProgress()
public function getJobOveralProgress()
{
$overalProgress = 0;
$this->load('features.tasks');
$totalPrice = $this->features->sum('price');
$this->load('jobs.tasks');
$totalPrice = $this->jobs->sum('price');
foreach ($this->features as $feature) {
$progress = $feature->tasks->avg('progress');
$index = $totalPrice ? ($feature->price / $totalPrice) : 1;
foreach ($this->jobs as $job) {
$progress = $job->tasks->avg('progress');
$index = $totalPrice ? ($job->price / $totalPrice) : 1;
$overalProgress += $progress * $index;
}
@ -100,13 +100,13 @@ class Project extends Model
public function getCollectibeEarnings()
{
// Collectible earnings is total of (price * avg task progress of each feature)
// Collectible earnings is total of (price * avg task progress of each job)
$collectibeEarnings = 0;
$this->load('features.tasks');
$this->load('jobs.tasks');
foreach ($this->features as $feature) {
$progress = $feature->tasks->avg('progress');
$collectibeEarnings += ($progress / 100) * $feature->price;
foreach ($this->jobs as $job) {
$progress = $job->tasks->avg('progress');
$collectibeEarnings += ($progress / 100) * $job->price;
}
return $collectibeEarnings;

28
app/Entities/Projects/ProjectsRepository.php

@ -77,12 +77,12 @@ class ProjectsRepository extends BaseRepository
// Delete project payments
$project->payments()->delete();
// Delete features tasks
$featureIds = $project->features->pluck('id')->all();
DB::table('tasks')->whereIn('feature_id', $featureIds)->delete();
// Delete jobs tasks
$jobIds = $project->jobs->pluck('id')->all();
DB::table('tasks')->whereIn('job_id', $jobIds)->delete();
// Delete features
$project->features()->delete();
// Delete jobs
$project->jobs()->delete();
// Delete project
$project->delete();
@ -91,9 +91,9 @@ class ProjectsRepository extends BaseRepository
return 'deleted';
}
public function getProjectFeatures($projectId, $type = null)
public function getProjectJobs($projectId, $type = null)
{
return Feature::where(function ($query) use ($projectId, $type) {
return Job::where(function ($query) use ($projectId, $type) {
$query->whereProjectId($projectId);
if ($type) {
$query->whereTypeId($type);
@ -111,15 +111,15 @@ class ProjectsRepository extends BaseRepository
return $project;
}
public function featuresReorder($sortedData)
public function jobsReorder($sortedData)
{
$featureOrder = explode(',', $sortedData);
foreach ($featureOrder as $order => $featureId) {
$feature = $this->requireFeatureById($featureId);
$feature->position = $order + 1;
$feature->save();
$jobOrder = explode(',', $sortedData);
foreach ($jobOrder as $order => $jobId) {
$job = $this->requireJobById($jobId);
$job->position = $order + 1;
$job->save();
}
return $featureOrder;
return $jobOrder;
}
}

10
app/Entities/Projects/Task.php

@ -3,20 +3,20 @@
namespace App\Entities\Projects;
use App\Entities\Projects\TaskPresenter;
use App\Entities\Users\User;
use Illuminate\Database\Eloquent\Model;
use Laracasts\Presenter\PresentableTrait;
class Task extends Model {
class Task extends Model
{
use PresentableTrait;
protected $presenter = TaskPresenter::class;
protected $guarded = ['id','created_at','updated_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
public function feature()
public function job()
{
return $this->belongsTo(Feature::class, 'project_id');
return $this->belongsTo(Job::class, 'project_id');
}
}

14
app/Entities/Projects/TasksRepository.php

@ -5,8 +5,8 @@ namespace App\Entities\Projects;
use App\Entities\BaseRepository;
/**
* Tasks Repository Class
*/
* Tasks Repository Class
*/
class TasksRepository extends BaseRepository
{
protected $model;
@ -16,14 +16,14 @@ class TasksRepository extends BaseRepository
parent::__construct($model);
}
public function createTask($taskData, $featureId)
public function createTask($taskData, $jobId)
{
$taskData['feature_id'] = $featureId;
$taskData['job_id'] = $jobId;
return $this->storeArray($taskData);
}
public function getTasksByFeatureId($featureId)
public function getTasksByJobId($jobId)
{
return Task::whereTaskId($featureId)->get();
return Task::whereTaskId($jobId)->get();
}
}
}

6
app/Http/Controllers/Api/ProjectsController.php

@ -24,13 +24,13 @@ class ProjectsController extends Controller
return $this->repo->requireById($id);
}
public function features($id)
public function jobs($id)
{
$project = $this->repo->requireById($id);
// $project->load('features.tasks');
// $project->load('jobs.tasks');
$response = fractal()
->item($project->toArray())
->transformWith(function($project) {
->transformWith(function ($project) {
return $project;
})
->toArray();

124
app/Http/Controllers/Projects/FeaturesController.php

@ -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;
}
}

23
app/Http/Controllers/Projects/FilesController.php

@ -4,9 +4,8 @@ namespace App\Http\Controllers\Projects;
use App\Entities\Projects\File;
use App\Http\Controllers\Controller;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use File as FileSystem;
use Illuminate\Http\Request;
class FilesController extends Controller
{
@ -17,7 +16,7 @@ class FilesController extends Controller
public function index(Request $request, $fileableId)
{
$editableFile = null;
$fileableType = $request->segment(1); // projects, features
$fileableType = $request->segment(1); // projects, jobs
$modelName = $this->getModelName($fileableType);
$modelShortName = $this->getModelShortName($modelName);
$model = $modelName::findOrFail($fileableId);
@ -33,10 +32,10 @@ class FilesController extends Controller
public function create(Request $request, $fileableId)
{
$this->validate($request, [
'fileable_type' => 'required',
'file' => 'required|file|max:10000',
'title' => 'required|max:60',
'description' => 'nullable|max:255',
'fileable_type' => 'required',
'file' => 'required|file|max:10000',
'title' => 'required|max:60',
'description' => 'nullable|max:255',
]);
$fileableExist = array_search($request->get('fileable_type'), $this->fileableTypes);
@ -44,12 +43,15 @@ class FilesController extends Controller
if ($fileableExist) {
$file = $this->proccessPhotoUpload($request->except('_token'), $request->get('fileable_type'), $fileableId);
if ($file->exists)
if ($file->exists) {
flash()->success('Upload file berhasil.');
else
} else {
flash()->error('Upload file gagal, coba kembali.');
} else
}
} else {
flash()->error('Upload file gagal, coba kembali.');
}
return back();
}
@ -109,7 +111,6 @@ class FilesController extends Controller
return $file;
}
public function getModelName($fileableType)
{
return isset($this->fileableTypes[$fileableType]) ? $this->fileableTypes[$fileableType] : false;

123
app/Http/Controllers/Projects/JobsController.php

@ -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;
}
}

34
app/Http/Controllers/Projects/ProjectsController.php

@ -81,11 +81,11 @@ class ProjectsController extends Controller
return redirect()->route('projects.index');
}
public function features($projectId)
public function jobs($projectId)
{
$project = $this->repo->requireById($projectId);
$features = $this->repo->getProjectFeatures($projectId);
return view('projects.features', compact('project', 'features'));
$jobs = $this->repo->getProjectJobs($projectId);
return view('projects.jobs', compact('project', 'jobs'));
}
public function subscriptions($projectId)
@ -94,28 +94,28 @@ class ProjectsController extends Controller
return view('projects.subscriptions', compact('project'));
}
public function featuresExport(Request $request, $projectId, $exportType = 'excel')
public function jobsExport(Request $request, $projectId, $exportType = 'excel')
{
$featureType = $request->get('feature_type', 1);
$jobType = $request->get('job_type', 1);
$project = $this->repo->requireById($projectId);
$features = $this->repo->getProjectFeatures($projectId, $featureType);
$jobs = $this->repo->getProjectJobs($projectId, $jobType);
if ($exportType == 'excel') {
return view('projects.features-export-excel', compact('project', 'features'));
\Excel::create(str_slug(trans('project.features').'-'.$project->name), function ($excel) use ($project, $features) {
$excel->sheet('testng', function ($sheet) use ($project, $features) {
$sheet->loadView('projects.features-export-excel', compact('project', 'features'));
return view('projects.jobs-export-excel', compact('project', 'jobs'));
\Excel::create(str_slug(trans('project.jobs').'-'.$project->name), function ($excel) use ($project, $jobs) {
$excel->sheet('testng', function ($sheet) use ($project, $jobs) {
$sheet->loadView('projects.jobs-export-excel', compact('project', 'jobs'));
});
})->download('xls');
} elseif ($exportType == 'excel-progress') {
return view('projects.features-export-progress-excel', compact('project', 'features'));
\Excel::create(str_slug(trans('project.features').'-'.$project->name), function ($excel) use ($project, $features) {
$excel->sheet('export-progress', function ($sheet) use ($project, $features) {
$sheet->loadView('projects.features-export-progress-excel', compact('project', 'features'));
return view('projects.jobs-export-progress-excel', compact('project', 'jobs'));
\Excel::create(str_slug(trans('project.jobs').'-'.$project->name), function ($excel) use ($project, $jobs) {
$excel->sheet('export-progress', function ($sheet) use ($project, $jobs) {
$sheet->loadView('projects.jobs-export-progress-excel', compact('project', 'jobs'));
});
})->download('xls');
} else {
return view('projects.features-export-html-2', compact('project', 'features'));
return view('projects.jobs-export-html-2', compact('project', 'jobs'));
}
}
@ -133,10 +133,10 @@ class ProjectsController extends Controller
return redirect()->route('projects.show', $projectId);
}
public function featuresReorder(Request $request, $projectId)
public function jobsReorder(Request $request, $projectId)
{
if ($request->ajax()) {
$data = $this->repo->featuresReorder($request->get('postData'));
$data = $this->repo->jobsReorder($request->get('postData'));
return 'oke';
}

84
app/Http/Controllers/Projects/TasksController.php

@ -2,51 +2,49 @@
namespace App\Http\Controllers\Projects;
use App\Entities\Projects\TasksRepository;
use App\Http\Controllers\Controller;
use App\Http\Requests\Tasks\CreateRequest;
use App\Http\Requests\Tasks\UpdateRequest;
use App\Http\Requests\Tasks\DeleteRequest;
use App\Http\Controllers\Controller;
use App\Entities\Projects\TasksRepository;
use App\Http\Requests\Tasks\UpdateRequest;
use Illuminate\Http\Request;
class TasksController extends Controller {
private $repo;
public function __construct(TasksRepository $repo)
{
$this->repo = $repo;
}
public function store(CreateRequest $req, $featureId)
{
$feature = $this->repo->createTask($req->except('_token'), $featureId);
flash()->success(trans('task.created'));
return redirect()->route('features.show', $featureId);
}
public function update(UpdateRequest $req, $taskId)
{
$task = $this->repo->update($req->except(['_method','_token']), $taskId);
flash()->success(trans('task.updated'));
return redirect()->route('features.show', $task->feature_id);
}
public function destroy(DeleteRequest $req, $taskId)
{
$task = $this->repo->requireById($taskId);
$featureId = $task->feature_id;
if ($taskId == $req->get('task_id'))
{
$task->delete();
flash()->success(trans('task.deleted'));
}
else
flash()->error(trans('task.undeleted'));
return redirect()->route('features.show', $featureId);
}
class TasksController extends Controller
{
private $repo;
public function __construct(TasksRepository $repo)
{
$this->repo = $repo;
}
public function store(CreateRequest $req, $jobId)
{
$job = $this->repo->createTask($req->except('_token'), $jobId);
flash()->success(trans('task.created'));
return redirect()->route('jobs.show', $jobId);
}
public function update(UpdateRequest $req, $taskId)
{
$task = $this->repo->update($req->except(['_method', '_token']), $taskId);
flash()->success(trans('task.updated'));
return redirect()->route('jobs.show', $task->job_id);
}
public function destroy(DeleteRequest $req, $taskId)
{
$task = $this->repo->requireById($taskId);
$jobId = $task->job_id;
if ($taskId == $req->get('task_id')) {
$task->delete();
flash()->success(trans('task.deleted'));
} else {
flash()->error(trans('task.undeleted'));
}
return redirect()->route('jobs.show', $jobId);
}
}

34
app/Http/Requests/Features/DeleteRequest.php

@ -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',
];
}
}

37
app/Http/Requests/Features/UpdateRequest.php

@ -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',
];
}
}

4
app/Http/Requests/Features/CreateRequest.php → app/Http/Requests/Jobs/CreateRequest.php

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests\Features;
namespace App\Http\Requests\Jobs;
use App\Entities\Projects\Project;
use App\Http\Requests\Request;
@ -15,7 +15,7 @@ class CreateRequest extends Request
public function authorize()
{
$project = Project::findOrFail($this->segment(2));
return auth()->user()->can('manage_features', $project);
return auth()->user()->can('manage_jobs', $project);
}
/**

35
app/Http/Requests/Jobs/DeleteRequest.php

@ -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',
];
}
}

38
app/Http/Requests/Jobs/UpdateRequest.php

@ -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',
];
}
}

54
app/Http/Requests/Tasks/CreateRequest.php

@ -2,35 +2,35 @@
namespace App\Http\Requests\Tasks;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Http\Requests\Request;
class CreateRequest extends Request {
class CreateRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$job = Job::findOrFail($this->segment(2));
return auth()->user()->can('manage_job', $job);
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$feature = Feature::findOrFail($this->segment(2));
return auth()->user()->can('manage_feature', $feature);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:60',
'description' => 'max:255',
'progress' => 'numeric|max:100',
'route_name' => 'max:255',
];
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:60',
'description' => 'max:255',
'progress' => 'numeric|max:100',
'route_name' => 'max:255',
];
}
}

49
app/Http/Requests/Tasks/DeleteRequest.php

@ -2,33 +2,34 @@
namespace App\Http\Requests\Tasks;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Http\Requests\Request;
class DeleteRequest extends Request {
class DeleteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$feature = Feature::findOrFail($this->get('feature_id'));
return auth()->user()->can('manage_feature', $feature);
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$job = Job::findOrFail($this->get('job_id'));
return auth()->user()->can('manage_job', $job);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'task_id' => 'required',
'feature_id' => 'required',
];
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'task_id' => 'required',
'job_id' => 'required',
];
}
}

53
app/Http/Requests/Tasks/UpdateRequest.php

@ -2,35 +2,36 @@
namespace App\Http\Requests\Tasks;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Http\Requests\Request;
class UpdateRequest extends Request {
class UpdateRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$feature = Feature::findOrFail($this->get('feature_id'));
return auth()->user()->can('manage_feature', $feature);
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$job = Job::findOrFail($this->get('job_id'));
return auth()->user()->can('manage_job', $job);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:60',
'description' => 'max:255',
'progress' => 'numeric|max:100',
'route_name' => 'max:255',
];
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:60',
'description' => 'max:255',
'progress' => 'numeric|max:100',
'route_name' => 'max:255',
];
}
}

6
app/Providers/AuthServiceProvider.php

@ -49,12 +49,12 @@ class AuthServiceProvider extends ServiceProvider
return true;
});
Gate::define('manage_features', function ($user, $project) {
Gate::define('manage_jobs', function ($user, $project) {
return true;
});
Gate::define('manage_feature', function ($user, $feature) {
return $user->id == $feature->worker_id;
Gate::define('manage_job', function ($user, $job) {
return $user->id == $job->worker_id;
});
}

2
config/queue.php

@ -37,7 +37,7 @@ return [
'database' => [
'driver' => 'database',
'table' => 'jobs',
'table' => 'laravel_jobs',
'queue' => 'default',
'expire' => 60,
],

58
database/factories/ModelFactory.php

@ -1,7 +1,7 @@
<?php
use App\Entities\Invoices\Invoice;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Entities\Projects\Project;
use App\Entities\Projects\Task;
use App\Entities\Users\Event;
@ -9,59 +9,59 @@ use App\Entities\Users\User;
$factory->define(User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->email,
'password' => 'member',
'name' => $faker->name,
'email' => $faker->unique()->email,
'password' => 'member',
'remember_token' => str_random(10),
'api_token' => str_random(32),
'api_token' => str_random(32),
];
});
$factory->define(Feature::class, function (Faker\Generator $faker) {
$factory->define(Job::class, function (Faker\Generator $faker) {
return [
'project_id' => function () {
'project_id' => function () {
return factory(Project::class)->create()->id;
},
'name' => $faker->sentence(3),
'price' => rand(1, 10) * 100000,
'name' => $faker->sentence(3),
'price' => rand(1, 10) * 100000,
'description' => $faker->paragraph,
'worker_id' => function () {
'worker_id' => function () {
return factory(User::class)->create()->id;
},
'type_id' => 1, // Main feature
'position' => rand(1, 10),
'type_id' => 1, // Main job
'position' => rand(1, 10),
];
});
$factory->define(Task::class, function (Faker\Generator $faker) {
return [
'feature_id' => function () {
return factory(Feature::class)->create()->id;
'job_id' => function () {
return factory(Job::class)->create()->id;
},
'name' => $faker->sentence(3),
'name' => $faker->sentence(3),
'description' => $faker->paragraph,
'progress' => rand(40, 100),
'route_name' => implode('.', $faker->words(3)),
'position' => rand(1, 10),
'progress' => rand(40, 100),
'route_name' => implode('.', $faker->words(3)),
'position' => rand(1, 10),
];
});
$factory->define(Event::class, function (Faker\Generator $faker) {
return [
'user_id' => function () {
'user_id' => function () {
return factory(User::class)->create()->id;
},
'project_id' => function () {
return factory(Project::class)->create()->id;
},
'title' => $faker->words(rand(2, 4), true),
'body' => $faker->sentence,
'start' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'end' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'is_allday' => rand(0, 1),
'title' => $faker->words(rand(2, 4), true),
'body' => $faker->sentence,
'start' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'end' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'is_allday' => rand(0, 1),
];
});
@ -70,11 +70,11 @@ $factory->define(Invoice::class, function (Faker\Generator $faker) {
'project_id' => function () {
return factory(Project::class)->create()->id;
},
'number' => (new Invoice)->generateNewNumber(),
'items' => [],
'amount' => 100000,
'notes' => $faker->paragraph,
'status_id' => 1,
'number' => (new Invoice)->generateNewNumber(),
'items' => [],
'amount' => 100000,
'notes' => $faker->paragraph,
'status_id' => 1,
'creator_id' => function () {
return factory(User::class)->create()->id;
},

40
database/migrations/2016_07_09_093439_create_features_table.php

@ -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');
}
}

39
database/migrations/2016_07_09_093439_create_jobs_table.php

@ -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');
}
}

59
database/migrations/2016_07_09_142833_create_tasks_table.php

@ -3,37 +3,36 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateTasksTable extends Migration {
class CreateTasksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function(Blueprint $table)
{
$table->increments('id');
$table->integer('feature_id')->unsigned();
$table->string('name', 60);
$table->string('description')->nullable();
$table->boolean('progress')->default(0);
$table->string('route_name')->nullable();
$table->boolean('position')->default(0);
$table->timestamps();
});
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->integer('job_id')->unsigned();
$table->string('name', 60);
$table->string('description')->nullable();
$table->boolean('progress')->default(0);
$table->string('route_name')->nullable();
$table->boolean('position')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tasks');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tasks');
}
}

2
database/migrations/2016_11_15_151228_create_payments_table.php

@ -17,7 +17,7 @@ class CreatePaymentsTable extends Migration
$table->increments('id');
$table->integer('project_id')->unsigned();
$table->integer('amount')->unsigned();
$table->boolean('type_id')->default(1)->comment('1:project, 2: add_feature, 3:maintenance');
$table->boolean('type_id')->default(1)->comment('1:project, 2: add_job, 3:maintenance');
$table->boolean('in_out')->default(1)->comment('0: out, 1: in');
$table->date('date');
$table->string('description');

37
resources/lang/id/feature.php

@ -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',
];

37
resources/lang/id/job.php

@ -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',
];

2
resources/lang/id/payment.php

@ -43,7 +43,7 @@ return [
// Types
'types' => [
'project' => 'Project',
'add_feature' => 'Add Feature',
'add_job' => 'Add Job',
'maintenance' => 'Maintenance',
],
];

32
resources/lang/id/project.php

@ -14,21 +14,21 @@ return [
'back_to_index' => 'Kembali ke daftar Project',
// Actions
'create' => 'Input Project Baru',
'created' => 'Input Project baru telah berhasil.',
'show' => 'Detail Project',
'edit' => 'Edit Project',
'update' => 'Update Project',
'updated' => 'Update data Project telah berhasil.',
'delete' => 'Hapus Project',
'deleted' => 'Hapus data Project telah berhasil.',
'undeleted' => 'Data Project gagal dihapus.',
'show_features' => 'Lihat Fitur',
'create' => 'Input Project Baru',
'created' => 'Input Project baru telah berhasil.',
'show' => 'Detail Project',
'edit' => 'Edit Project',
'update' => 'Update Project',
'updated' => 'Update data Project telah berhasil.',
'delete' => 'Hapus Project',
'deleted' => 'Hapus data Project telah berhasil.',
'undeleted' => 'Data Project gagal dihapus.',
'show_jobs' => 'Lihat Job',
'features_export_html' => 'Export HTML',
'features_export_excel' => 'Export Excel',
'features_export_progress_excel' => 'Export Progress',
'sort_features' => 'Urutkan Prioritas Fitur',
'jobs_export_html' => 'Export HTML',
'jobs_export_excel' => 'Export Excel',
'jobs_export_progress_excel' => 'Export Progress',
'sort_jobs' => 'Urutkan Prioritas Job',
// Attributes
'name' => 'Nama Project',
@ -41,8 +41,8 @@ return [
// Relations
'files' => 'List Dokumen',
'features' => 'Daftar Item Pekerjaan',
'no_features' => 'Belum ada Item Pekerjaan',
'jobs' => 'Daftar Job',
'no_jobs' => 'Belum ada Job',
'cost_proposal' => 'Pengajuan Biaya',
'invoices' => 'List Invoice',
'customer' => 'Customer',

6
resources/views/features/partials/breadcrumb.blade.php

@ -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>

14
resources/views/features/partials/feature-show.blade.php

@ -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>

63
resources/views/features/unfinished.blade.php

@ -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

4
resources/views/invoices/pdf.blade.php

@ -52,7 +52,7 @@
<p><strong>{{ $invoice->project->customer->name }}</strong></p>
</td>
</tr>
<tr><td colspan="3">{{ trans('project.features') }} :</td></tr>
<tr><td colspan="3">{{ trans('project.jobs') }} :</td></tr>
<tr>
<td colspan="3">
<table border="1" class="receipt-table" style="width: 100%">
@ -104,4 +104,4 @@
</tbody>
</table>
</body>
</html>
</html>

30
resources/views/features/add-from-other-project.blade.php → resources/views/jobs/add-from-other-project.blade.php

@ -1,14 +1,14 @@
@extends('layouts.app')
@section('title', trans('feature.add_from_other_project') . ' | ' . $project->name)
@section('title', trans('job.add_from_other_project') . ' | ' . $project->name)
@section('content')
@include('projects.partials.breadcrumb',['title' => trans('feature.add_from_other_project')])
@include('projects.partials.breadcrumb',['title' => trans('job.add_from_other_project')])
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{ trans('feature.add_from_other_project') }}</h3></div>
<div class="panel-heading"><h3 class="panel-title">{{ trans('job.add_from_other_project') }}</h3></div>
<div class="panel-body">
{!! Form::open(['method'=>'get']) !!}
<div class="form-group">
@ -18,23 +18,23 @@
'class' => 'form-control customer-select',
'placeholder' => '-- Pilih Project --'
]) !!}
<span class="input-group-btn"><button class="btn btn-default btn-sm" type="submit">{{ trans('project.show_features') }}</button></span>
<span class="input-group-btn"><button class="btn btn-default btn-sm" type="submit">{{ trans('project.show_jobs') }}</button></span>
</div>
</div>
{!! Form::close() !!}
@if ($selectedProject)
{!! Form::open(['route'=>['features.store-from-other-project', $project->id]]) !!}
{!! Form::open(['route'=>['jobs.store-from-other-project', $project->id]]) !!}
<ul class="list-unstyled">
@forelse($selectedProject->features as $key => $feature)
@forelse($selectedProject->jobs as $key => $job)
<li>
<label for="feature_id_{{ $feature->id }}">
{!! Form::checkbox('feature_ids[' . $feature->id . ']', $feature->id, null, ['id' => 'feature_id_' . $feature->id]) !!}
{{ $feature->name }}</label>
<label for="job_id_{{ $job->id }}">
{!! Form::checkbox('job_ids[' . $job->id . ']', $job->id, null, ['id' => 'job_id_' . $job->id]) !!}
{{ $job->name }}</label>
<ul style="list-style-type:none">
@foreach($feature->tasks as $task)
@foreach($job->tasks as $task)
<li>
<label for="{{ $feature->id }}_task_id_{{ $task->id }}" style="font-weight:normal">
{!! Form::checkbox($feature->id . '_task_ids[' . $task->id . ']', $task->id, null, ['id' => $feature->id . '_task_id_' . $task->id]) !!}
<label for="{{ $job->id }}_task_id_{{ $task->id }}" style="font-weight:normal">
{!! Form::checkbox($job->id . '_task_ids[' . $task->id . ']', $task->id, null, ['id' => $job->id . '_task_id_' . $task->id]) !!}
{{ $task->name }}</label>
</li>
@endforeach
@ -47,12 +47,12 @@
@else
<div class="alert alert-info">Pilih salah satu project</div>
@endif
{!! Form::submit(trans('feature.create'), ['class'=>'btn btn-primary']) !!}
{!! Form::submit(trans('job.create'), ['class'=>'btn btn-primary']) !!}
{!! Form::close() !!}
</div>
<div class="panel-footer">
{!! link_to_route('projects.features', trans('app.cancel'), [$project->id], ['class'=>'btn btn-default']) !!}
{!! link_to_route('projects.jobs', trans('app.cancel'), [$project->id], ['class'=>'btn btn-default']) !!}
</div>
</div>
</div>
@ -76,4 +76,4 @@
});
})();
</script>
@endsection
@endsection

24
resources/views/features/create.blade.php → resources/views/jobs/create.blade.php

@ -1,34 +1,34 @@
@extends('layouts.app')
@section('title', trans('feature.create'))
@section('title', trans('job.create'))
@section('content')
@include('projects.partials.breadcrumb',['title' => trans('feature.create')])
@include('projects.partials.breadcrumb',['title' => trans('job.create')])
<div class="row">
<div class="col-sm-6">
{!! Form::open(['route'=>['features.store', $project->id]]) !!}
{!! Form::open(['route'=>['jobs.store', $project->id]]) !!}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{ trans('feature.create') }}</h3></div>
<div class="panel-heading"><h3 class="panel-title">{{ trans('job.create') }}</h3></div>
<div class="panel-body">
{!! FormField::text('name',['label'=> trans('feature.name')]) !!}
{!! FormField::text('name',['label'=> trans('job.name')]) !!}
<div class="row">
<div class="col-sm-4">
{!! FormField::price('price', ['label'=> trans('feature.price')]) !!}
{!! FormField::price('price', ['label'=> trans('job.price')]) !!}
</div>
<div class="col-sm-4">
{!! FormField::select('worker_id', $workers, ['label'=> trans('feature.worker'),'value' => 1]) !!}
{!! FormField::select('worker_id', $workers, ['label'=> trans('job.worker'),'value' => 1]) !!}
</div>
<div class="col-sm-4">
{!! FormField::radios('type_id', [1 => 'Project','Tambahan'], ['value' => 1,'label'=> trans('feature.type'),'list_style' => 'unstyled']) !!}
{!! FormField::radios('type_id', [1 => 'Project','Tambahan'], ['value' => 1,'label'=> trans('job.type'),'list_style' => 'unstyled']) !!}
</div>
</div>
{!! FormField::textarea('description',['label'=> trans('feature.description')]) !!}
{!! FormField::textarea('description',['label'=> trans('job.description')]) !!}
</div>
<div class="panel-footer">
{!! Form::submit(trans('feature.create'), ['class'=>'btn btn-primary']) !!}
{!! link_to_route('projects.features', trans('app.cancel'), [$project->id], ['class'=>'btn btn-default']) !!}
{!! Form::submit(trans('job.create'), ['class'=>'btn btn-primary']) !!}
{!! link_to_route('projects.jobs', trans('app.cancel'), [$project->id], ['class'=>'btn btn-default']) !!}
</div>
</div>
{!! Form::close() !!}
@ -53,4 +53,4 @@
});
})();
</script>
@endsection
@endsection

14
resources/views/features/delete.blade.php → resources/views/jobs/delete.blade.php

@ -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

30
resources/views/features/edit.blade.php → resources/views/jobs/edit.blade.php

@ -1,41 +1,41 @@
@extends('layouts.app')
@section('title', trans('feature.edit'))
@section('title', trans('job.edit'))
@section('content')
<div class="row"><br>
<div class="col-md-6">
{!! Form::model($feature, ['route'=>['features.update', $feature->id], 'method' => 'patch']) !!}
{!! Form::model($job, ['route'=>['jobs.update', $job->id], 'method' => 'patch']) !!}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{ $feature->name }} <small>{{ trans('feature.edit') }}</small></h3></div>
<div class="panel-heading"><h3 class="panel-title">{{ $job->name }} <small>{{ trans('job.edit') }}</small></h3></div>
<div class="panel-body">
{!! FormField::text('name',['label'=> trans('feature.name')]) !!}
{!! FormField::text('name',['label'=> trans('job.name')]) !!}
<div class="row">
<div class="col-sm-4">
{!! FormField::price('price', ['label'=> trans('feature.price')]) !!}
{!! FormField::price('price', ['label'=> trans('job.price')]) !!}
</div>
<div class="col-sm-4">
{!! FormField::select('worker_id', $workers, ['label'=> trans('feature.worker'),'value' => 1]) !!}
{!! FormField::select('worker_id', $workers, ['label'=> trans('job.worker'),'value' => 1]) !!}
</div>
<div class="col-sm-4">
{!! FormField::radios('type_id', [1 => 'Project','Tambahan'], ['value' => 1,'label'=> trans('feature.type'),'list_style' => 'unstyled']) !!}
{!! FormField::radios('type_id', [1 => 'Project','Tambahan'], ['value' => 1,'label'=> trans('job.type'),'list_style' => 'unstyled']) !!}
</div>
</div>
{!! FormField::textarea('description',['label'=> trans('feature.description')]) !!}
{!! FormField::textarea('description',['label'=> trans('job.description')]) !!}
</div>
<div class="panel-footer">
{!! Form::hidden('project_id', $feature->project_id) !!}
{!! Form::submit(trans('feature.update'), ['class'=>'btn btn-primary']) !!}
{!! link_to_route('features.show', trans('app.show'), [$feature->id], ['class' => 'btn btn-info']) !!}
{!! link_to_route('projects.features', trans('feature.back_to_index'), [$feature->project_id], ['class' => 'btn btn-default']) !!}
{!! link_to_route('features.delete', trans('feature.delete'), [$feature->id], ['class'=>'btn btn-danger pull-right']) !!}
{!! Form::hidden('project_id', $job->project_id) !!}
{!! Form::submit(trans('job.update'), ['class'=>'btn btn-primary']) !!}
{!! link_to_route('jobs.show', trans('app.show'), [$job->id], ['class' => 'btn btn-info']) !!}
{!! link_to_route('projects.jobs', trans('job.back_to_index'), [$job->project_id], ['class' => 'btn btn-default']) !!}
{!! link_to_route('jobs.delete', trans('job.delete'), [$job->id], ['class'=>'btn btn-danger pull-right']) !!}
</div>
</div>
{!! Form::close() !!}
</div>
<div class="col-sm-6">
@include('projects.partials.project-show', ['project' => $feature->project])
@include('projects.partials.project-show', ['project' => $job->project])
</div>
</div>
@endsection
@ -54,4 +54,4 @@
});
})();
</script>
@endsection
@endsection

6
resources/views/jobs/partials/breadcrumb.blade.php

@ -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>

14
resources/views/jobs/partials/job-show.blade.php

@ -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>

10
resources/views/features/partials/feature-tasks-operation.blade.php → resources/views/jobs/partials/job-tasks-operation.blade.php

@ -1,5 +1,5 @@
@if (Request::has('action') == false)
{!! Form::open(['route' => ['tasks.store', $feature->id]])!!}
{!! Form::open(['route' => ['tasks.store', $job->id]])!!}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">{{ trans('task.create') }}</h3></div>
<div class="panel-body">
@ -38,11 +38,11 @@
<strong id="ap_weight">{{ $editableTask->progress }}</strong> %
</div>
<div class="col-md-6">
{!! FormField::select('feature_id', $feature->project->features->pluck('name','id'), ['label' => 'Pindahkan ke Fitur lain']) !!}
{!! FormField::select('job_id', $job->project->jobs->pluck('name','id'), ['label' => 'Pindahkan ke Fitur lain']) !!}
</div>
</div>
{!! Form::submit(trans('task.update'), ['class' => 'btn btn-warning']) !!}
{!! 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']) !!}
{!! Form::close() !!}
</div>
</div>
@ -56,7 +56,7 @@
</div>
<div class="panel-footer">
{{ 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']) !!}
<div class="pull-right">
{!! FormField::delete([
'route'=>['tasks.destroy',$editableTask->id]],
@ -64,7 +64,7 @@
['class'=>'btn btn-danger'],
[
'task_id' => $editableTask->id,
'feature_id' => $editableTask->feature_id,
'job_id' => $editableTask->job_id,
]) !!}
</div>
</div>

26
resources/views/features/partials/feature-tasks.blade.php → resources/views/jobs/partials/job-tasks.blade.php

@ -1,11 +1,11 @@
<div id="feature-tasks" class="panel panel-default">
<div id="job-tasks" class="panel panel-default">
<div class="panel-heading">
@if (request('action') == 'sort_tasks')
{{ link_to_route('features.show', trans('app.done'), [$feature->id], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin: -2px -8px']) }}
{{ link_to_route('jobs.show', trans('app.done'), [$job->id], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin: -2px -8px']) }}
@else
{{ link_to_route('features.show', trans('feature.sort_tasks'), [$feature->id, 'action' => 'sort_tasks', '#feature-tasks'], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin: -2px -8px']) }}
{{ link_to_route('jobs.show', trans('job.sort_tasks'), [$job->id, 'action' => 'sort_tasks', '#job-tasks'], ['class' => 'btn btn-default btn-xs pull-right', 'style' => 'margin: -2px -8px']) }}
@endif
<h3 class="panel-title">{{ trans('feature.tasks') }}</h3>
<h3 class="panel-title">{{ trans('job.tasks') }}</h3>
</div>
<table class="table table-condensed">
<thead>
@ -16,7 +16,7 @@
<th class="col-md-2 text-center">{{ trans('app.action') }}</th>
</thead>
<tbody id="sort-tasks">
@forelse($feature->tasks as $key => $task)
@forelse($job->tasks as $key => $task)
<tr id="{{ $task->id }}">
<td>{{ 1 + $key }}</td>
<td>
@ -26,8 +26,8 @@
<td>{{ $task->route_name }}</td>
<td class="text-center">{{ $task->progress }} %</td>
<td class="text-center">
{!! html_link_to_route('features.show', '', [
$feature->id,
{!! html_link_to_route('jobs.show', '', [
$job->id,
'action' => 'task_edit',
'task_id' => $task->id
],[
@ -36,8 +36,8 @@
'id' => $task->id . '-tasks-edit',
'icon' => 'edit'
]) !!}
{!! html_link_to_route('features.show', '', [
$feature->id,
{!! html_link_to_route('jobs.show', '', [
$job->id,
'action' => 'task_delete',
'task_id' => $task->id
],[
@ -55,12 +55,12 @@
<tfoot>
<tr>
<th class="text-right" colspan="3">Total</th>
<th class="text-center">{{ formatDecimal($feature->tasks->avg('progress')) }} %</th>
<th class="text-center">{{ formatDecimal($job->tasks->avg('progress')) }} %</th>
<th>
@if (request('action') == 'sort_tasks')
{{ link_to_route('features.show', trans('app.done'), [$feature->id], ['class' => 'btn btn-default btn-xs pull-right']) }}
{{ link_to_route('jobs.show', trans('app.done'), [$job->id], ['class' => 'btn btn-default btn-xs pull-right']) }}
@else
{{ link_to_route('features.show', trans('feature.sort_tasks'), [$feature->id, 'action' => 'sort_tasks', '#feature-tasks'], ['class' => 'btn btn-default btn-xs pull-right']) }}
{{ link_to_route('jobs.show', trans('job.sort_tasks'), [$job->id, 'action' => 'sort_tasks', '#job-tasks'], ['class' => 'btn btn-default btn-xs pull-right']) }}
@endif
</th>
</tr>
@ -82,7 +82,7 @@
update: function (event, ui) {
var data = $(this).sortable('toArray').toString();
// console.log(data);
$.post("{{ route('features.tasks-reorder', $feature->id) }}", {postData: data});
$.post("{{ route('jobs.tasks-reorder', $job->id) }}", {postData: data});
}
});
})();

18
resources/views/features/show.blade.php → resources/views/jobs/show.blade.php

@ -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)

63
resources/views/jobs/unfinished.blade.php

@ -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

2
resources/views/layouts/partials/sidebar.blade.php

@ -16,7 +16,7 @@
<ul class="nav" id="side-menu">
<li>{!! html_link_to_route('home', trans('nav_menu.dashboard'), [], ['icon' => 'dashboard']) !!}</li>
@can('manage_agency')
<li>{!! html_link_to_route('features.index', trans('feature.on_progress'), [], ['icon' => 'tasks']) !!}</li>
<li>{!! html_link_to_route('jobs.index', trans('job.on_progress'), [], ['icon' => 'tasks']) !!}</li>
<li>
{!! html_link_to_route('projects.index', trans('project.projects') . ' <span class="fa arrow"></span>', [], ['icon' => 'table']) !!}
@include('view-components.sidebar-project-list-links')

129
resources/views/projects/features.blade.php

@ -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

32
resources/views/projects/features-export-excel.blade.php → resources/views/projects/jobs-export-excel.blade.php

@ -1,7 +1,7 @@
<?php
// $filename = str_slug(trans('project.features') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
// $filename = str_slug(trans('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
<!DOCTYPE html>
<html>
@ -24,29 +24,29 @@
<thead>
<tr>
<td colspan="4" style="text-align:center">
<strong>{{ trans('project.features') }} {{ $project->name }}</strong>
<strong>{{ trans('project.jobs') }} {{ $project->name }}</strong>
</td>
</tr>
<tr>
<th>{{ trans('app.table_no') }}</th>
<th>{{ trans('feature.name') }}</th>
<th class="text-right">{{ trans('feature.price') }}</th>
<th>{{ trans('job.name') }}</th>
<th class="text-right">{{ trans('job.price') }}</th>
<th>{{ trans('app.description') }}</th>
</tr>
</thead>
<tbody id="sort-features">
@forelse($features as $key => $feature)
<tbody id="sort-jobs">
@forelse($jobs as $key => $job)
<tr>
<td>{{ 1 + $key }}</td>
<td>
{{ $feature->name }}
{{ $job->name }}
</td>
<td class="text-right">{{ $feature->price }}</td>
<td style="wrap-text: true;">{!! nl2br($feature->description) !!}</td>
<td class="text-right">{{ $job->price }}</td>
<td style="wrap-text: true;">{!! nl2br($job->description) !!}</td>
</tr>
@if ($feature->tasks->count())
@foreach($feature->tasks as $task)
@if ($job->tasks->count())
@foreach($job->tasks as $task)
<tr>
<td></td>
<td>{{ $task->name }}</td>
@ -56,16 +56,16 @@
@endforeach
@endif
@empty
<tr><td colspan="7">{{ trans('feature.empty') }}</td></tr>
<tr><td colspan="7">{{ trans('job.empty') }}</td></tr>
@endforelse
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">Total</th>
<th class="text-right">{{ $features->sum('price') }}</th>
<th class="text-right">{{ $jobs->sum('price') }}</th>
<th></th>
</tr>
</tfoot>
</table>
</body>
</html>
</html>

28
resources/views/projects/features-export-html-2.blade.php → resources/views/projects/jobs-export-html-2.blade.php

@ -1,5 +1,5 @@
<?php
// $filename = str_slug(trans('project.features') . '-' . $project->name) . '.xls';
// $filename = str_slug(trans('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
@ -9,20 +9,20 @@
<meta charset="utf-8">
{{-- <meta http-equiv="X-UA-Compatible" content="IE=edge"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{ trans('project.features') }} {{ $project->name }}</title>
<title>{{ trans('project.jobs') }} {{ $project->name }}</title>
{!! Html::style('assets/css/app.s.css') !!}
</head>
<body style="font-family:'Liberation Serif';font-size: 16px;">
<div class="container">
<h1 class="page-header text-center">{{ trans('project.features') }} {{ $project->name }}</h1>
<h1 class="page-header text-center">{{ trans('project.jobs') }} {{ $project->name }}</h1>
@foreach($features as $key => $feature)
<h2 class="feature-title">{{ ++$key }}. {{ $feature->name }}</h2>
<p style="padding-left: 30px">{!! nl2br($feature->description) !!}</p>
@if ($feature->tasks->count())
@foreach($jobs as $key => $job)
<h2 class="job-title">{{ ++$key }}. {{ $job->name }}</h2>
<p style="padding-left: 30px">{!! nl2br($job->description) !!}</p>
@if ($job->tasks->count())
<div style="padding-left: 30px">
<h3>Sub Fitur</h3>
@foreach($feature->tasks as $taskKey => $task)
@foreach($job->tasks as $taskKey => $task)
<h4>{{ ++$taskKey }}) {{ $task->name }}</h4>
<p style="padding-left: 21px">{!! nl2br($task->description) !!}</p>
@endforeach
@ -35,21 +35,21 @@
<tbody>
<tr>
<th class="text-center">{{ trans('app.table_no') }}</th>
<th>{{ trans('feature.name') }}</th>
<th class="text-center">{{ trans('feature.price') }}</th>
<th>{{ trans('job.name') }}</th>
<th class="text-center">{{ trans('job.price') }}</th>
</tr>
@foreach($features as $key => $feature)
@foreach($jobs as $key => $job)
<tr>
<td class="text-center">{{ 1 + $key }}</td>
<td>{{ $feature->name }}</td>
<td class="text-right">{{ formatRp($feature->price) }}</td>
<td>{{ $job->name }}</td>
<td class="text-right">{{ formatRp($job->price) }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">Total</th>
<th class="text-right">{{ formatRp($features->sum('price')) }}</th>
<th class="text-right">{{ formatRp($jobs->sum('price')) }}</th>
</tr>
</tfoot>
</table>

34
resources/views/projects/features-export-html.blade.php → resources/views/projects/jobs-export-html.blade.php

@ -1,7 +1,7 @@
<?php
// $filename = str_slug(trans('project.features') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
// $filename = str_slug(trans('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
<!DOCTYPE html>
<html>
@ -9,27 +9,27 @@
<meta charset="utf-8">
{{-- <meta http-equiv="X-UA-Compatible" content="IE=edge"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{ trans('project.features') }} {{ $project->name }}</title>
<title>{{ trans('project.jobs') }} {{ $project->name }}</title>
{!! Html::style('assets/css/app.s.css') !!}
</head>
<body style="font-family:'Liberation Serif'">
<div class="container">
<h1 class="page-header text-center">{{ trans('project.features') }} {{ $project->name }}</h1>
<h1 class="page-header text-center">{{ trans('project.jobs') }} {{ $project->name }}</h1>
@foreach($features as $key => $feature)
<h2 class="feature-title">{{ $feature->name }}</h2>
@foreach($jobs as $key => $job)
<h2 class="job-title">{{ $job->name }}</h2>
<table width="100%" class="table table-condensed table-bordered">
<tbody>
<tr style="background-color: #ffd298"><th colspan="2">{{ trans('app.description') }}</th></tr>
<tr><td colspan="2">{!! nl2br($feature->description) !!}</td></tr>
<tr><td colspan="2">{!! nl2br($job->description) !!}</td></tr>
@if ($feature->tasks->count())
@if ($job->tasks->count())
<tr><td colspan="2">&nbsp;</td></tr>
<tr style="background-color: #ffd298">
<th class="col-md-3">Sub Fitur</th>
<th class="col-md-6">{{ trans('app.description') }}</th>
</tr>
@foreach($feature->tasks as $task)
@foreach($job->tasks as $task)
<tr>
<td>{{ $task->name }}</td>
<td>{!! nl2br($task->description) !!}</td>
@ -45,25 +45,25 @@
<tbody>
<tr>
<th class="text-center">{{ trans('app.table_no') }}</th>
<th>{{ trans('feature.name') }}</th>
<th class="text-center">{{ trans('feature.price') }}</th>
<th>{{ trans('job.name') }}</th>
<th class="text-center">{{ trans('job.price') }}</th>
</tr>
@foreach($features as $key => $feature)
@foreach($jobs as $key => $job)
<tr>
<td class="text-center">{{ 1 + $key }}</td>
<td>{{ $feature->name }}</td>
<td class="text-right">{{ formatRp($feature->price) }}</td>
<td>{{ $job->name }}</td>
<td class="text-right">{{ formatRp($job->price) }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">Total</th>
<th class="text-right">{{ formatRp($features->sum('price')) }}</th>
<th class="text-right">{{ formatRp($jobs->sum('price')) }}</th>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
</html>

40
resources/views/projects/features-export-progress-excel.blade.php → resources/views/projects/jobs-export-progress-excel.blade.php

@ -1,7 +1,7 @@
<?php
// $filename = str_slug(trans('project.features') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
// $filename = str_slug(trans('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
<!DOCTYPE html>
<html>
@ -21,31 +21,31 @@
<thead>
<tr>
<td colspan="4" style="text-align:center">
<h1>{{ trans('project.features') }} {{ $project->name }}</h1>
<h1>{{ trans('project.jobs') }} {{ $project->name }}</h1>
</td>
</tr>
<tr>
<th>{{ trans('app.table_no') }}</th>
<th>{{ trans('feature.name') }}</th>
<th class="text-center">{{ trans('feature.progress') }}</th>
<th class="text-right">{{ trans('feature.price') }}</th>
<th>{{ trans('job.name') }}</th>
<th class="text-center">{{ trans('job.progress') }}</th>
<th class="text-right">{{ trans('job.price') }}</th>
{{-- <th>{{ trans('app.description') }}</th> --}}
</tr>
</thead>
<tbody id="sort-features">
@forelse($features as $key => $feature)
<tbody id="sort-jobs">
@forelse($jobs as $key => $job)
<tr>
<td>{{ 1 + $key }}</td>
<td>
{{ $feature->name }}
{{ $job->name }}
</td>
<td class="text-center">{{ formatDecimal($feature->progress = $feature->tasks->avg('progress')) }}</td>
<td class="text-right">{{ $feature->price }}</td>
{{-- <td style="wrap-text: true;">{!! nl2br($feature->description) !!}</td> --}}
<td class="text-center">{{ formatDecimal($job->progress = $job->tasks->avg('progress')) }}</td>
<td class="text-right">{{ $job->price }}</td>
{{-- <td style="wrap-text: true;">{!! nl2br($job->description) !!}</td> --}}
</tr>
@if ($feature->tasks->count())
@foreach($feature->tasks as $task)
@if ($job->tasks->count())
@foreach($job->tasks as $task)
<tr>
<td></td>
<td>{{ $task->name }}</td>
@ -56,17 +56,17 @@
@endforeach
@endif
<?php /*
*/ ?>
*/?>
@empty
<tr><td colspan="7">{{ trans('feature.empty') }}</td></tr>
<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">{{ formatDecimal($features->sum('progress') / count($features)) }}</th> --}}
<th class="text-center">{{ formatDecimal($project->getFeatureOveralProgress()) }} %</th>
<th class="text-right">{{ $features->sum('price') }}</th>
{{-- <th class="text-center">{{ formatDecimal($jobs->sum('progress') / count($jobs)) }}</th> --}}
<th class="text-center">{{ formatDecimal($project->getJobOveralProgress()) }} %</th>
<th class="text-right">{{ $jobs->sum('price') }}</th>
{{-- <th></th> --}}
</tr>
</tfoot>

129
resources/views/projects/jobs.blade.php

@ -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

4
resources/views/projects/partials/nav-tabs.blade.php

@ -3,8 +3,8 @@
<li class="{{ Request::segment(3) == null ? 'active' : '' }}">
{!! link_to_route('projects.show', trans('project.show'), [$project->id]) !!}
</li>
<li class="{{ Request::segment(3) == 'features' ? 'active' : '' }}">
{!! link_to_route('projects.features', trans('project.features').' ('.$project->features->count().')', [$project->id]) !!}
<li class="{{ Request::segment(3) == 'jobs' ? 'active' : '' }}">
{!! link_to_route('projects.jobs', trans('project.jobs').' ('.$project->jobs->count().')', [$project->id]) !!}
</li>
<li class="{{ Request::segment(3) == 'payments' ? 'active' : '' }}">
{!! link_to_route('projects.payments', trans('project.payments').' ('.$project->payments->count().')', [$project->id]) !!}

10
resources/views/projects/partials/project-stats.blade.php

@ -1,12 +1,12 @@
<div class="row">
<div class="col-lg-6 col-md-12">
<a href="{{ route('projects.features',[$project->id]) }}" title="Progress Berdasarkan Index Bobot Biaya Fitur">
<a href="{{ route('projects.jobs',[$project->id]) }}" title="Progress Berdasarkan Index Bobot Biaya Fitur">
<div class="panel panel-info">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3"><i class="fa fa-tasks fa-4x"></i></div>
<div class="col-xs-9 text-right">
<div class="huge">{{ formatDecimal($project->getFeatureOveralProgress()) }} %</div>
<div class="huge">{{ formatDecimal($project->getJobOveralProgress()) }} %</div>
<div class="lead">Overall Progress</div>
</div>
</div>
@ -15,13 +15,13 @@
</a>
</div>
<div class="col-lg-6 col-md-12">
<a href="{{ route('projects.features',[$project->id]) }}" title="Total Fitur dan Task">
<a href="{{ route('projects.jobs',[$project->id]) }}" title="Total Fitur dan Task">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3"><i class="fa fa-paperclip fa-4x"></i></div>
<div class="col-xs-9 text-right">
<div class="huge">{{ $project->features->count() }} Fitur</div>
<div class="huge">{{ $project->jobs->count() }} Fitur</div>
<div class="lead">{{ $project->tasks->count() }} Task</div>
</div>
</div>
@ -30,7 +30,7 @@
</a>
</div>
<div class="col-lg-6 col-md-12">
<a href="{{ route('projects.features',[$project->id]) }}" title="Collectible Earnings">
<a href="{{ route('projects.jobs',[$project->id]) }}" title="Collectible Earnings">
<div class="panel panel-success">
<div class="panel-heading">
<div class="row">

2
routes/api/projects.php

@ -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']);

26
routes/web/projects.php

@ -5,11 +5,11 @@ Route::group(['middleware' => ['web', 'auth'], 'namespace' => 'Projects'], funct
* Projects Routes
*/
Route::get('projects/{id}/delete', ['as' => 'projects.delete', 'uses' => 'ProjectsController@delete']);
Route::get('projects/{id}/features', ['as' => 'projects.features', 'uses' => 'ProjectsController@features']);
Route::get('projects/{id}/features-export/{type?}', ['as' => 'projects.features-export', 'uses' => 'ProjectsController@featuresExport']);
Route::get('projects/{id}/jobs', ['as' => 'projects.jobs', 'uses' => 'ProjectsController@jobs']);
Route::get('projects/{id}/jobs-export/{type?}', ['as' => 'projects.jobs-export', 'uses' => 'ProjectsController@jobsExport']);
Route::get('projects/{id}/payments', ['as' => 'projects.payments', 'uses' => 'ProjectsController@payments']);
Route::get('projects/{id}/subscriptions', ['as' => 'projects.subscriptions', 'uses' => 'ProjectsController@subscriptions']);
Route::post('projects/{id}/features-reorder', ['as' => 'projects.features-reorder', 'uses' => 'ProjectsController@featuresReorder']);
Route::post('projects/{id}/jobs-reorder', ['as' => 'projects.jobs-reorder', 'uses' => 'ProjectsController@jobsReorder']);
Route::patch('projects/{id}/status-update', ['as' => 'projects.status-update', 'uses' => 'ProjectsController@statusUpdate']);
Route::resource('projects', 'ProjectsController');
@ -19,21 +19,21 @@ Route::group(['middleware' => ['web', 'auth'], 'namespace' => 'Projects'], funct
Route::get('projects/{project}/invoices', ['as' => 'projects.invoices', 'uses' => 'InvoicesController@index']);
/**
* Features Routes
* Jobs Routes
*/
Route::get('projects/{id}/features/create', ['as' => 'features.create', 'uses' => 'FeaturesController@create']);
Route::get('projects/{id}/features/add-from-other-project', ['as' => 'features.add-from-other-project', 'uses' => 'FeaturesController@addFromOtherProject']);
Route::post('features/{id}/tasks-reorder', ['as' => 'features.tasks-reorder', 'uses' => 'FeaturesController@tasksReorder']);
Route::post('projects/{id}/features', ['as' => 'features.store', 'uses' => 'FeaturesController@store']);
Route::post('projects/{id}/features/store-from-other-project', ['as' => 'features.store-from-other-project', 'uses' => 'FeaturesController@storeFromOtherProject']);
Route::get('features/{id}/delete', ['as' => 'features.delete', 'uses' => 'FeaturesController@delete']);
Route::resource('features', 'FeaturesController', ['except' => ['create', 'store']]);
Route::get('projects/{id}/jobs/create', ['as' => 'jobs.create', 'uses' => 'JobsController@create']);
Route::get('projects/{id}/jobs/add-from-other-project', ['as' => 'jobs.add-from-other-project', 'uses' => 'JobsController@addFromOtherProject']);
Route::post('jobs/{id}/tasks-reorder', ['as' => 'jobs.tasks-reorder', 'uses' => 'JobsController@tasksReorder']);
Route::post('projects/{id}/jobs', ['as' => 'jobs.store', 'uses' => 'JobsController@store']);
Route::post('projects/{id}/jobs/store-from-other-project', ['as' => 'jobs.store-from-other-project', 'uses' => 'JobsController@storeFromOtherProject']);
Route::get('jobs/{id}/delete', ['as' => 'jobs.delete', 'uses' => 'JobsController@delete']);
Route::resource('jobs', 'JobsController', ['except' => ['create', 'store']]);
/**
* Tasks Routes
*/
Route::get('features/{id}/tasks/create', ['as' => 'tasks.create', 'uses' => 'TasksController@create']);
Route::post('features/{id}/tasks', ['as' => 'tasks.store', 'uses' => 'TasksController@store']);
Route::get('jobs/{id}/tasks/create', ['as' => 'tasks.create', 'uses' => 'TasksController@create']);
Route::post('jobs/{id}/tasks', ['as' => 'tasks.store', 'uses' => 'TasksController@store']);
Route::patch('task/{id}', ['as' => 'tasks.update', 'uses' => 'TasksController@update']);
Route::delete('task/{id}', ['as' => 'tasks.destroy', 'uses' => 'TasksController@destroy']);

190
tests/Feature/ManageFeaturesTest.php

@ -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'));
}
}

190
tests/Feature/ManageJobsTest.php

@ -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'));
}
}

24
tests/Feature/ManageProjectsTest.php

@ -4,7 +4,7 @@ namespace Tests\Feature;
use App\Entities\Partners\Customer;
use App\Entities\Payments\Payment;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Entities\Projects\Project;
use App\Entities\Projects\Task;
use Tests\TestCase;
@ -14,7 +14,7 @@ class ManageProjectsTest extends TestCase
/** @test */
public function admin_can_input_new_project_with_existing_customer()
{
$user = $this->adminUserSigningIn();
$user = $this->adminUserSigningIn();
$customer = factory(Customer::class)->create();
$this->visit(route('projects.create'));
@ -81,12 +81,12 @@ class ManageProjectsTest extends TestCase
/** @test */
public function admin_can_delete_a_project()
{
$user = $this->adminUserSigningIn();
$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]);
$task = factory(Task::class)->create(['feature_id' => $feature->id]);
$job = factory(Job::class)->create(['project_id' => $project->id]);
$task = factory(Task::class)->create(['job_id' => $job->id]);
$payment = factory(Payment::class)->create(['project_id' => $project->id]);
$this->visit('projects/'.$project->id);
@ -105,21 +105,21 @@ class ManageProjectsTest extends TestCase
'project_id' => $project->id,
]);
$this->notSeeInDatabase('features', [
$this->notSeeInDatabase('jobs', [
'project_id' => $project->id,
]);
$this->notSeeInDatabase('tasks', [
'feature_id' => $feature->id,
'job_id' => $job->id,
]);
}
/** @test */
public function admin_can_edit_a_project()
{
$user = $this->adminUserSigningIn();
$user = $this->adminUserSigningIn();
$customer = factory(Customer::class)->create();
$project = factory(Project::class)->create(['customer_id' => $customer->id]);
$project = factory(Project::class)->create(['customer_id' => $customer->id]);
$this->visit('projects/'.$project->id.'/edit');
$this->seePageIs('projects/'.$project->id.'/edit');
@ -149,7 +149,7 @@ class ManageProjectsTest extends TestCase
/** @test */
public function form_is_validated_on_invalid_project_entry()
{
$user = $this->adminUserSigningIn();
$user = $this->adminUserSigningIn();
$customer = factory(Customer::class)->create();
$this->visit(route('projects.index'));
@ -170,9 +170,9 @@ class ManageProjectsTest extends TestCase
/** @test */
public function admin_can_update_project_status_on_project_detail_page()
{
$user = $this->adminUserSigningIn();
$user = $this->adminUserSigningIn();
$customer = factory(Customer::class)->create();
$project = factory(Project::class)->create([
$project = factory(Project::class)->create([
'customer_id' => $customer->id,
'status_id' => 1,
]);

44
tests/Feature/ManageTasksTest.php

@ -2,7 +2,7 @@
namespace Tests\Feature;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Entities\Projects\Task;
use Tests\TestCase;
@ -11,12 +11,12 @@ class ManageTasksTest extends TestCase
/** @test */
public function admin_can_entry_task()
{
$user = $this->adminUserSigningIn();
$feature = factory(Feature::class)->create(['worker_id' => $user->id]);
$user = $this->adminUserSigningIn();
$job = factory(Job::class)->create(['worker_id' => $user->id]);
$this->visit(route('features.show', $feature->id));
$this->seePageIs(route('features.show', $feature->id));
$this->see(trans('feature.tasks'));
$this->visit(route('jobs.show', $job->id));
$this->seePageIs(route('jobs.show', $job->id));
$this->see(trans('job.tasks'));
// Fill Form
$this->submitForm(trans('task.create'), [
@ -26,14 +26,14 @@ class ManageTasksTest extends TestCase
'route_name' => 'tasks/create',
]);
$this->seePageIs(route('features.show', $feature->id));
$this->seePageIs(route('jobs.show', $job->id));
$this->see(trans('task.created'));
$this->seeInDatabase('tasks', [
'name' => 'Nama Task Baru',
'description' => 'Deskripsi task yang dikerjakani.',
'progress' => 70,
'feature_id' => $feature->id,
'job_id' => $job->id,
'route_name' => 'tasks/create',
]);
}
@ -41,13 +41,13 @@ class ManageTasksTest extends TestCase
/** @test */
public function admin_can_edit_task_data()
{
$user = $this->adminUserSigningIn();
$feature = factory(Feature::class)->create(['worker_id' => $user->id]);
$task = factory(Task::class)->create(['feature_id' => $feature->id]);
$user = $this->adminUserSigningIn();
$job = factory(Job::class)->create(['worker_id' => $user->id]);
$task = factory(Task::class)->create(['job_id' => $job->id]);
$this->visit(route('features.show', $feature->id));
$this->visit(route('jobs.show', $job->id));
$this->click($task->id.'-tasks-edit');
$this->seePageIs(route('features.show', [$feature->id, 'action' => 'task_edit', 'task_id' => $task->id]));
$this->seePageIs(route('jobs.show', [$job->id, 'action' => 'task_edit', 'task_id' => $task->id]));
$this->see(trans('task.edit'));
// Fill Form
@ -56,29 +56,29 @@ class ManageTasksTest extends TestCase
'progress' => 77,
]);
$this->seePageIs(route('features.show', $feature->id));
$this->seePageIs(route('jobs.show', $job->id));
$this->see(trans('task.updated'));
$this->seeInDatabase('tasks', [
'name' => 'Nama Task Edit',
'progress' => 77,
'feature_id' => $feature->id,
'name' => 'Nama Task Edit',
'progress' => 77,
'job_id' => $job->id,
]);
}
/** @test */
public function admin_can_delete_a_task()
{
$user = $this->adminUserSigningIn();
$feature = factory(Feature::class)->create(['worker_id' => $user->id]);
$task = factory(Task::class)->create(['feature_id' => $feature->id]);
$user = $this->adminUserSigningIn();
$job = factory(Job::class)->create(['worker_id' => $user->id]);
$task = factory(Task::class)->create(['job_id' => $job->id]);
$this->visit(route('features.show', $feature->id));
$this->visit(route('jobs.show', $job->id));
$this->click($task->id.'-tasks-delete');
$this->see(trans('app.delete_confirm_button'));
$this->press(trans('app.delete_confirm_button'));
$this->seePageIs(route('features.show', $feature->id));
$this->seePageIs(route('jobs.show', $job->id));
$this->see(trans('task.deleted'));
}
}

92
tests/Unit/Models/ProjectTest.php

@ -4,7 +4,7 @@ namespace Tests\Unit\Models;
use App\Entities\Partners\Customer;
use App\Entities\Payments\Payment;
use App\Entities\Projects\Feature;
use App\Entities\Projects\Job;
use App\Entities\Projects\Project;
use App\Entities\Projects\Task;
use App\Entities\Subscriptions\Subscription;
@ -14,38 +14,38 @@ use Tests\TestCase;
class ProjectTest extends TestCase
{
/** @test */
public function a_project_has_many_features()
public function a_project_has_many_jobs()
{
$project = factory(Project::class)->create();
$feature = factory(Feature::class)->create(['project_id' => $project->id]);
$this->assertInstanceOf(Collection::class, $project->features);
$this->assertInstanceOf(Feature::class, $project->features->first());
$job = factory(Job::class)->create(['project_id' => $project->id]);
$this->assertInstanceOf(Collection::class, $project->jobs);
$this->assertInstanceOf(Job::class, $project->jobs->first());
}
/** @test */
public function a_project_has_many_main_features()
public function a_project_has_many_main_jobs()
{
$project = factory(Project::class)->create();
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1]);
$this->assertInstanceOf(Collection::class, $project->mainFeatures);
$this->assertInstanceOf(Feature::class, $project->mainFeatures->first());
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1]);
$this->assertInstanceOf(Collection::class, $project->mainJobs);
$this->assertInstanceOf(Job::class, $project->mainJobs->first());
}
/** @test */
public function a_project_has_many_additional_features()
public function a_project_has_many_additional_jobs()
{
$project = factory(Project::class)->create();
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 2]);
$this->assertInstanceOf(Collection::class, $project->additionalFeatures);
$this->assertInstanceOf(Feature::class, $project->additionalFeatures->first());
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 2]);
$this->assertInstanceOf(Collection::class, $project->additionalJobs);
$this->assertInstanceOf(Job::class, $project->additionalJobs->first());
}
/** @test */
public function a_project_has_feature_tasks()
public function a_project_has_job_tasks()
{
$project = factory(Project::class)->create();
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 2]);
$tasks = factory(Task::class, 2)->create(['feature_id' => $feature->id]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 2]);
$tasks = factory(Task::class, 2)->create(['job_id' => $job->id]);
$this->assertInstanceOf(Collection::class, $project->tasks);
$this->assertInstanceOf(Task::class, $project->tasks->first());
}
@ -62,7 +62,7 @@ class ProjectTest extends TestCase
/** @test */
public function a_project_has_many_subscriptions()
{
$project = factory(Project::class)->create();
$project = factory(Project::class)->create();
$subscription = factory(Subscription::class)->create(['project_id' => $project->id]);
$this->assertInstanceOf(Collection::class, $project->subscriptions);
$this->assertInstanceOf(Subscription::class, $project->subscriptions->first());
@ -72,7 +72,7 @@ class ProjectTest extends TestCase
public function a_project_belongs_to_a_customer()
{
$customer = factory(Customer::class)->create();
$project = factory(Project::class)->create(['customer_id' => $customer->id]);
$project = factory(Project::class)->create(['customer_id' => $customer->id]);
$this->assertInstanceOf(Customer::class, $project->customer);
$this->assertEquals($project->customer_id, $customer->id);
@ -81,7 +81,7 @@ class ProjectTest extends TestCase
/** @test */
public function a_project_has_cash_in_total_method()
{
$project = factory(Project::class)->create();
$project = factory(Project::class)->create();
$payments = factory(Payment::class, 2)->create(['project_id' => $project->id, 'in_out' => 1, 'amount' => 20000]);
$this->assertEquals(40000, $project->cashInTotal());
}
@ -89,43 +89,43 @@ class ProjectTest extends TestCase
/** @test */
public function a_project_has_cash_out_total_method()
{
$project = factory(Project::class)->create();
$project = factory(Project::class)->create();
$payments = factory(Payment::class, 2)->create(['project_id' => $project->id, 'in_out' => 0, 'amount' => 10000]);
factory(Payment::class)->create(['project_id' => $project->id, 'in_out' => 1, 'amount' => 10000]);
$this->assertEquals(20000, $project->cashOutTotal());
}
/** @test */
public function a_project_has_feature_overall_progress_method()
public function a_project_has_job_overall_progress_method()
{
$project = factory(Project::class)->create();
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 2000]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 20]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 2000]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 20]);
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 3000]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 30]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 3000]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 30]);
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 100]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 100]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$this->assertEquals(53.75, $project->getFeatureOveralProgress());
$this->assertEquals(53.75, $project->getJobOveralProgress());
}
/** @test */
public function a_project_returns_0_on_feature_overall_progress_method_if_all_feature_is_free()
public function a_project_returns_0_on_job_overall_progress_method_if_all_job_is_free()
{
$project = factory(Project::class)->create();
factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 0]);
$this->assertEquals(0, $project->getFeatureOveralProgress());
$this->assertEquals(0, $project->getJobOveralProgress());
}
/** @test */
@ -145,25 +145,25 @@ class ProjectTest extends TestCase
/** @test */
public function a_project_has_collectible_earnings_method()
{
// Collectible earnings is total of (price * avg task progress of each feature)
// Collectible earnings is total of (price * avg task progress of each job)
$project = factory(Project::class)->create();
$collectibeEarnings = 0;
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 2000]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 20]);
$collectibeEarnings += (2000 * (20 / 100)); // feature price * avg task progress
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 2000]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 20]);
$collectibeEarnings += (2000 * (20 / 100)); // job price * avg task progress
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 3000]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 30]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 3000]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 30]);
$collectibeEarnings += (3000 * (30 / 100));
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 100]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$collectibeEarnings += (1500 * (100 / 100));
$feature = factory(Feature::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['feature_id' => $feature->id, 'progress' => 100]);
$job = factory(Job::class)->create(['project_id' => $project->id, 'type_id' => 1, 'price' => 1500]);
factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$collectibeEarnings += (1500 * (100 / 100));
// $collectibeEarnings = 400 + 900 + 1500 + 1500;

4
tests/Unit/References/PaymentTypeTest.php

@ -14,7 +14,7 @@ class PaymentTypeTest extends TestCase
$this->assertEquals([
1 => trans('payment.types.project'),
2 => trans('payment.types.add_feature'),
2 => trans('payment.types.add_job'),
3 => trans('payment.types.maintenance'),
], $paymentType->toArray());
}
@ -25,7 +25,7 @@ class PaymentTypeTest extends TestCase
$paymentType = new Type;
$this->assertEquals(trans('payment.types.project'), $paymentType->getNameById(1));
$this->assertEquals(trans('payment.types.add_feature'), $paymentType->getNameById(2));
$this->assertEquals(trans('payment.types.add_job'), $paymentType->getNameById(2));
$this->assertEquals(trans('payment.types.maintenance'), $paymentType->getNameById(3));
}

Loading…
Cancel
Save