Refactor expenses; move finance service classes; add daily/monthly skeletons #130 #136

This commit is contained in:
Nabeel Shahzad 2018-03-05 22:49:42 -06:00
parent db10ebf807
commit 9d3953f3ac
14 changed files with 164 additions and 155 deletions

View File

@ -6,9 +6,7 @@ use Faker\Generator as Faker;
$factory->define(App\Models\Expense::class, function (Faker $faker) { $factory->define(App\Models\Expense::class, function (Faker $faker) {
return [ return [
'id' => null, 'id' => null,
'airline_id' => function () { 'airline_id' => null,
return factory(App\Models\Airline::class)->create()->id;
},
'name' => $faker->text(20), 'name' => $faker->text(20),
'amount' => $faker->randomFloat(2, 100, 1000), 'amount' => $faker->randomFloat(2, 100, 1000),
'type' => ExpenseType::FLIGHT, 'type' => ExpenseType::FLIGHT,

View File

@ -1,7 +1,7 @@
<?php <?php
use App\Models\Enums\NavaidType;
use Faker\Generator as Faker; use Faker\Generator as Faker;
use \App\Models\Enums\NavaidType;
$factory->define(App\Models\Navdata::class, function (Faker $faker) { $factory->define(App\Models\Navdata::class, function (Faker $faker) {
return [ return [

View File

@ -1,7 +1,7 @@
<?php <?php
use Faker\Generator as Faker;
use App\Models\Enums\UserState; use App\Models\Enums\UserState;
use Faker\Generator as Faker;
$factory->define(App\Models\User::class, function (Faker $faker) $factory->define(App\Models\User::class, function (Faker $faker)
{ {

View File

@ -1,8 +1,8 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateExpensesTable extends Migration class CreateExpensesTable extends Migration
{ {

View File

@ -251,15 +251,6 @@ subfleets:
type: 772-36ER-GE90-115B type: 772-36ER-GE90-115B
ground_handling_multiplier: 150 ground_handling_multiplier: 150
#subfleet_expenses:
# - id: 1
# subfleet_id: 1
# name: Catering
# amount: 1000
# type: 0
# created_at: now
# updated_at: now
# add a few mods to aircraft and fares # add a few mods to aircraft and fares
subfleet_fare: subfleet_fare:

View File

@ -7,7 +7,7 @@ use App\Models\Journal;
use App\Models\JournalTransaction; use App\Models\JournalTransaction;
use App\Repositories\AirlineRepository; use App\Repositories\AirlineRepository;
use App\Repositories\JournalRepository; use App\Repositories\JournalRepository;
use App\Services\FinanceService; use App\Services\Finance\PirepFinanceService;
use App\Support\Dates; use App\Support\Dates;
use App\Support\Money; use App\Support\Money;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -23,12 +23,12 @@ class FinanceController extends BaseController
$journalRepo; $journalRepo;
/** /**
* @param FinanceService $financeSvc * @param PirepFinanceService $financeSvc
* @param JournalRepository $journalRepo * @param JournalRepository $journalRepo
*/ */
public function __construct( public function __construct(
AirlineRepository $airlineRepo, AirlineRepository $airlineRepo,
FinanceService $financeSvc, PirepFinanceService $financeSvc,
JournalRepository $journalRepo JournalRepository $journalRepo
) { ) {
$this->airlineRepo = $airlineRepo; $this->airlineRepo = $airlineRepo;

View File

@ -26,7 +26,7 @@ use App\Models\PirepComment;
use App\Repositories\AcarsRepository; use App\Repositories\AcarsRepository;
use App\Repositories\JournalRepository; use App\Repositories\JournalRepository;
use App\Repositories\PirepRepository; use App\Repositories\PirepRepository;
use App\Services\FinanceService; use App\Services\Finance\PirepFinanceService;
use App\Services\GeoService; use App\Services\GeoService;
use App\Services\PIREPService; use App\Services\PIREPService;
use App\Services\UserService; use App\Services\UserService;
@ -47,7 +47,7 @@ class PirepController extends RestController
/** /**
* PirepController constructor. * PirepController constructor.
* @param AcarsRepository $acarsRepo * @param AcarsRepository $acarsRepo
* @param FinanceService $financeSvc * @param PirepFinanceService $financeSvc
* @param GeoService $geoSvc * @param GeoService $geoSvc
* @param JournalRepository $journalRepo * @param JournalRepository $journalRepo
* @param PirepRepository $pirepRepo * @param PirepRepository $pirepRepo
@ -56,7 +56,7 @@ class PirepController extends RestController
*/ */
public function __construct( public function __construct(
AcarsRepository $acarsRepo, AcarsRepository $acarsRepo,
FinanceService $financeSvc, PirepFinanceService $financeSvc,
GeoService $geoSvc, GeoService $geoSvc,
JournalRepository $journalRepo, JournalRepository $journalRepo,
PirepRepository $pirepRepo, PirepRepository $pirepRepo,

View File

@ -4,7 +4,7 @@ namespace App\Listeners;
use App\Events\PirepAccepted; use App\Events\PirepAccepted;
use App\Events\PirepRejected; use App\Events\PirepRejected;
use App\Services\FinanceService; use App\Services\Finance\PirepFinanceService;
/** /**
* Subscribe for events that we do some financial processing for * Subscribe for events that we do some financial processing for
@ -16,7 +16,7 @@ class FinanceEvents
private $financeSvc; private $financeSvc;
public function __construct( public function __construct(
FinanceService $financeSvc PirepFinanceService $financeSvc
) { ) {
$this->financeSvc = $financeSvc; $this->financeSvc = $financeSvc;
} }

View File

@ -37,8 +37,6 @@ class ExpenseRepository extends BaseRepository implements CacheableInterface
if($ref_class) { if($ref_class) {
$where['ref_class'] = $ref_class; $where['ref_class'] = $ref_class;
} else {
$where[] = ['ref_class', '=', null];
} }
$expenses = $this->findWhere($where); $expenses = $this->findWhere($where);
@ -52,12 +50,9 @@ class ExpenseRepository extends BaseRepository implements CacheableInterface
if ($ref_class) { if ($ref_class) {
$where['ref_class'] = $ref_class; $where['ref_class'] = $ref_class;
} else {
$where[] = ['ref_class', '=', null];
} }
$airline_expenses = $this->findWhere($where); $airline_expenses = $this->findWhere($where);
$expenses = $expenses->concat($airline_expenses); $expenses = $expenses->concat($airline_expenses);
} }

View File

@ -0,0 +1,20 @@
<?php
namespace App\Services\Finance;
use App\Services\BaseService;
/**
* Class DailyFinanceService
* @package App\Services\Finance
*/
class DailyFinanceService extends BaseService
{
/**
* Run all of the daily expense/financials
*/
public function processFinances()
{
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Services\Finance;
use App\Services\BaseService;
/**
* Class MonthlyFinanceService
* @package App\Services\Finance
*/
class MonthlyFinanceService extends BaseService
{
/**
* Run all of the daily expense/financials
*/
public function processFinances()
{
}
}

View File

@ -1,15 +1,17 @@
<?php <?php
namespace App\Services; namespace App\Services\Finance;
use App\Events\Expenses as ExpensesEvent; use App\Events\Expenses as ExpensesEvent;
use App\Models\Enums\ExpenseType; use App\Models\Enums\ExpenseType;
use App\Models\Enums\PirepSource; use App\Models\Enums\PirepSource;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Pirep; use App\Models\Pirep;
use App\Models\Subfleet;
use App\Repositories\ExpenseRepository; use App\Repositories\ExpenseRepository;
use App\Repositories\JournalRepository; use App\Repositories\JournalRepository;
use App\Services\BaseService;
use App\Services\FareService;
use App\Services\PIREPService;
use App\Support\Math; use App\Support\Math;
use App\Support\Money; use App\Support\Money;
use Log; use Log;
@ -19,7 +21,7 @@ use Log;
* @package App\Services * @package App\Services
* *
*/ */
class FinanceService extends BaseService class PirepFinanceService extends BaseService
{ {
private $expenseRepo, private $expenseRepo,
$fareSvc, $fareSvc,
@ -45,27 +47,6 @@ class FinanceService extends BaseService
$this->pirepSvc = $pirepSvc; $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 * Process all of the finances for a pilot report. This is called
* from a listener (FinanceEvents) * from a listener (FinanceEvents)
@ -92,7 +73,7 @@ class FinanceService extends BaseService
# Now start and pay from scratch # Now start and pay from scratch
$this->payFaresForPirep($pirep); $this->payFaresForPirep($pirep);
$this->payExpensesForPirep($pirep); $this->payExpensesForPirep($pirep);
$this->paySubfleetExpenses($pirep); $this->payExpensesEventsForPirep($pirep);
$this->payGroundHandlingForPirep($pirep); $this->payGroundHandlingForPirep($pirep);
$this->payPilotForPirep($pirep); $this->payPilotForPirep($pirep);
@ -147,16 +128,34 @@ class FinanceService extends BaseService
* @param Pirep $pirep * @param Pirep $pirep
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @throws \Prettus\Validator\Exceptions\ValidatorException
*/ */
public function payExpensesForPirep(Pirep $pirep): void public function payExpensesForPirep(Pirep $pirep): void
{ {
$expenses = $this->getExpenses($pirep); $expenses = $this->expenseRepo->getAllForType(
/** @var \App\Models\Expense $expense */ ExpenseType::FLIGHT,
foreach ($expenses as $expense) { $pirep->airline_id
);
/**
* Go through the expenses and apply a mulitplier if present
*/
$expenses->map(function ($expense, $i) use ($pirep)
{
if ($expense->multiplier) {
# TODO: Modify the amount
}
Log::info('Finance: PIREP: ' . $pirep->id . ', expense:', $expense->toArray()); Log::info('Finance: PIREP: ' . $pirep->id . ', expense:', $expense->toArray());
# Get the transaction group name from the ref_class name
# This way it can be more dynamic and don't have to add special
# tables or specific expense calls to accomodate all of these
$transaction_group = 'Expense';
if($expense->ref_class) {
$ref = explode('\\', $expense->ref_class);
$transaction_group = end($ref);
}
$debit = Money::createFromAmount($expense->amount); $debit = Money::createFromAmount($expense->amount);
$this->journalRepo->post( $this->journalRepo->post(
$pirep->airline->journal, $pirep->airline->journal,
@ -165,42 +164,56 @@ class FinanceService extends BaseService
$pirep, $pirep,
'Expense: ' . $expense->name, 'Expense: ' . $expense->name,
null, null,
'Expenses' $transaction_group
); );
} });
} }
/** /**
* Pay out the expenses for the subfleet * Collect all of the expenses from the listeners and apply those to the journal
* @param Pirep $pirep * @param Pirep $pirep
* @throws \UnexpectedValueException
* @throws \InvalidArgumentException
* @throws \Prettus\Validator\Exceptions\ValidatorException * @throws \Prettus\Validator\Exceptions\ValidatorException
*/ */
public function paySubfleetExpenses(Pirep $pirep) public function payExpensesEventsForPirep(Pirep $pirep): void
{ {
$subfleet = $pirep->aircraft->subfleet; /**
$subfleet_expenses = Expense::where([ * Throw an event and collect any expenses returned from it
'ref_class' => Subfleet::class, */
'ref_class_id' => $subfleet->id, $gathered_expenses = event(new ExpensesEvent($pirep));
])->get(); if (!\is_array($gathered_expenses)) {
if(!$subfleet_expenses) {
return; return;
} }
foreach ($subfleet_expenses as $expense) { foreach ($gathered_expenses as $event_expense) {
if (!\is_array($event_expense)) {
continue;
}
Log::info('Finance: PIREP: '.$pirep->id foreach ($event_expense as $expense) {
.'; subfleet expense: "'.$expense->name.'", cost: "'.$expense->amount); # Make sure it's of type expense Model
if (!($expense instanceof Expense)) {
continue;
}
$this->journalRepo->post( # If an airline_id is filled, then see if it matches
$pirep->airline->journal, if ($expense->airline_id !== $pirep->airline_id) {
null, continue;
Money::createFromAmount($expense->amount), }
$pirep,
'Subfleet ('.$subfleet->type.'): '.$expense->name, $debit = Money::createFromAmount($expense->amount);
null,
'Subfleet Expense' $this->journalRepo->post(
); $pirep->airline->journal,
null,
$debit,
$pirep,
'Expense: ' . $expense->name,
null,
$expense->transaction_group ?? 'Expenses'
);
}
} }
} }
@ -325,7 +338,7 @@ class FinanceService extends BaseService
if(filled($pirep->aircraft->subfleet->ground_handling_multiplier)) { if(filled($pirep->aircraft->subfleet->ground_handling_multiplier)) {
// force into percent mode // force into percent mode
$multiplier = $pirep->aircraft->subfleet->ground_handling_multiplier.'%'; $multiplier = $pirep->aircraft->subfleet->ground_handling_multiplier.'%';
return $this->applyAmountOrPercent( return Math::applyAmountOrPercent(
$pirep->arr_airport->ground_handling_cost, $pirep->arr_airport->ground_handling_cost,
$multiplier $multiplier
); );
@ -334,68 +347,6 @@ class FinanceService extends BaseService
return $pirep->arr_airport->ground_handling_cost; 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,
Expense::class
);
/**
* 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
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;
}
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
@ -435,7 +386,7 @@ class FinanceService extends BaseService
} }
Log::debug('pilot pay: base rate=' . $base_rate . ', override=' . $override_rate); Log::debug('pilot pay: base rate=' . $base_rate . ', override=' . $override_rate);
return $this->applyAmountOrPercent( return Math::applyAmountOrPercent(
$base_rate, $base_rate,
$override_rate $override_rate
); );

View File

@ -8,6 +8,26 @@ namespace App\Support;
*/ */
class Math class Math
{ {
/**
* 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 static 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 static::addPercent($base_rate, $override_rate);
}
/** /**
* Add/subtract a percentage to a number * Add/subtract a percentage to a number
@ -25,8 +45,6 @@ class Math
$percent = (float) $percent; $percent = (float) $percent;
} }
return $number + ($number * ($percent/100)); return $number + ($number * ($percent/100));
} }
} }

View File

@ -5,7 +5,7 @@ use App\Repositories\ExpenseRepository;
use App\Services\PIREPService; 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\Finance\PirepFinanceService;
use App\Services\FleetService; use App\Services\FleetService;
use App\Support\Math; use App\Support\Math;
use App\Support\Money; use App\Support\Money;
@ -28,7 +28,7 @@ class FinanceTest extends TestCase
$this->expenseRepo = app(ExpenseRepository::class); $this->expenseRepo = app(ExpenseRepository::class);
$this->fareSvc = app(FareService::class); $this->fareSvc = app(FareService::class);
$this->financeSvc = app(FinanceService::class); $this->financeSvc = app(PirepFinanceService::class);
$this->fleetSvc = app(FleetService::class); $this->fleetSvc = app(FleetService::class);
$this->pirepSvc = app(PIREPService::class); $this->pirepSvc = app(PIREPService::class);
} }
@ -80,7 +80,6 @@ class FinanceTest extends TestCase
* Add fares to the subfleet, and then add the fares * Add fares to the subfleet, and then add the fares
* to the PIREP when it's saved, and set the capacity * to the PIREP when it's saved, and set the capacity
*/ */
$fare_counts = [];
$fares = factory(App\Models\Fare::class, 3)->create([ $fares = factory(App\Models\Fare::class, 3)->create([
'price' => 100, 'price' => 100,
'cost' => 50, 'cost' => 50,
@ -571,7 +570,7 @@ class FinanceTest extends TestCase
$airline = factory(App\Models\Airline::class)->create(); $airline = factory(App\Models\Airline::class)->create();
$airline2 = factory(App\Models\Airline::class)->create(); $airline2 = factory(App\Models\Airline::class)->create();
$expense = factory(App\Models\Expense::class)->create([ factory(App\Models\Expense::class)->create([
'airline_id' => $airline->id 'airline_id' => $airline->id
]); ]);
@ -599,6 +598,23 @@ class FinanceTest extends TestCase
$found = $expenses->where('airline_id', $airline2->id); $found = $expenses->where('airline_id', $airline2->id);
$this->assertCount(0, $found); $this->assertCount(0, $found);
/*
* Test the subfleet class
*/
factory(App\Models\Expense::class)->create([
'airline_id' => null,
'ref_class' => \App\Models\Subfleet::class,
]);
$expenses = $this->expenseRepo->getAllForType(
ExpenseType::FLIGHT,
$airline->id,
\App\Models\Subfleet::class
);
$this->assertCount(1, $expenses);
} }
/** /**
@ -610,8 +626,7 @@ class FinanceTest extends TestCase
$journalRepo = app(JournalRepository::class); $journalRepo = app(JournalRepository::class);
[$user, $pirep, $fares] = $this->createFullPirep(); [$user, $pirep, $fares] = $this->createFullPirep();
$user->airline->initJournal(config('phpvms.currency'));
$journal = $user->airline->initJournal(config('phpvms.currency'));
# Override the fares # Override the fares
$fare_counts = []; $fare_counts = [];
@ -635,12 +650,13 @@ class FinanceTest extends TestCase
$this->assertEquals(1840, $transactions['debits']->getValue()); $this->assertEquals(1840, $transactions['debits']->getValue());
# Check that all the different transaction types are there # Check that all the different transaction types are there
# test by the different groups that exist
$transaction_types = [ $transaction_types = [
'Expenses' => 1, 'Expense' => 1,
'Subfleet' => 1,
'Fares' => 3, 'Fares' => 3,
'Ground Handling' => 1, 'Ground Handling' => 1,
'Pilot Pay' => 2, # debit on the airline, credit to the pilot 'Pilot Pay' => 2, # debit on the airline, credit to the pilot
'Subfleet Expense' => 1,
]; ];
foreach($transaction_types as $type => $count) { foreach($transaction_types as $type => $count) {