Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop

This commit is contained in:
James Cole 2024-12-30 07:36:12 +01:00
commit e68c4d4408
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
45 changed files with 661 additions and 311 deletions

View File

@ -406,16 +406,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.65.0",
"version": "v3.66.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f"
"reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
"reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/5f5f2a142ff36b93c41885bca29cc5f861c013e6",
"reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6",
"shasum": ""
},
"require": {
@ -441,7 +441,7 @@
"symfony/polyfill-mbstring": "^1.28",
"symfony/polyfill-php80": "^1.28",
"symfony/polyfill-php81": "^1.28",
"symfony/process": "^5.4 || ^6.0 || ^7.0",
"symfony/process": "^5.4 || ^6.0 || ^7.0 <7.2",
"symfony/stopwatch": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
@ -497,7 +497,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.65.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.66.0"
},
"funding": [
{
@ -505,7 +505,7 @@
"type": "github"
}
],
"time": "2024-11-25T00:39:24+00:00"
"time": "2024-12-29T13:46:23+00:00"
},
{
"name": "psr/container",
@ -2246,16 +2246,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.0",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
"shasum": ""
},
"require": {
@ -2287,7 +2287,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.0"
"source": "https://github.com/symfony/process/tree/v7.1.8"
},
"funding": [
{
@ -2303,7 +2303,7 @@
"type": "tidelift"
}
],
"time": "2024-11-06T14:24:19+00:00"
"time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/service-contracts",

View File

@ -98,7 +98,7 @@ class AccountController extends Controller
);
}
$return[] = [
$return[] = [
'id' => (string) $account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,

View File

@ -72,19 +72,19 @@ class AccountController extends Controller
public function overview(DateRequest $request): JsonResponse
{
// parameters for chart:
$dates = $request->getAll();
$dates = $request->getAll();
/** @var Carbon $start */
$start = $dates['start'];
$start = $dates['start'];
/** @var Carbon $end */
$end = $dates['end'];
$end = $dates['end'];
// user's preferences
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
/** @var Preference $frontpage */
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
$frontpage->data = $defaultSet;
@ -92,14 +92,14 @@ class AccountController extends Controller
}
// get accounts:
$accounts = $this->repository->getAccountsById($frontpage->data);
$chartData = [];
$accounts = $this->repository->getAccountsById($frontpage->data);
$chartData = [];
/** @var Account $account */
foreach ($accounts as $account) {
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$field = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? 'native_balance' : 'balance';
$currentSet = [
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$field = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? 'native_balance' : 'balance';
$currentSet = [
'label' => $account->name,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
@ -116,14 +116,14 @@ class AccountController extends Controller
$range = app('steam')->finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$previous = array_values($range)[0][$field];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format][$field] : $previous;
$previous = $balance;
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format][$field] : $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
}
$chartData[] = $currentSet;
$chartData[] = $currentSet;
}
return response()->json($chartData);

View File

@ -94,8 +94,8 @@ abstract class Controller extends BaseController
if ($page < 1) {
$page = 1;
}
if ($page > pow(2,16)) {
$page = pow(2, 16);
if ($page > 2 ** 16) {
$page = 2 ** 16;
}
$bag->set('page', $page);

View File

@ -63,8 +63,8 @@ class DestroyController extends Controller
*/
public function destroy(DestroyRequest $request): JsonResponse
{
$objects = $request->getObjects();
$this->unused = $request->boolean('unused', false);
$objects = $request->getObjects();
$this->unused = $request->boolean('unused', false);
$allExceptAssets = [AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::REVENUE->value];
$all = [AccountTypeEnum::ASSET->value, AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::RECONCILIATION->value];
@ -101,11 +101,11 @@ class DestroyController extends Controller
private function destroyBudgets(): void
{
/** @var AvailableBudgetRepositoryInterface $abRepository */
$abRepository = app(AvailableBudgetRepositoryInterface::class);
$abRepository = app(AvailableBudgetRepositoryInterface::class);
$abRepository->destroyAll();
/** @var BudgetLimitRepositoryInterface $blRepository */
$blRepository = app(BudgetLimitRepositoryInterface::class);
$blRepository = app(BudgetLimitRepositoryInterface::class);
$blRepository->destroyAll();
/** @var BudgetRepositoryInterface $budgetRepository */

View File

@ -79,11 +79,11 @@ class BillController extends Controller
}
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setRange($start, $end)->setSourceAccounts($accounts);
$collector->setBills($bills);
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
$billId = (int) $journal['bill_id'];
$currencyId = (int) $journal['currency_id'];
@ -102,10 +102,10 @@ class BillController extends Controller
}
Log::debug(sprintf('Journal #%d in bill #%d will use %s (%s %s)', $journal['transaction_group_id'], $billId, $field, $currencyCode, $journal[$field] ?? '0'));
$key = sprintf('%d-%d', $billId, $currencyId);
$key = sprintf('%d-%d', $billId, $currencyId);
if (0 !== $currencyId) {
$response[$key] ??= [
$response[$key] ??= [
'id' => (string) $billId,
'name' => $journal['bill_name'],
'difference' => '0',
@ -137,11 +137,11 @@ class BillController extends Controller
$response = [];
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setRange($start, $end)->setSourceAccounts($accounts);
$collector->withoutBill();
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
$currencyId = (int) $journal['currency_id'];
@ -161,7 +161,7 @@ class BillController extends Controller
Log::debug(sprintf('Journal #%d will use %s (%s %s)', $journal['transaction_group_id'], $field, $currencyCode, $journal[$field] ?? '0'));
if (0 !== $currencyId) {
$response[$currencyId] ??= [
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,

View File

@ -51,14 +51,14 @@ class PeriodController extends Controller
$default = Amount::getDefaultCurrency();
// collect all expenses in this period (regardless of type)
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setRange($start, $end)->setSourceAccounts($accounts);
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// same code as many other sumExpense methods. I think this needs some kind of generic method.
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
if ($convertToNative) {
$amount = Amount::getAmountFromJournal($journal);
if ($default->id !== (int) $journal['currency_id'] && $default->id !== (int) $journal['foreign_currency_id']) {
@ -78,7 +78,7 @@ class PeriodController extends Controller
}
$response[$currencyId] ??= [
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,

View File

@ -72,17 +72,17 @@ class TagController extends Controller
$default = Amount::getDefaultCurrency();
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setRange($start, $end)->setSourceAccounts($accounts);
$collector->withoutTags();
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// same code as many other sumExpense methods. I think this needs some kind of generic method.
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
if ($convertToNative) {
$amount = Amount::getAmountFromJournal($journal);
if ($default->id !== (int) $journal['currency_id'] && $default->id !== (int) $journal['foreign_currency_id']) {
@ -101,7 +101,7 @@ class TagController extends Controller
$amount = $journal['amount'];
}
$response[$currencyId] ??= [
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
@ -122,11 +122,11 @@ class TagController extends Controller
*/
public function tag(GenericRequest $request): JsonResponse
{
$accounts = $request->getAssetAccounts();
$tags = $request->getTags();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$accounts = $request->getAssetAccounts();
$tags = $request->getTags();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
// get all tags:
if (0 === $tags->count()) {
@ -134,7 +134,7 @@ class TagController extends Controller
}
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setRange($start, $end)->setSourceAccounts($accounts);
$collector->setTags($tags);
$genericSet = $collector->getExtractedJournals();
@ -152,7 +152,7 @@ class TagController extends Controller
// on currency ID
if (0 !== $currencyId) {
$response[$key] ??= [
$response[$key] ??= [
'id' => (string) $tagId,
'name' => $tag['name'],
'difference' => '0',

View File

@ -50,14 +50,14 @@ class PeriodController extends Controller
$default = Amount::getDefaultCurrency();
// collect all expenses in this period (regardless of type)
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value])->setRange($start, $end)->setDestinationAccounts($accounts);
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// currency
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
// perhaps use default currency instead?
if ($convertToNative && $journal['currency_id'] !== $default->id) {
@ -69,7 +69,7 @@ class PeriodController extends Controller
$field = 'foreign_amount';
}
$response[$currencyId] ??= [
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,

View File

@ -64,25 +64,25 @@ class TagController extends Controller
*/
public function noTag(GenericRequest $request): JsonResponse
{
$accounts = $request->getAssetAccounts();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$accounts = $request->getAssetAccounts();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$convertToNative = Amount::convertToNative();
$default = Amount::getDefaultCurrency();
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value])->setRange($start, $end)->setDestinationAccounts($accounts);
$collector->withoutTags();
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// currency
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
// perhaps use default currency instead?
if ($convertToNative && $journal['currency_id'] !== $default->id) {
@ -95,13 +95,13 @@ class TagController extends Controller
}
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_code' => $currencyCode,
];
$response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], app('steam')->positive($journal[$field]));
$response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference'];
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_code' => $currencyCode,
];
$response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], app('steam')->positive($journal[$field]));
$response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference'];
}

View File

@ -42,22 +42,22 @@ class PeriodController extends Controller
*/
public function total(GenericRequest $request): JsonResponse
{
$accounts = $request->getAssetAccounts();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$accounts = $request->getAssetAccounts();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$convertToNative = Amount::convertToNative();
$default = Amount::getDefaultCurrency();
// collect all expenses in this period (regardless of type)
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionType::TRANSFER])->setRange($start, $end)->setDestinationAccounts($accounts);
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// currency
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
// perhaps use default currency instead?
if ($convertToNative && $journal['currency_id'] !== $default->id) {
@ -69,14 +69,14 @@ class PeriodController extends Controller
$field = 'foreign_amount';
}
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_code' => $currencyCode,
];
$response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], app('steam')->positive($journal[$field]));
$response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference'];
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_code' => $currencyCode,
];
$response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], app('steam')->positive($journal[$field]));
$response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference'];
}

View File

@ -71,17 +71,17 @@ class TagController extends Controller
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionType::TRANSFER])->setRange($start, $end)->setDestinationAccounts($accounts);
$collector->withoutTags();
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
// currency
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
$currencyId = $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$field = $convertToNative && $currencyId !== $default->id ? 'native_amount' : 'amount';
// perhaps use default currency instead?
if ($convertToNative && $journal['currency_id'] !== $default->id) {
@ -93,7 +93,7 @@ class TagController extends Controller
$field = 'foreign_amount';
}
$response[$currencyId] ??= [
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
@ -115,11 +115,11 @@ class TagController extends Controller
*/
public function tag(GenericRequest $request): JsonResponse
{
$accounts = $request->getAssetAccounts();
$tags = $request->getTags();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
$accounts = $request->getAssetAccounts();
$tags = $request->getTags();
$start = $request->getStart();
$end = $request->getEnd();
$response = [];
// get all tags:
if (0 === $tags->count()) {
@ -127,7 +127,7 @@ class TagController extends Controller
}
// collect all expenses in this period (regardless of type) by the given bills and accounts.
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes([TransactionType::TRANSFER])->setRange($start, $end)->setDestinationAccounts($accounts);
$collector->setTags($tags);
$genericSet = $collector->getExtractedJournals();
@ -145,7 +145,7 @@ class TagController extends Controller
// on currency ID
if (0 !== $currencyId) {
$response[$key] ??= [
$response[$key] ??= [
'id' => (string) $tagId,
'name' => $tag['name'],
'difference' => '0',

View File

@ -105,18 +105,18 @@ class ShowController extends Controller
public function show(TransactionCurrency $currency): JsonResponse
{
/** @var User $user */
$user = auth()->user();
$manager = $this->getManager();
$user = auth()->user();
$manager = $this->getManager();
$this->parameters->set('defaultCurrency', $this->defaultCurrency);
// update fields with user info.
$currency->refreshForUser($user);
/** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class);
$transformer = app(CurrencyTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($currency, $transformer, 'currencies');
$resource = new Item($currency, $transformer, 'currencies');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@ -93,8 +93,8 @@ class Controller extends BaseController
if ($page < 1) {
$page = 1;
}
if ($page > pow(2,16)) {
$page = pow(2, 16);
if ($page > 2 ** 16) {
$page = 2 ** 16;
}
$bag->set('page', $page);

View File

@ -43,11 +43,11 @@ class PiggyBankFactory
public User $user {
set(User $value) {
$this->user = $value;
$this->currencyRepository->setUser($value);
$this->accountRepository->setUser($value);
$this->piggyBankRepository->setUser($value);
}
$this->user = $value;
$this->currencyRepository->setUser($value);
$this->accountRepository->setUser($value);
$this->piggyBankRepository->setUser($value);
}
}
private AccountRepositoryInterface $accountRepository;
private CurrencyRepositoryInterface $currencyRepository;
@ -62,15 +62,11 @@ class PiggyBankFactory
/**
* Store a piggy bank or come back with an exception.
*
* @param array $data
*
* @return PiggyBank
*/
public function store(array $data): PiggyBank
{
$piggyBankData = $data;
$piggyBankData = $data;
// unset some fields
unset($piggyBankData['object_group_title'], $piggyBankData['transaction_currency_code'], $piggyBankData['transaction_currency_id'], $piggyBankData['accounts'], $piggyBankData['object_group_id'], $piggyBankData['notes']);
@ -94,11 +90,11 @@ class PiggyBankFactory
throw new FireflyException('400005: Could not store new piggy bank.', 0, $e);
}
$piggyBank = $this->setOrder($piggyBank, $data);
$piggyBank = $this->setOrder($piggyBank, $data);
$this->linkToAccountIds($piggyBank, $data['accounts']);
$this->piggyBankRepository->updateNote($piggyBank, $data['notes']);
$objectGroupTitle = $data['object_group_title'] ?? '';
$objectGroupTitle = $data['object_group_title'] ?? '';
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@ -106,7 +102,7 @@ class PiggyBankFactory
}
}
// try also with ID
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@ -114,9 +110,10 @@ class PiggyBankFactory
}
}
Log::debug('Touch piggy bank');
$piggyBank->encrypted = false;
$piggyBank->encrypted = false;
$piggyBank->save();
$piggyBank->touch();
return $piggyBank;
}
@ -132,6 +129,7 @@ class PiggyBankFactory
$currency = $this->currencyRepository->find((int) ($data['transaction_currency_id'] ?? 0));
}
$currency ??= $defaultCurrency;
return $currency;
}
@ -144,12 +142,12 @@ class PiggyBankFactory
}
// first find by ID:
if ($piggyBankId > 0) {
$piggyBank = PiggyBank
::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
$piggyBank = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)
->first(['piggy_banks.*']);
->first(['piggy_banks.*'])
;
if (null !== $piggyBank) {
return $piggyBank;
}
@ -169,23 +167,24 @@ class PiggyBankFactory
public function findByName(string $name): ?PiggyBank
{
return PiggyBank
::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)
->first(['piggy_banks.*']);
->first(['piggy_banks.*'])
;
}
private function setOrder(PiggyBank $piggyBank, array $data): PiggyBank
{
$this->resetOrder();
$order = $this->getMaxOrder() + 1;
$order = $this->getMaxOrder() + 1;
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
$piggyBank->order = $order;
$piggyBank->saveQuietly();
return $piggyBank;
}
@ -193,8 +192,7 @@ class PiggyBankFactory
public function resetOrder(): void
{
// TODO duplicate code
$set = PiggyBank
::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
$set = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
@ -202,7 +200,8 @@ class PiggyBankFactory
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']);
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*'])
;
$current = 1;
foreach ($set as $piggyBank) {
if ($piggyBank->order !== $current) {
@ -214,7 +213,6 @@ class PiggyBankFactory
}
}
private function getMaxOrder(): int
{
return (int) $this->piggyBankRepository->getPiggyBanks()->max('order');

View File

@ -99,7 +99,7 @@ class PreferencesEventHandler
{
$repository = app(BudgetRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
$set = $repository->getBudgets();
/** @var Budget $budget */
foreach ($set as $budget) {
@ -127,13 +127,15 @@ class PreferencesEventHandler
{
// custom query because of the potential size of this update.
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')
->orWhereNotNull('native_foreign_amount');
})
->update(['native_amount' => null, 'native_foreign_amount' => null]);
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')
->orWhereNotNull('native_foreign_amount')
;
})
->update(['native_amount' => null, 'native_foreign_amount' => null])
;
Log::debug(sprintf('Reset %d transactions.', $success));
}
}

View File

@ -39,7 +39,7 @@ class AvailableBudgetObserver
public function updated(AvailableBudget $availableBudget): void
{
//Log::debug('Observe "updated" of an available budget.');
// Log::debug('Observe "updated" of an available budget.');
$this->updateNativeAmount($availableBudget);
}

View File

@ -234,9 +234,9 @@ class BudgetController extends Controller
$key = sprintf('%d-%d', $journal['source_account_id'], $journal['currency_id']);
$amount = $journal['amount'];
$symbol = $journal['currency_symbol'];
$code = $journal['currency_code'];
$name = $journal['currency_name'];
$symbol = $journal['currency_symbol'];
$code = $journal['currency_code'];
$name = $journal['currency_name'];
// if convert to native, use the native things, unless it's the foreign amount which is in the native currency.
if ($this->convertToNative && $journal['currency_id'] !== $this->defaultCurrency->id) {

View File

@ -50,13 +50,13 @@ class Account extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'virtual_balance' => 'string',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'virtual_balance' => 'string',
'native_virtual_balance' => 'string',
];

View File

@ -48,10 +48,10 @@ class AutoBudget extends Model
public const int AUTO_BUDGET_ROLLOVER = 2;
protected $casts
= [
'amount' => 'string',
'amount' => 'string',
'native_amount' => 'string',
];
protected $fillable = ['budget_id', 'amount', 'period','native_amount'];
protected $fillable = ['budget_id', 'amount', 'period', 'native_amount'];
public function budget(): BelongsTo
{

View File

@ -53,7 +53,7 @@ class AvailableBudget extends Model
'native_amount' => 'string',
];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz','native_amount'];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'native_amount'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@ -43,12 +43,12 @@ class BudgetLimit extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'auto_budget' => 'boolean',
'amount' => 'string',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'auto_budget' => 'boolean',
'amount' => 'string',
'native_amount' => 'string',
];
protected $dispatchesEvents

View File

@ -44,19 +44,19 @@ class PiggyBank extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'start_date' => 'date',
'target_date' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'target_amount' => 'string',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'start_date' => 'date',
'target_date' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'target_amount' => 'string',
'native_target_amount' => 'string',
];
protected $fillable = ['name', 'order', 'target_amount', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'active', 'transaction_currency_id','native_target_amount'];
protected $fillable = ['name', 'order', 'target_amount', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'active', 'transaction_currency_id', 'native_target_amount'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@ -45,7 +45,7 @@ class PiggyBankEvent extends Model
'amount' => 'native_string',
];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'date_tz', 'amount','native_amount'];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'date_tz', 'amount', 'native_amount'];
protected $hidden = ['amount_encrypted'];

View File

@ -28,12 +28,10 @@ use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class OperationsRepository
@ -68,7 +66,7 @@ class OperationsRepository implements OperationsRepositoryInterface
return $collector->getExtractedJournals();
}
public function setUser(null | Authenticatable | User $user): void
public function setUser(null|Authenticatable|User $user): void
{
if ($user instanceof User) {
$this->user = $user;
@ -79,8 +77,8 @@ class OperationsRepository implements OperationsRepositoryInterface
{
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$journalId = (int) $journal['transaction_journal_id'];
$currencyId = (int) $journal['currency_id'];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId] ??= [
'currency_id' => $journal['currency_id'],
'currency_name' => $journal['currency_name'],
@ -132,8 +130,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByCurrency($journals, 'negative');
@ -150,8 +147,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $opposing = null,
?TransactionCurrency $currency = null
): array
{
): array {
$start->startOfDay();
$end->endOfDay();
@ -184,14 +180,15 @@ class OperationsRepository implements OperationsRepositoryInterface
if (null !== $currency) {
$collector->setCurrency($currency);
}
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for foreign currencies:
if (null !== $currency) {
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([$type])->withAccountInformation()
->setForeignCurrency($currency);
->setForeignCurrency($currency)
;
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
if (null !== $accounts) {
$collector->setSourceAccounts($accounts);
@ -209,10 +206,10 @@ class OperationsRepository implements OperationsRepositoryInterface
}
}
$result = $collector->getExtractedJournals();
$result = $collector->getExtractedJournals();
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
$journals = $result + $journals;
$journals = $result + $journals;
}
return $journals;
@ -221,6 +218,7 @@ class OperationsRepository implements OperationsRepositoryInterface
private function groupByCurrency(array $journals, string $direction): array
{
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByCurrencyId($journals, $direction);
}
@ -233,8 +231,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByDirection($journals, 'destination', 'negative');
@ -243,6 +240,7 @@ class OperationsRepository implements OperationsRepositoryInterface
private function groupByDirection(array $journals, string $direction, string $method): array
{
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByDirection($journals, $method, $direction);
}
@ -255,8 +253,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByDirection($journals, 'source', 'negative');
@ -271,8 +268,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByCurrency($journals, 'positive');
@ -287,8 +283,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByDirection($journals, 'destination', 'positive');
@ -303,8 +298,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByDirection($journals, 'source', 'positive');
@ -325,7 +319,7 @@ class OperationsRepository implements OperationsRepositoryInterface
foreach ($journals as $journal) {
$return = $this->groupByEitherJournal($return, $journal);
}
$final = [];
$final = [];
foreach ($return as $array) {
$array['difference_float'] = (float) $array['difference'];
$array['in_float'] = (float) $array['in'];
@ -338,12 +332,12 @@ class OperationsRepository implements OperationsRepositoryInterface
private function groupByEitherJournal(array $return, array $journal): array
{
$sourceId = $journal['source_account_id'];
$destinationId = $journal['destination_account_id'];
$currencyId = $journal['currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['amount']);
$sourceId = $journal['source_account_id'];
$destinationId = $journal['destination_account_id'];
$currencyId = $journal['currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['amount']);
// source first
$return[$sourceKey] ??= [
@ -360,7 +354,7 @@ class OperationsRepository implements OperationsRepositoryInterface
];
// dest next:
$return[$destKey] ??= [
$return[$destKey] ??= [
'id' => (string) $destinationId,
'name' => $journal['destination_account_name'],
'difference' => '0',
@ -378,15 +372,15 @@ class OperationsRepository implements OperationsRepositoryInterface
$return[$sourceKey]['difference'] = bcadd($return[$sourceKey]['out'], $return[$sourceKey]['in']);
// destination account? money comes in:
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
// foreign currency
if (null !== $journal['foreign_currency_id'] && null !== $journal['foreign_amount']) {
$currencyId = $journal['foreign_currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['foreign_amount']);
$currencyId = $journal['foreign_currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['foreign_amount']);
// same as above:
// source first
@ -404,7 +398,7 @@ class OperationsRepository implements OperationsRepositoryInterface
];
// dest next:
$return[$destKey] ??= [
$return[$destKey] ??= [
'id' => (string) $destinationId,
'name' => $journal['destination_account_name'],
'difference' => '0',
@ -421,8 +415,8 @@ class OperationsRepository implements OperationsRepositoryInterface
$return[$sourceKey]['difference'] = bcadd($return[$sourceKey]['out'], $return[$sourceKey]['in']);
// destination account? money comes in:
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
}
return $return;

View File

@ -586,7 +586,7 @@ class BillRepository implements BillRepositoryInterface
$minField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_min' : 'amount_min';
$maxField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_max' : 'amount_max';
Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
if ($total > 0) {
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;

View File

@ -29,12 +29,10 @@ use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class NoBudgetRepository
@ -93,7 +91,7 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
if (null !== $accounts && $accounts->count() > 0) {
@ -104,8 +102,9 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface
}
$collector->withoutBudget();
$collector->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByCurrencyId($journals);
}
}

View File

@ -218,10 +218,10 @@ class OperationsRepository implements OperationsRepositoryInterface
// 2024-12-24 disable the exclusion for now.
$repository = app(AccountRepositoryInterface::class);
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
/** @var Account $account */
foreach ($subset as $account) {
@ -231,7 +231,7 @@ class OperationsRepository implements OperationsRepositoryInterface
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
// ->excludeDestinationAccounts($selection)
@ -249,13 +249,14 @@ class OperationsRepository implements OperationsRepositoryInterface
$collector->setNormalCurrency($currency);
}
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for transactions in the foreign currency:
if (null !== $currency) {
Log::debug('STOP looking for transactions in the foreign currency.');
}
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByCurrencyId($journals);
}
}

View File

@ -27,12 +27,10 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class NoCategoryRepository
@ -146,14 +144,15 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutCategory();
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByCurrencyId($journals);
}

View File

@ -328,7 +328,7 @@ class OperationsRepository implements OperationsRepositoryInterface
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if (null !== $accounts && $accounts->count() > 0) {
@ -339,8 +339,9 @@ class OperationsRepository implements OperationsRepositoryInterface
}
$collector->setCategories($categories);
$collector->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
$summarizer = new TransactionSummarizer($this->user);
return $summarizer->groupByCurrencyId($journals);
}

View File

@ -85,16 +85,16 @@ class Amount
*/
public function getAmountFromJournalObject(TransactionJournal $journal): string
{
$convertToNative = $this->convertToNative();
$currency = $this->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
$convertToNative = $this->convertToNative();
$currency = $this->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $sourceTransaction) {
return '0';
}
$amount = $sourceTransaction->{$field} ?? '0';
$amount = $sourceTransaction->{$field} ?? '0';
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
@ -113,15 +113,15 @@ class Amount
*/
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
{
$locale = app('steam')->getLocale();
$rounded = app('steam')->bcround($amount, $decimalPlaces);
$locale = app('steam')->getLocale();
$rounded = app('steam')->bcround($amount, $decimalPlaces);
$coloured ??= true;
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$fmt->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $symbol);
$fmt->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
$fmt->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
$result = (string) $fmt->format((float) $rounded); // intentional float
$result = (string) $fmt->format((float) $rounded); // intentional float
if (true === $coloured) {
if (1 === bccomp($rounded, '0')) {
@ -166,12 +166,13 @@ class Amount
return $this->getDefaultCurrencyByUserGroup($user->userGroup);
}
}
return $this->getSystemCurrency();
}
public function getDefaultCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
{
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty('getDefaultCurrencyByGroup');
$cache->addProperty($userGroup->id);
if ($cache->has()) {
@ -234,20 +235,20 @@ class Amount
private function getLocaleInfo(): array
{
// get config from preference, not from translation:
$locale = app('steam')->getLocale();
$array = app('steam')->getLocaleArray($locale);
$locale = app('steam')->getLocale();
$array = app('steam')->getLocaleArray($locale);
setlocale(LC_MONETARY, $array);
$info = localeconv();
$info = localeconv();
// correct variables
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
$info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
@ -270,7 +271,7 @@ class Amount
public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string
{
// negative first:
$space = ' ';
$space = ' ';
// require space between symbol and amount?
if (false === $sepBySpace) {
@ -279,11 +280,11 @@ class Amount
// there are five possible positions for the "+" or "-" sign (if it is even used)
// pos_a and pos_e could be the ( and ) symbol.
$posA = ''; // before everything
$posB = ''; // before currency symbol
$posC = ''; // after currency symbol
$posD = ''; // before amount
$posE = ''; // after everything
$posA = ''; // before everything
$posB = ''; // before currency symbol
$posC = ''; // after currency symbol
$posD = ''; // before amount
$posE = ''; // after everything
// format would be (currency before amount)
// AB%sC_D%vE
@ -325,11 +326,11 @@ class Amount
}
// default is amount before currency
$format = $posA . $posD . '%v' . $space . $posB . '%s' . $posC . $posE;
$format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE;
if ($csPrecedes) {
// alternative is currency before amount
$format = $posA . $posB . '%s' . $posC . $space . $posD . '%v' . $posE;
$format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE;
}
return $format;

View File

@ -507,12 +507,12 @@ class Navigation
$diff = $start->diffInMonths($end, true);
Log::debug(sprintf('preferredCarbonFormat(%s, %s) = %f', $start->format('Y-m-d'), $end->format('Y-m-d'), $diff));
if ($diff >= 1.001) {
// Log::debug(sprintf('Return Y-m because %s', $diff));
// Log::debug(sprintf('Return Y-m because %s', $diff));
$format = 'Y-m';
}
if ($diff >= 12.001) {
// Log::debug(sprintf('Return Y because %s', $diff));
// Log::debug(sprintf('Return Y because %s', $diff));
$format = 'Y';
}

View File

@ -1,4 +1,5 @@
<?php
/*
* TransactionSummarizer.php
* Copyright (c) 2024 james@firefly-iii.org.
@ -52,14 +53,14 @@ class TransactionSummarizer
{
$array = [];
foreach ($journals as $journal) {
$field = 'amount';
$field = 'amount';
// grab default currency information.
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($this->convertToNative) {
// if convert to native, use the native amount yes or no?
$useNative = $this->default->id !== (int) $journal['currency_id'];
@ -83,11 +84,11 @@ class TransactionSummarizer
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
}
}
if(!$this->convertToNative) {
if (!$this->convertToNative) {
// default to the normal amount, but also
}
$amount = (string) ($journal[$field] ?? '0');
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $currencyName,
@ -95,14 +96,16 @@ class TransactionSummarizer
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->$method($amount));
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->{$method}($amount));
Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
}
Log::debug('End of sumExpenses.', $array);
return $array;
}
public function groupByDirection(array $journals, string $method, string $direction): array {
public function groupByDirection(array $journals, string $method, string $direction): array
{
$array = [];
$idKey = sprintf('%s_account_id', $direction);
@ -136,7 +139,7 @@ class TransactionSummarizer
if ($convertToNative && $journal['currency_id'] !== $default->id && $default->id === $journal['foreign_currency_id']) {
$field = 'foreign_amount';
}
$key = sprintf('%s-%s', $journal[$idKey], $currencyId);
$key = sprintf('%s-%s', $journal[$idKey], $currencyId);
// sum it all up or create a new array.
$array[$key] ??= [
'id' => $journal[$idKey],
@ -150,7 +153,7 @@ class TransactionSummarizer
];
// add the data from the $field to the array.
$array[$key]['sum'] = bcadd($array[$key]['sum'], app('steam')->{$method}((string) ($journal[$field] ?? '0'))); // @phpstan-ignore-line
$array[$key]['sum'] = bcadd($array[$key]['sum'], app('steam')->{$method}((string) ($journal[$field] ?? '0'))); // @phpstan-ignore-line
Log::debug(sprintf('Field for transaction #%d is "%s" (%s). Sum: %s', $journal['transaction_group_id'], $currencyCode, $field, $array[$key]['sum']));
// also do foreign amount, but only when convertToNative is false (otherwise we have it already)
@ -158,7 +161,7 @@ class TransactionSummarizer
if ((!$convertToNative || $journal['foreign_currency_id'] !== $default->id) && 0 !== (int) $journal['foreign_currency_id']) {
Log::debug(sprintf('Use foreign amount from transaction #%d: %s %s. Sum: %s', $journal['transaction_group_id'], $currencyCode, $journal['foreign_amount'], $array[$key]['sum']));
$key = sprintf('%s-%s', $journal[$idKey], $journal['foreign_currency_id']);
$array[$key] ??= [
$array[$key] ??= [
'id' => $journal[$idKey],
'name' => $journal[$nameKey],
'sum' => '0',
@ -174,5 +177,4 @@ class TransactionSummarizer
return $array;
}
}

View File

@ -58,7 +58,7 @@ class AccountTransformer extends AbstractTransformer
// get account type:
$fullType = $account->accountType->type;
$accountType = (string) config(sprintf( 'firefly.shortNamesByFullName.%s', $fullType));
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $fullType));
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $fullType));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $this->repository->getMetaValue($account, 'liability_direction');

View File

@ -16,9 +16,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [Issue 7945](https://github.com/firefly-iii/firefly-iii/issues/7945) ("Rules" that only trigger manually) reported by @SekoiaTree
- [Issue 6760](https://github.com/firefly-iii/firefly-iii/issues/6760) (Add a new trigger for automated rules) reported by @Gsyltc
- [Issue 6557](https://github.com/firefly-iii/firefly-iii/issues/6557) (Piggy Banks - Draw Funds from Multiple Accounts) reported by @BugPhobic
- #5532
- #6314
- #9586
- [Issue 5532](https://github.com/firefly-iii/firefly-iii/issues/5532) (Asset prices and exchange rates) reported by @svozniuk
- [Issue 6314](https://github.com/firefly-iii/firefly-iii/issues/6314) (Currencies and exchange rates) reported by @JC5
- [Issue 9586](https://github.com/firefly-iii/firefly-iii/issues/9586) (Non en_US translated string in sign-up mail) reported by @benni347
### Changed

26
composer.lock generated
View File

@ -2614,16 +2614,16 @@
},
{
"name": "league/commonmark",
"version": "2.6.0",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "d150f911e0079e90ae3c106734c93137c184f932"
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932",
"reference": "d150f911e0079e90ae3c106734c93137c184f932",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
"shasum": ""
},
"require": {
@ -2717,7 +2717,7 @@
"type": "tidelift"
}
],
"time": "2024-12-07T15:34:16+00:00"
"time": "2024-12-29T14:10:59+00:00"
},
{
"name": "league/config",
@ -10193,20 +10193,20 @@
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v3.3.0",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44"
"reference": "2a41415f01bf3c409d200f6cdd940c1e7d86cfd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b7675670f75914bf34afdea52a6c2fe3781f7c44",
"reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/2a41415f01bf3c409d200f6cdd940c1e7d86cfd3",
"reference": "2a41415f01bf3c409d200f6cdd940c1e7d86cfd3",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.1.2",
"barryvdh/reflection-docblock": "^2.2",
"composer/class-map-generator": "^1.0",
"ext-json": "*",
"illuminate/console": "^11.15",
@ -10239,7 +10239,7 @@
]
},
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "3.4-dev"
}
},
"autoload": {
@ -10271,7 +10271,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.3.0"
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.4.0"
},
"funding": [
{
@ -10283,7 +10283,7 @@
"type": "github"
}
],
"time": "2024-12-18T08:24:19+00:00"
"time": "2024-12-29T12:10:58+00:00"
},
{
"name": "barryvdh/reflection-docblock",

View File

@ -81,7 +81,7 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-12-28',
'version' => 'develop/2024-12-30',
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25,

View File

@ -133,7 +133,7 @@
"header_exchange_rates": "Wechselkurse",
"exchange_rates_intro": "Firefly III unterst\u00fctzt das Herunterladen und Verwenden von Wechselkursen. Lesen Sie mehr dar\u00fcber in <a href=\u201ehttps:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\u201c>der Dokumentation<\/a>.",
"exchange_rates_from_to": "Zwischen {from} und {to} (und umgekehrt)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
"exchange_rates_intro_rates": "Firefly III verwendet die folgenden Wechselkurse. Der Kehrwert wird automatisch berechnet, wenn er nicht angegeben wurde. Wenn f\u00fcr das Datum der Transaktion kein Wechselkurs vorhanden ist, sucht Firefly III in der Vergangenheit nach einem Kurs. Wenn keine vorhanden sind, wird der Kurs \u201e1\u201c verwendet.",
"header_exchange_rates_rates": "Wechselkurse",
"header_exchange_rates_table": "Tabelle mit Wechselkursen",
"help_rate_form": "An diesem Tag, wie viele {to} werden Sie f\u00fcr {from} bekommen?",

View File

@ -36,7 +36,7 @@
"is_reconciled_fields_dropped": "Omdat deze transactie al is afgestemd, kan je het bedrag noch de rekeningen wijzigen.",
"tags": "Tags",
"no_budget": "(geen budget)",
"no_bill": "(no subscription)",
"no_bill": "(geen abonnement)",
"category": "Categorie",
"attachments": "Bijlagen",
"notes": "Notities",
@ -52,7 +52,7 @@
"destination_account_reconciliation": "Je kan de doelrekening van een afstemming niet wijzigen.",
"source_account_reconciliation": "Je kan de bronrekening van een afstemming niet wijzigen.",
"budget": "Budget",
"bill": "Subscription",
"bill": "Abonnement",
"you_create_withdrawal": "Je maakt een uitgave.",
"you_create_transfer": "Je maakt een overschrijving.",
"you_create_deposit": "Je maakt inkomsten.",
@ -133,7 +133,7 @@
"header_exchange_rates": "Wisselkoersen",
"exchange_rates_intro": "Firefly III kan wisselkoersen downloaden en gebruiken. Lees hier meer over in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">de documentatie<\/a>.",
"exchange_rates_from_to": "Tussen {from} en {to} (en andersom)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
"exchange_rates_intro_rates": "Firefly III gebruikt de volgende wisselkoersen. De inverse berekent zichzelf als deze niet is opgegeven. Als er geen wisselkoers bestaat voor de datum van de transactie, gaat Firefly III terug in de tijd om er een te vinden. Als er geen aanwezig is, zal de koers \"1\" gebruikt worden.",
"header_exchange_rates_rates": "Wisselkoersen",
"header_exchange_rates_table": "Tabel met wisselkoersen",
"help_rate_form": "Hoeveel {to} krijg je op deze dag voor \u00e9\u00e9n {from}?",

View File

@ -48,13 +48,14 @@ final class BillControllerTest extends TestCase
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
return User::create([
$user= User::create([
'email' => 'test@email.com',
'password' => 'password',
'user_group_id' => $userGroup->id,
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestBills(int $count, User $user): void
{
for ($i = 1; $i <= $count; ++$i) {

View File

@ -48,11 +48,13 @@ final class BudgetControllerTest extends TestCase
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
return User::create([
$user= User::create([
'email' => 'test@email.com',
'password' => 'password',
'user_group_id' => $userGroup->id,
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestBudgets(int $count, User $user): void

View File

@ -28,6 +28,7 @@ use FireflyIII\Models\Category;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class CategoryControllerTest
@ -45,10 +46,15 @@ final class CategoryControllerTest extends TestCase
protected function createAuthenticatedUser(): User
{
return User::create([
'email' => 'test@email.com',
'password' => 'password',
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user= User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestCategories(int $count, User $user): void

View File

@ -0,0 +1,186 @@
<?php
/*
* CurrencyControllerTest.php
* Copyright (c) 2024 tasnim0tantawi
*
* 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\Autocomplete;
use FireflyIII\Models\TransactionCurrency;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class CurrencyControllerTest
*
* @internal
*
* @coversNothing
*/
final class CurrencyControllerTest extends TestCase {
/**
* @covers \FireflyIII\Api\V1\Controllers\Autocomplete\CurrencyController
*/
use RefreshDatabase;
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user= User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestCurrencies(int $count, bool $enabled): void
{
for ($i = 1; $i <= $count; ++$i) {
$currency = TransactionCurrency::create([
'name' => 'Currency '.$i,
'code' => 'CUR'.$i,
'symbol' => 'C'.$i,
'decimal_places' => $i,
'enabled' => $enabled
]);
}
}
public function testGivenAnUnauthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturns401HttpCode(): void
{
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(401);
$response->assertHeader('Content-Type', 'application/json');
$response->assertContent('{"message":"Unauthenticated","exception":"AuthenticationException"}');
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturns200HttpCode(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturnsACollectionOfEnabledCurrencies(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(10, true);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonFragment(['name' => 'Currency 1']);
$response->assertJsonFragment(['code' => 'CUR1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'code',
'symbol',
'decimal_places',
],
]);
$response->assertJsonCount(10);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointDoesNotReturnDisabledCurrencies(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(10, false);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(0);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointWithQueryThenReturnsCurrenciesWithLimit(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(5, true);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies', ['query' => 'Currency 1']), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonFragment(['name' => 'Currency 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'code',
'symbol',
'decimal_places',
],
]);
$response->assertJsonCount(1);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointWithQueryThenReturnsCurrenciesThatMatchQuery(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestCurrencies(20, true);
$response = $this->get(route('api.v1.autocomplete.currencies', [
'query' => 'Currency 1',
'limit' => 20,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
// Currency 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11)
$response->assertJsonCount(11);
}
}

View File

@ -0,0 +1,157 @@
<?php
/*
* ObjectGroupControllerTest.php
* Copyright (c) 2024 tasnim0tantawi
*
* 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\Autocomplete;
use FireflyIII\Models\ObjectGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class ObjectGroupControllerTest
*
* @internal
*
* @coversNothing
*/
final class ObjectGroupControllerTest extends TestCase {
/**
* @covers \FireflyIII\Api\V1\Controllers\Autocomplete\ObjectGroupController
*/
use RefreshDatabase;
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user= User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestObjectGroups(int $count, User $user): void
{
for ($i = 1; $i <= $count; ++$i) {
$objectGroup = ObjectGroup::create([
'title' => 'Object Group '.$i,
'order' => $i,
'user_group_id' => $user->user_group_id,
'user_id' => $user->id,
]);
}
}
public function testGivenAnUnauthenticatedRequestWhenCallingTheObjectGroupEndpointThenReturn401HttpCode(): void
{
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(401);
$response->assertHeader('Content-Type', 'application/json');
$response->assertContent('{"message":"Unauthenticated","exception":"AuthenticationException"}');
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointThenReturns200HttpCode(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// test API
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointThenReturnsObjectGroups(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(5, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(5);
$response->assertJsonFragment(['title' => 'Object Group 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'title'
],
]);
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointWithQueryThenReturnsObjectGroupsWithLimit(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(5, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups', [
'query' => 'Object Group',
'limit' => 3,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(3);
$response->assertJsonFragment(['name' => 'Object Group 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'title',
],
]);
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointWithQueryThenReturnsObjectGroupsThatMatchQuery(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(20, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups', [
'query' => 'Object Group 1',
'limit' => 20,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
// Object Group 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11)
$response->assertJsonCount(11);
$response->assertJsonMissing(['name' => 'Object Group 2']);
}
}

View File

@ -54,9 +54,10 @@ abstract class TestCase extends BaseTestCase
protected function createAuthenticatedUser(): User
{
$group = UserGroup::create(['title' => 'test@email.com']);
return User::create([
'email' => 'test@email.com',
'password' => 'password',
'email' => 'test@email.com',
'password' => 'password',
'user_group_id' => $group->id,
]);
}