mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-12 23:45:10 +00:00
Compare commits
8 Commits
develop-20
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
7ce055a22c | ||
|
7bd915930c | ||
|
75aa2d99fd | ||
|
f52bc0e242 | ||
|
55cf924794 | ||
|
df3e4a6554 | ||
|
7c4ada458e | ||
|
2a4a98dd10 |
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\Account;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\PaginationRequest;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@@ -69,22 +70,25 @@ class ListController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function attachments(Account $account): JsonResponse
|
||||
public function attachments(Account $account, PaginationRequest $request): JsonResponse
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
[
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'page' => $page,
|
||||
] = $request->attributes->all();
|
||||
$collection = $this->repository->getAttachments($account);
|
||||
|
||||
$count = $collection->count();
|
||||
$attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$attachments = $collection->slice($offset, $limit);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator = new LengthAwarePaginator($attachments, $count, $limit, $page);
|
||||
$paginator->setPath(route('api.v1.accounts.attachments', [$account->id]).$this->buildParams());
|
||||
|
||||
/** @var AttachmentTransformer $transformer */
|
||||
$transformer = app(AttachmentTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($attachments, $transformer, 'attachments');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
@@ -39,7 +39,7 @@ class DateRangeRequest extends ApiRequest
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
if (!$validator->valid()) {
|
||||
if ($validator->failed()) {
|
||||
// set null values
|
||||
$this->attributes->set('start', null);
|
||||
$this->attributes->set('end', null);
|
||||
|
@@ -39,8 +39,7 @@ class DateRequest extends ApiRequest
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
$this->attributes->set('date', null);
|
||||
if (!$validator->valid()) {
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
$date = $this->getCarbonDate('date')?->endOfDay();
|
||||
|
@@ -42,7 +42,7 @@ class AccountTypeApiRequest extends ApiRequest
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
if (!$validator->valid()) {
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ class PaginationRequest extends ApiRequest
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
if (!$validator->valid()) {
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -84,8 +84,10 @@ class AttachmentFactory
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
public function setUser(User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -221,10 +221,26 @@ class TransactionJournalFactory
|
||||
];
|
||||
Log::debug('Source info:', $sourceInfo);
|
||||
Log::debug('Destination info:', $destInfo);
|
||||
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
||||
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
|
||||
$destinationAccount = null;
|
||||
$sourceAccount = null;
|
||||
if (TransactionTypeEnum::DEPOSIT->value === $type->type) {
|
||||
Log::debug('Transaction type is deposit, start with destination first.');
|
||||
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
|
||||
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount);
|
||||
}
|
||||
if (TransactionTypeEnum::DEPOSIT->value !== $type->type) {
|
||||
Log::debug('Transaction type is not deposit, start with source first.');
|
||||
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
|
||||
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
|
||||
}
|
||||
|
||||
Log::debug('Done with getAccount(2x)');
|
||||
|
||||
// there is a safety catch here. If either account is NULL, they will be replaced with the cash account.
|
||||
if (null === $destinationAccount) {
|
||||
Log::warning('Destination account is NULL, will replace with cash account.');
|
||||
$destinationAccount = $this->accountRepository->getCashAccount();
|
||||
}
|
||||
|
||||
// this is the moment for a reconciliation sanity check (again).
|
||||
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
|
||||
|
@@ -88,6 +88,7 @@ trait JournalServiceTrait
|
||||
|
||||
// the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case
|
||||
$creatableType = $this->getCreatableType($expectedTypes[$transactionType]);
|
||||
Log::debug(sprintf('Creatable type is "%s"', $creatableType), $expectedTypes[$transactionType]);
|
||||
|
||||
// if the result is NULL but the ID is set, an account could exist of the wrong type.
|
||||
// that data can be used to create a new account of the right type.
|
||||
@@ -227,9 +228,11 @@ trait JournalServiceTrait
|
||||
}
|
||||
|
||||
// find by preferred type.
|
||||
Log::debug('Find by preferred type.');
|
||||
$result = $this->accountRepository->findByName($data['name'], [$types[0]]);
|
||||
|
||||
// or any expected type.
|
||||
Log::debug('Find by any expected type.');
|
||||
$result ??= $this->accountRepository->findByName($data['name'], $types);
|
||||
|
||||
if (null !== $result) {
|
||||
|
12
composer.lock
generated
12
composer.lock
generated
@@ -11920,16 +11920,16 @@
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rectorphp/rector.git",
|
||||
"reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed"
|
||||
"reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/5b353f7457b9a0c63fc91ef340f5d119a40991ed",
|
||||
"reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f",
|
||||
"reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11968,7 +11968,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/rectorphp/rector/issues",
|
||||
"source": "https://github.com/rectorphp/rector/tree/2.2.2"
|
||||
"source": "https://github.com/rectorphp/rector/tree/2.2.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11976,7 +11976,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-09T19:50:20+00:00"
|
||||
"time": "2025-10-11T21:50:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@@ -78,8 +78,8 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2025-10-11',
|
||||
'build_time' => 1760188898,
|
||||
'version' => 'develop/2025-10-12',
|
||||
'build_time' => 1760277899,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 28, // field is no longer used.
|
||||
|
||||
|
24
database/factories/AccountFactory.php
Normal file
24
database/factories/AccountFactory.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class AccountFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->name(),
|
||||
'active' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public function withType(AccountTypeEnum $type): static
|
||||
{
|
||||
return $this->for(AccountType::where('type', $type->value)->first());
|
||||
}
|
||||
}
|
12
package-lock.json
generated
12
package-lock.json
generated
@@ -3173,9 +3173,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
|
||||
"integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==",
|
||||
"version": "24.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
|
||||
"integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4521,9 +4521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001749",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
|
||||
"integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
|
||||
"version": "1.0.30001750",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
|
||||
"integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
88
tests/integration/Api/Models/Account/ListControllerTest.php
Normal file
88
tests/integration/Api/Models/Account/ListControllerTest.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* AccountControllerTest.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\integration\Api\Models\Account;
|
||||
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Factory\AttachmentFactory;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\integration\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @covers \FireflyIII\Api\V1\Controllers\Models\Account\ListController
|
||||
*/
|
||||
final class ListControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
private User $user;
|
||||
private Account $account;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = $this->createAuthenticatedUser();
|
||||
$this->actingAs($this->user);
|
||||
|
||||
$this->account = Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
|
||||
app(AttachmentFactory::class)->setUser($this->user)->create([
|
||||
'filename' => 'test 1',
|
||||
'title' => 'test 1',
|
||||
'attachable_type' => Account::class,
|
||||
'attachable_id' => $this->account->id,
|
||||
]);
|
||||
app(AttachmentFactory::class)->setUser($this->user)->create([
|
||||
'filename' => 'test 2',
|
||||
'title' => 'test 2',
|
||||
'attachable_type' => Account::class,
|
||||
'attachable_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIndex(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
$response = $this->getJson(route('api.v1.accounts.attachments', ['account' => $this->account->id]));
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'meta' => ['pagination' => ['total' => 2, 'total_pages' => 1]],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIndexCanChangePageSize(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
$response = $this->getJson(route('api.v1.accounts.attachments', ['account' => $this->account->id, 'limit' => 1]));
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'meta' => ['pagination' => ['total' => 2, 'total_pages' => 2]],
|
||||
]);
|
||||
}
|
||||
}
|
89
tests/integration/Api/Models/Account/ShowControllerTest.php
Normal file
89
tests/integration/Api/Models/Account/ShowControllerTest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* AccountControllerTest.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\integration\Api\Models\Account;
|
||||
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\integration\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @covers \FireflyIII\Api\V1\Controllers\Models\Account\ShowController
|
||||
*/
|
||||
final class ShowControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
private User $user;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = $this->createAuthenticatedUser();
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
|
||||
Account::factory()->for($this->user)->withType(AccountTypeEnum::REVENUE)->create();
|
||||
Account::factory()->for($this->user)->withType(AccountTypeEnum::EXPENSE)->create();
|
||||
Account::factory()->for($this->user)->withType(AccountTypeEnum::DEBT)->create();
|
||||
Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
|
||||
}
|
||||
|
||||
public function testIndex(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
$response = $this->getJson(route('api.v1.accounts.index'));
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'meta' => ['pagination' => ['total' => 5]],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIndexFailsOnUnknownAccountType(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
$response = $this->getJson(route('api.v1.accounts.index').'?type=foobar');
|
||||
$response->assertStatus(422);
|
||||
$response->assertJson(['errors' => ['type' => ['The selected type is invalid.']]]);
|
||||
}
|
||||
|
||||
public function testIndexCanFilterOnAccountType(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
$response = $this->getJson(route('api.v1.accounts.index').'?type=asset');
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
['attributes' => ['type' => 'asset']],
|
||||
['attributes' => ['type' => 'asset']],
|
||||
],
|
||||
'meta' => ['pagination' => ['total' => 2]],
|
||||
]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user