diff --git a/app/Api/V2/Controllers/Model/Bill/ShowController.php b/app/Api/V2/Controllers/Model/Bill/ShowController.php index a0c1cdbfff..3f0cb7c5db 100644 --- a/app/Api/V2/Controllers/Model/Bill/ShowController.php +++ b/app/Api/V2/Controllers/Model/Bill/ShowController.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Api\V2\Controllers\Model\Bill; use FireflyIII\Api\V2\Controllers\Controller; +use FireflyIII\Models\Bill; use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface; +use FireflyIII\Transformers\V2\AccountTransformer; use FireflyIII\Transformers\V2\BillTransformer; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -71,4 +73,17 @@ class ShowController extends Controller ->json($this->jsonApiList('subscriptions', $paginator, $transformer)) ->header('Content-Type', self::CONTENT_TYPE); } + + /** + * TODO this endpoint is not documented + */ + public function show(Request $request, Bill $bill): JsonResponse + { + $transformer = new BillTransformer(); + $transformer->setParameters($this->parameters); + + return response() + ->api($this->jsonApiObject('subscriptions', $bill, $transformer)) + ->header('Content-Type', self::CONTENT_TYPE); + } } diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index 46f3a09c71..7c280d9f1a 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -138,7 +138,7 @@ class IndexController extends Controller // get budgeted for default currency: if (0 === count($availableBudgets)) { - $budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency, ); + $budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency,); $spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $defaultCurrency); $spent = $spentArr[$defaultCurrency->id]['sum'] ?? '0'; unset($spentArr); @@ -197,7 +197,7 @@ class IndexController extends Controller $array['spent'] = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0'; // budgeted in period: - $budgeted = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency, ); + $budgeted = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency,); $array['budgeted'] = $budgeted; $availableBudgets[] = $array; unset($spentArr); @@ -227,6 +227,7 @@ class IndexController extends Controller Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name)); $array = $current->toArray(); $array['spent'] = []; + $array['spent_total'] = []; $array['budgeted'] = []; $array['attachments'] = $this->repository->getAttachments($current); $array['auto_budget'] = $this->repository->getAutoBudget($current); @@ -235,9 +236,10 @@ class IndexController extends Controller foreach ($budgetLimits as $limit) { Log::debug(sprintf('Working on budget limit #%d', $limit->id)); $currency = $limit->transactionCurrency ?? $defaultCurrency; + $amount = app('steam')->bcround($limit->amount, $currency->decimal_places); $array['budgeted'][] = [ 'id' => $limit->id, - 'amount' => app('steam')->bcround($limit->amount, $currency->decimal_places), + 'amount' => $amount, 'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat), 'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat), 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), @@ -246,6 +248,7 @@ class IndexController extends Controller 'currency_name' => $currency->name, 'currency_decimal_places' => $currency->decimal_places, ]; + Log::debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount)); } /** @var TransactionCurrency $currency */ diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 32b797cb67..2e67291a74 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -28,7 +28,10 @@ use Exception; use FireflyConfig; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Middleware\IsDemoUser; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\TransactionType; use FireflyIII\Support\Http\Controllers\GetConfigurationData; +use FireflyIII\User; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -37,6 +40,8 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Log; use Illuminate\View\View; use Monolog\Handler\RotatingFileHandler; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; /** * Class DebugController @@ -117,86 +122,8 @@ class DebugController extends Controller */ public function index(Request $request) { - // basic scope information: - $now = now(config('app.timezone'))->format('Y-m-d H:i:s e'); - $buildNr = '(unknown)'; - $buildDate = '(unknown)'; - $baseBuildNr = '(unknown)'; - $baseBuildDate = '(unknown)'; - $expectedDBversion = config('firefly.db_version'); - $foundDBversion = FireflyConfig::get('db_version', 1)->data; - try { - if (file_exists('/var/www/counter-main.txt')) { - $buildNr = trim(file_get_contents('/var/www/counter-main.txt')); - } - } catch (Exception $e) { // generic catch for open basedir. - Log::debug('Could not check counter.'); - Log::warning($e->getMessage()); - } - try { - if (file_exists('/var/www/build-date-main.txt')) { - $buildDate = trim(file_get_contents('/var/www/build-date-main.txt')); - } - } catch (Exception $e) { // generic catch for open basedir. - Log::debug('Could not check date.'); - Log::warning($e->getMessage()); - } - if ('' !== (string)env('BASE_IMAGE_BUILD')) { - $baseBuildNr = env('BASE_IMAGE_BUILD'); - } - if ('' !== (string)env('BASE_IMAGE_DATE')) { - $baseBuildDate = env('BASE_IMAGE_DATE'); - } - - $phpVersion = PHP_VERSION; - $phpOs = PHP_OS; - - // system information - $tz = env('TZ'); - $appEnv = config('app.env'); - $appDebug = var_export(config('app.debug'), true); - $cacheDriver = config('cache.default'); - $logChannel = config('logging.default'); - $appLogLevel = config('logging.level'); - $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - $displayErrors = ini_get('display_errors'); - $errorReporting = $this->errorReporting((int)ini_get('error_reporting')); - $interface = PHP_SAPI; - $defaultLanguage = (string)config('firefly.default_language'); - $defaultLocale = (string)config('firefly.default_locale'); - $bcscale = bcscale(); - $drivers = implode(', ', DB::availableDrivers()); - $currentDriver = DB::getDriverName(); - $trustedProxies = config('firefly.trusted_proxies'); - - // user info - $loginProvider = config('auth.providers.users.driver'); - $userGuard = config('auth.defaults.guard'); - $remoteHeader = $userGuard === 'remote_user_guard' ? config('auth.guard_header') : 'N/A'; - $remoteMailHeader = $userGuard === 'remote_user_guard' ? config('auth.guard_email') : 'N/A'; - $userLanguage = app('steam')->getLanguage(); - $userLocale = app('steam')->getLocale(); - $userAgent = $request->header('user-agent'); - $stateful = join(', ', config('sanctum.stateful')); - - - // expected + found DB version: - - // some new vars. - $isDocker = env('IS_DOCKER', false); - - // set languages, see what happens: - $original = setlocale(LC_ALL, 0); - $localeAttempts = []; - $parts = app('steam')->getLocaleArray(app('steam')->getLocale()); - foreach ($parts as $code) { - $code = trim($code); - Log::debug(sprintf('Trying to set %s', $code)); - $localeAttempts[$code] = var_export(setlocale(LC_ALL, $code), true); - } - setlocale(LC_ALL, $original); + $table = $this->generateTable(); + $now = now(config('app.timezone'))->format('Y-m-d H:i:s'); // get latest log file: $logger = Log::driver(); @@ -213,50 +140,190 @@ class DebugController extends Controller } if ('' !== $logContent) { // last few lines - $logContent = 'Truncated from this point <----|' . substr($logContent, -8192); + $logContent = 'Truncated from this point <----|' . substr($logContent, -16384); } - return view( - 'debug', - compact( - 'phpVersion', - 'localeAttempts', - 'expectedDBversion', - 'foundDBversion', - 'appEnv', - 'appDebug', - 'logChannel', - 'stateful', - 'tz', - 'uploadSize', - 'appLogLevel', - 'remoteHeader', - 'remoteMailHeader', - 'now', - 'drivers', - 'currentDriver', - 'userGuard', - 'loginProvider', - 'buildNr', - 'buildDate', - 'baseBuildNr', - 'baseBuildDate', - 'bcscale', - 'userAgent', - 'displayErrors', - 'errorReporting', - 'phpOs', - 'interface', - 'logContent', - 'cacheDriver', - 'trustedProxies', - 'userLanguage', - 'userLocale', - 'defaultLanguage', - 'defaultLocale', - 'isDocker' - ) - ); + return view('debug', compact('table', 'now', 'logContent')); + } + + /** + * @return string + */ + private function generateTable(): string + { + // system information: + $system = $this->getSystemInformation(); + $docker = $this->getBuildInfo(); + $app = $this->getAppInfo(); + $user = $this->getuserInfo(); + + return (string)view('partials.debug-table', compact('system', 'docker', 'app', 'user')); + } + + /** + * @return array + */ + private function getSystemInformation(): array + { + $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); + $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); + $drivers = DB::availableDrivers(); + $currentDriver = DB::getDriverName(); + return [ + 'db_version' => app('fireflyconfig')->get('db_version', 1)->data, + 'php_version' => PHP_VERSION, + 'php_os' => PHP_OS, + 'interface' => PHP_SAPI, + 'bcscale' => bcscale(), + 'display_errors' => ini_get('display_errors'), + 'error_reporting' => $this->errorReporting((int)ini_get('error_reporting')), + 'upload_size' => min($maxFileSize, $maxPostSize), + 'all_drivers' => $drivers, + 'current_driver' => $currentDriver, + ]; + } + + /** + * @return array + */ + private function getBuildInfo(): array + { + $return = [ + 'is_docker' => env('IS_DOCKER', false), + 'build' => '(unknown)', + 'build_date' => '(unknown)', + 'base_build' => '(unknown)', + 'base_build_date' => '(unknown)', + + ]; + try { + if (file_exists('/var/www/counter-main.txt')) { + $return['build'] = trim(file_get_contents('/var/www/counter-main.txt')); + } + } catch (Exception $e) { // generic catch for open basedir. + Log::debug('Could not check build counter, but thats ok.'); + Log::warning($e->getMessage()); + } + try { + if (file_exists('/var/www/build-date-main.txt')) { + $return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt')); + } + } catch (Exception $e) { // generic catch for open basedir. + Log::debug('Could not check build date, but thats ok.'); + Log::warning($e->getMessage()); + } + if ('' !== (string)env('BASE_IMAGE_BUILD')) { + $return['base_build'] = env('BASE_IMAGE_BUILD'); + } + if ('' !== (string)env('BASE_IMAGE_DATE')) { + $return['base_build_date'] = env('BASE_IMAGE_DATE'); + } + return $return; + } + + /** + * @return array + */ + private function getAppInfo(): array + { + $userGuard = config('auth.defaults.guard'); + + return [ + 'tz' => env('TZ'), + 'debug' => var_export(config('app.debug'), true), + 'log_channel' => env('LOG_CHANNEL'), + 'audit_log_channel' => envNonEmpty('AUDIT_LOG_CHANNEL', '(empty)'), + 'default_language' => (string)config('firefly.default_language'), + 'default_locale' => (string)config('firefly.default_locale'), + 'remote_header' => $userGuard === 'remote_user_guard' ? config('auth.guard_header') : 'N/A', + 'remote_mail_header' => $userGuard === 'remote_user_guard' ? config('auth.guard_email') : 'N/A', + 'stateful_domains' => join(', ', config('sanctum.stateful')), + ]; + } + + /** + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function getuserInfo(): array + { + $userFlags = $this->getUserFlags(); + + // user info + $userAgent = request()->header('user-agent'); + + // set languages, see what happens: + $original = setlocale(LC_ALL, 0); + $localeAttempts = []; + $parts = app('steam')->getLocaleArray(app('steam')->getLocale()); + foreach ($parts as $code) { + $code = trim($code); + Log::debug(sprintf('Trying to set %s', $code)); + $result = setlocale(LC_ALL, $code); + $localeAttempts[$code] = $result === $code; + } + setlocale(LC_ALL, $original); + + return [ + 'user_id' => auth()->user()->id, + 'user_count' => User::count(), + 'user_flags' => $userFlags, + 'user_agent' => $userAgent, + 'locale_attempts' => $localeAttempts, + 'locale' => app('steam')->getLocale(), + 'language' => app('steam')->getLanguage(), + 'view_range' => app('preferences')->get('viewRange', '1M')->data, + ]; + } + + /** + * @return string + */ + private function getUserFlags(): string + { + $flags = []; + /** @var User $user */ + $user = auth()->user(); + + // has liabilities + if ($user->accounts()->accountTypeIn([AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE])->count() > 0) { + $flags[] = ':credit_card:'; + } + + // has piggies + if ($user->piggyBanks()->count() > 0) { + $flags[] = ':pig:'; + } + + // has stored reconciliations + $type = TransactionType::whereType(TransactionType::RECONCILIATION)->first(); + if ($user->transactionJournals()->where('transaction_type_id', $type->id)->count()) { + $flags[] = ':ledger:'; + } + + // has used importer? + + // has rules + if ($user->rules()->count() > 0) { + $flags[] = ':wrench:'; + } + + // has recurring transactions + if ($user->recurrences()->count() > 0) { + $flags[] = ':clock130:'; + } + + // has groups + if ($user->objectGroups()->count() > 0) { + $flags[] = ':bookmark_tabs:'; + } + + // uses bills + if ($user->bills()->count() > 0) { + $flags[] = ':email:'; + } + return join(' ', $flags); } /** @@ -275,4 +342,5 @@ class DebugController extends Controller return redirect(route('home')); } + } diff --git a/app/Support/Binder/UserGroupBill.php b/app/Support/Binder/UserGroupBill.php new file mode 100644 index 0000000000..09530c1230 --- /dev/null +++ b/app/Support/Binder/UserGroupBill.php @@ -0,0 +1,58 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Support\Binder; + +use FireflyIII\Models\Bill; +use FireflyIII\User; +use Illuminate\Routing\Route; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Class UserGroupBill. + */ +class UserGroupBill implements BinderInterface +{ + /** + * @param string $value + * @param Route $route + * + * @return Bill + * @throws NotFoundHttpException + * + */ + public static function routeBinder(string $value, Route $route): Bill + { + if (auth()->check()) { + /** @var User $user */ + $user = auth()->user(); + $currency = Bill::where('id', (int)$value) + ->where('user_group_id', $user->user_group_id) + ->first(); + if (null !== $currency) { + return $currency; + } + } + throw new NotFoundHttpException(); + } +} diff --git a/app/Transformers/V2/BillTransformer.php b/app/Transformers/V2/BillTransformer.php index 19bd4cf8ef..3693384e3e 100644 --- a/app/Transformers/V2/BillTransformer.php +++ b/app/Transformers/V2/BillTransformer.php @@ -28,9 +28,11 @@ use Carbon\CarbonInterface; use FireflyIII\Models\Bill; use FireflyIII\Models\Note; use FireflyIII\Models\ObjectGroup; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Log; @@ -40,6 +42,7 @@ use Log; */ class BillTransformer extends AbstractTransformer { + private ExchangeRateConverter $converter; private array $currencies; private TransactionCurrency $default; private array $groups; @@ -102,21 +105,71 @@ class BillTransformer extends AbstractTransformer ]; } - $this->default = app('amount')->getDefaultCurrency(); - + $this->default = app('amount')->getDefaultCurrency(); + $this->converter = new ExchangeRateConverter(); // grab all paid dates: if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { - $journals = TransactionJournal::whereIn('bill_id', $bills) - ->where('date', '>=', $this->parameters->get('start')) - ->where('date', '<=', $this->parameters->get('end')) - ->get(['transaction_journals.id', 'transaction_journals.transaction_group_id', 'transaction_journals.date', 'transaction_journals.bill_id']); + $journals = TransactionJournal::whereIn('bill_id', $bills) + ->where('date', '>=', $this->parameters->get('start')) + ->where('date', '<=', $this->parameters->get('end')) + ->get(['transaction_journals.id', 'transaction_journals.transaction_group_id', 'transaction_journals.date', 'transaction_journals.bill_id']); + $journalIds = $journals->pluck('id')->toArray(); + + // grab transactions for amount: + $set = Transaction::whereIn('transaction_journal_id', $journalIds) + ->where('transactions.amount', '<', 0) + ->get(['transactions.id', 'transactions.transaction_journal_id', 'transactions.amount', 'transactions.foreign_amount', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']); + // convert to array for easy finding: + $transactions = []; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + $journalId = (int)$transaction->transaction_journal_id; + $transactions[$journalId] = $transaction->toArray(); + } /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $billId = (int)$journal->bill_id; + $transaction = $transactions[(int)$journal->id] ?? []; + $billId = (int)$journal->bill_id; + $currencyId = (int)$transaction['transaction_currency_id'] ?? 0; + $currencies[$currencyId] = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId); + + // foreign currency + $foreignCurrencyId = null; + $foreignCurrencyCode = null; + $foreignCurrencyName = null; + $foreignCurrencySymbol = null; + $foreignCurrencyDp = null; + if (null !== $transaction['foreign_currency_id']) { + $foreignCurrencyId = (int)$transaction['foreign_currency_id']; + $currencies[$foreignCurrencyId] = $currencies[$foreignCurrencyId] ?? TransactionCurrency::find($foreignCurrencyId); + $foreignCurrencyCode = $currencies[$foreignCurrencyId]->code; + $foreignCurrencyName = $currencies[$foreignCurrencyId]->name; + $foreignCurrencySymbol = $currencies[$foreignCurrencyId]->symbol; + $foreignCurrencyDp = (int)$currencies[$foreignCurrencyId]->decimal_places; + } + $this->paidDates[$billId][] = [ - 'transaction_group_id' => (string)$journal->id, - 'transaction_journal_id' => (string)$journal->transaction_group_id, - 'date' => $journal->date->toAtomString(), + 'transaction_group_id' => (string)$journal->id, + 'transaction_journal_id' => (string)$journal->transaction_group_id, + 'date' => $journal->date->toAtomString(), + 'currency_id' => (int)$currencies[$currencyId]->id, + 'currency_code' => $currencies[$currencyId]->code, + 'currency_name' => $currencies[$currencyId]->name, + 'currency_symbol' => $currencies[$currencyId]->symbol, + 'currency_decimal_places' => (int)$currencies[$currencyId]->decimal_places, + 'native_id' => (int)$currencies[$currencyId]->id, + 'native_code' => $currencies[$currencyId]->code, + 'native_symbol' => $currencies[$currencyId]->symbol, + 'native_decimal_places' => (int)$currencies[$currencyId]->decimal_places, + 'foreign_currency_id' => $foreignCurrencyId, + 'foreign_currency_code' => $foreignCurrencyCode, + 'foreign_currency_name' => $foreignCurrencyName, + 'foreign_currency_symbol' => $foreignCurrencySymbol, + 'foreign_currency_decimal_places' => $foreignCurrencyDp, + 'amount' => $transaction['amount'], + 'foreign_amount' => $transaction['foreign_amount'], + 'native_amount' => $this->converter->convert($currencies[$currencyId], $this->default, $journal->date, $transaction['amount']), + 'foreign_native_amount' => null === $transaction['foreign_amount'] ? null : $this->converter->convert($currencies[$foreignCurrencyId], $this->default, $journal->date, $transaction['foreign_amount']), ]; } } @@ -131,11 +184,19 @@ class BillTransformer extends AbstractTransformer */ public function transform(Bill $bill): array { - $paidData = $this->paidDates[(int)$bill->id] ?? []; - $nextExpectedMatch = $this->nextExpectedMatch($bill, $this->paidDates[(int)$bill->id] ?? []); - $payDates = $this->payDates($bill); - $currency = $this->currencies[(int)$bill->transaction_currency_id]; - $group = $this->groups[(int)$bill->id] ?? null; + $paidData = $this->paidDates[(int)$bill->id] ?? []; + $nextExpectedMatch = $this->nextExpectedMatch($bill, $this->paidDates[(int)$bill->id] ?? []); + $payDates = $this->payDates($bill); + $currency = $this->currencies[(int)$bill->transaction_currency_id]; + $group = $this->groups[(int)$bill->id] ?? null; + + // date for currency conversion + /** @var Carbon|null $startParam */ + $startParam = $this->parameters->get('start'); + /** @var Carbon|null $start */ + $date = null === $startParam ? today() : clone $startParam; + + $nextExpectedMatchDiff = $this->getNextExpectedMatchDiff($nextExpectedMatch, $payDates); return [ 'id' => (int)$bill->id, @@ -144,10 +205,18 @@ class BillTransformer extends AbstractTransformer 'name' => $bill->name, 'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places), 'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places), + 'native_amount_min' => $this->converter->convert($currency, $this->default, $date, $bill->amount_min), + 'native_amount_max' => $this->converter->convert($currency, $this->default, $date, $bill->amount_max), 'currency_id' => (string)$bill->transaction_currency_id, 'currency_code' => $currency->code, + 'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => (int)$currency->decimal_places, + 'native_id' => $this->default->id, + 'native_code' => $this->default->code, + 'native_name' => $this->default->name, + 'native_symbol' => $this->default->symbol, + 'native_decimal_places' => (int)$this->default->decimal_places, 'date' => $bill->date->toAtomString(), 'end_date' => $bill->end_date?->toAtomString(), 'extension_date' => $bill->extension_date?->toAtomString(), @@ -185,8 +254,11 @@ class BillTransformer extends AbstractTransformer // 2023-07-1 sub one day from the start date to fix a possible bug (see #7704) // 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->parameters->get('start'); + + /** @var Carbon|null $startParam */ + $startParam = $this->parameters->get('start'); + /** @var Carbon|null $start */ + $start = null === $startParam ? today() : clone $startParam; $start->subDay(); $lastPaidDate = $this->lastPaidDate($dates, $start); diff --git a/changelog.md b/changelog.md index a7af740f57..469b1f3d44 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 6.0.23 - 2023-09-04 + +### Changed + +- New debug information tables are in HTML + +### Fixed + +- Remove extra slashes from paths, breaking CSS + ## 6.0.22 - 2023-09-02 ### API @@ -15,7 +25,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Rules will now report failures if a Slack/Discord notification channel is configured - Notifications can be sent to Discord -- Beta layout `v2`, activate with `FIRELY_III_LAYOUT=v2` +- Beta layout `v2`, activate with `FIREFLY_III_LAYOUT=v2` ### Changed diff --git a/config/firefly.php b/config/firefly.php index c799b28ae7..71137d7db1 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -61,6 +61,7 @@ use FireflyIII\Support\Binder\JournalList; use FireflyIII\Support\Binder\TagList; use FireflyIII\Support\Binder\TagOrId; use FireflyIII\Support\Binder\UserGroupAccount; +use FireflyIII\Support\Binder\UserGroupBill; use FireflyIII\TransactionRules\Actions\AddTag; use FireflyIII\TransactionRules\Actions\AppendDescription; use FireflyIII\TransactionRules\Actions\AppendDescriptionToNotes; @@ -110,7 +111,7 @@ return [ 'handle_debts' => true, // see cer.php for exchange rates feature flag. ], - 'version' => '6.0.22', + 'version' => '6.0.23', 'api_version' => '2.0.7', 'db_version' => 20, @@ -480,6 +481,7 @@ return [ // V2 API endpoints: 'userGroupAccount' => UserGroupAccount::class, + 'userGroupBill' => UserGroupBill::class, ], diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 4edf9d4843..1067b29fff 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -159,7 +159,7 @@ - {# bar to visualise spending in budget .#} + {# bar to visualise spending in budget . #}
{{ budget.name }} - {% endfor %} + {% endfor %} {% endif %} {% if budget.budgeted|length > 1 %} @@ -233,15 +233,15 @@ {% endif %} {% if budget.auto_budget %} - {% if 1 == budget.auto_budget.auto_budget_type %} - - {% endif %} - {% if 2 == budget.auto_budget.auto_budget_type %} - - {% endif %} - {% if 3 == budget.auto_budget.auto_budget_type %} - - {% endif %} + {% if 1 == budget.auto_budget.auto_budget_type %} + + {% endif %} + {% if 2 == budget.auto_budget.auto_budget_type %} + + {% endif %} + {% if 3 == budget.auto_budget.auto_budget_type %} + + {% endif %} {% endif %} {% if budget.attachments.count() > 0 %} @@ -253,11 +253,11 @@
{{ defaultCurrency.symbol }}
+ data-currency="{{ defaultCurrency.id }}" data-limit="0" value="0" autocomplete="off" min="0" name="amount" + type="number">
+ style="display:none;"> {% endif %} {% if budget.budgeted|length > 0 %} {% for budgetLimit in budget.budgeted %} @@ -270,9 +270,9 @@
{{ budgetLimit.currency_symbol }}
+ data-id="{{ budget.id }}" data-limit="{{ budgetLimit.id }}" value="{{ budgetLimit.amount }}" + autocomplete="off" + min="0" name="amount" type="number">
@@ -284,7 +284,7 @@
+ style="display:none;"> {% endfor %} {% endif %} {% if budget.budgeted|length < currencies.count() %} @@ -305,14 +305,18 @@ {% endif %} {% endfor %} + {# this cell displays the amount left in the budget, per budget limit. #} {% for spentInfo in budget.spent %} {% set countLimit = 0 %} {% for budgetLimit in budget.budgeted %} + {# now looping a single budget limit. #} {% if spentInfo.currency_id == budgetLimit.currency_id and budgetLimit.in_range %} + {# the code below is used for budget limits INSIDE the current view range. #} {% set countLimit = countLimit + 1 %} + data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left"> + {# the amount left is automatically calculated. #} {{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }} {% if spentInfo.spent + budgetLimit.amount > 0 %} ({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / activeDaysLeft, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}) @@ -324,8 +328,9 @@ {% endif %} {% endfor %} {% if countLimit == 0 %} + {# this code is used for budget limits OUTSIDE the current view range. #} + class="amount_left" data-value="{{ spentInfo.spent }}"> {{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
@@ -333,14 +338,14 @@ {% endfor %} {% for budgetLimit in budget.budgeted %} {% if null == budget.spent[budgetLimit.currency_id] %} - + {{ formatAmountBySymbol(budgetLimit.amount, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }} - {% if budgetLimit.in_range %} - ({{ formatAmountBySymbol(budgetLimit.amount / activeDaysLeft, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}) - {% endif %} + {% if budgetLimit.in_range %} + ({{ formatAmountBySymbol(budgetLimit.amount / activeDaysLeft, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}) + {% endif %} -
+
{% endif %} {% endfor %} @@ -433,7 +438,7 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} diff --git a/resources/views/debug.twig b/resources/views/debug.twig index edb14e2561..0492d0897a 100644 --- a/resources/views/debug.twig +++ b/resources/views/debug.twig @@ -11,58 +11,11 @@

{{ trans('firefly.debug_pretty_table') }}

-