Merge pull request #10689 from firefly-iii/release-1754232349

🤖 Automatically merge the PR into the develop branch.
This commit is contained in:
github-actions[bot]
2025-08-03 16:45:56 +02:00
committed by GitHub
19 changed files with 402 additions and 390 deletions

View File

@@ -72,12 +72,12 @@ class ShowController extends Controller
*/
public function index(): JsonResponse
{
$manager = $this->getManager();
$manager = $this->getManager();
// types to get, page size:
$pageSize = $this->parameters->get('limit');
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
$pageSize = $this->parameters->get('limit');
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
// get list of available budgets. Count it and split it.
$collection = $this->abRepository->getAvailableBudgetsByDate($start, $end);
@@ -86,20 +86,20 @@ class ShowController extends Controller
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$enrichment->setUser($admin);
$availableBudgets = $enrichment->enrich($availableBudgets);
// make paginator:
$paginator = new LengthAwarePaginator($availableBudgets, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.available-budgets.index') . $this->buildParams());
$paginator = new LengthAwarePaginator($availableBudgets, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.available-budgets.index').$this->buildParams());
/** @var AvailableBudgetTransformer $transformer */
$transformer = app(AvailableBudgetTransformer::class);
$transformer = app(AvailableBudgetTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($availableBudgets, $transformer, 'available_budgets');
$resource = new FractalCollection($availableBudgets, $transformer, 'available_budgets');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
@@ -113,25 +113,25 @@ class ShowController extends Controller
*/
public function show(AvailableBudget $availableBudget): JsonResponse
{
$manager = $this->getManager();
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
$manager = $this->getManager();
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
/** @var AvailableBudgetTransformer $transformer */
$transformer = app(AvailableBudgetTransformer::class);
$transformer = app(AvailableBudgetTransformer::class);
$transformer->setParameters($this->parameters);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$admin = auth()->user();
$enrichment = new AvailableBudgetEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$availableBudget = $enrichment->enrichSingle($availableBudget);
$resource = new Item($availableBudget, $transformer, 'available_budgets');
$resource = new Item($availableBudget, $transformer, 'available_budgets');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\PiggyBank;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidZeroOrMoreAmount;
@@ -97,7 +96,7 @@ class StoreRequest extends FormRequest
// validate start before end only if both are there.
$data = $validator->getData();
$currency = $this->getCurrencyFromData($validator, $data);
if(null === $currency) {
if (null === $currency) {
return;
}
$targetAmount = (string) ($data['target_amount'] ?? '0');
@@ -148,6 +147,7 @@ class StoreRequest extends FormRequest
}
}
$validator->errors()->add('transaction_currency_id', trans('validation.require_currency_id_code'));
return null;
}
}

View File

@@ -32,7 +32,6 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Webhook;
use FireflyIII\Models\WebhookMessage;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer;

View File

@@ -151,6 +151,7 @@ class ShowController extends Controller
$enrichment->setUser($admin);
$enrichment->setStart($start);
$enrichment->setEnd($end);
/** @var Bill $bill */
$bill = $enrichment->enrichSingle($bill);

View File

@@ -32,7 +32,7 @@ use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
/**
* Class NoBudgetRepository
@@ -100,10 +100,11 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
return $summarizer->groupByCurrencyId($journals);
}
#[\Override] public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
#[Override]
public function collectExpenses(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([TransactionTypeEnum::WITHDRAWAL->value]);
if ($accounts instanceof Collection && $accounts->count() > 0) {
@@ -114,7 +115,7 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
}
$collector->withoutBudget();
$collector->withBudgetInformation();
return $collector->getExtractedJournals();
}
}

View File

@@ -39,6 +39,7 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
/**
* Class OperationsRepository
@@ -65,7 +66,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
++$count;
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
}
$avg = $total;
$avg = $total;
if ($count > 0) {
$avg = bcdiv($total, (string) $count);
}
@@ -86,21 +87,21 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// get all transactions:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// loop transactions:
/** @var array $journal */
foreach ($journals as $journal) {
// prep data array for currency:
$budgetId = (int) $journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$budgetId = (int) $journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$data[$key] ??= [
$data[$key] ??= [
'id' => $budgetId,
'name' => sprintf('%s (%s)', $budgetName, $journal['currency_name']),
'sum' => '0',
@@ -126,7 +127,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = 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 ($accounts instanceof Collection && $accounts->count() > 0) {
$collector->setAccounts($accounts);
@@ -138,8 +139,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setBudgets($this->getBudgets());
}
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
// if needs conversion to primary.
$convertToPrimary = Amount::convertToPrimary($this->user);
@@ -155,8 +156,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
];
foreach ($journals as $journal) {
$amount = app('steam')->negative($journal['amount']);
$journalCurrencyId = (int) $journal['currency_id'];
$amount = app('steam')->negative($journal['amount']);
$journalCurrencyId = (int) $journal['currency_id'];
if (false === $convertToPrimary) {
$currencyId = $journalCurrencyId;
$currencyName = $journal['currency_name'];
@@ -166,11 +167,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
if (true === $convertToPrimary && $journalCurrencyId !== $currencyId) {
$currencies[$journalCurrencyId] ??= TransactionCurrency::find($journalCurrencyId);
$amount = $converter->convert($currencies[$journalCurrencyId], $primaryCurrency, $journal['date'], $amount);
$amount = $converter->convert($currencies[$journalCurrencyId], $primaryCurrency, $journal['date'], $amount);
}
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
// catch "no budget" entries.
if (0 === $budgetId) {
@@ -178,7 +179,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'budgets' => [],
'currency_id' => $currencyId,
'currency_name' => $currencyName,
@@ -231,8 +232,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $budgets = null,
?TransactionCurrency $currency = null,
bool $convertToPrimary = false
): array
{
): array {
Log::debug(sprintf('Start of %s(date, date, array, array, "%s", %s).', __METHOD__, $currency?->code, var_export($convertToPrimary, true)));
// this collector excludes all transfers TO liabilities (which are also withdrawals)
// because those expenses only become expenses once they move from the liability to the friend.
@@ -240,8 +240,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$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) {
@@ -251,11 +251,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->setRange($start, $end)
// ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
if ($accounts instanceof Collection) {
$collector->setAccounts($accounts);
@@ -270,7 +271,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if ($budgets->count() > 0) {
$collector->setBudgets($budgets);
}
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for transactions in the foreign currency:
if ($currency instanceof TransactionCurrency) {
@@ -290,14 +291,15 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$summarizer->setConvertToPrimary($convertToPrimary);
// filter $journals by range.
$expenses = array_filter($expenses, static function (array $expense) use ($start, $end, $transactionCurrency): bool {
$expenses = array_filter($expenses, static function (array $expense) use ($start, $end, $transactionCurrency): bool {
return $expense['date']->between($start, $end) && $expense['currency_id'] === $transactionCurrency->id;
});
return $summarizer->groupByCurrencyId($expenses, 'negative', false);
}
#[\Override] public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null): array
#[Override]
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('Start of %s(date, date, array, array, "%s").', __METHOD__, $currency?->code));
// this collector excludes all transfers TO liabilities (which are also withdrawals)
@@ -306,8 +308,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$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) {
@@ -317,11 +319,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->setRange($start, $end)
// ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
if ($accounts instanceof Collection) {
$collector->setAccounts($accounts);
@@ -336,6 +339,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if ($budgets->count() > 0) {
$collector->setBudgets($budgets);
}
return $collector->getExtractedJournals();
}
}

View File

@@ -142,7 +142,7 @@ class Amount
$cache->addProperty('getPrimaryCurrencyByGroup');
$cache->addProperty($userGroup->id);
if ($cache->has()) {
return $cache->get();
return $cache->get();
}
/** @var null|TransactionCurrency $primary */

View File

@@ -86,7 +86,7 @@ class AccountEnrichment implements EnrichmentInterface
}
#[Override]
public function enrichSingle(array | Model $model): Account | array
public function enrichSingle(array|Model $model): Account|array
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
@@ -141,9 +141,10 @@ class AccountEnrichment implements EnrichmentInterface
private function collectMetaData(): void
{
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
->whereIn('account_id', $this->accountIds)
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray();
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
->whereIn('account_id', $this->accountIds)
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
;
/** @var array $entry */
foreach ($set as $entry) {
@@ -152,7 +153,7 @@ class AccountEnrichment implements EnrichmentInterface
$this->currencies[(int) $entry['data']] = true;
}
}
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
foreach ($currencies as $currency) {
$this->currencies[(int) $currency->id] = $currency;
}
@@ -167,9 +168,10 @@ class AccountEnrichment implements EnrichmentInterface
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->accountIds)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
@@ -179,14 +181,15 @@ class AccountEnrichment implements EnrichmentInterface
private function collectLocations(): void
{
$locations = Location::query()->whereIn('locatable_id', $this->accountIds)
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray();
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
;
foreach ($locations as $location) {
$this->locations[(int) $location['locatable_id']]
= [
'latitude' => (float) $location['latitude'],
'longitude' => (float) $location['longitude'],
'zoom_level' => (int) $location['zoom_level'],
];
'latitude' => (float) $location['latitude'],
'longitude' => (float) $location['longitude'],
'zoom_level' => (int) $location['zoom_level'],
];
}
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
}
@@ -201,19 +204,20 @@ class AccountEnrichment implements EnrichmentInterface
->setUserGroup($this->userGroup)
->setAccounts($this->collection)
->withAccountInformation()
->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]);
$journals = $collector->getExtractedJournals();
->setTypes([TransactionTypeEnum::OPENING_BALANCE->value])
;
$journals = $collector->getExtractedJournals();
foreach ($journals as $journal) {
$this->openingBalances[(int) $journal['source_account_id']]
= [
'amount' => Steam::negative($journal['amount']),
'date' => $journal['date'],
];
'amount' => Steam::negative($journal['amount']),
'date' => $journal['date'],
];
$this->openingBalances[(int) $journal['destination_account_id']]
= [
'amount' => Steam::positive($journal['amount']),
'date' => $journal['date'],
];
'amount' => Steam::positive($journal['amount']),
'date' => $journal['date'],
];
}
}
@@ -268,27 +272,27 @@ class AccountEnrichment implements EnrichmentInterface
// add balances
// get currencies:
$currency = $this->primaryCurrency; // assume primary currency
$currency = $this->primaryCurrency; // assume primary currency
if (null !== $accountMeta['currency']) {
$currency = $accountMeta['currency'];
}
// get the current balance:
$date = $this->getDate();
$finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
$date = $this->getDate();
$finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
// collect current balances:
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
$openingBalance = Steam::bcround($accountMeta['opening_balance_amount'] ?? '0', $currency->decimal_places);
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
$debtAmount = $accountMeta['current_debt'] ?? null;
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
$openingBalance = Steam::bcround($accountMeta['opening_balance_amount'] ?? '0', $currency->decimal_places);
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
$debtAmount = $accountMeta['current_debt'] ?? null;
// set some pc_ default values to NULL:
$pcCurrentBalance = null;
$pcOpeningBalance = null;
$pcVirtualBalance = null;
$pcDebtAmount = null;
$pcCurrentBalance = null;
$pcOpeningBalance = null;
$pcVirtualBalance = null;
$pcDebtAmount = null;
// convert to primary currency if needed:
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
@@ -331,7 +335,7 @@ class AccountEnrichment implements EnrichmentInterface
if (array_key_exists($item->id, $lastActivities)) {
$accountMeta['last_activity'] = $lastActivities[$item->id];
}
$item->meta = $accountMeta;
$item->meta = $accountMeta;
return $item;
});
@@ -354,8 +358,7 @@ class AccountEnrichment implements EnrichmentInterface
if (null === $this->date) {
return today();
}
return $this->date;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* AvailableBudgetEnrichment.php
* Copyright (c) 2025 james@firefly-iii.org.
@@ -35,6 +36,7 @@ use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
class AvailableBudgetEnrichment implements EnrichmentInterface
{
@@ -53,8 +55,8 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
private readonly BudgetRepositoryInterface $repository;
private ?Carbon $start = null;
private ?Carbon $end = null;
private ?Carbon $start = null;
private ?Carbon $end = null;
public function __construct()
{
@@ -65,7 +67,8 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
$this->repository = app(BudgetRepositoryInterface::class);
}
#[\Override] public function enrich(Collection $collection): Collection
#[Override]
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->collectIds();
@@ -75,7 +78,8 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
return $this->collection;
}
#[\Override] public function enrichSingle(Model | array $model): array | Model
#[Override]
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
@@ -84,13 +88,15 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
return $collection->first();
}
#[\Override] public function setUser(User $user): void
#[Override]
public function setUser(User $user): void
{
$this->user = $user;
$this->setUserGroup($user->userGroup);
}
#[\Override] public function setUserGroup(UserGroup $userGroup): void
#[Override]
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
$this->noBudgetRepository->setUserGroup($userGroup);
@@ -115,9 +121,9 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
$spentInBudgets = $this->opsRepository->collectExpenses($start, $end, null, $allActive, null);
$spentOutsideBudgets = $this->noBudgetRepository->collectExpenses($start, $end, null, null, null);
foreach ($this->collection as $availableBudget) {
$id = (int) $availableBudget->id;
$filteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
$filteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
$id = (int) $availableBudget->id;
$filteredSpentInBudgets = $this->opsRepository->sumCollectedExpenses($spentInBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
$filteredSpentOutsideBudgets = $this->opsRepository->sumCollectedExpenses($spentOutsideBudgets, $availableBudget->start_date, $availableBudget->end_date, $availableBudget->transactionCurrency, false);
$this->spentInBudgets[$id] = array_values($filteredSpentInBudgets);
$this->spentOutsideBudgets[$id] = array_values($filteredSpentOutsideBudgets);
@@ -153,9 +159,8 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
'pc_spent_outside_budgets' => $pcSpentOutsideBudgets[$id] ?? [],
];
$item->meta = $meta;
return $item;
});
}
}

View File

@@ -61,11 +61,11 @@ class SubscriptionEnrichment implements EnrichmentInterface
$paidDates = $this->paidDates;
$payDates = $this->payDates;
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups, $paidDates, $payDates) {
$id = (int) $item->id;
$currency = $item->transactionCurrency;
$nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
$id = (int) $item->id;
$currency = $item->transactionCurrency;
$nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
$meta = [
$meta = [
'notes' => null,
'object_group_id' => null,
'object_group_title' => null,
@@ -76,7 +76,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
'nem' => $nem,
'nem_diff' => $this->getNextExpectedMatchDiff($nem, $payDates[$id] ?? []),
];
$amounts = [
$amounts = [
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
@@ -117,7 +117,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
return $collection;
}
public function enrichSingle(array | Model $model): array | Model
public function enrichSingle(array|Model $model): array|Model
{
Log::debug(__METHOD__);
$collection = new Collection([$model]);
@@ -129,9 +129,10 @@ class SubscriptionEnrichment implements EnrichmentInterface
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
->whereNotNull('notes.text')
->where('notes.text', '!=', '')
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
;
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
@@ -160,12 +161,13 @@ class SubscriptionEnrichment implements EnrichmentInterface
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->subscriptionIds)
->where('object_groupable_type', Bill::class)
->get(['object_groupable_id', 'object_group_id']);
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->subscriptionIds)
->where('object_groupable_type', Bill::class)
->get(['object_groupable_id', 'object_group_id'])
;
$ids = array_unique($set->pluck('object_group_id')->toArray());
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int) $entry->object_groupable_id] = (int) $entry->object_group_id;
@@ -193,13 +195,13 @@ class SubscriptionEnrichment implements EnrichmentInterface
// 2023-07-18 this particular date is used to search for the last paid date.
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
/** @var Carbon $start */
$start = clone $this->start;
$searchStart = clone $start;
$start = clone $this->start;
$searchStart = clone $start;
$start->subDay();
/** @var Carbon $end */
$end = clone $this->end;
$searchEnd = clone $end;
$end = clone $this->end;
$searchEnd = clone $end;
// move the search dates to the start of the day.
$searchStart->startOfDay();
@@ -208,13 +210,13 @@ class SubscriptionEnrichment implements EnrichmentInterface
Log::debug(sprintf('Search parameters are: start: %s, end: %s', $searchStart->format('Y-m-d H:i:s'), $searchEnd->format('Y-m-d H:i:s')));
// Get from database when bills were paid.
$set = $this->user->transactionJournals()
->whereIn('bill_id', $this->subscriptionIds)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
->where('transactions.amount', '>', 0)
->before($searchEnd)->after($searchStart)->get(
$set = $this->user->transactionJournals()
->whereIn('bill_id', $this->subscriptionIds)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
->where('transactions.amount', '>', 0)
->before($searchEnd)->after($searchStart)->get(
[
'transaction_journals.id',
'transaction_journals.date',
@@ -231,43 +233,44 @@ class SubscriptionEnrichment implements EnrichmentInterface
'transactions.amount',
'transactions.foreign_amount',
]
);
)
;
Log::debug(sprintf('Count %d entries in set', $set->count()));
// for each bill, do a loop.
$converter = new ExchangeRateConverter();
$converter = new ExchangeRateConverter();
/** @var Bill $subscription */
foreach ($this->collection as $subscription) {
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
$lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
$lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
// At this point the "next match" is exactly after the last time the bill was paid.
$result = [];
$filtered = $set->filter(function (TransactionJournal $journal) use ($subscription) {
$result = [];
$filtered = $set->filter(function (TransactionJournal $journal) use ($subscription) {
return (int) $journal->bill_id === (int) $subscription->id;
});
foreach ($filtered as $entry) {
$array = [
'transaction_group_id' => (string) $entry->transaction_group_id,
'transaction_journal_id' => (string) $entry->id,
'date' => $entry->date->toAtomString(),
'date_object' => $entry->date,
$array = [
'transaction_group_id' => (string) $entry->transaction_group_id,
'transaction_journal_id' => (string) $entry->id,
'date' => $entry->date->toAtomString(),
'date_object' => $entry->date,
'subscription_id' => (string) $entry->bill_id,
'currency_id' => (string) $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_symbol' => $entry->transaction_currency_symbol,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
'pc_amount' => null,
'foreign_amount' => null,
'pc_foreign_amount' => null,
'currency_id' => (string) $entry->transaction_currency_id,
'currency_code' => $entry->transaction_currency_code,
'currency_symbol' => $entry->transaction_currency_symbol,
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
'primary_currency_decimal_places' => $this->primaryCurrency->decimal_places,
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
'pc_amount' => null,
'foreign_amount' => null,
'pc_foreign_amount' => null,
];
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
@@ -292,7 +295,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
// convert to primary, but foreign is NOT already primary.
if ($this->convertToPrimary && null !== $entry->foreign_currency_id && (int) $entry->foreign_currency_id !== $this->primaryCurrency->id) {
// TODO this is very database intensive.
$foreignCurrency = TransactionCurrency::find($entry->foreign_currency_id);
$foreignCurrency = TransactionCurrency::find($entry->foreign_currency_id);
$array['pc_foreign_amount'] = $converter->convert($foreignCurrency, $this->primaryCurrency, $entry->date, $entry->amount);
}
$result[] = $array;
@@ -325,7 +328,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
return $default;
}
$latest = $filtered->first()->date;
$latest = $filtered->first()->date;
/** @var TransactionJournal $journal */
foreach ($filtered as $journal) {
@@ -371,12 +374,12 @@ class SubscriptionEnrichment implements EnrichmentInterface
/** @var Bill $subscription */
foreach ($this->collection as $subscription) {
$id = (int) $subscription->id;
$lastPaidDate = $this->getLastPaidDate($paidDates[$id] ?? []);
$payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
$payDatesFormatted = [];
$id = (int) $subscription->id;
$lastPaidDate = $this->getLastPaidDate($paidDates[$id] ?? []);
$payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
$payDatesFormatted = [];
foreach ($payDates as $string) {
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
if (!$date instanceof Carbon) {
$date = today(config('app.timezone'));
}
@@ -406,7 +409,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
if (!$nemDate instanceof Carbon) {
$nemDate = today(config('app.timezone'));
}
$nem = $nemDate;
$nem = $nemDate;
// nullify again when it's outside the current view range.
if (
@@ -435,7 +438,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
$current = $payDates[0] ?? null;
if (null !== $current && !$nem->isToday()) {
$temp2 = Carbon::parse($current, config('app.timezone'));
$temp2 = Carbon::parse($current, config('app.timezone'));
if (!$temp2 instanceof Carbon) {
$temp2 = today(config('app.timezone'));
}

View File

@@ -66,24 +66,24 @@ class AccountTransformer extends AbstractTransformer
}
// get account type:
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $account->meta['liability_direction'] ?? null;
$accountRole = $this->getAccountRole($account, $accountType);
$hasCurrencySettings = null !== $account->meta['currency'];
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
$longitude = $account->meta['location']['longitude'] ?? null;
$latitude = $account->meta['location']['latitude'] ?? null;
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
$order = $account->order;
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
$liabilityDirection = $account->meta['liability_direction'] ?? null;
$accountRole = $this->getAccountRole($account, $accountType);
$hasCurrencySettings = null !== $account->meta['currency'];
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
$longitude = $account->meta['location']['longitude'] ?? null;
$latitude = $account->meta['location']['latitude'] ?? null;
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
$order = $account->order;
// date (for balance etc.)
$date = $this->getDate();
$date = $this->getDate();
$date->endOfDay();
// get primary currency as fallback:
$currency = $this->primary; // assume primary currency
$currency = $this->primary; // assume primary currency
if ($hasCurrencySettings) {
$currency = $account->meta['currency'];
}
@@ -95,8 +95,8 @@ class AccountTransformer extends AbstractTransformer
// get some listed information from the account meta-data:
[$creditCardType, $monthlyPaymentDate] = $this->getCCInfo($account, $accountRole, $accountType);
$openingBalanceDate = $this->getOpeningBalance($account, $accountType);
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
$openingBalanceDate = $this->getOpeningBalance($account, $accountType);
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
return [
'id' => (string) $account->id,
@@ -125,33 +125,33 @@ class AccountTransformer extends AbstractTransformer
'current_balance' => $account->meta['balances']['current_balance'],
'pc_current_balance' => $account->meta['balances']['pc_current_balance'],
'opening_balance' => $account->meta['balances']['opening_balance'],
'pc_opening_balance' => $account->meta['balances']['pc_opening_balance'],
'opening_balance' => $account->meta['balances']['opening_balance'],
'pc_opening_balance' => $account->meta['balances']['pc_opening_balance'],
'virtual_balance' => $account->meta['balances']['virtual_balance'],
'pc_virtual_balance' => $account->meta['balances']['pc_virtual_balance'],
'virtual_balance' => $account->meta['balances']['virtual_balance'],
'pc_virtual_balance' => $account->meta['balances']['pc_virtual_balance'],
'debt_amount' => $account->meta['balances']['debt_amount'],
'pc_debt_amount' => $account->meta['balances']['pc_debt_amount'],
'debt_amount' => $account->meta['balances']['debt_amount'],
'pc_debt_amount' => $account->meta['balances']['pc_debt_amount'],
'current_balance_date' => $date->toAtomString(),
'notes' => $account->meta['notes'] ?? null,
'monthly_payment_date' => $monthlyPaymentDate,
'credit_card_type' => $creditCardType,
'account_number' => $account->meta['account_number'] ?? null,
'iban' => '' === $account->iban ? null : $account->iban,
'bic' => $account->meta['BIC'] ?? null,
'opening_balance_date' => $openingBalanceDate,
'liability_type' => $liabilityType,
'liability_direction' => $liabilityDirection,
'interest' => $interest,
'interest_period' => $interestPeriod,
'include_net_worth' => $includeNetWorth,
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
'links' => [
'current_balance_date' => $date->toAtomString(),
'notes' => $account->meta['notes'] ?? null,
'monthly_payment_date' => $monthlyPaymentDate,
'credit_card_type' => $creditCardType,
'account_number' => $account->meta['account_number'] ?? null,
'iban' => '' === $account->iban ? null : $account->iban,
'bic' => $account->meta['BIC'] ?? null,
'opening_balance_date' => $openingBalanceDate,
'liability_type' => $liabilityType,
'liability_direction' => $liabilityDirection,
'interest' => $interest,
'interest_period' => $interestPeriod,
'include_net_worth' => $includeNetWorth,
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
'links' => [
[
'rel' => 'self',
'uri' => sprintf('/accounts/%d', $account->id),
@@ -193,7 +193,7 @@ class AccountTransformer extends AbstractTransformer
if (null !== $monthlyPaymentDate) {
// try classic date:
if (10 === strlen($monthlyPaymentDate)) {
$object = Carbon::createFromFormat('!Y-m-d', $monthlyPaymentDate, config('app.timezone'));
$object = Carbon::createFromFormat('!Y-m-d', $monthlyPaymentDate, config('app.timezone'));
if (!$object instanceof Carbon) {
$object = today(config('app.timezone'));
}
@@ -214,7 +214,7 @@ class AccountTransformer extends AbstractTransformer
$openingBalanceDate = $account->meta['opening_balance_date'] ?? null;
}
if (null !== $openingBalanceDate) {
$object = Carbon::createFromFormat('Y-m-d H:i:s', $openingBalanceDate, config('app.timezone'));
$object = Carbon::createFromFormat('Y-m-d H:i:s', $openingBalanceDate, config('app.timezone'));
if (!$object instanceof Carbon) {
$object = today(config('app.timezone'));
}

View File

@@ -58,17 +58,17 @@ class AvailableBudgetTransformer extends AbstractTransformer
$pcAmount = app('steam')->bcround($availableBudget->native_amount, $this->primary->decimal_places);
}
$data = [
'id' => (string) $availableBudget->id,
'created_at' => $availableBudget->created_at->toAtomString(),
'updated_at' => $availableBudget->updated_at->toAtomString(),
return [
'id' => (string) $availableBudget->id,
'created_at' => $availableBudget->created_at->toAtomString(),
'updated_at' => $availableBudget->updated_at->toAtomString(),
// currencies according to 6.3.0
'object_has_currency_setting' => true,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => (string) $this->primary->id,
'primary_currency_code' => $this->primary->code,
@@ -76,23 +76,20 @@ class AvailableBudgetTransformer extends AbstractTransformer
'primary_currency_decimal_places' => $this->primary->decimal_places,
'amount' => $amount,
'pc_amount' => $pcAmount,
'start' => $availableBudget->start_date->toAtomString(),
'end' => $availableBudget->end_date->endOfDay()->toAtomString(),
'spent_in_budgets' => $availableBudget->meta['spent_in_budgets'],
'pc_spent_in_budgets' => $availableBudget->meta['pc_spent_in_budgets'],
'spent_outside_budgets' => $availableBudget->meta['spent_outside_budgets'],
'pc_spent_outside_budgets' => $availableBudget->meta['pc_spent_outside_budgets'],
'links' => [
'amount' => $amount,
'pc_amount' => $pcAmount,
'start' => $availableBudget->start_date->toAtomString(),
'end' => $availableBudget->end_date->endOfDay()->toAtomString(),
'spent_in_budgets' => $availableBudget->meta['spent_in_budgets'],
'pc_spent_in_budgets' => $availableBudget->meta['pc_spent_in_budgets'],
'spent_outside_budgets' => $availableBudget->meta['spent_outside_budgets'],
'pc_spent_outside_budgets' => $availableBudget->meta['pc_spent_outside_budgets'],
'links' => [
[
'rel' => 'self',
'uri' => '/available_budgets/' . $availableBudget->id,
'uri' => '/available_budgets/'.$availableBudget->id,
],
],
];
return $data;
}
}

View File

@@ -52,17 +52,17 @@ class BillTransformer extends AbstractTransformer
return [
'id' => $bill->id,
'created_at' => $bill->created_at->toAtomString(),
'updated_at' => $bill->updated_at->toAtomString(),
'name' => $bill->name,
'id' => $bill->id,
'created_at' => $bill->created_at->toAtomString(),
'updated_at' => $bill->updated_at->toAtomString(),
'name' => $bill->name,
// currencies according to 6.3.0
'object_has_currency_setting' => true,
'currency_id' => (string) $bill->transaction_currency_id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'currency_id' => (string) $bill->transaction_currency_id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'primary_currency_id' => (string) $this->primary->id,
'primary_currency_code' => $this->primary->code,
@@ -73,34 +73,34 @@ class BillTransformer extends AbstractTransformer
'amount_min' => $bill->amounts['amount_min'],
'pc_amount_min' => $bill->amounts['pc_amount_min'],
'amount_max' => $bill->amounts['amount_max'],
'pc_amount_max' => $bill->amounts['pc_amount_max'],
'amount_max' => $bill->amounts['amount_max'],
'pc_amount_max' => $bill->amounts['pc_amount_max'],
'amount_avg' => $bill->amounts['average'],
'pc_amount_avg' => $bill->amounts['pc_average'],
'amount_avg' => $bill->amounts['average'],
'pc_amount_avg' => $bill->amounts['pc_average'],
'date' => $bill->date->toAtomString(),
'end_date' => $bill->end_date?->toAtomString(),
'extension_date' => $bill->extension_date?->toAtomString(),
'repeat_freq' => $bill->repeat_freq,
'skip' => $bill->skip,
'active' => $bill->active,
'order' => $bill->order,
'notes' => $bill->meta['notes'],
'object_group_id' => $bill->meta['object_group_id'],
'object_group_order' => $bill->meta['object_group_order'],
'object_group_title' => $bill->meta['object_group_title'],
'date' => $bill->date->toAtomString(),
'end_date' => $bill->end_date?->toAtomString(),
'extension_date' => $bill->extension_date?->toAtomString(),
'repeat_freq' => $bill->repeat_freq,
'skip' => $bill->skip,
'active' => $bill->active,
'order' => $bill->order,
'notes' => $bill->meta['notes'],
'object_group_id' => $bill->meta['object_group_id'],
'object_group_order' => $bill->meta['object_group_order'],
'object_group_title' => $bill->meta['object_group_title'],
'paid_dates' => $bill->meta['paid_dates'],
'pay_dates' => $bill->meta['pay_dates'],
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
'next_expected_match_diff' => $bill->meta['nem_diff'],
'paid_dates' => $bill->meta['paid_dates'],
'pay_dates' => $bill->meta['pay_dates'],
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
'next_expected_match_diff' => $bill->meta['nem_diff'],
'links' => [
'links' => [
[
'rel' => 'self',
'uri' => '/bills/' . $bill->id,
'uri' => '/bills/'.$bill->id,
],
],
];

View File

@@ -94,7 +94,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'links' => [
[
'rel' => 'self',
'uri' => '/transactions/' . $first['transaction_group_id'],
'uri' => '/transactions/'.$first['transaction_group_id'],
],
],
];
@@ -117,8 +117,8 @@ class TransactionGroupTransformer extends AbstractTransformer
private function transformTransaction(array $transaction): array
{
// amount:
$amount = Steam::positive((string) ($transaction['amount'] ?? '0'));
$foreignAmount = null;
$amount = Steam::positive((string) ($transaction['amount'] ?? '0'));
$foreignAmount = null;
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) {
$foreignAmount = Steam::positive($transaction['foreign_amount']);
}
@@ -131,7 +131,7 @@ class TransactionGroupTransformer extends AbstractTransformer
if (array_key_exists('pc_amount', $transaction) && null !== $transaction['pc_amount']) {
$transaction['pc_amount'] = Steam::positive($transaction['pc_amount']);
}
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
// must be 0 (int) or NULL
$recurrenceTotal = $transaction['meta']['recurrence_total'] ?? null;
@@ -140,20 +140,20 @@ class TransactionGroupTransformer extends AbstractTransformer
$recurrenceCount = null !== $recurrenceCount ? (int) $recurrenceCount : null;
return [
'user' => (string) $transaction['user_id'],
'transaction_journal_id' => (string) $transaction['transaction_journal_id'],
'type' => strtolower((string) $type),
'date' => $transaction['date']->toAtomString(),
'order' => $transaction['order'],
'user' => (string) $transaction['user_id'],
'transaction_journal_id' => (string) $transaction['transaction_journal_id'],
'type' => strtolower((string) $type),
'date' => $transaction['date']->toAtomString(),
'order' => $transaction['order'],
// currency information, structured for 6.3.0.
'object_has_currency_setting' => true,
'object_has_currency_setting' => true,
'currency_id' => (string) $transaction['currency_id'],
'currency_code' => $transaction['currency_code'],
'currency_name' => $transaction['currency_name'],
'currency_symbol' => $transaction['currency_symbol'],
'currency_decimal_places' => (int) $transaction['currency_decimal_places'],
'currency_id' => (string) $transaction['currency_id'],
'currency_code' => $transaction['currency_code'],
'currency_name' => $transaction['currency_name'],
'currency_symbol' => $transaction['currency_symbol'],
'currency_decimal_places' => (int) $transaction['currency_decimal_places'],
'foreign_currency_id' => $this->stringFromArray($transaction, 'foreign_currency_id', null),
'foreign_currency_code' => $transaction['foreign_currency_code'],
@@ -171,76 +171,76 @@ class TransactionGroupTransformer extends AbstractTransformer
'amount' => $amount,
'pc_amount' => $transaction['pc_amount'] ?? null,
'foreign_amount' => $foreignAmount,
'pc_foreign_amount' => null,
'foreign_amount' => $foreignAmount,
'pc_foreign_amount' => null,
'source_balance_after' => $transaction['source_balance_after'] ?? null,
'pc_source_balance_after' => null,
'source_balance_after' => $transaction['source_balance_after'] ?? null,
'pc_source_balance_after' => null,
// destination balance after
'destination_balance_after' => $transaction['destination_balance_after'] ?? null,
'pc_destination_balance_after' => null,
'destination_balance_after' => $transaction['destination_balance_after'] ?? null,
'pc_destination_balance_after' => null,
'source_balance_dirty' => $transaction['source_balance_dirty'],
'destination_balance_dirty' => $transaction['destination_balance_dirty'],
'source_balance_dirty' => $transaction['source_balance_dirty'],
'destination_balance_dirty' => $transaction['destination_balance_dirty'],
'description' => $transaction['description'],
'description' => $transaction['description'],
'source_id' => (string) $transaction['source_account_id'],
'source_name' => $transaction['source_account_name'],
'source_iban' => $transaction['source_account_iban'],
'source_type' => $transaction['source_account_type'],
'source_id' => (string) $transaction['source_account_id'],
'source_name' => $transaction['source_account_name'],
'source_iban' => $transaction['source_account_iban'],
'source_type' => $transaction['source_account_type'],
'destination_id' => (string) $transaction['destination_account_id'],
'destination_name' => $transaction['destination_account_name'],
'destination_iban' => $transaction['destination_account_iban'],
'destination_type' => $transaction['destination_account_type'],
'destination_id' => (string) $transaction['destination_account_id'],
'destination_name' => $transaction['destination_account_name'],
'destination_iban' => $transaction['destination_account_iban'],
'destination_type' => $transaction['destination_account_type'],
'budget_id' => $this->stringFromArray($transaction, 'budget_id', null),
'budget_name' => $transaction['budget_name'],
'budget_id' => $this->stringFromArray($transaction, 'budget_id', null),
'budget_name' => $transaction['budget_name'],
'category_id' => $this->stringFromArray($transaction, 'category_id', null),
'category_name' => $transaction['category_name'],
'category_id' => $this->stringFromArray($transaction, 'category_id', null),
'category_name' => $transaction['category_name'],
'bill_id' => $this->stringFromArray($transaction, 'bill_id', null),
'bill_name' => $transaction['bill_name'],
'subscription_id' => $this->stringFromArray($transaction, 'bill_id', null),
'subscription_name' => $transaction['bill_name'],
'bill_id' => $this->stringFromArray($transaction, 'bill_id', null),
'bill_name' => $transaction['bill_name'],
'subscription_id' => $this->stringFromArray($transaction, 'bill_id', null),
'subscription_name' => $transaction['bill_name'],
'reconciled' => $transaction['reconciled'],
'notes' => $transaction['notes'],
'tags' => $transaction['tags'],
'reconciled' => $transaction['reconciled'],
'notes' => $transaction['notes'],
'tags' => $transaction['tags'],
'internal_reference' => $transaction['meta']['internal_reference'] ?? null,
'external_id' => $transaction['meta']['external_id'] ?? null,
'original_source' => $transaction['meta']['original_source'] ?? null,
'recurrence_id' => $transaction['meta']['recurrence_id'] ?? null,
'recurrence_total' => $recurrenceTotal,
'recurrence_count' => $recurrenceCount,
'external_url' => $transaction['meta']['external_url'] ?? null,
'import_hash_v2' => $transaction['meta']['import_hash_v2'] ?? null,
'internal_reference' => $transaction['meta']['internal_reference'] ?? null,
'external_id' => $transaction['meta']['external_id'] ?? null,
'original_source' => $transaction['meta']['original_source'] ?? null,
'recurrence_id' => $transaction['meta']['recurrence_id'] ?? null,
'recurrence_total' => $recurrenceTotal,
'recurrence_count' => $recurrenceCount,
'external_url' => $transaction['meta']['external_url'] ?? null,
'import_hash_v2' => $transaction['meta']['import_hash_v2'] ?? null,
'sepa_cc' => $transaction['meta']['sepa_cc'] ?? null,
'sepa_ct_op' => $transaction['meta']['sepa_ct_op'] ?? null,
'sepa_ct_id' => $transaction['meta']['sepa_ct_id'] ?? null,
'sepa_db' => $transaction['meta']['sepa_db'] ?? null,
'sepa_country' => $transaction['meta']['sepa_country'] ?? null,
'sepa_ep' => $transaction['meta']['sepa_ep'] ?? null,
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
'sepa_cc' => $transaction['meta']['sepa_cc'] ?? null,
'sepa_ct_op' => $transaction['meta']['sepa_ct_op'] ?? null,
'sepa_ct_id' => $transaction['meta']['sepa_ct_id'] ?? null,
'sepa_db' => $transaction['meta']['sepa_db'] ?? null,
'sepa_country' => $transaction['meta']['sepa_country'] ?? null,
'sepa_ep' => $transaction['meta']['sepa_ep'] ?? null,
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
// location data
'longitude' => $transaction['location']['longitude'],
'latitude' => $transaction['location']['latitude'],
'zoom_level' => $transaction['location']['zoom_level'],
'has_attachments' => $transaction['attachment_count'] > 0,
'longitude' => $transaction['location']['longitude'],
'latitude' => $transaction['location']['latitude'],
'zoom_level' => $transaction['location']['zoom_level'],
'has_attachments' => $transaction['attachment_count'] > 0,
];
}
@@ -283,7 +283,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'links' => [
[
'rel' => 'self',
'uri' => '/transactions/' . $group->id,
'uri' => '/transactions/'.$group->id,
],
],
];
@@ -338,10 +338,10 @@ class TransactionGroupTransformer extends AbstractTransformer
$foreignAmount = Steam::bcround($foreignAmount, $foreignCurrency['decimal_places'] ?? 0);
}
$longitude = null;
$latitude = null;
$zoomLevel = null;
$location = $this->getLocation($journal);
$longitude = null;
$latitude = null;
$zoomLevel = null;
$location = $this->getLocation($journal);
if ($location instanceof Location) {
$longitude = $location->longitude;
$latitude = $location->latitude;
@@ -349,77 +349,77 @@ class TransactionGroupTransformer extends AbstractTransformer
}
return [
'user' => $journal->user_id,
'transaction_journal_id' => (string) $journal->id,
'type' => strtolower((string) $type),
'date' => $journal->date->toAtomString(),
'order' => $journal->order,
'user' => $journal->user_id,
'transaction_journal_id' => (string) $journal->id,
'type' => strtolower((string) $type),
'date' => $journal->date->toAtomString(),
'order' => $journal->order,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'foreign_currency_id' => (string) $foreignCurrency['id'],
'foreign_currency_code' => $foreignCurrency['code'],
'foreign_currency_symbol' => $foreignCurrency['symbol'],
'foreign_currency_decimal_places' => $foreignCurrency['decimal_places'],
'amount' => Steam::bcround($amount, $currency->decimal_places),
'foreign_amount' => $foreignAmount,
'amount' => Steam::bcround($amount, $currency->decimal_places),
'foreign_amount' => $foreignAmount,
'description' => $journal->description,
'description' => $journal->description,
'source_id' => (string) $source->account_id,
'source_name' => $source->account->name,
'source_iban' => $source->account->iban,
'source_type' => $source->account->accountType->type,
'source_id' => (string) $source->account_id,
'source_name' => $source->account->name,
'source_iban' => $source->account->iban,
'source_type' => $source->account->accountType->type,
'destination_id' => (string) $destination->account_id,
'destination_name' => $destination->account->name,
'destination_iban' => $destination->account->iban,
'destination_type' => $destination->account->accountType->type,
'destination_id' => (string) $destination->account_id,
'destination_name' => $destination->account->name,
'destination_iban' => $destination->account->iban,
'destination_type' => $destination->account->accountType->type,
'budget_id' => (string) $budget['id'],
'budget_name' => $budget['name'],
'budget_id' => (string) $budget['id'],
'budget_name' => $budget['name'],
'category_id' => (string) $category['id'],
'category_name' => $category['name'],
'category_id' => (string) $category['id'],
'category_name' => $category['name'],
'bill_id' => (string) $bill['id'],
'bill_name' => $bill['name'],
'bill_id' => (string) $bill['id'],
'bill_name' => $bill['name'],
'reconciled' => $source->reconciled,
'notes' => $this->groupRepos->getNoteText($journal->id),
'tags' => $this->groupRepos->getTags($journal->id),
'reconciled' => $source->reconciled,
'notes' => $this->groupRepos->getNoteText($journal->id),
'tags' => $this->groupRepos->getTags($journal->id),
'internal_reference' => $metaFieldData['internal_reference'],
'external_id' => $metaFieldData['external_id'],
'original_source' => $metaFieldData['original_source'],
'recurrence_id' => $metaFieldData['recurrence_id'],
'bunq_payment_id' => $metaFieldData['bunq_payment_id'],
'import_hash_v2' => $metaFieldData['import_hash_v2'],
'internal_reference' => $metaFieldData['internal_reference'],
'external_id' => $metaFieldData['external_id'],
'original_source' => $metaFieldData['original_source'],
'recurrence_id' => $metaFieldData['recurrence_id'],
'bunq_payment_id' => $metaFieldData['bunq_payment_id'],
'import_hash_v2' => $metaFieldData['import_hash_v2'],
'sepa_cc' => $metaFieldData['sepa_cc'],
'sepa_ct_op' => $metaFieldData['sepa_ct_op'],
'sepa_ct_id' => $metaFieldData['sepa_ct_id'],
'sepa_db' => $metaFieldData['sepa_db'],
'sepa_country' => $metaFieldData['sepa_country'],
'sepa_ep' => $metaFieldData['sepa_ep'],
'sepa_ci' => $metaFieldData['sepa_ci'],
'sepa_batch_id' => $metaFieldData['sepa_batch_id'],
'sepa_cc' => $metaFieldData['sepa_cc'],
'sepa_ct_op' => $metaFieldData['sepa_ct_op'],
'sepa_ct_id' => $metaFieldData['sepa_ct_id'],
'sepa_db' => $metaFieldData['sepa_db'],
'sepa_country' => $metaFieldData['sepa_country'],
'sepa_ep' => $metaFieldData['sepa_ep'],
'sepa_ci' => $metaFieldData['sepa_ci'],
'sepa_batch_id' => $metaFieldData['sepa_batch_id'],
'interest_date' => $metaDates['interest_date'],
'book_date' => $metaDates['book_date'],
'process_date' => $metaDates['process_date'],
'due_date' => $metaDates['due_date'],
'payment_date' => $metaDates['payment_date'],
'invoice_date' => $metaDates['invoice_date'],
'interest_date' => $metaDates['interest_date'],
'book_date' => $metaDates['book_date'],
'process_date' => $metaDates['process_date'],
'due_date' => $metaDates['due_date'],
'payment_date' => $metaDates['payment_date'],
'invoice_date' => $metaDates['invoice_date'],
// location data
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
];
}
@@ -494,7 +494,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getForeignCurrency(?TransactionCurrency $currency): array
{
$array = [
$array = [
'id' => null,
'code' => null,
'symbol' => null,
@@ -513,7 +513,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getBudget(?Budget $budget): array
{
$array = [
$array = [
'id' => null,
'name' => null,
];
@@ -528,7 +528,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getCategory(?Category $category): array
{
$array = [
$array = [
'id' => null,
'name' => null,
];
@@ -543,7 +543,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getBill(?Bill $bill): array
{
$array = [
$array = [
'id' => null,
'name' => null,
];

10
composer.lock generated
View File

@@ -3711,16 +3711,16 @@
},
{
"name": "nesbot/carbon",
"version": "3.10.1",
"version": "3.10.2",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00"
"reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
"reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
"reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
"shasum": ""
},
"require": {
@@ -3812,7 +3812,7 @@
"type": "tidelift"
}
],
"time": "2025-06-21T15:19:35+00:00"
"time": "2025-08-02T09:36:06+00:00"
},
{
"name": "nette/schema",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-08-01',
'build_time' => 1754070848,
'version' => 'develop/2025-08-03',
'build_time' => 1754232243,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26,

0
public/v1/js/.gitkeep Executable file → Normal file
View File

View File

@@ -110,7 +110,6 @@
"/public/v1/js/lib/jquery.autocomplete.min.js": "/public/v1/js/lib/jquery.autocomplete.min.js",
"/public/v1/js/lib/jquery.color-2.1.2.min.js": "/public/v1/js/lib/jquery.color-2.1.2.min.js",
"/public/v1/js/lib/modernizr-custom.js": "/public/v1/js/lib/modernizr-custom.js",
"/public/v1/js/lib/moment/af_ZA.js": "/public/v1/js/lib/moment/af_ZA.js",
"/public/v1/js/lib/moment/bg_BG.js": "/public/v1/js/lib/moment/bg_BG.js",
"/public/v1/js/lib/moment/ca_ES.js": "/public/v1/js/lib/moment/ca_ES.js",
"/public/v1/js/lib/moment/cs_CZ.js": "/public/v1/js/lib/moment/cs_CZ.js",

View File

@@ -154,7 +154,7 @@
"url": "URL",
"active": "Aktywny",
"interest_date": "Data odsetek",
"administration_currency": "Primary currency",
"administration_currency": "Waluta podstawowa",
"title": "Tytu\u0142",
"date": "Data",
"book_date": "Data ksi\u0119gowania",
@@ -174,7 +174,7 @@
"list": {
"title": "Tytu\u0142",
"active": "Jest aktywny?",
"primary_currency": "Primary currency",
"primary_currency": "Waluta podstawowa",
"trigger": "Wyzwalacz",
"response": "Odpowied\u017a",
"delivery": "Dor\u0119czenie",