From 498e795e4b77ef574fd8091353e6aa45a0f831a5 Mon Sep 17 00:00:00 2001 From: Nabeel Shahzad Date: Wed, 28 Feb 2018 21:52:36 -0600 Subject: [PATCH] Fixes and tests for the journal and journaled transactions #130 --- app/Database/factories/JournalFactory.php | 9 ++ .../factories/JournalTransactionsFactory.php | 17 ++++ ...1807_create_journal_transactions_table.php | 2 +- ...018_02_28_231813_create_journals_table.php | 6 +- app/Models/JournalTransaction.php | 4 +- app/Repositories/JournalRepository.php | 93 +++++++++++++++---- app/Services/FinanceService.php | 2 +- app/Support/Money.php | 55 ++++++++++- tests/FinanceTest.php | 58 +++++++++++- 9 files changed, 213 insertions(+), 33 deletions(-) create mode 100644 app/Database/factories/JournalFactory.php create mode 100644 app/Database/factories/JournalTransactionsFactory.php diff --git a/app/Database/factories/JournalFactory.php b/app/Database/factories/JournalFactory.php new file mode 100644 index 00000000..497868f2 --- /dev/null +++ b/app/Database/factories/JournalFactory.php @@ -0,0 +1,9 @@ +define(App\Models\Journal::class, function (Faker $faker) { + return [ + 'currency' => 'USD', + ]; +}); diff --git a/app/Database/factories/JournalTransactionsFactory.php b/app/Database/factories/JournalTransactionsFactory.php new file mode 100644 index 00000000..81ba2a64 --- /dev/null +++ b/app/Database/factories/JournalTransactionsFactory.php @@ -0,0 +1,17 @@ +define(App\Models\JournalTransactions::class, function (Faker $faker) { + return [ + 'transaction_group' => \Ramsey\Uuid\Uuid::uuid4()->toString(), + 'journal_id' => function () { + return factory(App\Models\Journal::class)->create()->id; + }, + 'credit' => $faker->numberBetween(100, 10000), + 'debit' => $faker->numberBetween(100, 10000), + 'currency' => 'USD', + 'memo' => $faker->sentence(6), + 'post_date' => \Carbon\Carbon::now(), + ]; +}); diff --git a/app/Database/migrations/2018_02_28_231807_create_journal_transactions_table.php b/app/Database/migrations/2018_02_28_231807_create_journal_transactions_table.php index 3ce67d65..c6d3f14e 100644 --- a/app/Database/migrations/2018_02_28_231807_create_journal_transactions_table.php +++ b/app/Database/migrations/2018_02_28_231807_create_journal_transactions_table.php @@ -17,8 +17,8 @@ class CreateJournalTransactionsTable extends Migration $table->char('id', 36)->unique(); $table->char('transaction_group', 36)->nullable(); $table->integer('journal_id'); - $table->unsignedBigInteger('debit')->nullable(); $table->unsignedBigInteger('credit')->nullable(); + $table->unsignedBigInteger('debit')->nullable(); $table->char('currency', 5); $table->text('memo')->nullable(); $table->char('ref_class', 32)->nullable(); diff --git a/app/Database/migrations/2018_02_28_231813_create_journals_table.php b/app/Database/migrations/2018_02_28_231813_create_journals_table.php index 52ccdca7..f8d5b125 100644 --- a/app/Database/migrations/2018_02_28_231813_create_journals_table.php +++ b/app/Database/migrations/2018_02_28_231813_create_journals_table.php @@ -17,9 +17,9 @@ class CreateJournalsTable extends Migration $table->increments('id'); $table->unsignedInteger('ledger_id')->nullable(); $table->bigInteger('balance')->default(0); - $table->char('currency', 5); - $table->char('morphed_type', 32); - $table->integer('morphed_id'); + $table->string('currency', 5); + $table->string('morphed_type', 32)->nullable(); + $table->unsignedInteger('morphed_id')->nullable(); $table->timestamps(); }); } diff --git a/app/Models/JournalTransaction.php b/app/Models/JournalTransaction.php index 32991edf..5069e874 100644 --- a/app/Models/JournalTransaction.php +++ b/app/Models/JournalTransaction.php @@ -24,8 +24,8 @@ class JournalTransaction extends BaseModel public $fillable = [ 'transaction_group', 'journal_id', - 'debit', 'credit', + 'debit', 'currency', 'memo', 'tags', @@ -35,8 +35,8 @@ class JournalTransaction extends BaseModel ]; protected $casts = [ - 'debit' => 'integer', 'credits' => 'integer', + 'debit' => 'integer', 'post_date' => 'datetime', 'tags' => 'array', ]; diff --git a/app/Repositories/JournalRepository.php b/app/Repositories/JournalRepository.php index 8c774bbd..2a1f80be 100644 --- a/app/Repositories/JournalRepository.php +++ b/app/Repositories/JournalRepository.php @@ -11,7 +11,10 @@ use Prettus\Repository\Contracts\CacheableInterface; use Prettus\Repository\Traits\CacheableRepository; use Prettus\Validator\Exceptions\ValidatorException; - +/** + * Class JournalRepository + * @package App\Repositories + */ class JournalRepository extends BaseRepository implements CacheableInterface { use CacheableRepository; @@ -39,7 +42,7 @@ class JournalRepository extends BaseRepository implements CacheableInterface * @throws ValidatorException */ public function post( - Journal $journal, + Journal &$journal, Money $credit = null, Money $debit = null, $reference = null, @@ -52,7 +55,7 @@ class JournalRepository extends BaseRepository implements CacheableInterface 'journal_id' => $journal->id, 'credit' => $credit ? $credit->getAmount():null, 'debit' => $debit ? $debit->getAmount():null, - 'currency_code' => config('phpvms.currency'), + 'currency' => config('phpvms.currency'), 'memo' => $memo, 'post_date' => $post_date ?: Carbon::now(), 'transaction_group' => $transaction_group, @@ -70,17 +73,16 @@ class JournalRepository extends BaseRepository implements CacheableInterface } # Adjust the balance on the journal - $balance = new Money($journal->balance); if($credit) { - $balance = $balance->add($credit); + $journal->balance->add($credit); } if($debit) { - $balance = $balance->subtract($debit); + $journal->balance->subtract($debit); } - $journal->balance = $balance->getAmount(); $journal->save(); + $journal->refresh(); return $transaction; } @@ -92,14 +94,14 @@ class JournalRepository extends BaseRepository implements CacheableInterface * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ - public function getBalance(Journal $journal, Carbon $date=null) + public function getBalance(Journal $journal=null, Carbon $date=null) { if(!$date) { $date = Carbon::now(); } - $credit = $this->getCreditBalanceOn($journal, $date); - $debit = $this->getDebitBalanceOn($journal, $date); + $credit = $this->getCreditBalanceBetween($date, $journal); + $debit = $this->getDebitBalanceBetween($date, $journal); return $credit->subtract($debit); } @@ -112,12 +114,27 @@ class JournalRepository extends BaseRepository implements CacheableInterface * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ - public function getCreditBalanceOn(Journal $journal, Carbon $date) - { - $balance = $this->findWhere([ - 'journal_id' => $journal->id, + public function getCreditBalanceBetween( + Carbon $date, + Journal $journal=null, + Carbon $start_date=null + ): Money { + + $where = [ ['post_date', '<=', $date] - ], ['id', 'credit'])->sum('credit') ?: 0; + ]; + + if($journal) { + $where['journal_id'] = $journal->id; + } + + if ($start_date) { + $where[] = ['post_date', '>=', $start_date]; + } + + $balance = $this + ->findWhere($where, ['id', 'credit']) + ->sum('credit') ?: 0; return new Money($balance); } @@ -129,13 +146,49 @@ class JournalRepository extends BaseRepository implements CacheableInterface * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ - public function getDebitBalanceOn(Journal $journal, Carbon $date): Money - { - $balance = $this->findWhere([ - 'journal_id' => $journal->id, + public function getDebitBalanceBetween( + Carbon $date, + Journal $journal=null, + Carbon $start_date=null + ): Money { + + $where = [ ['post_date', '<=', $date] - ], ['id', 'debit'])->sum('debit') ?: 0; + ]; + + if ($journal) { + $where['journal_id'] = $journal->id; + } + + if($start_date) { + $where[] = ['post_date', '>=', $start_date]; + } + + $balance = $this + ->findWhere($where, ['id', 'debit']) + ->sum('debit') ?: 0; return new Money($balance); } + + /** + * Return all transactions for a given object + * @param $object + * @return array + * @throws \UnexpectedValueException + * @throws \InvalidArgumentException + */ + public function getAllForObject($object) + { + $transactions = $this->findWhere([ + 'ref_class' => \get_class($object), + 'ref_class_id' => $object->id, + ]); + + return [ + 'credits' => new Money($transactions->sum('credit')), + 'debits' => new Money($transactions->sum('debit')), + 'transactions' => $transactions, + ]; + } } diff --git a/app/Services/FinanceService.php b/app/Services/FinanceService.php index efdde1a4..3163a345 100644 --- a/app/Services/FinanceService.php +++ b/app/Services/FinanceService.php @@ -80,6 +80,6 @@ class FinanceService extends BaseService $pilot_rate = $this->getPayRateForPirep($pirep) / 60; $payment = round($pirep->flight_time * $pilot_rate, 2); - return new Money($payment); + return Money::createFromAmount($payment); } } diff --git a/app/Support/Money.php b/app/Support/Money.php index 105f057b..03109928 100644 --- a/app/Support/Money.php +++ b/app/Support/Money.php @@ -31,6 +31,20 @@ class Money return new MoneyBase($amount, static::currency()); } + /** + * Create from a dollar amount + * @param $amount + * @return Money + * @throws \UnexpectedValueException + * @throws \InvalidArgumentException + */ + public static function createFromAmount($amount) + { + return new Money( + static::convertToSubunit($amount) + ); + } + /** * Convert a whole unit into it's subunit, e,g: dollar to cents * @param $amount @@ -65,7 +79,6 @@ class Money */ public function __construct($amount) { - $amount = static::convertToSubunit($amount); $this->money = static::create($amount); } @@ -78,9 +91,17 @@ class Money return $this->money->getAmount(); } + /** + * Alias of getAmount() + */ + public function toAmount() + { + return $this->getAmount(); + } + /** * Returns the value in whole amounts, e.g: 100.00 - * vs returning in all cents + * instead of returning in the smallest denomination * @return float */ public function getValue() @@ -88,6 +109,14 @@ class Money return $this->money->getValue(); } + /** + * Alias of getValue() + */ + public function toValue() + { + return $this->getValue(); + } + /** * @return MoneyBase */ @@ -116,10 +145,16 @@ class Money /** * Add an amount * @param $amount + * @throws \UnexpectedValueException + * @throws \InvalidArgumentException */ public function add($amount) { - $this->money = $this->money->add($amount); + if(!($amount instanceof self)) { + $amount = static::createFromAmount($amount); + } + + $this->money = $this->money->add($amount->money); } /** @@ -143,11 +178,16 @@ class Money * Subtract an amount * @param $amount * @return Money + * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ public function subtract($amount) { - $this->money = $this->money->subtract($amount); + if (!($amount instanceof self)) { + $amount = static::createFromAmount($amount); + } + + $this->money = $this->money->subtract($amount->money); return $this; } @@ -155,12 +195,17 @@ class Money * Multiply by an amount * @param $amount * @return Money + * @throws \UnexpectedValueException * @throws \OutOfBoundsException * @throws \InvalidArgumentException */ public function multiply($amount) { - $this->money = $this->money->multiply($amount); + if (!($amount instanceof self)) { + $amount = static::createFromAmount($amount); + } + + $this->money = $this->money->multiply($amount->money); return $this; } diff --git a/tests/FinanceTest.php b/tests/FinanceTest.php index 713f1ef4..f3ca10ef 100644 --- a/tests/FinanceTest.php +++ b/tests/FinanceTest.php @@ -1,9 +1,11 @@ financeSvc->getPilotPilotPay($pirep_acars); - $this->assertEquals($payment->getValue(), 100); + $this->assertEquals(100, $payment->getValue()); $pirep_acars = factory(App\Models\Pirep::class)->create([ 'user_id' => $this->user->id, @@ -385,4 +387,58 @@ class FinanceTest extends TestCase $payment = $this->financeSvc->getPilotPilotPay($pirep_acars); $this->assertEquals($payment->getValue(), 150); } + + /** + * @throws \Prettus\Validator\Exceptions\ValidatorException + */ + public function testJournalOperations() + { + $journalRepo = app(JournalRepository::class); + + $user = factory(App\Models\User::class)->create(); + $journal = factory(App\Models\Journal::class)->create(); + + $journalRepo->post( + $journal, + Money::createFromAmount(100), + null, + $user + ); + + $balance = $journalRepo->getBalance($journal); + $this->assertEquals(100, $balance->getValue()); + + # add another transaction + + $journalRepo->post( + $journal, + Money::createFromAmount(25), + null, + $user + ); + + $balance = $journalRepo->getBalance($journal); + $this->assertEquals(125, $balance->getValue()); + + # Get the total balance + $this->assertEquals(125, $journal->balance->getValue()); + + # debit an amount + + $journalRepo->post( + $journal, + null, + Money::createFromAmount(25), + $user + ); + + $this->assertEquals(100, $journal->balance->getValue()); + + # find all transactions + $transactions = $journalRepo->getAllForObject($user); + + $this->assertCount(3, $transactions['transactions']); + $this->assertEquals(125, $transactions['credits']->getValue()); + $this->assertEquals(25, $transactions['debits']->getValue()); + } }