Merge pull request #12942 from firefly-iii/release-1790793485

🤖 Automatically merge the PR into the develop branch.
This commit is contained in:
github-actions[bot] authored and GitHub committed 2026-09-30 20:38:19 +02:00
commit a412b063c4
21 files changed
+140 -97

No files matched your search

@@ -158,7 +158,10 @@ final class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
}
}
@@ -155,7 +155,10 @@ final class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
}
}
@@ -52,7 +52,7 @@ class FilterRequest extends ApiRequest
public function rules(): array
{
return [
'filter' => ['min:0', 'max:255', $this->required, new IsValidFilterInstruction((string)$this->filterClass)],
'filter' => ['min:0', 'max:255', $this->required, new IsValidFilterInstruction((string) $this->filterClass)],
];
}
@@ -198,7 +198,13 @@ final class BudgetLimitController extends Controller
if ($request->expectsJson()) {
$array = $limit->toArray();
// add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentArr = $this->opsRepository->sumExpenses(
$limit->start_date,
$limit->end_date,
null,
new Collection()->push($budget),
$currency
);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);
@@ -272,7 +272,10 @@ final class IndexController extends Controller
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
$array['spent'][$currency->id]['spent_outside'] = Steam::negative(bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']));
$array['spent'][$currency->id]['spent_outside'] = Steam::negative(bcsub(
$spentInLimits[$currency->id],
$spentArr[$currency->id]['sum']
));
$array['spent'][$currency->id]['currency_id'] = $currency->id;
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;
@@ -539,7 +539,13 @@ final class BudgetController extends Controller
}
// get spent amount in this period for this currency.
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
$sum = $this->opsRepository->sumExpenses(
$currentStart,
$currentEnd,
$accounts,
new Collection()->push($budget),
$currency
);
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);
@@ -39,6 +39,7 @@ use Illuminate\Http\Request;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Safe\Exceptions\UrlException;
use function Safe\parse_url;
/**
@@ -56,7 +57,7 @@ final class CreateController extends Controller
parent::__construct();
$this->middleware(function ($request, $next) {
app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('title', (string) trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'bi-shuffle');
$this->repository = app(TransactionGroupRepositoryInterface::class);
@@ -71,9 +72,9 @@ final class CreateController extends Controller
if ($validator->fails()) {
throw new FireflyException(trans('validation.bad_url_parts'));
}
$from = $request->input('_from');
$from = $request->input('_from');
$groupId = (int)$request->input('id');
$groupId = (int) $request->input('id');
if (0 !== $groupId) {
$group = $this->repository->find($groupId);
if ($group instanceof TransactionGroup) {
@@ -83,20 +84,20 @@ final class CreateController extends Controller
Preferences::mark();
$title = '' === (string)$newGroup->title ? $newGroup->transactionJournals()->first()->description : $newGroup->title;
$link = route('transactions.show', [$newGroup->id]);
$title = '' === (string) $newGroup->title ? $newGroup->transactionJournals()->first()->description : $newGroup->title;
$link = route('transactions.show', [$newGroup->id]);
session()->flash('success', trans('firefly.stored_journal', ['description' => $title]));
session()->flash('success_url', $link);
if ('edit' === $request->input('redirect')) {
return response()->json(['redirect' => route('transactions.edit', [$newGroup->id]) . '?_from=' . urlencode($from)]);
return response()->json(['redirect' => route('transactions.edit', [$newGroup->id]).'?_from='.urlencode($from)]);
}
return response()->json(['redirect' => route('index') . $from]);
return response()->json(['redirect' => route('index').$from]);
}
}
return response()->json(['redirect' => route('index') . $from]);
return response()->json(['redirect' => route('index').$from]);
}
/**
@@ -106,31 +107,31 @@ final class CreateController extends Controller
* @throws NotFoundExceptionInterface
* @throws UrlException
*/
public function create(?string $objectType): Factory | View
public function create(?string $objectType): Factory|View
{
Preferences::mark();
$sourceId = (int)request()->get('source');
$destinationId = (int)request()->get('destination');
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string)trans(sprintf('breadcrumbs.create_%s', strtolower((string)$objectType)));
$subTitleIcon = 'bi-plus';
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'bi-plus';
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string)$previousUrl);
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
// map info:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
session()->put('preFilled', $preFilled);
+14 -2
View File
@@ -132,7 +132,13 @@ class CreateAutoBudgetLimits implements ShouldQueue
// if has one, calculate expenses and use that as a base.
$repository = app(OperationsRepositoryInterface::class);
$repository->setUser($autoBudget->budget->user);
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
$spent = $repository->sumExpenses(
$previousStart,
$previousEnd,
null,
new Collection()->push($autoBudget->budget),
$autoBudget->transactionCurrency
);
$currencyId = $autoBudget->transaction_currency_id;
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
@@ -222,7 +228,13 @@ class CreateAutoBudgetLimits implements ShouldQueue
// if has one, calculate expenses and use that as a base.
$repository = app(OperationsRepositoryInterface::class);
$repository->setUser($autoBudget->budget->user);
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
$spent = $repository->sumExpenses(
$previousStart,
$previousEnd,
null,
new Collection()->push($autoBudget->budget),
$autoBudget->transactionCurrency
);
$currencyId = $autoBudget->transaction_currency_id;
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
@@ -222,7 +222,14 @@ trait AugumentData
$currentEnd->addMonth();
}
// primary currency amount.
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToPrimary);
$expenses = $opsRepository->sumExpenses(
$currentStart,
$currentEnd,
null,
$budgetCollection,
$entry->transactionCurrency,
$this->convertToPrimary
);
$spent = $expenses[$currency->id]['sum'] ?? '0';
$entry->pc_spent = $spent;
@@ -354,7 +354,11 @@ class RecurringEnrichment implements EnrichmentInterface
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)->first();
$recurrence = $this->collection->filter(
static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id
)
->first()
;
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
$recurrenceId = (int) $repetition->recurrence_id;
$repId = (int) $repetition->id;
Generated
+5 -5
View File
@@ -1679,12 +1679,12 @@
"source": {
"type": "git",
"url": "https://github.com/JC5/google2fa-laravel.git",
"reference": "81eb57e2883261cfd3b675e00eaf9e930e19a1da"
"reference": "bad5291cf4db21f9fbe79cfd4122362c18cf867f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JC5/google2fa-laravel/zipball/81eb57e2883261cfd3b675e00eaf9e930e19a1da",
"reference": "81eb57e2883261cfd3b675e00eaf9e930e19a1da",
"url": "https://api.github.com/repos/JC5/google2fa-laravel/zipball/bad5291cf4db21f9fbe79cfd4122362c18cf867f",
"reference": "bad5291cf4db21f9fbe79cfd4122362c18cf867f",
"shasum": ""
},
"require": {
@@ -1750,9 +1750,9 @@
],
"support": {
"issues": "https://github.com/JC5/google2fa-laravel/issues",
"source": "https://github.com/JC5/google2fa-laravel/tree/main"
"source": "https://github.com/JC5/google2fa-laravel/tree/v2.0.14"
},
"time": "2026-09-30T15:29:59+00:00"
"time": "2026-09-30T15:40:39+00:00"
},
{
"name": "jc5/recovery",
+1 -1
View File
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-09-30',
'build_time' => 1790782627,
'build_time' => 1790793485,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
+5 -4
View File
@@ -112,16 +112,17 @@ export default () => ({
let ytd = this.ytd();
// set the title:
let locale = window.store.get('locale');
let formatString = this.i18next.t('config.month_and_day_fns', {lng: locale});
let locale = window.store.get("locale");
let formatString = this.i18next.t("config.month_and_day_fns", { lng: locale });
let element = document.getElementsByClassName("daterange-holder")[0];
element.textContent = format(this.range.start,formatString) + " - " + format(this.range.end,formatString);
element.textContent = format(this.range.start, formatString) + " - " + format(this.range.end, formatString);
element.setAttribute("data-start", format(this.range.start, this.preferredFormat, "en-US"));
element.setAttribute("data-end", format(this.range.end, this.preferredFormat, "en-US"));
// set the current one
element = document.getElementsByClassName("daterange-current")[0];
element.textContent = format(this.defaultRange.start, formatString) + " - " + format(this.defaultRange.end, formatString);
element.textContent =
format(this.defaultRange.start, formatString) + " - " + format(this.defaultRange.end, formatString);
element.setAttribute("data-start", format(this.defaultRange.start, this.preferredFormat, "en-US"));
element.setAttribute("data-end", format(this.defaultRange.end, this.preferredFormat, "en-US"));
@@ -47,7 +47,7 @@ import {
clearCategory,
clearDescription,
clearAmount,
clearForeignAmount
clearForeignAmount,
} from "./shared/clear-fields.js";
import { detectTransactionType } from "./shared/detect-transaction-type.js";
import { determineAmountCurrency } from "./shared/determine-amount-currency.js";
@@ -21,61 +21,61 @@
import "../../boot/bootstrap.js";
import dates from "../../pages/shared/dates.js";
import Get from "../../api/model/transaction/get.js";
import {parseDownloadedSplits} from "./shared/parse-downloaded-splits.js";
import {addAllAutocompleteToForm} from "./shared/add-autocomplete.js";
import {loadCurrencies} from "./shared/load-currencies.js";
import {loadBudgets} from "./shared/load-budgets.js";
import {loadPiggyBanks} from "./shared/load-piggy-banks.js";
import {processUploadError} from "./shared/process-upload-error.js";
import {loadSubscriptions} from "./shared/load-subscriptions.js";
import { parseDownloadedSplits } from "./shared/parse-downloaded-splits.js";
import { addAllAutocompleteToForm } from "./shared/add-autocomplete.js";
import { loadCurrencies } from "./shared/load-currencies.js";
import { loadBudgets } from "./shared/load-budgets.js";
import { loadPiggyBanks } from "./shared/load-piggy-banks.js";
import { processUploadError } from "./shared/process-upload-error.js";
import { loadSubscriptions } from "./shared/load-subscriptions.js";
import i18next from "i18next";
import {defaultErrorSet} from "./shared/create-empty-split.js";
import {parseFromEntries} from "./shared/parse-from-entries.js";
import { defaultErrorSet } from "./shared/create-empty-split.js";
import { parseFromEntries } from "./shared/parse-from-entries.js";
import Put from "../../api/model/transaction/put.js";
import {showMessageOrRedirectUser} from "./shared/show-message-or-redirect.js";
import {processAttachments} from "./shared/process-attachments.js";
import { showMessageOrRedirectUser } from "./shared/show-message-or-redirect.js";
import { processAttachments } from "./shared/process-attachments.js";
import sidebar from "../shared/sidebar.js";
import {disableSplitInputs} from "./shared/disable-split-inputs.js";
import {parseTotalAmount} from "./shared/parse-total-amount.js";
import {keyUpFromCategory} from "./shared/keyup-from-category.js";
import {keyUpFromSource} from "./shared/keyup-from-source.js";
import {keyUpFromDestination} from "./shared/keyup-from-destination.js";
import {keyUpFromDescription} from "./shared/keyup-from-description.js";
import {changedAmount} from "./shared/changed-amount.js";
import {changedForeignAmount} from "./shared/changed-foreign-amount.js";
import {parseErrors} from "./shared/parse-errors.js";
import {addSplit} from "./shared/add-split.js";
import { disableSplitInputs } from "./shared/disable-split-inputs.js";
import { parseTotalAmount } from "./shared/parse-total-amount.js";
import { keyUpFromCategory } from "./shared/keyup-from-category.js";
import { keyUpFromSource } from "./shared/keyup-from-source.js";
import { keyUpFromDestination } from "./shared/keyup-from-destination.js";
import { keyUpFromDescription } from "./shared/keyup-from-description.js";
import { changedAmount } from "./shared/changed-amount.js";
import { changedForeignAmount } from "./shared/changed-foreign-amount.js";
import { parseErrors } from "./shared/parse-errors.js";
import { addSplit } from "./shared/add-split.js";
import {
clearAmount,
clearCategory,
clearDescription,
clearForeignAmount,
clearDestinationAccount,
clearSourceAccount
clearSourceAccount,
} from "./shared/clear-fields.js";
import {detectTransactionType} from "./shared/detect-transaction-type.js";
import {determineAmountCurrency} from "./shared/determine-amount-currency.js";
import {loadCustomFields} from "./shared/load-custom-fields.js";
import {displayMap} from "./shared/display-map.js";
import {loadDefaultCoordinates} from "./shared/load-default-coordinates.js";
import {renderMap} from "./shared/render-map.js";
import { detectTransactionType } from "./shared/detect-transaction-type.js";
import { determineAmountCurrency } from "./shared/determine-amount-currency.js";
import { loadCustomFields } from "./shared/load-custom-fields.js";
import { displayMap } from "./shared/display-map.js";
import { loadDefaultCoordinates } from "./shared/load-default-coordinates.js";
import { renderMap } from "./shared/render-map.js";
import "leaflet/dist/leaflet.css";
import {onMapClick} from "./shared/on-map-click.js";
import {onMapZoom} from "./shared/on-map-zoom.js";
import {clearLocation} from "./shared/clear-location.js";
import {removeSplit} from "./shared/remove-split.js";
import {loadTransactionLinks} from "./shared/load-transaction-links.js";
import {createLinkAutocomplete} from "./shared/create-link-autocomplete.js";
import {editLink} from "./shared/edit-link.js";
import {switchLink} from "./shared/switch-link.js";
import {removeLink} from "./shared/remove-link.js";
import {saveEditedLink} from "./shared/save-edited-link.js";
import {saveNewLink} from "./shared/save-new-link.js";
import {processTransactionLinks} from "./shared/process-transaction-links.js";
import {redirectAfterTransactionLinks} from "./shared/redirect-after-transaction-links.js";
import {addTabListener} from "./shared/add-tab-listener.js";
import {autoStep} from "./shared/auto-step.js";
import {respondToTabSwitch} from "./shared/respond-to-tab-switch.js";
import { onMapClick } from "./shared/on-map-click.js";
import { onMapZoom } from "./shared/on-map-zoom.js";
import { clearLocation } from "./shared/clear-location.js";
import { removeSplit } from "./shared/remove-split.js";
import { loadTransactionLinks } from "./shared/load-transaction-links.js";
import { createLinkAutocomplete } from "./shared/create-link-autocomplete.js";
import { editLink } from "./shared/edit-link.js";
import { switchLink } from "./shared/switch-link.js";
import { removeLink } from "./shared/remove-link.js";
import { saveEditedLink } from "./shared/save-edited-link.js";
import { saveNewLink } from "./shared/save-new-link.js";
import { processTransactionLinks } from "./shared/process-transaction-links.js";
import { redirectAfterTransactionLinks } from "./shared/redirect-after-transaction-links.js";
import { addTabListener } from "./shared/add-tab-listener.js";
import { autoStep } from "./shared/auto-step.js";
import { respondToTabSwitch } from "./shared/respond-to-tab-switch.js";
import Alpine from "@alpinejs/csp";
import focusFirstInput from "../../shared/focus-first-input.js";
import filterForeignCurrencies from "./shared/filter-foreign-currencies.js";
@@ -412,7 +412,7 @@ let transactions = function () {
// submit the transaction. Multi-stage process thing going on here!
let putter = new Put();
putter
.put(submission, {id: this.groupProperties.id})
.put(submission, { id: this.groupProperties.id })
.then((response) => {
this.formStates.storedGroup = true;
const group = response.data.data;
@@ -465,7 +465,7 @@ let transactions = function () {
};
};
let comps = {transactions, sidebar, dates};
let comps = { transactions, sidebar, dates };
function loadPage() {
Object.keys(comps).forEach((comp) => {
@@ -24,13 +24,12 @@ export function changedAmount(e) {
let value = e.target.value;
// switch commas for dots.
let len = (value.match(/,/g) || []).length;
if(1 === len) {
value = value.replace(',', '.');
if (1 === len) {
value = value.replace(",", ".");
}
this.entries[index].amount = parseFloat(value);
}
this.groupProperties.totalAmount = 0;
for (let i in this.entries) {
if (Object.hasOwn(this.entries, i)) {
@@ -25,7 +25,7 @@ export function changedForeignAmount(e) {
// switch commas for dots.
let len = (value.match(/,/g) || []).length;
if (1 === len) {
value = value.replace(',', '.');
value = value.replace(",", ".");
}
this.entries[index].foreign_amount = parseFloat(value);
}
@@ -44,4 +44,4 @@ export function clearForeignAmount(index) {
export function clearCategory(index) {
this.entries[index].category_name = "";
};
}
@@ -50,7 +50,7 @@ export function detectTransactionType() {
this.determineAmountCurrency(this.entries[0].source_account.account_currency_code);
this.disableSplitInputs();
if("Expense account" !== destType) {
if ("Expense account" !== destType) {
// in this particular case, the foreign amount input is also disabled when both accounts are
// of the same currency.
this.determineAmountCurrency(this.entries[0].source_account.account_currency_code);
@@ -107,8 +107,6 @@ export function detectTransactionType() {
this.determineAmountCurrency(this.entries[0].source_account.account_currency_code);
this.filterForeignCurrencies(this.entries[0].destination_account.account_currency_code);
return;
}
console.warn('Unknown account combination between "' + sourceType + '" and "' + destType + '".');
+1 -1
View File
@@ -93,7 +93,7 @@ const locales = {
// it will format dates in whichever way is appropriate to the locale
export default function (date, formatStr = "PP", locale) {
if (typeof locale === "undefined") {
locale = window.__localeId__.replace("_", "").replace('-','');
locale = window.__localeId__.replace("_", "").replace("-", "");
}
let res = "(DATE ERROR)";
try {
+1 -1
View File
@@ -2324,7 +2324,7 @@ return [
'account_type_loan' => 'Loan',
'account_type_mortgage' => 'Mortgage',
'account_type_Credit card' => 'Credit card',
'account_type_Cash account' => 'Cash account',
'account_type_Cash account' => 'Cash account',
'credit_card_type_monthlyFull' => 'Full payment every month',
'liability_direction_credit' => 'I am owed this debt',
'liability_direction_debit' => 'I owe this debt to somebody else',