Browse Source

Add Job Progress export in html format and Job Model unit test

pull/6/head
Nafies Luthfi 8 years ago
parent
commit
a30b02649c
  1. 15
      app/Entities/Projects/Job.php
  2. 9
      app/Http/Controllers/Projects/JobsController.php
  3. 1
      resources/lang/id/app.php
  4. 32
      resources/lang/id/project.php
  5. 25
      resources/views/invoices/pdf.blade.php
  6. 20
      resources/views/projects/jobs/export-html.blade.php
  7. 3
      resources/views/projects/jobs/index.blade.php
  8. 57
      resources/views/projects/jobs/progress-export-html.blade.php
  9. 1
      routes/web/projects.php
  10. 59
      tests/Unit/Models/JobTest.php

15
app/Entities/Projects/Job.php

@ -6,6 +6,11 @@ use App\Entities\Users\User;
use Illuminate\Database\Eloquent\Model;
use Laracasts\Presenter\PresentableTrait;
/**
* Job Model.
*
* @author Nafies Luthfi <nafiesl@gmail.com>
*/
class Job extends Model
{
use PresentableTrait;
@ -32,4 +37,14 @@ class Job extends Model
{
return $this->type_id == 1 ? 'Project' : 'Additional';
}
public function getProgressAttribute()
{
return $this->tasks->isEmpty() ? 0 : $this->tasks->avg('progress');
}
public function getReceiveableEarningAttribute()
{
return $this->price * ($this->progress / 100);
}
}

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

@ -71,6 +71,13 @@ class JobsController extends Controller
{
$jobs = $project->getJobList(request('job_type', 1));
return view('projects.jobs-export-html', compact('project', 'jobs'));
return view('projects.jobs.export-html', compact('project', 'jobs'));
}
public function jobProgressExport(Project $project, $exportType = 'html')
{
$jobs = $project->getJobList(request('job_type', 1));
return view('projects.jobs.progress-export-html', compact('project', 'jobs'));
}
}

1
resources/lang/id/app.php

@ -16,6 +16,7 @@ return [
'type' => 'Jenis',
'total' => 'Total',
'count' => 'Jumlah',
'remark' => 'Keterangan',
// Action
'add' => 'Tambah',

32
resources/lang/id/project.php

@ -2,17 +2,19 @@
return [
// Labels
'project' => 'Project',
'projects' => 'Daftar Project',
'work_duration' => 'Durasi',
'cash_in_total' => 'Total Pemasukan',
'cash_out_total' => 'Total Pengeluaran',
'search' => 'Cari Project',
'found' => 'Project ditemukan',
'not_found' => 'Project tidak ditemukan',
'select' => 'Pilih Project',
'empty' => 'Belum ada Project',
'back_to_index' => 'Kembali ke daftar Project',
'project' => 'Project',
'projects' => 'Daftar Project',
'work_duration' => 'Durasi',
'cash_in_total' => 'Total Pemasukan',
'cash_out_total' => 'Total Pengeluaran',
'search' => 'Cari Project',
'found' => 'Project ditemukan',
'not_found' => 'Project tidak ditemukan',
'select' => 'Pilih Project',
'empty' => 'Belum ada Project',
'back_to_index' => 'Kembali ke daftar Project',
'receiveable_earnings' => 'Pendapatan',
'earnings_calculation' => 'Rumus Pendapatan',
// Actions
'create' => 'Input Project Baru',
@ -26,10 +28,10 @@ return [
'undeleted' => 'Data Project gagal dihapus.',
'show_jobs' => 'Lihat Job',
'jobs_export_html' => 'Export HTML',
'jobs_export_excel' => 'Export Excel',
'jobs_export_progress_excel' => 'Export Progress',
'sort_jobs' => 'Urutkan Prioritas Job',
'jobs_list_export_html' => 'Export HTML',
'jobs_export_excel' => 'Export Excel',
'jobs_progress_export_html' => 'Export Progress',
'sort_jobs' => 'Urutkan Prioritas Job',
// Attributes
'name' => 'Nama Project',

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

@ -5,7 +5,8 @@
<title>{{ trans('invoice.print') }} - {{ $invoice->number }}</title>
<style>
html {
margin: 10px 20px 0px 20px;
/*margin: 10px 20px 0px 20px;*/
font-family: 'Trebuchet MS';
}
p {
margin: 10px 0px;
@ -41,7 +42,7 @@
{!! appLogoImage(['style' => 'width:100%']) !!}
</td>
<td style="width:380px">
<div style="width:280px">
<div style="width:300px">
<h4 style="margin:0px; border-bottom: 3px; font-size: 21.5px">
{{ Option::get('agency_name') }}
</h4>
@ -59,8 +60,8 @@
</td>
<td style="width:270px; text-align: center;">
<h3 style="margin: 3px 0; font-size: 24px">{{ trans('invoice.invoice') }}</h3>
<div>{{ trans('invoice.number') }} : INV-{{ $invoice->number }}</div>
<div>{{ trans('app.date') }} : {{ dateId($invoice->created_at->format('Y-m-d')) }}</div>
<div style="margin: 5px">{{ trans('invoice.number') }} : INV-{{ $invoice->number }}</div>
<div>{{ trans('app.date') }} : {{ dateId($invoice->date) }}</div>
</td>
</tr>
<tr>
@ -125,26 +126,32 @@
{{ ucwords(Terbilang::make($invoice->amount)) }} Rupiah
</td>
</tr>
@if ($invoice->notes)
<tr style="vertical-align: top;">
<td colspan="3">
<p style="font-style: italic;"><strong>Catatan</strong> : {!! nl2br($invoice->notes) !!}</p>
</td>
</tr>
@endif
{{-- TODO : Add dynamic bank account based on agency bank account. --}}
<tr style="vertical-align: top;">
<td colspan="3">
{{--
<p>Pembayaran dapat dilakukan melalui transfer ke rekening berikut:</p>
No. Rek : <strong>BCA // 782-0088-543</strong><br>
An. <strong>NAFIES LUTHFI</strong>
--}}
<table>
<tr><td>Bank</td><td>: <strong>BCA</strong></td></tr>
<tr><td>No. Rek</td><td>: <strong>782-0088-543</strong></td></tr>
<tr><td>Atas nama</td><td>: <strong>NAFIES LUTHFI</strong></td></tr>
</table>
<p>Terima kasih atas kerjasamanya.</p>
</td>
</tr>
<tr>
<td colspan="3" class="text-center">
{{ Option::get('agency_city') ? Option::get('agency_city').', ' : '' }}
{{ dateId($invoice->created_at->format('Y-m-d')) }} <br><br><br><br>
{{ dateId($invoice->date) }} <br><br><br><br>
<div style="font-weight: bold;">{{ Option::get('agency_name') }}</div>
</td>
</tr>

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

@ -1,5 +1,5 @@
<?php
// $filename = str_slug(trans('project.jobs') . '-' . $project->name) . '.xls';
// $filename = str_slug(__('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
@ -9,25 +9,25 @@
<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.jobs') }} {{ $project->name }}</title>
<title>{{ __('project.jobs') }} {{ $project->name }}</title>
{!! Html::style('assets/css/app.css') !!}
</head>
<body style="font-family:'Liberation Serif'">
<div class="container">
<h1 class="page-header text-center">{{ trans('project.jobs') }} {{ $project->name }}</h1>
<h1 class="page-header text-center">{{ __('project.jobs') }} {{ $project->name }}</h1>
@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 style="background-color: #ffd298"><th colspan="2">{{ __('app.description') }}</th></tr>
<tr><td colspan="2">{!! nl2br($job->description) !!}</td></tr>
@if ($job->tasks->count())
<tr><td colspan="2">&nbsp;</td></tr>
<tr style="background-color: #ffd298">
<th class="col-md-3">{{ trans('task.list') }}</th>
<th class="col-md-6">{{ trans('app.description') }}</th>
<th class="col-md-3">{{ __('task.list') }}</th>
<th class="col-md-6">{{ __('app.description') }}</th>
</tr>
@foreach($job->tasks as $task)
<tr>
@ -40,13 +40,13 @@
</table>
@endforeach
<h1 class="page-header text-center">{{ trans('project.cost_proposal') }}</h1>
<h1 class="page-header text-center">{{ __('project.cost_proposal') }}</h1>
<table width="100%" class="table table-condensed table-bordered">
<tbody>
<tr>
<th class="text-center">{{ trans('app.table_no') }}</th>
<th>{{ trans('job.name') }}</th>
<th class="text-center">{{ trans('job.price') }}</th>
<th class="text-center">{{ __('app.table_no') }}</th>
<th>{{ __('job.name') }}</th>
<th class="text-center">{{ __('job.price') }}</th>
</tr>
@foreach($jobs as $key => $job)
<tr>

3
resources/views/projects/jobs/index.blade.php

@ -24,7 +24,8 @@
{{ link_to_route('projects.jobs.index', 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.index', 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_list_export_html'), [$project->id, 'html', 'job_type' => $key], ['class' => '','target' => '_blank']) !!} |
{!! link_to_route('projects.job-progress-export', trans('project.jobs_progress_export_html'), [$project->id, 'html', 'job_type' => $key], ['class' => '','target' => '_blank']) !!}
@endif
</div>
<h3 class="panel-title">

57
resources/views/projects/jobs/progress-export-html.blade.php

@ -0,0 +1,57 @@
<?php
// $filename = str_slug(__('project.jobs') . '-' . $project->name) . '.xls';
// header("Content-Disposition: attachment; filename=\"$filename\"");
// header("Content-Type: application/vnd.ms-excel");
?>
<!DOCTYPE html>
<html>
<head>
<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>{{ __('project.jobs') }} {{ $project->name }}</title>
{!! Html::style('assets/css/app.css') !!}
</head>
<body style="font-family:'Liberation Serif'; font-size: 16px;">
<div class="container">
<h1 class="page-header text-center">{{ __('project.jobs') }} {{ $project->name }}</h1>
<h2 class="text-center">{{ __('project.progress') }}</h2>
<table width="100%" class="table table-condensed table-bordered">
<tbody>
<tr>
<th class="text-center">{{ __('app.table_no') }}</th>
<th>{{ __('job.name') }}</th>
<th class="text-center">{{ __('job.price') }}</th>
<th class="text-center">{{ __('job.progress') }}</th>
<th class="text-center">{{ __('project.receiveable_earnings') }}</th>
</tr>
@foreach($jobs->sortByDesc('progress') as $key => $job)
<tr>
<td class="text-center">{{ 1 + $key }}</td>
<td>{{ $job->name }}</td>
<td class="text-right">{{ formatRp($job->price) }}</td>
<td class="text-center">{{ $job->progress }} %</td>
<td class="text-right">{{ formatRp($job->receiveable_earning) }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">{{ __('app.total') }}</th>
<th class="text-right">{{ formatRp($jobs->sum('price')) }}</th>
<th class="text-center">{{ formatDecimal($project->getJobOveralProgress()) }} %</th>
<th class="text-right">{{ formatRp($jobs->sum('receiveable_earning')) }}</th>
</tr>
</tfoot>
</table>
<div style="margin-bottom: 50px;">
<p>{{ __('app.remark') }}:</p>
<p>
<strong>{{ __('project.earnings_calculation') }} : </strong><br>
<strong>{{ __('project.receiveable_earnings') }}</strong> = <strong>{{ __('job.price') }}</strong> * <strong>{{ __('job.progress') }} (%)</strong>
</p>
</div>
</div>
</body>
</html>

1
routes/web/projects.php

@ -29,6 +29,7 @@ Route::group(['middleware' => ['web', 'role:admin'], 'namespace' => 'Projects'],
* Project Jobs Routes
*/
Route::get('projects/{project}/jobs-export/{type?}', ['as' => 'projects.jobs-export', 'uses' => 'JobsController@jobsExport']);
Route::get('projects/{project}/job-progress-export/{type?}', ['as' => 'projects.job-progress-export', 'uses' => 'JobsController@jobProgressExport']);
Route::get('projects/{id}/jobs/create', ['as' => 'projects.jobs.create', 'uses' => 'JobsController@create']);
Route::post('projects/{id}/jobs', ['as' => 'projects.jobs.store', 'uses' => 'JobsController@store']);
Route::get('projects/{id}/jobs/add-from-other-project', ['as' => 'projects.jobs.add-from-other-project', 'uses' => 'JobsController@addFromOtherProject']);

59
tests/Unit/Models/JobTest.php

@ -0,0 +1,59 @@
<?php
namespace Tests\Unit\Models;
use App\Entities\Projects\Job;
use App\Entities\Projects\Project;
use App\Entities\Projects\Task;
use Illuminate\Support\Collection;
use Tests\TestCase;
/**
* Job Model Unit Test.
*
* @author Nafies Luthfi <nafiesl@gmail.com>
*/
class JobTest extends TestCase
{
/** @test */
public function a_job_belongs_to_a_project()
{
$project = factory(Project::class)->create();
$job = factory(Job::class)->create(['project_id' => $project->id]);
$this->assertInstanceOf(Project::class, $job->project);
$this->assertEquals($project->id, $job->project->id);
}
/** @test */
public function a_job_has_many_tasks()
{
$job = factory(Job::class)->create();
$tasks = factory(Task::class, 2)->create(['job_id' => $job->id]);
$this->assertInstanceOf(Collection::class, $job->tasks);
$this->assertInstanceOf(Task::class, $job->tasks->first());
}
/** @test */
public function a_job_has_progress_attribute()
{
$job = factory(Job::class)->create();
$task1 = factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$task2 = factory(Task::class)->create(['job_id' => $job->id, 'progress' => 50]);
// Job progress = job tasks average progress
$this->assertEquals(75, $job->progress);
}
/** @test */
public function a_job_has_receiveable_earning_attribute()
{
$job = factory(Job::class)->create(['price' => 1000]);
$task1 = factory(Task::class)->create(['job_id' => $job->id, 'progress' => 100]);
$task2 = factory(Task::class)->create(['job_id' => $job->id, 'progress' => 50]);
// Job receiveable earning = job tasks average progress (%) * job price
$this->assertEquals(750, $job->receiveable_earning);
}
}
Loading…
Cancel
Save