set fare classes on aircraft w/ overrides (no admin ui yet)

This commit is contained in:
Nabeel Shahzad 2017-06-10 18:27:19 -05:00
parent dee3503ad6
commit bf910e4549
27 changed files with 859 additions and 88 deletions

View File

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreateFareRequest;
use App\Http\Requests\UpdateFareRequest;
use App\Repositories\FareRepository;
use App\Http\Controllers\AppBaseController;
use Illuminate\Http\Request;
use Flash;
use Prettus\Repository\Criteria\RequestCriteria;
use Response;
class FareController extends AppBaseController
{
/** @var FareRepository */
private $fareRepository;
public function __construct(FareRepository $fareRepo)
{
$this->fareRepository = $fareRepo;
}
/**
* Display a listing of the Fare.
*
* @param Request $request
*
* @return Response
*/
public function index(Request $request)
{
$this->fareRepository->pushCriteria(new RequestCriteria($request));
$fares = $this->fareRepository->all();
return view('admin.fares.index')
->with('fares', $fares);
}
/**
* Show the form for creating a new Fare.
*
* @return Response
*/
public function create()
{
return view('admin.fares.create');
}
/**
* Store a newly created Fare in storage.
*
* @param CreateFareRequest $request
*
* @return Response
*/
public function store(CreateFareRequest $request)
{
$input = $request->all();
$fare = $this->fareRepository->create($input);
Flash::success('Fare saved successfully.');
return redirect(route('admin.fares.index'));
}
/**
* Display the specified Fare.
*
* @param int $id
*
* @return Response
*/
public function show($id)
{
$fare = $this->fareRepository->findWithoutFail($id);
if (empty($fare)) {
Flash::error('Fare not found');
return redirect(route('admin.fares.index'));
}
return view('admin.fares.show')->with('fare', $fare);
}
/**
* Show the form for editing the specified Fare.
*
* @param int $id
*
* @return Response
*/
public function edit($id)
{
$fare = $this->fareRepository->findWithoutFail($id);
if (empty($fare)) {
Flash::error('Fare not found');
return redirect(route('admin.fares.index'));
}
return view('admin.fares.edit')->with('fare', $fare);
}
/**
* Update the specified Fare in storage.
*
* @param int $id
* @param UpdateFareRequest $request
*
* @return Response
*/
public function update($id, UpdateFareRequest $request)
{
$fare = $this->fareRepository->findWithoutFail($id);
if (empty($fare)) {
Flash::error('Fare not found');
return redirect(route('admin.fares.index'));
}
$fare = $this->fareRepository->update($request->all(), $id);
Flash::success('Fare updated successfully.');
return redirect(route('admin.fares.index'));
}
/**
* Remove the specified Fare from storage.
*
* @param int $id
*
* @return Response
*/
public function destroy($id)
{
$fare = $this->fareRepository->findWithoutFail($id);
if (empty($fare)) {
Flash::error('Fare not found');
return redirect(route('admin.fares.index'));
}
$this->fareRepository->delete($id);
Flash::success('Fare deleted successfully.');
return redirect(route('admin.fares.index'));
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Models\Fare;
class CreateFareRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return Fare::$rules;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Models\Fare;
class UpdateFareRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return Fare::$rules;
}
}

View File

@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class Aircraft
*
* @package App\Models
* @version June 9, 2017, 1:06 am UTC
*/
@ -15,42 +16,46 @@ class Aircraft extends Model
use SoftDeletes;
public $table = 'aircraft';
protected $dates = ['deleted_at'];
public $fillable = [
'aircraft_class_id',
'icao',
'name',
'full_name',
'registration',
'active'
];
public $fillable
= [
'aircraft_class_id',
'icao',
'name',
'full_name',
'registration',
'active',
];
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts = [
'icao' => 'string',
'name' => 'string',
'full_name' => 'string',
'registration' => 'string',
'active' => 'boolean',
];
protected $casts
= [
'icao' => 'string',
'name' => 'string',
'full_name' => 'string',
'registration' => 'string',
'active' => 'boolean',
];
/**
* Validation rules
*
* @var array
*/
public static $rules = [
'icao' => 'required|max:4',
'name' => 'required',
'full_name' => 'required',
'registration' => 'required',
'active' => 'default:1'
];
public static $rules
= [
'icao' => 'required|max:4',
'name' => 'required',
'full_name' => 'required',
'registration' => 'required',
'active' => 'default:1',
];
/**
* foreign key
@ -63,8 +68,12 @@ class Aircraft extends Model
);
}
public function fares() {
# aircraft_fare == table name
return $this->belongsToMany('App\Models\Fare', 'aircraft_fare');
public function fares()
{
$r = $this->belongsToMany(
'App\Models\Fare',
'aircraft_fare'
)->withPivot('price', 'cost', 'capacity');
return $r;
}
}

66
app/Models/Fare.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace App\Models;
use Eloquent as Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class Fare
*
* @package App\Models
* @version June 10, 2017, 4:03 am UTC
*/
class Fare extends Model
{
use SoftDeletes;
public $table = 'fares';
protected $dates = ['deleted_at'];
public $fillable
= [
'code',
'name',
'price',
'cost',
'notes',
'active',
];
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts
= [
'code' => 'string',
'name' => 'string',
'price' => 'float',
'cost' => 'float',
'notes' => 'string',
'active' => 'boolean',
];
/**
* Validation rules
*
* @var array
*/
public static $rules
= [
'code' => 'required',
'name' => 'required',
'cost' => 'default:0.0',
];
public function aircraft() {
return $this->belongsToMany(
'App\Models\Aircraft',
'aircraft_fare'
)->withPivot('price', 'cost', 'capacity');
}
}

View File

@ -4,6 +4,8 @@ namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\AircraftService;
class AppServiceProvider extends ServiceProvider
{
/**
@ -23,6 +25,13 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
//
// bind all the app services...
$this->app->bind('App\Services\AircraftService', function($app) {
return new \App\Services\AircraftService();
});
$this->app->bind('App\Services\AircraftFareService', function($app) {
return new \App\Services\AircraftFareService();
});
}
}

View File

@ -3,7 +3,6 @@
namespace App\Repositories;
use App\Models\AircraftClass;
use InfyOm\Generator\Common\BaseRepository;
class AircraftClassRepository extends BaseRepository
{

View File

@ -3,20 +3,20 @@
namespace App\Repositories;
use App\Models\Aircraft;
use InfyOm\Generator\Common\BaseRepository;
class AircraftRepository extends BaseRepository
{
/**
* @var array
*/
protected $fieldSearchable = [
'icao',
'name',
'full_name',
'registration',
'active',
];
protected $fieldSearchable
= [
'icao',
'name',
'full_name',
'registration',
'active',
];
/**
* Configure the Model
@ -25,4 +25,9 @@ class AircraftRepository extends BaseRepository
{
return Aircraft::class;
}
public function findByICAO($icao)
{
return $this->findByField('icao', $icao)->first();
}
}

View File

@ -3,7 +3,6 @@
namespace App\Repositories;
use App\Models\Airlines;
use InfyOm\Generator\Common\BaseRepository;
class AirlinesRepository extends BaseRepository
{

View File

@ -0,0 +1,21 @@
<?php
namespace App\Repositories;
use Illuminate\Validation\Validator;
abstract class BaseRepository extends \InfyOm\Generator\Common\BaseRepository {
public function validate($values) {
$validator = Validator::make(
$values,
$this->model()->rules
);
if($validator->fails()) {
return $validator->messages();
}
return true;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Repositories;
use App\Models\Fare;
class FareRepository extends BaseRepository
{
/**
* @var array
*/
protected $fieldSearchable = [
'code',
'name',
'price',
'cost',
'notes',
'active'
];
/**
* Configure the Model
**/
public function model()
{
return Fare::class;
}
public function findByCode($code) {
return $this->findByField('code', $code)->first();
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Services;
use App\Repositories\FareRepository;
use App\Repositories\AircraftRepository;
class AircraftFareService {
protected $aircraft, $fare;
/**
* return a PIREP model
* @param $aircraft AircraftRepository
* @param $fare FareRepository
*/
public function __construct(AircraftRepository $aircraft, FareRepository $fare) {
$this->fare = $fare;
$this->aircraft = $aircraft;
}
public function link(int $aircraft_id, int $fare_id) {
}
public function unlink(int $aircraft_id, int $fare_id) {
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services;
use App\Models\Aircraft;
use App\Models\AircraftClass;
use Dompdf\Exception;
class AircraftService extends BaseService
{
public function create(
array $attributes,
AircraftClass $class = null
) {
$repo = app('App\Repositories\AircraftRepository');
try {
$model = $repo->create($attributes);
} catch (Exception $e) {
return false;
}
if ($class != null) {
$model->class()->associate($class);
$model->save();
}
return $model;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Services;
class BaseService {
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Services;
use App\Models\Aircraft;
use App\Models\Fare;
class FareService extends BaseService {
/**
* Attach a fare to an aircraft
*
* @param Aircraft $aircraft
* @param Fare $fare
* @param array set the price/cost/capacity
*
* @return Aircraft
*/
public function set_for_aircraft(
Aircraft &$aircraft,
Fare &$fare,
array $override=[]
) {
$aircraft->fares()->syncWithoutDetaching([$fare->id]);
# modify any pivot values?
if(count($override) > 0) {
$aircraft->fares()->updateExistingPivot($fare->id, $override);
}
$aircraft->save();
$aircraft = $aircraft->fresh();
return $aircraft;
}
/**
* return all the fares for an aircraft. check the pivot
* table to see if the price/cost/capacity has been overridden
* and return the correct amounts.
* @param Aircraft $aircraft
* @return Fare[]
*/
public function get_for_aircraft(Aircraft &$aircraft)
{
$fares = [];
foreach($aircraft->fares as $fare) {
if(!is_null($fare->pivot->price)) {
$fare->price = $fare->pivot->price;
}
if(!is_null($fare->pivot->cost)) {
$fare->cost = $fare->pivot->cost;
}
if(!is_null($fare->pivot->capacity)) {
$fare->capacity = $fare->pivot->capacity;
}
array_push($fares, $fare);
}
return $fares;
}
public function delete_from_aircraft(Aircraft &$aircraft, Fare &$fare)
{
$aircraft->fares()->detach($fare->id);
$aircraft = $aircraft->fresh();
return $aircraft;
}
}

View File

@ -5,7 +5,7 @@ namespace App\Services;
use App\Repositories\AircraftRepository;
class PIREPService {
class PIREPService extends BaseService {
protected $aircraft;

View File

@ -2,6 +2,9 @@
$factory->define(App\Models\Fare::class, function (Faker\Generator $faker) {
return [
'code' => 'Y',
'name' => 'Economy',
'price' => '100',
'capacity' => '200',
];
});

View File

@ -9,7 +9,7 @@ class CreateAircraftsTable extends Migration
{
Schema::create('aircraft', function (Blueprint $table) {
$table->increments('id');
$table->integer('aircraft_class_id')->unsigned();
$table->integer('aircraft_class_id')->unsigned()->nullable();
$table->string('icao');
$table->string('name');
$table->string('full_name')->nullable();

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateFaresTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('fares', function (Blueprint $table) {
$table->increments('id');
$table->string('code');
$table->string('name');
$table->float('price');
$table->float('cost')->default(0.0);
$table->integer('capacity')->default(0);
$table->string('notes')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();
$table->softDeletes();
});
Schema::create('aircraft_fare', function (Blueprint $table) {
$table->increments('id');
$table->integer('aircraft_id');
$table->integer('fare_id');
$table->float('price')->nullable();
$table->float('cost')->nullable();
$table->float('capacity')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('fares');
Schema::drop('aircraft_fare');
}
}

View File

@ -0,0 +1,24 @@
@extends('admin.app')
@section('content')
<section class="content-header">
<h1>
Fare
</h1>
</section>
<div class="content">
@include('adminlte-templates::common.errors')
<div class="box box-primary">
<div class="box-body">
<div class="row">
{!! Form::open(['route' => 'admin.fares.store']) !!}
@include('admin.fares.fields')
{!! Form::close() !!}
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,23 @@
@extends('admin.app')
@section('content')
<section class="content-header">
<h1>
Fare
</h1>
</section>
<div class="content">
@include('adminlte-templates::common.errors')
<div class="box box-primary">
<div class="box-body">
<div class="row">
{!! Form::model($fare, ['route' => ['admin.fares.update', $fare->id], 'method' => 'patch']) !!}
@include('admin.fares.fields')
{!! Form::close() !!}
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,7 @@
<!-- Submit Field -->
<div class="form-group col-sm-12">
{!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}
<a href="{!! route('admin.fares.index') !!}" class="btn btn-default">Cancel</a>
</div>

View File

@ -0,0 +1,23 @@
@extends('admin.app')
@section('content')
<section class="content-header">
<h1 class="pull-left">Fares</h1>
<h1 class="pull-right">
<a class="btn btn-primary pull-right" style="margin-top: -10px;margin-bottom: 5px" href="{!! route('admin.fares.create') !!}">Add New</a>
</h1>
</section>
<div class="content">
<div class="clearfix"></div>
@include('flash::message')
<div class="clearfix"></div>
<div class="box box-primary">
<div class="box-body">
@include('admin.fares.table')
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,19 @@
@extends('admin.app')
@section('content')
<section class="content-header">
<h1>
Fare
</h1>
</section>
<div class="content">
<div class="box box-primary">
<div class="box-body">
<div class="row" style="padding-left: 20px">
@include('admin.fares.show_fields')
<a href="{!! route('admin.fares.index') !!}" class="btn btn-default">Back</a>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,54 @@
<!-- Id Field -->
<div class="form-group">
{!! Form::label('id', 'Id:') !!}
<p>{!! $fare->id !!}</p>
</div>
<!-- Code Field -->
<div class="form-group">
{!! Form::label('code', 'Code:') !!}
<p>{!! $fare->code !!}</p>
</div>
<!-- Name Field -->
<div class="form-group">
{!! Form::label('name', 'Name:') !!}
<p>{!! $fare->name !!}</p>
</div>
<!-- Price Field -->
<div class="form-group">
{!! Form::label('price', 'Price:') !!}
<p>{!! $fare->price !!}</p>
</div>
<!-- Cost Field -->
<div class="form-group">
{!! Form::label('cost', 'Cost:') !!}
<p>{!! $fare->cost !!}</p>
</div>
<!-- Notes Field -->
<div class="form-group">
{!! Form::label('notes', 'Notes:') !!}
<p>{!! $fare->notes !!}</p>
</div>
<!-- Active Field -->
<div class="form-group">
{!! Form::label('active', 'Active:') !!}
<p>{!! $fare->active !!}</p>
</div>
<!-- Created At Field -->
<div class="form-group">
{!! Form::label('created_at', 'Created At:') !!}
<p>{!! $fare->created_at !!}</p>
</div>
<!-- Updated At Field -->
<div class="form-group">
{!! Form::label('updated_at', 'Updated At:') !!}
<p>{!! $fare->updated_at !!}</p>
</div>

View File

@ -0,0 +1,32 @@
<table class="table table-responsive" id="fares-table">
<thead>
<th>Code</th>
<th>Name</th>
<th>Price</th>
<th>Cost</th>
<th>Notes</th>
<th>Active</th>
<th colspan="3">Action</th>
</thead>
<tbody>
@foreach($fares as $fare)
<tr>
<td>{!! $fare->code !!}</td>
<td>{!! $fare->name !!}</td>
<td>{!! $fare->price !!}</td>
<td>{!! $fare->cost !!}</td>
<td>{!! $fare->notes !!}</td>
<td>{!! $fare->active !!}</td>
<td>
{!! Form::open(['route' => ['admin.fares.destroy', $fare->id], 'method' => 'delete']) !!}
<div class='btn-group'>
<a href="{!! route('admin.fares.show', [$fare->id]) !!}" class='btn btn-default btn-xs'><i class="glyphicon glyphicon-eye-open"></i></a>
<a href="{!! route('admin.fares.edit', [$fare->id]) !!}" class='btn btn-default btn-xs'><i class="glyphicon glyphicon-edit"></i></a>
{!! Form::button('<i class="glyphicon glyphicon-trash"></i>', ['type' => 'submit', 'class' => 'btn btn-danger btn-xs', 'onclick' => "return confirm('Are you sure?')"]) !!}
</div>
{!! Form::close() !!}
</td>
</tr>
@endforeach
</tbody>
</table>

View File

@ -1,40 +1,151 @@
<?php
use App\Models\Aircraft;
use App\Services\AircraftService;
class AircraftTest extends TestCase
{
protected $aircraft, $aircraft_class;
protected $ac_svc,
$ICAO = 'B777';
public function setUp() {
public function setUp()
{
parent::setUp();
$this->aircraft = $this->createRepository('AircraftRepository');
$this->aircraft_class = $this->createRepository('AircraftClassRepository');
# add an aircraft_class
factory(App\Models\AircraftClass::class)->create();
$this->setup_data();
}
protected function add_fares(Aircraft $aircraft) {
/**
* add the fares to a given aircraft
* run the factory for incl the fares
*/
protected function setup_data()
{
factory(App\Models\AircraftClass::class)->create();
factory(App\Models\Fare::class)->create();
}
protected function get_ac_class()
{
return app('App\Repositories\AircraftClassRepository')
->findByField('code', 'H')->first();
}
protected function find_by_icao($icao)
{
$ac_repo = app('App\Repositories\AircraftRepository');
return $ac_repo->findByICAO($icao);
}
protected function get_fare_by_code($code)
{
return app('App\Repositories\FareRepository')->findByCode($code);
}
/**
* Check the association of the aircraft class to an aircraft
* Mostly to experiment with the ORM type stuff. This isn't
* where most of the testing, etc is required.
*/
protected function add_aircraft()
{
$svc = app('App\Services\AircraftService');
$err = $svc->create([
'icao' => $this->ICAO,
'name' => 'Boeing 777',
], $this->get_ac_class());
$this->assertNotFalse($err);
return $this->find_by_icao($this->ICAO);
}
public function testAircraftClasses()
{
# add a few fare classes
$aircraft = $this->add_aircraft();
$this->assertEquals($this->ICAO, $aircraft->icao, 'ICAO matching');
$this->assertEquals(
$this->get_ac_class(),
$aircraft->class,
'Check belongsTo relationship'
);
}
$this->aircraft->create([
'aircraft_class_id' => 1,
'icao' => 'B777',
'name' => 'Boeing 777',
public function testAircraftFaresNoOverride()
{
$fare_svc = app('App\Services\FareService');
$aircraft = $this->add_aircraft();
$fare = $this->get_fare_by_code('Y');
$fare_svc->set_for_aircraft($aircraft, $fare);
$ac_fares = $fare_svc->get_for_aircraft($aircraft);
$this->assertCount(1, $ac_fares);
$this->assertEquals($fare->price, $ac_fares[0]->price);
$this->assertEquals($fare->capacity, $ac_fares[0]->capacity);
#
# set an override now
#
$fare_svc->set_for_aircraft($aircraft, $fare, [
'price' => 50, 'capacity' => 400
]);
$aircraft = Aircraft::where('icao', 'B777')->first();
$this->assertEquals('B777', $aircraft->icao, 'ICAO matching');
$this->assertEquals('H', $aircraft->class->code, 'Check belongsTo relationship');
# look for them again
$ac_fares = $fare_svc->get_for_aircraft($aircraft);
// check to see if the fares are properly applied to this aircraft
$this->add_fares($aircraft);
$this->assertCount(1, $ac_fares);
$this->assertEquals(50, $ac_fares[0]->price);
$this->assertEquals(400, $ac_fares[0]->capacity);
# delete
$fare_svc->delete_from_aircraft($aircraft, $fare);
$this->assertCount(0, $fare_svc->get_for_aircraft($aircraft));
}
public function testAircraftFaresOverride()
{
$fare_svc = app('App\Services\FareService');
$aircraft = $this->add_aircraft();
$fare = $this->get_fare_by_code('Y');
$fare_svc->set_for_aircraft($aircraft, $fare, [
'price' => 50, 'capacity' => 400
]);
$ac_fares = $fare_svc->get_for_aircraft($aircraft);
$this->assertCount(1, $ac_fares);
$this->assertEquals(50, $ac_fares[0]->price);
$this->assertEquals(400, $ac_fares[0]->capacity);
#
# update the override to a different amount and make sure it updates
#
$fare_svc->set_for_aircraft($aircraft, $fare, [
'price' => 150, 'capacity' => 50
]);
$ac_fares = $fare_svc->get_for_aircraft($aircraft);
$this->assertCount(1, $ac_fares);
$this->assertEquals(150, $ac_fares[0]->price);
$this->assertEquals(50, $ac_fares[0]->capacity);
# delete
$fare_svc->delete_from_aircraft($aircraft, $fare);
$this->assertCount(0, $fare_svc->get_for_aircraft($aircraft));
}
/**
* @expectedException Exception
*/
public function testAircraftMissingField()
{
# missing the name field
$svc = app('App\Services\AircraftService');
$svc->create(['icao' => $this->ICAO]);
}
}