Add subfleet specific expenses, fixed bug for pirep fares #130 #136

This commit is contained in:
Nabeel Shahzad 2018-03-05 12:21:38 -06:00
parent bf94a20e57
commit 96e8fbfa78
12 changed files with 357 additions and 44 deletions

View File

@ -0,0 +1,11 @@
<?php
use Faker\Generator as Faker;
$factory->define(App\Models\SubfleetExpense::class, function (Faker $faker) {
return [
'subfleet_id' => null,
'name' => $faker->text(20),
'amount' => $faker->randomFloat(2, 100, 1000),
];
});

View File

@ -27,11 +27,13 @@ class CreateSubfleetTables extends Migration
});
Schema::create('subfleet_expenses', function(Blueprint $table) {
$table->increments('id');
$table->unsignedBigInteger('subfleet_id');
$table->string('name', 50);
$table->unsignedDecimal('cost');
$table->unsignedDecimal('amount');
$table->timestamps();
$table->primary(['subfleet_id', 'name']);
$table->index('subfleet_id');
});
Schema::create('subfleet_fare', function (Blueprint $table) {

View File

@ -238,6 +238,14 @@ subfleets:
type: 772-36ER-GE90-115B
ground_handling_multiplier: 150
subfleet_expenses:
- id: 1
subfleet_id: 1
name: Catering
amount: 1000
created_at: now
updated_at: now
# add a few mods to aircraft and fares
subfleet_fare:
@ -433,20 +441,20 @@ pirep_comments:
journals:
- id: '1'
ledger_id: null
balance: '7970000'
balance: '15840000'
currency: USD
morphed_type: App\Models\Airline
morphed_id: '1'
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
created_at: now
updated_at: now
- id: '2'
ledger_id: null
balance: '15000'
balance: '30000'
currency: USD
morphed_type: App\Models\User
morphed_id: '1'
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
created_at: now
updated_at: now
journal_transactions:
- id: 81e9d86c-fede-467d-befd-887e046d9c48
@ -458,9 +466,9 @@ journal_transactions:
memo: 'Fares Y300; price:200, cost: 0'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: b12a81a9-1273-4413-a46a-96b2925cfefb
transaction_group: fares
@ -471,9 +479,9 @@ journal_transactions:
memo: 'Fares B10; price:1100, cost: 0'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: 8688ff40-aed4-4d60-90b7-0c5a88c12fbc
transaction_group: fares
@ -484,9 +492,9 @@ journal_transactions:
memo: 'Fares F10; price:1000, cost: 0'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: d34a9d1e-0d54-4191-bf9f-0043062c04c9
transaction_group: expenses
@ -497,9 +505,9 @@ journal_transactions:
memo: 'Expense: Per-Flight (no muliplier)'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: bdc9d50d-ac3d-4334-997c-8f13b8328ab8
transaction_group: expenses
@ -510,9 +518,9 @@ journal_transactions:
memo: 'Expense: Per-Flight (multiplier)'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: b5c45ad5-af73-4d7c-9352-3dfb8de292a0
transaction_group: expenses
@ -523,9 +531,9 @@ journal_transactions:
memo: 'Expense: Per-Flight (multiplier, on airline)'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: e65083f9-23c3-4e98-8d63-cd7f35732f7b
transaction_group: ground_handling
@ -536,9 +544,9 @@ journal_transactions:
memo: 'Ground handling'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: 9825a96e-58b5-465f-8fb8-4c8e1e5567eb
transaction_group: pilot_pay
@ -549,9 +557,9 @@ journal_transactions:
memo: 'Pilot Payment @ 50'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: 2e3118b3-c98f-41d1-b2b6-ccb4f34e86b0
transaction_group: pilot_pay
@ -562,7 +570,20 @@ journal_transactions:
memo: 'Pilot Payment @ 50'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: '2018-03-02 23:50:01'
updated_at: '2018-03-02 23:50:01'
post_date: '2018-03-02 23:50:01'
created_at: now
updated_at: now
post_date: now
deleted_at: null
- id: b98a837a-aa59-4630-a547-5a9d90b5b541
transaction_group: subfleet_expense
journal_id: 1
credit: null
debit: 100000
currency: USD
memo: 'Subfleet Expense: Catering'
ref_class: App\Models\Pirep
ref_class_id: pirepid_1
created_at: now
updated_at: now
post_date: now
deleted_at: null

View File

@ -7,6 +7,7 @@ use App\Http\Requests\UpdateSubfleetRequest;
use App\Models\Airline;
use App\Models\Enums\FuelType;
use App\Models\Subfleet;
use App\Models\SubfleetExpense;
use App\Repositories\AircraftRepository;
use App\Repositories\FareRepository;
use App\Repositories\RankRepository;
@ -232,7 +233,7 @@ class SubfleetController extends BaseController
* @param Subfleet $subfleet
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
protected function return_ranks_view(Subfleet $subfleet)
protected function return_ranks_view(?Subfleet $subfleet)
{
$subfleet->refresh();
@ -248,7 +249,7 @@ class SubfleetController extends BaseController
* @param Subfleet $subfleet
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
protected function return_fares_view(Subfleet $subfleet)
protected function return_fares_view(?Subfleet $subfleet)
{
$subfleet->refresh();
@ -303,6 +304,57 @@ class SubfleetController extends BaseController
return $this->return_ranks_view($subfleet);
}
/**
* @param Subfleet $subfleet
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
protected function return_expenses_view(?Subfleet $subfleet)
{
$subfleet->refresh();
return view('admin.subfleets.expenses', [
'subfleet' => $subfleet,
]);
}
/**
* Operations for associating ranks to the subfleet
* @param $id
* @param Request $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Exception
*/
public function expenses($id, Request $request)
{
$subfleet = $this->subfleetRepo->findWithoutFail($id);
if (empty($subfleet)) {
return $this->return_expenses_view($subfleet);
}
if ($request->isMethod('get')) {
return $this->return_expenses_view($subfleet);
}
/**
* update specific rank data
*/
if ($request->isMethod('post')) {
$expense = new SubfleetExpense($request->post());
$expense->subfleet_id = $subfleet->id;
$expense->save();
$subfleet->refresh();
} elseif ($request->isMethod('put')) {
$expense = SubfleetExpense::findOrFail($request->input('expense_id'));
$expense->{$request->name} = $request->value;
$expense->save();
} // dissassociate fare from teh aircraft
elseif ($request->isMethod('delete')) {
$expense = SubfleetExpense::findOrFail($request->input('expense_id'));
$expense->delete();
}
return $this->return_expenses_view($subfleet);
}
/**
* Operations on fares to the subfleet
* @param $id

View File

@ -74,6 +74,9 @@ class Subfleet extends BaseModel
* Relationships
*/
/**
* @return $this
*/
public function aircraft()
{
return $this->hasMany(Aircraft::class, 'subfleet_id')
@ -85,6 +88,14 @@ class Subfleet extends BaseModel
return $this->belongsTo(Airline::class, 'airline_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function expenses()
{
return $this->hasMany(SubfleetExpense::class, 'subfleet_id');
}
public function fares()
{
return $this->belongsToMany(Fare::class, 'subfleet_fare')

View File

@ -0,0 +1,45 @@
<?php
namespace App\Models;
/**
* Class SubfleetExpense
* @package App\Models
*/
class SubfleetExpense extends BaseModel
{
public $table = 'subfleet_expenses';
public $fillable = [
'subfleet_id',
'name',
'amount',
];
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts = [
'amount' => 'float',
];
public static $rules = [
'name' => 'required',
'amount' => 'required|numeric',
];
/**
* Relationships
*/
/**
* Has a subfleet
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function subfleet()
{
return $this->belongsTo(Subfleet::class, 'subfleet_id');
}
}

View File

@ -46,6 +46,7 @@ Route::group([
# subfleet
Route::resource('subfleets', 'SubfleetController');
Route::match(['get', 'post', 'put', 'delete'], 'subfleets/{id}/expenses', 'SubfleetController@expenses');
Route::match(['get', 'post', 'put', 'delete'], 'subfleets/{id}/fares', 'SubfleetController@fares');
Route::match(['get', 'post', 'put', 'delete'], 'subfleets/{id}/ranks', 'SubfleetController@ranks');

View File

@ -7,6 +7,7 @@ use App\Models\Enums\ExpenseType;
use App\Models\Enums\PirepSource;
use App\Models\Expense;
use App\Models\Pirep;
use App\Models\SubfleetExpense;
use App\Repositories\ExpenseRepository;
use App\Repositories\JournalRepository;
use App\Support\Math;
@ -85,8 +86,13 @@ class FinanceService extends BaseService
$pirep->user->journal = $pirep->user->initJournal(config('phpvms.currency'));
}
# Clean out the expenses first
$this->deleteFinancesForPirep($pirep);
# Now start and pay from scratch
$this->payFaresForPirep($pirep);
$this->payExpensesForPirep($pirep);
$this->paySubfleetExpenses($pirep);
$this->payGroundHandlingForPirep($pirep);
$this->payPilotForPirep($pirep);
@ -118,7 +124,7 @@ class FinanceService extends BaseService
/** @var \App\Models\Fare $fare */
foreach ($fares as $fare) {
Log::info('Finance: PIREP: ' . $pirep->id . ', fare:', $fare->toArray());
Log::info('Finance: PIREP: '.$pirep->id.', fare:', $fare->toArray());
$credit = Money::createFromAmount($fare->count * $fare->price);
$debit = Money::createFromAmount($fare->count * $fare->cost);
@ -129,7 +135,7 @@ class FinanceService extends BaseService
$debit,
$pirep,
'Fares ' . $fare->code . $fare->count
. '; price:' . $fare->price . ', cost: ' . $fare->cost,
.'; price: '.$fare->price.', cost: '.$fare->cost,
null,
'fares'
);
@ -164,6 +170,38 @@ class FinanceService extends BaseService
}
}
/**
* Pay out the expenses for the subfleet
* @param Pirep $pirep
* @throws \Prettus\Validator\Exceptions\ValidatorException
*/
public function paySubfleetExpenses(Pirep $pirep)
{
$subfleet_expenses = SubfleetExpense::where([
'subfleet_id' => $pirep->aircraft->subfleet_id,
])->get();
if(!$subfleet_expenses) {
return;
}
foreach ($subfleet_expenses as $expense) {
Log::info('Finance: PIREP: '.$pirep->id
.'; subfleet expense: "'.$expense->name.'", cost: "'.$expense->amount);
$this->journalRepo->post(
$pirep->airline->journal,
null,
Money::createFromAmount($expense->amount),
$pirep,
'Subfleet Expense: '.$expense->name,
null,
'subfleet_expense'
);
}
}
/**
* Collect and apply the ground handling cost
* @param Pirep $pirep
@ -174,12 +212,13 @@ class FinanceService extends BaseService
public function payGroundHandlingForPirep(Pirep $pirep)
{
$ground_handling_cost = $this->getGroundHandlingCost($pirep);
Log::info('Finance: PIREP: '.$pirep->id.'; ground handling: '.$ground_handling_cost);
$this->journalRepo->post(
$pirep->airline->journal,
null,
Money::createFromAmount($ground_handling_cost),
$pirep,
'Ground handling',
'Ground Handling',
null,
'ground_handling'
);
@ -199,6 +238,9 @@ class FinanceService extends BaseService
$pilot_pay_rate = $this->getPilotPayRateForPirep($pirep);
$memo = 'Pilot Payment @ ' . $pilot_pay_rate;
Log::info('Finance: PIREP: '.$pirep->id
.'; pilot pay: '.$pilot_pay_rate.', total: '.$pilot_pay);
$this->journalRepo->post(
$pirep->airline->journal,
null,
@ -239,13 +281,20 @@ class FinanceService extends BaseService
# Collect all of the fares and prices
$flight_fares = $this->fareSvc->getForPirep($pirep);
Log::info('Finance: PIREP: ' . $pirep->id . ', flight fares: ', $flight_fares->toArray());
$all_fares = $this->fareSvc->getAllFares($flight, $pirep->aircraft->subfleet);
$fares = $all_fares->map(function($fare, $i) use ($flight_fares) {
$fares = $all_fares->map(function($fare, $i) use ($flight_fares, $pirep) {
$fare_count = $flight_fares->whereStrict('id', $fare->id)->first();
$fare_count = $flight_fares
->where('fare_id', $fare->id)
->first();
if($fare_count) {
Log::info('Finance: PIREP: ' . $pirep->id . ', fare count: '. $fare_count);
# If the count is greater than capacity, then just set it
# to the maximum amount
if($fare_count->count > $fare->capacity) {
@ -253,6 +302,8 @@ class FinanceService extends BaseService
} else {
$fare->count = $fare_count->count;
}
} else {
Log::info('Finance: PIREP: ' . $pirep->id . ', no fare count found', $fare->toArray());
}
return $fare;
@ -307,6 +358,9 @@ class FinanceService extends BaseService
return $expense;
});
/**
* Throw an event and collect any expenses returned from it
*/
$gathered_expenses = event(new ExpensesEvent($pirep));
if (!\is_array($gathered_expenses)) {
return $expenses;

View File

@ -21,5 +21,11 @@
@include('admin.subfleets.fares')
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
@include('admin.subfleets.expenses')
</div>
</div>
@endsection
@include('admin.subfleets.script')

View File

@ -0,0 +1,60 @@
<div id="subfleet-expenses-wrapper" class="col-12">
<div class="header">
<h3>expenses</h3>
@component('admin.components.info')
These expenses are only applied to this subfleet
@endcomponent
</div>
<br/>
<table class="table table-responsive" id="subfleet-expenses">
@if(count($subfleet->expenses))
<thead>
<th>Name</th>
<th>Cost&nbsp;<span class="small">{!! currency(config('phpvms.currency')) !!}</span></th>
<th></th>
</thead>
@endif
<tbody>
@foreach($subfleet->expenses as $expense)
<tr>
<td>
<p>
<a href="#" data-pk="{!! $expense->id !!}"
data-name="name">{!! $expense->name !!}</a>
</p>
</td>
<td>
<p>
<a href="#" data-pk="{!! $expense->id !!}"
data-name="amount">{!! $expense->amount !!}</a>
</p>
</td>
<td align="right">
{!! Form::open(['url' => url('/admin/subfleets/'.$subfleet->id.'/expenses'),
'method' => 'delete', 'class' => 'modify_expense form-inline']) !!}
{!! Form::hidden('expense_id', $expense->id) !!}
{!! Form::button('<i class="fa fa-times"></i>', ['type' => 'submit',
'class' => 'btn btn-sm btn-danger btn-icon',
'onclick' => "return confirm('Are you sure?')",
]) !!}
{!! Form::close() !!}
</td>
</tr>
@endforeach
</tbody>
</table>
<hr/>
<div class="row">
<div class="col-sm-12">
<div class="text-right">
{!! Form::open(['url' => url('/admin/subfleets/'.$subfleet->id.'/expenses'),
'method' => 'post', 'class' => 'modify_expense form-inline']) !!}
{!! Form::input('text', 'name', null, ['class' => 'form-control input-sm']) !!}
{!! Form::number('cost', null, ['class' => 'form-control input-sm']) !!}
{!! Form::button('<i class="fa fa-plus"></i> Add', ['type' => 'submit',
'class' => 'btn btn-success btn-small']) !!}
{!! Form::close() !!}
</div>
</div>
</div>
</div>

View File

@ -32,6 +32,22 @@ function setEditable() {
}
}
});
$('#subfleet-expenses a').editable({
type: 'text',
mode: 'inline',
emptytext: '0',
url: '{!! url('/admin/subfleets/'.$subfleet->id.'/expenses') !!}',
title: 'Enter override value',
ajaxOptions: {'type': 'put'},
params: function (params) {
return {
expense_id: params.pk,
name: params.name,
value: params.value
}
}
});
}
$(document).ready(function() {
@ -51,6 +67,12 @@ $(document).ready(function() {
$.pjax.submit(event, '#subfleet_ranks_wrapper', {push: false});
});
$(document).on('submit', 'form.modify_expense', function (event) {
event.preventDefault();
console.log(event);
$.pjax.submit(event, '#subfleet-expenses-wrapper', {push: false});
});
$(document).on('pjax:complete', function() {
$(".select2").select2();
setEditable();

View File

@ -91,6 +91,18 @@ class FinanceTest extends TestCase
$this->fareSvc->setForSubfleet($subfleet['subfleet'], $fare);
}
# Add an expense
factory(App\Models\Expense::class)->create([
'airline_id' => null,
'amount' => 100
]);
# Add a subfleet expense
factory(App\Models\SubfleetExpense::class)->create([
'subfleet_id' => $subfleet['subfleet']->id,
'amount' => 200
]);
$pirep = $this->pirepSvc->create($pirep, []);
return [$user, $pirep, $fares];
@ -614,8 +626,24 @@ class FinanceTest extends TestCase
$transactions = $journalRepo->getAllForObject($pirep);
$this->assertCount(6, $transactions['transactions']);
$this->assertCount(8, $transactions['transactions']);
$this->assertEquals(3020, $transactions['credits']->getValue());
$this->assertEquals(1540, $transactions['debits']->getValue());
$this->assertEquals(1840, $transactions['debits']->getValue());
# Check that all the different transaction types are there
$transaction_types = [
'expenses' => 1,
'fares' => 3,
'ground_handling' => 1,
'pilot_pay' => 2, # debit on the airline, credit to the pilot
'subfleet_expense' => 1,
];
foreach($transaction_types as $type => $count) {
$find = $transactions['transactions']
->where('transaction_group', $type);
$this->assertEquals($count, $find->count());
}
}
}