Post fares/expenses on PIREP to Airline journal #130

This commit is contained in:
Nabeel Shahzad
2018-03-01 16:20:13 -06:00
parent f1b9ea94dc
commit 2c52a2f7e6
22 changed files with 625 additions and 85 deletions

View File

@@ -8,6 +8,9 @@ $factory->define(App\Models\Fare::class, function (Faker $faker) {
'code' => $faker->text(5), 'code' => $faker->text(5),
'name' => $faker->text(20), 'name' => $faker->text(20),
'price' => $faker->randomFloat(2, 100, 1000), 'price' => $faker->randomFloat(2, 100, 1000),
'cost' => function (array $fare) {
return round($fare['price'] / 2);
},
'capacity' => $faker->randomFloat(0, 20, 500), 'capacity' => $faker->randomFloat(0, 20, 500),
]; ];
}); });

View File

@@ -17,7 +17,7 @@ class CreateExpensesTable extends Migration
$table->increments('id'); $table->increments('id');
$table->unsignedInteger('airline_id')->nullable(); $table->unsignedInteger('airline_id')->nullable();
$table->string('name'); $table->string('name');
$table->unsignedDecimal('amount'); $table->unsignedInteger('amount');
$table->unsignedTinyInteger('type'); $table->unsignedTinyInteger('type');
$table->boolean('multiplier')->nullable()->default(0); $table->boolean('multiplier')->nullable()->default(0);
$table->boolean('active')->nullable()->default(1); $table->boolean('active')->nullable()->default(1);

View File

@@ -13,11 +13,8 @@ class PirepAccepted
public $pirep; public $pirep;
/**
* Create a new event instance.
*/
public function __construct(Pirep $pirep) public function __construct(Pirep $pirep)
{ {
// $this->pirep = $pirep;
} }
} }

View File

@@ -15,6 +15,7 @@ use App\Repositories\AirportRepository;
use App\Repositories\PirepFieldRepository; use App\Repositories\PirepFieldRepository;
use App\Repositories\PirepRepository; use App\Repositories\PirepRepository;
use App\Repositories\SubfleetRepository; use App\Repositories\SubfleetRepository;
use App\Services\FareService;
use App\Services\PIREPService; use App\Services\PIREPService;
use App\Services\UserService; use App\Services\UserService;
use App\Support\Units\Time; use App\Support\Units\Time;
@@ -31,6 +32,7 @@ class PirepController extends BaseController
private $airportRepo, private $airportRepo,
$airlineRepo, $airlineRepo,
$aircraftRepo, $aircraftRepo,
$fareSvc,
$pirepSvc, $pirepSvc,
$pirepRepo, $pirepRepo,
$pirepFieldRepo, $pirepFieldRepo,
@@ -51,6 +53,7 @@ class PirepController extends BaseController
AirportRepository $airportRepo, AirportRepository $airportRepo,
AirlineRepository $airlineRepo, AirlineRepository $airlineRepo,
AircraftRepository $aircraftRepo, AircraftRepository $aircraftRepo,
FareService $fareSvc,
PirepRepository $pirepRepo, PirepRepository $pirepRepo,
PirepFieldRepository $pirepFieldRepo, PirepFieldRepository $pirepFieldRepo,
PIREPService $pirepSvc, PIREPService $pirepSvc,
@@ -60,6 +63,7 @@ class PirepController extends BaseController
$this->airportRepo = $airportRepo; $this->airportRepo = $airportRepo;
$this->airlineRepo = $airlineRepo; $this->airlineRepo = $airlineRepo;
$this->aircraftRepo = $aircraftRepo; $this->aircraftRepo = $aircraftRepo;
$this->fareSvc = $fareSvc;
$this->pirepRepo = $pirepRepo; $this->pirepRepo = $pirepRepo;
$this->pirepFieldRepo = $pirepFieldRepo; $this->pirepFieldRepo = $pirepFieldRepo;
$this->pirepSvc = $pirepSvc; $this->pirepSvc = $pirepSvc;
@@ -143,7 +147,7 @@ class PirepController extends BaseController
]; ];
} }
$this->pirepSvc->saveFares($pirep->id, $fares); $this->fareSvc->saveForPirep($pirep, $fares);
} }
/** /**

View File

@@ -15,6 +15,7 @@ use App\Repositories\AirportRepository;
use App\Repositories\Criteria\WhereCriteria; use App\Repositories\Criteria\WhereCriteria;
use App\Repositories\PirepFieldRepository; use App\Repositories\PirepFieldRepository;
use App\Repositories\PirepRepository; use App\Repositories\PirepRepository;
use App\Services\FareService;
use App\Services\GeoService; use App\Services\GeoService;
use App\Services\PIREPService; use App\Services\PIREPService;
use App\Services\UserService; use App\Services\UserService;
@@ -29,10 +30,11 @@ class PirepController extends Controller
{ {
private $aircraftRepo, private $aircraftRepo,
$airlineRepo, $airlineRepo,
$fareSvc,
$geoSvc,
$pirepRepo, $pirepRepo,
$airportRepo, $airportRepo,
$pirepFieldRepo, $pirepFieldRepo,
$geoSvc,
$pirepSvc, $pirepSvc,
$userSvc; $userSvc;
@@ -41,9 +43,10 @@ class PirepController extends Controller
* @param AircraftRepository $aircraftRepo * @param AircraftRepository $aircraftRepo
* @param AirlineRepository $airlineRepo * @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo * @param AirportRepository $airportRepo
* @param FareService $fareSvc
* @param GeoService $geoSvc
* @param PirepRepository $pirepRepo * @param PirepRepository $pirepRepo
* @param PirepFieldRepository $pirepFieldRepo * @param PirepFieldRepository $pirepFieldRepo
* @param GeoService $geoSvc
* @param PIREPService $pirepSvc * @param PIREPService $pirepSvc
* @param UserService $userSvc * @param UserService $userSvc
*/ */
@@ -51,9 +54,10 @@ class PirepController extends Controller
AircraftRepository $aircraftRepo, AircraftRepository $aircraftRepo,
AirlineRepository $airlineRepo, AirlineRepository $airlineRepo,
AirportRepository $airportRepo, AirportRepository $airportRepo,
FareService $fareSvc,
GeoService $geoSvc,
PirepRepository $pirepRepo, PirepRepository $pirepRepo,
PirepFieldRepository $pirepFieldRepo, PirepFieldRepository $pirepFieldRepo,
GeoService $geoSvc,
PIREPService $pirepSvc, PIREPService $pirepSvc,
UserService $userSvc UserService $userSvc
) { ) {
@@ -63,6 +67,7 @@ class PirepController extends Controller
$this->airportRepo = $airportRepo; $this->airportRepo = $airportRepo;
$this->pirepFieldRepo = $pirepFieldRepo; $this->pirepFieldRepo = $pirepFieldRepo;
$this->fareSvc = $fareSvc;
$this->geoSvc = $geoSvc; $this->geoSvc = $geoSvc;
$this->pirepSvc = $pirepSvc; $this->pirepSvc = $pirepSvc;
$this->userSvc = $userSvc; $this->userSvc = $userSvc;
@@ -143,7 +148,7 @@ class PirepController extends Controller
]; ];
} }
$this->pirepSvc->saveFares($pirep->id, $fares); $this->fareSvc->saveForPirep($pirep, $fares);
} }
/** /**

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Listeners;
use App\Events\Expenses;
use App\Models\Enums\ExpenseType;
use App\Models\Expense;
class ExpenseListener
{
/**
* Return a list of additional expenses
* @param Expenses $event
* @return array
*/
public function handle(Expenses $event)
{
return [
new Expense([
'type' => ExpenseType::FLIGHT,
'amount' => 15000 # $150
]),
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Listeners;
use \App\Events\PirepAccepted;
use App\Services\FinanceService;
/**
* Subscribe for events that we do some financial processing for
* This includes when a PIREP is accepted, or rejected
* @package App\Listeners
*/
class FinanceEvents
{
private $financeSvc;
public function __construct(
FinanceService $financeSvc
) {
$this->financeSvc = $financeSvc;
}
public function subscribe($events)
{
$events->listen(
PirepAccepted::class,
'App\Listeners\FinanceEvents@onPirepAccept'
);
}
/**
* Kick off the finance events when a PIREP is accepted
* @param PirepAccepted $event
*/
public function onPirepAccept(PirepAccepted $event)
{
$this->financeSvc->processFinancesForPirep($event->pirep);
}
}

View File

@@ -12,7 +12,7 @@ use Log;
* Handle sending emails on different events * Handle sending emails on different events
* @package App\Listeners * @package App\Listeners
*/ */
class NotificationEventListener class NotificationEvents
{ {
public function subscribe($events) public function subscribe($events)
{ {

View File

@@ -2,12 +2,16 @@
namespace App\Models; namespace App\Models;
use App\Models\Traits\JournalTrait;
/** /**
* Class Airline * Class Airline
* @package App\Models * @package App\Models
*/ */
class Airline extends BaseModel class Airline extends BaseModel
{ {
use JournalTrait;
public $table = 'airlines'; public $table = 'airlines';
public $fillable = [ public $fillable = [
@@ -68,5 +72,12 @@ class Airline extends BaseModel
$model->icao = strtoupper($model->icao); $model->icao = strtoupper($model->icao);
} }
}); });
/**
* Make sure a new journal object is created
*/
static::created(function(Airline $model) {
$model->initJournal(config('phpvms.currency'));
});
} }
} }

View File

@@ -23,7 +23,7 @@ class Expense extends BaseModel
'active' => 'boolean', 'active' => 'boolean',
'airline_id' => 'integer', 'airline_id' => 'integer',
'amount' => 'float', 'amount' => 'float',
'multiplier' => 'integer', 'multiplier' => 'bool',
'type' => 'integer', 'type' => 'integer',
]; ];

View File

@@ -13,6 +13,13 @@ use PhpUnitsOfMeasure\Exception\NonStringUnitName;
/** /**
* Class Pirep * Class Pirep
* *
* @property integer airline_id
* @property Airline airline
* @property Airport arr_airport
* @property Airport dep_airport
* @property mixed flight_number
* @property mixed route_code
* @property mixed route_leg
* @package App\Models * @package App\Models
*/ */
class Pirep extends BaseModel class Pirep extends BaseModel

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Models\Traits;
use App\Models\Journal;
trait JournalTrait
{
/**
* Morph to Journal.
*
* @return mixed
*/
public function journal()
{
return $this->morphOne(Journal::class, 'morphed');
}
/**
* Initialize a journal for a given model object
*
* @param string $currency_code
* @return Journal
* @throws \Exception
*/
public function initJournal($currency_code = 'USD')
{
if (!$this->journal) {
$journal = new Journal();
$journal->currency = $currency_code;
$journal->balance = 0;
$this->journal()->save($journal);
$journal->refresh();
return $journal;
}
throw new \Exception('Journal already exists.');
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use App\Models\Enums\PirepState; use App\Models\Enums\PirepState;
use App\Models\Traits\JournalTrait;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laratrust\Traits\LaratrustUserTrait; use Laratrust\Traits\LaratrustUserTrait;
@@ -23,9 +24,9 @@ use Laratrust\Traits\LaratrustUserTrait;
*/ */
class User extends Authenticatable class User extends Authenticatable
{ {
use Notifiable; use JournalTrait;
use LaratrustUserTrait; use LaratrustUserTrait;
//use SoftDeletes; use Notifiable;
public $table = 'users'; public $table = 'users';

View File

@@ -2,8 +2,11 @@
namespace App\Providers; namespace App\Providers;
use App\Listeners\NotificationEventListener; use App\Listeners\FinanceEvents;
use App\Listeners\NotificationEvents;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use App\Listeners\ExpenseListener;
use App\Events\Expenses;
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
@@ -14,13 +17,14 @@ class EventServiceProvider extends ServiceProvider
* @var array * @var array
*/ */
protected $listen = [ protected $listen = [
/*'App\Events\TestEvent' => [ Expenses::class => [
'App\Listeners\EventListener', ExpenseListener::class
],*/ ],
]; ];
protected $subscribe = [ protected $subscribe = [
NotificationEventListener::class, FinanceEvents::class,
NotificationEvents::class,
]; ];
/** /**

View File

@@ -3,6 +3,7 @@
namespace App\Repositories; namespace App\Repositories;
use App\Models\Expense; use App\Models\Expense;
use Illuminate\Support\Collection;
use Prettus\Repository\Contracts\CacheableInterface; use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Traits\CacheableRepository; use Prettus\Repository\Traits\CacheableRepository;
@@ -18,4 +19,28 @@ class ExpenseRepository extends BaseRepository implements CacheableInterface
{ {
return Expense::class; return Expense::class;
} }
/**
* Get all of the expenses for a given type, and also
* include expenses for a given airline ID
* @param $type
* @return Collection
*/
public function getAllForType($type, $airline_id=null)
{
$expenses = $this->findWhere([
'type' => $type,
]);
if($airline_id) {
$airline_expenses = $this->findWhere([
'type' => $type,
'airline_id' => $airline_id
]);
$expenses = $expenses->concat($airline_expenses);
}
return $expenses;
}
} }

View File

@@ -31,24 +31,24 @@ class FlightRepository extends BaseRepository implements CacheableInterface
* Find a flight based on the given criterea * Find a flight based on the given criterea
* @param $airline_id * @param $airline_id
* @param $flight_num * @param $flight_num
* @param null $flight_code * @param null $route_code
* @param null $flight_leg * @param null $route_leg
* @return mixed * @return mixed
*/ */
public function findFlight($airline_id, $flight_num, $flight_code=null, $flight_leg=null) public function findFlight($airline_id, $flight_num, $route_code=null, $route_leg=null)
{ {
$where = [ $where = [
'airline_id' => $airline_id, 'airline_id' => $airline_id,
'flight_num' => $flight_num, 'flight_number' => $flight_num,
'active' => true, 'active' => true,
]; ];
if(filled($flight_code)) { if(filled($route_code)) {
$where['flight_code'] = $flight_code; $where['route_code'] = $route_code;
} }
if(filled('flight_leg')) { if(filled($route_leg)) {
$where['flight_leg'] = $flight_leg; $where['route_leg'] = $route_leg;
} }
return $this->findWhere($where); return $this->findWhere($where);

View File

@@ -19,6 +19,9 @@ class JournalRepository extends BaseRepository implements CacheableInterface
{ {
use CacheableRepository; use CacheableRepository;
/**
* @return string
*/
public function model() public function model()
{ {
return JournalTransaction::class; return JournalTransaction::class;
@@ -58,7 +61,6 @@ class JournalRepository extends BaseRepository implements CacheableInterface
'currency' => config('phpvms.currency'), 'currency' => config('phpvms.currency'),
'memo' => $memo, 'memo' => $memo,
'post_date' => $post_date ?: Carbon::now(), 'post_date' => $post_date ?: Carbon::now(),
'transaction_group' => $transaction_group,
]; ];
if($reference !== null) { if($reference !== null) {
@@ -66,6 +68,11 @@ class JournalRepository extends BaseRepository implements CacheableInterface
$attrs['ref_class_id'] = $reference->id; $attrs['ref_class_id'] = $reference->id;
} }
if($transaction_group) {
$transaction_group = str_replace(' ', '_', $transaction_group);
$attrs['transaction_group'] = $transaction_group;
}
try { try {
$transaction = $this->create($attrs); $transaction = $this->create($attrs);
} catch (ValidatorException $e) { } catch (ValidatorException $e) {

View File

@@ -4,6 +4,8 @@ namespace App\Services;
use App\Models\Fare; use App\Models\Fare;
use App\Models\Flight; use App\Models\Flight;
use App\Models\Pirep;
use App\Models\PirepFare;
use App\Models\Subfleet; use App\Models\Subfleet;
use App\Support\Math; use App\Support\Math;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -184,4 +186,47 @@ class FareService extends BaseService
$subfleet->refresh(); $subfleet->refresh();
return $subfleet; return $subfleet;
} }
/**
* Get the fares for a PIREP, this just returns the PirepFare
* model which includes the counts for that particular fare
* @param Pirep $pirep
* @return Collection
*/
public function getForPirep(Pirep $pirep)
{
$fares = [];
$found_fares = PirepFare::where('pirep_id', $pirep->id)->get();
return $found_fares;
/*foreach($found_fares as $fare) {
$fares[] = $fare->toArray();
}
return collect($fares);*/
}
/**
* Save the list of fares
* @param Pirep $pirep
* @param array $fares ['fare_id', 'count']
* @throws \Exception
*/
public function saveForPirep(Pirep $pirep, array $fares)
{
if (!$fares) {
return;
}
# Remove all the previous fares
PirepFare::where('pirep_id', $pirep->id)->delete();
# Add them in
foreach ($fares as $fare) {
$fare['pirep_id'] = $pirep->id;
# other fields: ['fare_id', 'count']
$field = new PirepFare($fare);
$field->save();
}
}
} }

View File

@@ -1,41 +1,266 @@
<?php <?php
/**
*
*/
namespace App\Services; namespace App\Services;
use App\Events\Expenses as ExpensesEvent;
use App\Models\Enums\ExpenseType;
use App\Models\Enums\PirepSource; use App\Models\Enums\PirepSource;
use App\Models\Expense;
use App\Models\Pirep; use App\Models\Pirep;
use App\Repositories\ExpenseRepository;
use App\Repositories\JournalRepository;
use App\Support\Math; use App\Support\Math;
use App\Support\Money; use App\Support\Money;
/**
* Class FinanceService
* @package App\Services
*
*/
class FinanceService extends BaseService class FinanceService extends BaseService
{ {
private $fareSvc, private $expenseRepo,
$flightSvc; $fareSvc,
$journalRepo,
$pirepSvc;
/** /**
* FinanceService constructor. * FinanceService constructor.
* @param ExpenseRepository $expenseRepo
* @param FareService $fareSvc * @param FareService $fareSvc
* @param FlightService $flightSvc * @param JournalRepository $journalRepo
* @param PIREPService $pirepSvc
*/ */
public function __construct( public function __construct(
ExpenseRepository $expenseRepo,
FareService $fareSvc, FareService $fareSvc,
FlightService $flightSvc JournalRepository $journalRepo,
PIREPService $pirepSvc
) { ) {
$this->expenseRepo = $expenseRepo;
$this->fareSvc = $fareSvc; $this->fareSvc = $fareSvc;
$this->flightSvc = $flightSvc; $this->journalRepo = $journalRepo;
$this->pirepSvc = $pirepSvc;
}
/**
* Determine from the base rate, if we want to return the overridden rate
* or if the overridden rate is a percentage, then return that amount
* @param $base_rate
* @param $override_rate
* @return float|null
*/
public function applyAmountOrPercent($base_rate, $override_rate=null): ?float
{
if (!$override_rate) {
return $base_rate;
}
# Not a percentage override
if (substr_count($override_rate, '%') === 0) {
return $override_rate;
}
return Math::addPercent($base_rate, $override_rate);
}
/**
* Process all of the finances for a pilot report. This is called
* from a listener (FinanceEvents)
* @param Pirep $pirep
* @return mixed
* @throws \UnexpectedValueException
* @throws \InvalidArgumentException
* @throws \Prettus\Validator\Exceptions\ValidatorException
* @throws \Exception
*/
public function processFinancesForPirep(Pirep $pirep)
{
$journal = $pirep->airline->journal;
if(!$journal) {
$journal = $pirep->airline->initJournal(config('phpvms.currency'));
}
/*
* Collect all of the fares and then post each fare class's profit and
* the costs for each seat and post it to the journal
*/
$fares = $this->getReconciledFaresForPirep($pirep);
foreach($fares as $fare) {
$credit = Money::createFromAmount($fare->count * $fare->price);
$debit = Money::createFromAmount($fare->count * $fare->cost);
$this->journalRepo->post(
$journal,
$credit,
$debit,
$pirep,
'Fares '. $fare->code.$fare->count
. '; price:'.$fare->price .', cost: '. $fare->cost,
null,
'fares'
);
}
/*
* Collect all of the expenses and apply those to the journal
*/
$expenses = $this->getExpenses($pirep);
foreach($expenses as $expense) {
$debit = Money::createFromAmount($expense->amount);
$this->journalRepo->post(
$journal,
null,
$debit,
$pirep,
'Expense: ' . $expense->name,
null,
'expenses'
);
}
/*
* Collect and apply the ground handling cost
*/
$ground_handling_cost = $this->getGroundHandlingCost($pirep);
$this->journalRepo->post(
$journal,
null,
Money::createFromAmount($ground_handling_cost),
$pirep,
'Ground handling',
null,
'ground_handling'
);
return $journal;
}
/**
* Return all of the fares for the PIREP. Reconcile the list;
* Get the fares that have been filled out for the PIREP, and
* then get the fares for the flight and subfleet. Then merge
* them together, and return the final list of:
* count = number of pax
* price = how much each pax unit paid
* capacity = max number of pax units
*
* If count > capacity, count will be adjusted to capacity
* @param $pirep
* @return \Illuminate\Support\Collection
*/
public function getReconciledFaresForPirep($pirep)
{
$flight = $this->pirepSvc->findFlight($pirep);
# Collect all of the fares and prices
$flight_fares = $this->fareSvc->getForPirep($pirep);
$all_fares = $this->fareSvc->getAllFares($flight, $pirep->aircraft->subfleet);
$fares = $all_fares->map(function($fare, $i) use ($flight_fares) {
$fare_count = $flight_fares->whereStrict('id', $fare->id)->first();
if($fare_count) {
# If the count is greater than capacity, then just set it
# to the maximum amount
if($fare_count->count > $fare->capacity) {
$fare->count = $fare->capacity;
} else {
$fare->count = $fare_count->count;
}
}
return $fare;
});
return $fares;
}
/**
* Return the costs for the ground handling, with the multiplier
* being applied from the subfleet
* @param Pirep $pirep
* @return float|null
*/
public function getGroundHandlingCost(Pirep $pirep)
{
if(filled($pirep->aircraft->subfleet->ground_handling_multiplier)) {
// force into percent mode
$multiplier = $pirep->aircraft->subfleet->ground_handling_multiplier.'%';
return $this->applyAmountOrPercent(
$pirep->arr_airport->ground_handling_cost,
$multiplier
);
}
return $pirep->arr_airport->ground_handling_cost;
}
/**
* Send out an event called ExpensesEvent, which picks up any
* event listeners and check if they return a list of additional
* Expense model objects.
* @param Pirep $pirep
* @return mixed
*/
public function getExpenses(Pirep $pirep)
{
$event_expenses = [];
$expenses = $this->expenseRepo
->getAllForType(ExpenseType::FLIGHT, $pirep->airline_id);
/**
* Go through the expenses and apply a mulitplier if present
*/
$expenses = $expenses->map(function($expense, $i) use ($pirep) {
if(!$expense->multiplier) {
return $expense;
}
// TODO Apply the multiplier from the subfleet
});
$gathered_expenses = event(new ExpensesEvent($pirep));
if (!\is_array($gathered_expenses)) {
return $expenses;
}
foreach ($gathered_expenses as $event_expense) {
if (!\is_array($event_expense)) {
continue;
}
foreach($event_expense as $expense) {
# Make sure it's of type expense Model
if(!($expense instanceof Expense)) {
continue;
}
# If an airline_id is filled, then see if it matches
if($expense->airline_id !== $pirep->airline_id) {
continue;
}
$event_expenses[] = $expense;
}
}
$expenses = $expenses->concat($event_expenses);
return $expenses;
} }
/** /**
* Return the pilot's hourly pay for the given PIREP * Return the pilot's hourly pay for the given PIREP
* @param Pirep $pirep * @param Pirep $pirep
* @return float * @return float
* @throws \Money\Exception\UnknownCurrencyException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function getPayRateForPirep(Pirep $pirep) public function getPilotPayRateForPirep(Pirep $pirep)
{ {
# Get the base rate for the rank # Get the base rate for the rank
$rank = $pirep->user->rank; $rank = $pirep->user->rank;
@@ -55,16 +280,10 @@ class FinanceService extends BaseService
$override_rate = $override_rate->manual_pay; $override_rate = $override_rate->manual_pay;
} }
if(!$override_rate) { return $this->applyAmountOrPercent(
return $base_rate; $base_rate,
} $override_rate
);
# Not a percentage override
if(substr_count($override_rate, '%') === 0) {
return $override_rate;
}
return Math::addPercent($base_rate, $override_rate);
} }
/** /**
@@ -73,11 +292,10 @@ class FinanceService extends BaseService
* @return Money * @return Money
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @throws \Money\Exception\UnknownCurrencyException
*/ */
public function getPilotPilotPay(Pirep $pirep) public function getPilotPilotPay(Pirep $pirep)
{ {
$pilot_rate = $this->getPayRateForPirep($pirep) / 60; $pilot_rate = $this->getPilotPayRateForPirep($pirep) / 60;
$payment = round($pirep->flight_time * $pilot_rate, 2); $payment = round($pirep->flight_time * $pilot_rate, 2);
return Money::createFromAmount($payment); return Money::createFromAmount($payment);

View File

@@ -16,6 +16,7 @@ use App\Models\PirepFare;
use App\Models\PirepFieldValues; use App\Models\PirepFieldValues;
use App\Models\User; use App\Models\User;
use App\Repositories\AcarsRepository; use App\Repositories\AcarsRepository;
use App\Repositories\FlightRepository;
use App\Repositories\NavdataRepository; use App\Repositories\NavdataRepository;
use App\Repositories\PirepRepository; use App\Repositories\PirepRepository;
use Carbon\Carbon; use Carbon\Carbon;
@@ -24,11 +25,12 @@ use Log;
class PIREPService extends BaseService class PIREPService extends BaseService
{ {
protected $acarsRepo, private $acarsRepo,
$geoSvc, $flightRepo,
$navRepo, $geoSvc,
$pilotSvc, $navRepo,
$pirepRepo; $pilotSvc,
$pirepRepo;
/** /**
* PIREPService constructor. * PIREPService constructor.
@@ -40,6 +42,7 @@ class PIREPService extends BaseService
*/ */
public function __construct( public function __construct(
AcarsRepository $acarsRepo, AcarsRepository $acarsRepo,
FlightRepository $flightRepo,
GeoService $geoSvc, GeoService $geoSvc,
NavdataRepository $navRepo, NavdataRepository $navRepo,
PirepRepository $pirepRepo, PirepRepository $pirepRepo,
@@ -47,6 +50,7 @@ class PIREPService extends BaseService
) )
{ {
$this->acarsRepo = $acarsRepo; $this->acarsRepo = $acarsRepo;
$this->flightRepo = $flightRepo;
$this->geoSvc = $geoSvc; $this->geoSvc = $geoSvc;
$this->pilotSvc = $pilotSvc; $this->pilotSvc = $pilotSvc;
$this->navRepo = $navRepo; $this->navRepo = $navRepo;
@@ -54,6 +58,21 @@ class PIREPService extends BaseService
} }
/** /**
* Find the flight that a PIREP is based on
* @param Pirep $pirep
* @return mixed
*/
public function findFlight(Pirep $pirep)
{
return $this->flightRepo->findFlight(
$pirep->airline_id,
$pirep->flight_number,
$pirep->route_code,
$pirep->route_leg
)->first();
}
/**π
* Find if there are duplicates to a given PIREP. Ideally, the passed * Find if there are duplicates to a given PIREP. Ideally, the passed
* in PIREP hasn't been saved or gone through the create() method * in PIREP hasn't been saved or gone through the create() method
* @param Pirep $pirep * @param Pirep $pirep
@@ -211,29 +230,6 @@ class PIREPService extends BaseService
} }
} }
/**
* Save the list of fares
* @param $pirep_id
* @param array $fares ['field_id', 'count']
* @throws \Exception
*/
public function saveFares($pirep_id, array $fares)
{
if(!$fares) { return; }
# Remove all the previous fares
PirepFare::where('pirep_id', $pirep_id)->delete();
# Add them in
foreach($fares as $fare) {
$fare['pirep_id'] = $pirep_id;
# other fields: ['fare_id', 'count']
$field = new PirepFare($fare);
$field->save();
}
}
/** /**
* @param Pirep $pirep * @param Pirep $pirep
* @param int $new_state * @param int $new_state

View File

@@ -36,9 +36,10 @@ class UserService extends BaseService
/** /**
* Register a pilot. Also attaches the initial roles * Register a pilot. Also attaches the initial roles
* required, and then triggers the UserRegistered event * required, and then triggers the UserRegistered event
* @param User $user User model * @param User $user User model
* @param array $groups Additional groups to assign * @param array $groups Additional groups to assign
* @return mixed * @return mixed
* @throws \Exception
*/ */
public function createPilot(User $user, array $groups=null) public function createPilot(User $user, array $groups=null)
{ {
@@ -64,6 +65,7 @@ class UserService extends BaseService
# Let's check their rank and where they should start # Let's check their rank and where they should start
$this->calculatePilotRank($user); $this->calculatePilotRank($user);
$user->initJournal(config('phpvms.currency'));
$user->refresh(); $user->refresh();

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Services\PIREPService;
use App\Repositories\JournalRepository; use App\Repositories\JournalRepository;
use App\Services\FareService; use App\Services\FareService;
use App\Services\FinanceService; use App\Services\FinanceService;
@@ -11,8 +12,12 @@ class FinanceTest extends TestCase
{ {
private $fareSvc, private $fareSvc,
$financeSvc, $financeSvc,
$fleetSvc; $fleetSvc,
$pirepSvc;
/**
* @throws Exception
*/
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
@@ -21,8 +26,60 @@ class FinanceTest extends TestCase
$this->fareSvc = app(FareService::class); $this->fareSvc = app(FareService::class);
$this->financeSvc = app(FinanceService::class); $this->financeSvc = app(FinanceService::class);
$this->fleetSvc = app(FleetService::class); $this->fleetSvc = app(FleetService::class);
$this->pirepSvc = app(PIREPService::class);
} }
/**
* Create a user and a PIREP, that has all of the data filled out
* so that we can test all of the disparate parts of the finances
* @return array
* @throws Exception
*/
public function createFullPirep()
{
/**
* Setup tests
*/
$subfleet = $this->createSubfleetWithAircraft(2);
$rank = $this->createRank(10, [$subfleet['subfleet']->id]);
$this->fleetSvc->addSubfleetToRank($subfleet['subfleet'], $rank);
$user = factory(App\Models\User::class)->create([
'rank_id' => $rank->id,
]);
$flight = factory(App\Models\Flight::class)->create([
'airline_id' => $user->airline_id
]);
$pirep = factory(App\Models\Pirep::class)->create([
'flight_number' => $flight->flight_number,
'route_code' => $flight->route_code,
'route_leg' => $flight->route_leg,
'user_id' => $user->id,
'airline_id' => $user->airline_id,
'aircraft_id' => $subfleet['aircraft']->random(),
'source' => PirepSource::ACARS,
]);
/**
* Add fares to the subfleet, and then add the fares
* to the PIREP when it's saved, and set the capacity
*/
$fare_counts = [];
$fares = factory(App\Models\Fare::class, 3)->create();
foreach ($fares as $fare) {
$this->fareSvc->setForSubfleet($subfleet['subfleet'], $fare);
}
$pirep = $this->pirepSvc->create($pirep, []);
return [$user, $pirep, $fares];
}
/**
*
*/
public function testFlightFaresNoOverride() public function testFlightFaresNoOverride()
{ {
$flight = factory(App\Models\Flight::class)->create(); $flight = factory(App\Models\Flight::class)->create();
@@ -297,7 +354,7 @@ class FinanceTest extends TestCase
'source' => PirepSource::ACARS, 'source' => PirepSource::ACARS,
]); ]);
$rate = $this->financeSvc->getPayRateForPirep($pirep); $rate = $this->financeSvc->getPilotPayRateForPirep($pirep);
$this->assertEquals($rank->acars_base_pay_rate, $rate); $this->assertEquals($rank->acars_base_pay_rate, $rate);
} }
@@ -324,7 +381,7 @@ class FinanceTest extends TestCase
'source' => PirepSource::ACARS, 'source' => PirepSource::ACARS,
]); ]);
$rate = $this->financeSvc->getPayRateForPirep($pirep_acars); $rate = $this->financeSvc->getPilotPayRateForPirep($pirep_acars);
$this->assertEquals($acars_pay_rate, $rate); $this->assertEquals($acars_pay_rate, $rate);
# Change to a percentage # Change to a percentage
@@ -342,11 +399,11 @@ class FinanceTest extends TestCase
'source' => PirepSource::MANUAL, 'source' => PirepSource::MANUAL,
]); ]);
$rate = $this->financeSvc->getPayRateForPirep($pirep_manual); $rate = $this->financeSvc->getPilotPayRateForPirep($pirep_manual);
$this->assertEquals($manual_pay_adjusted, $rate); $this->assertEquals($manual_pay_adjusted, $rate);
# And make sure the original acars override still works # And make sure the original acars override still works
$rate = $this->financeSvc->getPayRateForPirep($pirep_acars); $rate = $this->financeSvc->getPilotPayRateForPirep($pirep_acars);
$this->assertEquals($acars_pay_rate, $rate); $this->assertEquals($acars_pay_rate, $rate);
} }
@@ -444,4 +501,59 @@ class FinanceTest extends TestCase
$this->assertEquals(125, $transactions['credits']->getValue()); $this->assertEquals(125, $transactions['credits']->getValue());
$this->assertEquals(25, $transactions['debits']->getValue()); $this->assertEquals(25, $transactions['debits']->getValue());
} }
/**
*
* @throws Exception
*/
public function testPirepFares()
{
[$user, $pirep, $fares] = $this->createFullPirep();
# Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => round($fare->capacity / 2),
];
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);
$all_fares = $this->financeSvc->getReconciledFaresForPirep($pirep);
$fare_counts = collect($fare_counts);
foreach($all_fares as $fare) {
$set_fare = $fare_counts->where('fare_id', $fare->id)->first();
$this->assertEquals($set_fare['count'], $fare->count);
$this->assertEquals($set_fare['price'], $fare->price);
}
}
/**
*
* @throws Exception
*/
public function testPirepFinances()
{
[$user, $pirep, $fares] = $this->createFullPirep();
$journal = $user->airline->initJournal(config('phpvms.currency'));
# Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => round($fare->capacity / 2),
];
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);
# This should process all of the
$pirep = $this->pirepSvc->accept($pirep);
}
} }