diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5f069..dcfdeb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 5.0.2 - 2024-02-19 + +- Add Attachments to Transactions ## 5.0.1 - 2023-04-18 - Add Laravel 10 Compatibility diff --git a/database/migrations/2024_13_02_180135_add_transaction_attachments_columns.php b/database/migrations/2024_13_02_180135_add_transaction_attachments_columns.php new file mode 100644 index 0000000..52d4233 --- /dev/null +++ b/database/migrations/2024_13_02_180135_add_transaction_attachments_columns.php @@ -0,0 +1,50 @@ +unsignedBigInteger('attachment_id')->nullable(); + $table->string('attachment_type')->nullable(); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + config('ifrs.table_prefix') . 'transactions', + function (Blueprint $table) { + if (config('database.default') == 'sqlite') { + DB::statement('PRAGMA foreign_keys = OFF;'); // sqlite needs to drop the entire table to remove a column, which fails because the table is already referenced + } + $table->dropColumn('attachment_id'); + } + ); + + Schema::table( + config('ifrs.table_prefix') . 'transactions', + function (Blueprint $table) { + $table->dropColumn('attachment_type'); + } + ); + } +} diff --git a/src/Models/Transaction.php b/src/Models/Transaction.php index 574b2ae..5b34ead 100644 --- a/src/Models/Transaction.php +++ b/src/Models/Transaction.php @@ -112,6 +112,8 @@ class Transaction extends Model implements Segregatable, Recyclable, Clearable, 'transaction_type', 'transaction_no', 'entity_id', + 'attachment_id', + 'attachment_type' ]; protected $dates = [ @@ -194,9 +196,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['id']] = $compoundEntry['amount']; } /** @@ -333,6 +333,16 @@ public function lineItems() return $this->hasMany(LineItem::class, 'transaction_id', 'id'); } + /** + * The model attached to the transaction. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function attachment() + { + return $this->morphTo(); + } + /** * Instance Type Translator. * @@ -384,22 +394,21 @@ public function getAmountAttribute(): float if ($this->is_posted) { $query = $ledger->newQuery() - ->selectRaw("SUM(amount/rate) as amount") - ->where([ - "transaction_id" => $this->id, - "entry_type" => Transaction::getCompoundEntrytype($this->credited), - "currency_id" => $this->currency_id - ]); - - if(!$this->compound){ + ->selectRaw("SUM(amount/rate) as amount") + ->where([ + "transaction_id" => $this->id, + "entry_type" => Transaction::getCompoundEntrytype($this->credited), + "currency_id" => $this->currency_id + ]); + + if (!$this->compound) { $query->where("post_account", $this->account_id); } $amount = $query->get()[0]->amount; - } else { foreach ($this->getLineItems() as $lineItem) { - if($lineItem->credited != $this->credited){ + if ($lineItem->credited != $this->credited) { $amount += $lineItem->amount * $lineItem->quantity; if (!$lineItem->vat_inclusive) { $amount += $lineItem->vat['total']; @@ -417,18 +426,14 @@ public function getAmountAttribute(): float */ public function getCompoundEntries() { - if($this->compound){ - $this->compoundEntries[ - Transaction::getCompoundEntrytype($this->credited) - ][$this->account_id] = floatval($this->main_account_amount); - + 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[Transaction::getCompoundEntrytype($lineItem->credited)][$lineItem->account_id] = $lineItem->amount * $lineItem->quantity; } } - + return $this->compoundEntries; } @@ -456,12 +461,12 @@ public function getVatAttribute(): array { $vats = ['total' => 0]; foreach ($this->getLineItems() as $lineItem) { - foreach($lineItem->vat as $type => $amount) - if (array_key_exists($type, $vats)) { - $vats[$type] += $amount; - } else { - $vats[$type] = $amount; - } + foreach ($lineItem->vat as $type => $amount) + if (array_key_exists($type, $vats)) { + $vats[$type] += $amount; + } else { + $vats[$type] = $amount; + } } return $vats; } @@ -525,7 +530,7 @@ public function addLineItem(LineItem $lineItem): bool throw new RedundantTransaction(); } - if(!$this->compound){ + if (!$this->compound) { $lineItem->credited = !$this->credited; } @@ -555,9 +560,9 @@ public function removeLineItem(LineItem $lineItem): void unset($this->items[$key]); } - if($this->compound){ + if ($this->compound) { $entryType = Transaction::getCompoundEntrytype($lineItem->credited); - if(array_key_exists($lineItem->account_id, $this->compoundEntries[$entryType])){ + if (array_key_exists($lineItem->account_id, $this->compoundEntries[$entryType])) { unset($this->compoundEntries[$entryType][$lineItem->account_id]); } } @@ -698,10 +703,12 @@ public function save(array $options = []): bool if ($period->status == ReportingPeriod::ADJUSTING && $this->transaction_type != Transaction::JN) { throw new AdjustingReportingPeriod(); } - - if (in_array($this->account->account_type, config('ifrs.single_currency')) && - $this->account->currency_id != $this->currency_id && - $this->currency_id != $entity->currency_id) { + + if ( + in_array($this->account->account_type, config('ifrs.single_currency')) && + $this->account->currency_id != $this->currency_id && + $this->currency_id != $entity->currency_id + ) { throw new InvalidCurrency("Transaction", $this->account); } @@ -724,7 +731,7 @@ public function save(array $options = []): bool if ($this->isDirty('transaction_type') && $this->transaction_type != $this->getOriginal('transaction_type') && !is_null($this->id)) { throw new InvalidTransactionType(); } - + $save = parent::save(); $this->saveLineItems(); @@ -752,10 +759,10 @@ public static function transactionNo(string $type, Carbon $transaction_date = nu $periodStart = ReportingPeriod::periodStart($transaction_date, $entity); $nextId = Transaction::withTrashed() - ->where("transaction_type", $type) - ->where("transaction_date", ">=", $periodStart) - ->where("entity_id", '=', $entity->id) - ->count() + 1; + ->where("transaction_type", $type) + ->where("transaction_date", ">=", $periodStart) + ->where("entity_id", '=', $entity->id) + ->count() + 1; return $type . str_pad((string)$periodCount, 2, "0", STR_PAD_LEFT) . "/" . @@ -809,7 +816,7 @@ public function getHasIntegrityAttribute(): bool // verify transaction ledger hashes return $this->ledgers->every( function ($ledger, $key) { - return hash(config('ifrs')['hashing_algorithm'],$ledger->hashed()) == $ledger->hash; + return hash(config('ifrs')['hashing_algorithm'], $ledger->hashed()) == $ledger->hash; } ); } diff --git a/tests/Unit/TransactionTest.php b/tests/Unit/TransactionTest.php index c6a0007..589e25e 100644 --- a/tests/Unit/TransactionTest.php +++ b/tests/Unit/TransactionTest.php @@ -34,6 +34,7 @@ use IFRS\Exceptions\UnpostedAssignment; use IFRS\Exceptions\InvalidTransactionDate; use IFRS\Exceptions\InvalidTransactionType; +use IFRS\Models\Category; class TransactionTest extends TestCase { @@ -123,7 +124,7 @@ public function testTransactionEntityScope() $newEntity->currency()->associate(factory(Currency::class)->create()); $newEntity->save(); - + $currency = factory(Currency::class)->create(); $entity = new Entity(); @@ -230,7 +231,7 @@ public function testTransactionRecycling() $transaction->delete(); } - /** + /** * Test Transaction Model recylcling * * @return void @@ -428,7 +429,7 @@ public function testVat() $transaction->addLineItem($lineItem2); $transaction->post(); - $this->assertEquals($transaction->vat, ['total' => 7.0,'E' => 2.0, 'L' => 5.0]); + $this->assertEquals($transaction->vat, ['total' => 7.0, 'E' => 2.0, 'L' => 5.0]); } /** @@ -1082,9 +1083,9 @@ public function testInvalidTransactionDate() { $this->expectException(InvalidTransactionDate::class); $this->expectExceptionMessage('Transaction date cannot be at the beginning of the first day of the Reporting Period. Use a Balance object instead'); - + Transaction::create([ - 'transaction_date' => Carbon::parse(date('Y').'-01-01'), + 'transaction_date' => Carbon::parse(date('Y') . '-01-01'), ]); } @@ -1133,4 +1134,46 @@ public function testInvalidTransactionType() 'transaction_type' => Transaction::IN ]); } + + /** + * Test Transaction Attachments. + * + * @return void + */ + public function testTransactionAttachments() + { + $account = factory(Account::class)->create([ + 'account_type' => Account::RECEIVABLE, + 'category_id' => null + ]); + + $category = new Category([ + 'name' => $this->faker->word, + 'category_type' => Account::BANK, + ]); + $category->save(); + + $transaction = new JournalEntry([ + "account_id" => $account->id, + "transaction_date" => Carbon::now(), + "narration" => $this->faker->word, + "attachment_id" => $category->id, + "attachment_type" => Category::class + ]); + + $line = new LineItem([ + 'vat_id' => factory(Vat::class)->create(["rate" => 0])->id, + 'account_id' => factory(Account::class)->create([ + 'category_id' => null + ])->id, + 'amount' => 125, + ]); + + $transaction->addLineItem($line); + $transaction->save(); + $transaction->refresh(); + + $this->assertEquals($transaction->attachment->id, $category->id); + $this->assertEquals($transaction->attachment->name, $category->name); + } }