Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple compound line items per account #164

Merged
merged 2 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ clover.*

# PHPSTORM
.idea
Dockerfile
Dockerfile
.vscode
ifrs.code-workspace
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ This Package enables any Laravel application to generate [International Financia

The package supports multiple Entities (Companies), Account Categorization, Transaction assignment, Start of Year Opening Balances and accounting for VAT Transactions. Transactions are also protected against tampering via direct database changes ensuring the integrity of the Ledger. Outstanding amounts for clients and suppliers can also be displayed according to how long they have been outstanding using configurable time periods (Current, 31 - 60 days, 61 - 90 days etc). Finally, the package supports the automated posting of forex difference transactions both within the reporting period as well as translating foreign denominated account balances at a set closing rate.

This package's functionality is now available as a REST API Service. More details can be found [here](https://microbooks.io)
This package is a community initiative of [microbooks.io](https://microbooks.io).

## Table of contents
- [Eloquent IFRS](#eloquent-ifrs)
- [Table of contents](#table-of-contents)
Expand Down
38 changes: 19 additions & 19 deletions src/Models/Ledger.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private static function getLedgers(Transaction $transaction): array
private static function postVat($appliedVats, $transaction, $lineItem): void
{
$rate = $transaction->exchangeRate->rate;
foreach($appliedVats as $appliedVat){
foreach ($appliedVats as $appliedVat) {
list($post, $folio) = Ledger::getLedgers($transaction);

// identical double entry data
Expand All @@ -104,7 +104,7 @@ private static function postVat($appliedVats, $transaction, $lineItem): void
// different double entry data
$post->post_account = $folio->folio_account = $lineItem->vat_inclusive ? $lineItem->account_id : $transaction->account_id;
$post->folio_account = $folio->post_account = $appliedVat->vat->account_id;

$post->save();
$folio->save();
}
Expand Down Expand Up @@ -139,12 +139,12 @@ private static function postBasic(Transaction $transaction): void

$post->save();
$folio->save();

if (count($lineItem->appliedVats) > 0) {
Ledger::postVat($lineItem->appliedVats, $transaction, $lineItem);
}
}

// reload ledgers to reflect changes
$transaction->load('ledgers');
}
Expand All @@ -161,13 +161,11 @@ private static function postBasic(Transaction $transaction): void
*/
private static function makeCompountEntryLedgers(array $posts, array $folios, Transaction $transaction, $entryType): bool
{
if(count($posts) == 0){
if (count($posts) == 0) {
return true;
} else {
$postAccount = array_key_first($posts);
$amount = $posts[$postAccount];

return Ledger::allocateAmount($postAccount, $amount, $posts, $folios, $transaction, $entryType);
$key = array_key_first($posts);
return Ledger::allocateAmount($posts[$key]['id'], $posts[$key]['amount'], $posts, $folios, $transaction, $entryType);
}
}

Expand All @@ -185,12 +183,14 @@ private static function makeCompountEntryLedgers(array $posts, array $folios, Tr
*/
private static function allocateAmount($postAccount, $amount, $posts, $folios, $transaction, $entryType): bool
{
if($amount == 0){
unset($posts[$postAccount]);
if ($amount == 0) {
$key = array_key_first($posts);
unset($posts[$key]);
return Ledger::makeCompountEntryLedgers($posts, $folios, $transaction, $entryType);
} else {

$folioAccount = array_key_first($folios);
$key = array_key_first($folios);
$folioAccount = $folios[$key]['id'];

$ledger = new Ledger();

Expand All @@ -202,18 +202,18 @@ private static function allocateAmount($postAccount, $amount, $posts, $folios, $
$ledger->post_account = $postAccount;
$ledger->folio_account = $folioAccount;

if($folios[$folioAccount] > $amount){
if ($folios[$key]['amount'] > $amount) {
$ledger->amount = $amount;
$ledger->save();

$folios[$folioAccount] -= $ledger->amount;
$folios[$key]['amount'] -= $ledger->amount;
$amount = 0;
} else {
$debitAmount = $folios[$folioAccount];
$debitAmount = $folios[$key]['amount'];
$ledger->amount = $debitAmount;
$ledger->save();
unset($folios[$folioAccount]);

unset($folios[$key]);
$amount -= $ledger->amount;
}

Expand Down Expand Up @@ -252,9 +252,9 @@ public static function post(Transaction $transaction): void
//Remove current ledgers if any prior to creating new ones (prevents bypassing Posted Transaction Exception)
$transaction->ledgers()->delete();

if($transaction->compound){
if ($transaction->compound) {
Ledger::postCompound($transaction);
}else{
} else {
Ledger::postBasic($transaction);
}
}
Expand Down
47 changes: 35 additions & 12 deletions src/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,7 @@ class Transaction extends Model implements Segregatable, Recyclable, Clearable,
* @var array $compoundEntries
*/

protected $compoundEntries = [
Balance::CREDIT => [],
Balance::DEBIT => []
];
protected $compoundEntries = [];

/**
* Check if LineItem already exists.
Expand Down Expand Up @@ -188,6 +185,22 @@ private static function getCompoundEntrytype(bool $credited): string
return $credited ? Balance::CREDIT : Balance::DEBIT;
}

/**
* Get the sum of the amounts on the given side of the compound entries
*
* @param string entryType
* @return float
*/
private function entriesSum(string $entryType): float
{
$sum = 0;
foreach ($this->compoundEntries[$entryType] as $entry) {
$sum += $entry['amount'];
}
return $sum;
}


/**
* Add Compound Entry to Transaction CompoundEntries.
*
Expand All @@ -196,7 +209,7 @@ private static function getCompoundEntrytype(bool $credited): string
*/
protected function addCompoundEntry(array $compoundEntry, bool $credited): void
{
$this->compoundEntries[Transaction::getCompoundEntrytype($credited)][$compoundEntry['id']] = $compoundEntry['amount'];
$this->compoundEntries[Transaction::getCompoundEntrytype($credited)][] = $compoundEntry;
}

/**
Expand Down Expand Up @@ -427,10 +440,16 @@ public function getAmountAttribute(): float
public function getCompoundEntries()
{
if ($this->compound) {
$this->compoundEntries[Transaction::getCompoundEntrytype($this->credited)][$this->account_id] = floatval($this->main_account_amount);

foreach ($this->lineItems as $lineItem) {
$this->compoundEntries[Transaction::getCompoundEntrytype($lineItem->credited)][$lineItem->account_id] = $lineItem->amount * $lineItem->quantity;
$this->compoundEntries = [
Balance::CREDIT => [],
Balance::DEBIT => []
];

$this->compoundEntries[Transaction::getCompoundEntrytype($this->credited)][] = ['id' => $this->account_id, 'amount' => floatval($this->main_account_amount)];

foreach ($this->getLineItems() as $lineItem) {
$this->compoundEntries[Transaction::getCompoundEntrytype($lineItem->credited)][] = ['id' => $lineItem->account_id, 'amount' => $lineItem->amount * $lineItem->quantity];
}
}

Expand Down Expand Up @@ -562,8 +581,11 @@ public function removeLineItem(LineItem $lineItem): void

if ($this->compound) {
$entryType = Transaction::getCompoundEntrytype($lineItem->credited);
if (array_key_exists($lineItem->account_id, $this->compoundEntries[$entryType])) {
unset($this->compoundEntries[$entryType][$lineItem->account_id]);

foreach ($this->compoundEntries[$entryType] as $index => $entry) {
if ($lineItem->account_id == $entry['id']) {
unset($this->compoundEntries[$entryType][$index]);
}
}
}

Expand Down Expand Up @@ -661,8 +683,9 @@ public function post(): void

$this->save();

extract($this->getCompoundEntries());
if ($this->compound && array_sum($C) != array_sum($D)) {
$this->getCompoundEntries();
// dd($this->getCompoundEntries());
if ($this->compound && $this->entriesSum(Balance::CREDIT) != $this->entriesSum(Balance::DEBIT)) {
throw new UnbalancedTransaction();
}

Expand Down
18 changes: 4 additions & 14 deletions src/Transactions/JournalEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ public function __construct($attributes = [])
*/
public function addLineItem(LineItem $lineItem): bool
{
if ($this->compound && count($lineItem->vat) > 1) {
if ($this->compound && count($lineItem->getVats()) > 0) {
throw new MultipleVatError('Compound Journal Entries cannot have Vat');
}

$success = parent::addLineItem($lineItem);
if($success && $this->compound){

if ($success && $this->compound) {
parent::addCompoundEntry(['id' => $lineItem->account_id, 'amount' => $lineItem->amount * $lineItem->quantity], $lineItem->credited);
}
return $success;
Expand All @@ -96,19 +96,9 @@ public function addLineItem(LineItem $lineItem): bool
public function save(array $options = []): bool
{

if($this->compound && (is_null($this->main_account_amount) || $this->main_account_amount == 0)){
if ($this->compound && (is_null($this->main_account_amount) || $this->main_account_amount == 0)) {
throw new MissingMainAccountAmount();
}

if($this->compound){
parent::addCompoundEntry([
'id' => $this->account_id,
'amount' => $this->main_account_amount
],
$this->credited
);
}

return parent::save();
}
}
15 changes: 7 additions & 8 deletions tests/Feature/AccountScheduleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public function testClientAccountScheduleTest()
$this->assertEquals($schedule->balances["originalAmount"], 241);
$this->assertEquals($schedule->balances["amountCleared"], 95);
$this->assertEquals($schedule->balances["unclearedAmount"], 146);
$this->assertEquals($schedule->balances["totalAge"], 365);
$this->assertEquals($schedule->balances["totalAge"], Carbon::now()->isLeapYear() ? 366 : 365);
$this->assertEquals($schedule->balances["averageAge"], 122);
}

Expand Down Expand Up @@ -447,7 +447,7 @@ public function testSupplierAccountAccountSchedule()
$this->assertEquals($schedule->balances['originalAmount'], 686.4);
$this->assertEquals($schedule->balances['amountCleared'], 221.8);
$this->assertEqualsWithDelta($schedule->balances['unclearedAmount'], 464.6, 0.1);
$this->assertEquals($schedule->balances['totalAge'], 365);
$this->assertEquals($schedule->balances['totalAge'], Carbon::now()->isLeapYear() ? 366 : 365);
$this->assertEquals($schedule->balances['averageAge'], 122.0);
}

Expand Down Expand Up @@ -498,7 +498,7 @@ public function testAccountScheduleCurrencyFilters()
])->id,
"quantity" => 1,
]);

$supplierPayment1->addLineItem($lineItem);

$supplierPayment1->post();
Expand All @@ -509,7 +509,7 @@ public function testAccountScheduleCurrencyFilters()
'cleared_type' => "IFRS\Models\Balance",
"amount" => 24,
]);

// Foreign currency opening balances
$balance2 = factory(Balance::class)->create([
"account_id" => $account->id,
Expand Down Expand Up @@ -700,7 +700,7 @@ public function testAccountScheduleCurrencyFilters()
$this->assertEquals($schedule->balances['originalAmount'], 43248.0);
$this->assertEquals($schedule->balances['amountCleared'], 11554);
$this->assertEquals($schedule->balances['unclearedAmount'], 31694.0);
$this->assertEquals($schedule->balances['totalAge'], 730);
$this->assertEquals($schedule->balances['totalAge'], Carbon::now()->isLeapYear() ? 732 : 730);
$this->assertEquals($schedule->balances['averageAge'], 183.0);

// Base Currency transactions
Expand All @@ -722,7 +722,7 @@ public function testAccountScheduleCurrencyFilters()
$this->assertEquals($schedule->balances['originalAmount'], 408.0);
$this->assertEquals($schedule->balances['amountCleared'], 109);
$this->assertEquals($schedule->balances['unclearedAmount'], 299.0);
$this->assertEquals($schedule->balances['totalAge'], 365);
$this->assertEquals($schedule->balances['totalAge'], Carbon::now()->isLeapYear() ? 366 : 365);
$this->assertEquals($schedule->balances['averageAge'], 183.0);

// Foreign Currency transactions
Expand All @@ -744,8 +744,7 @@ public function testAccountScheduleCurrencyFilters()
$this->assertEquals($schedule->balances['originalAmount'], 408.0);
$this->assertEquals($schedule->balances['amountCleared'], 109);
$this->assertEquals($schedule->balances['unclearedAmount'], 299.0);
$this->assertEquals($schedule->balances['totalAge'], 365);
$this->assertEquals($schedule->balances['totalAge'], Carbon::now()->isLeapYear() ? 366 : 365);
$this->assertEquals($schedule->balances['averageAge'], 183.0);

}
}
9 changes: 4 additions & 5 deletions tests/Unit/AssignmentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -817,8 +817,8 @@ public function testUnassignableTransaction()
$this->expectException(UnassignableTransaction::class);
$this->expectExceptionMessage(
"Client Invoice Transaction cannot have assignments. "
. "Assignment Transaction must be one of: "
. "Client Receipt, Supplier Payment, Credit Note, Debit Note, Journal Entry"
. "Assignment Transaction must be one of: "
. "Client Receipt, Supplier Payment, Credit Note, Debit Note, Journal Entry"
);

$assignment = new Assignment([
Expand Down Expand Up @@ -883,8 +883,8 @@ public function testUnclearableTransaction()
$this->expectException(UnclearableTransaction::class);
$this->expectExceptionMessage(
"Client Receipt Transaction cannot be cleared. "
. "Transaction to be cleared must be one of: "
. "Client Invoice, Supplier Bill, Journal Entry"
. "Transaction to be cleared must be one of: "
. "Client Invoice, Supplier Bill, Journal Entry"
);

$assignment = new Assignment([
Expand Down Expand Up @@ -1540,7 +1540,6 @@ public function testAssignmentCompoundTransaction()
$transaction->addLineItem($line);

$transaction->post();

$cleared = new JournalEntry([
"account_id" => $account->id,
"transaction_date" => Carbon::now(),
Expand Down
Loading
Loading